Open visualjerk opened 4 years ago
@visualjerk this looks awesome!
Related: https://github.com/storybookjs/storybook/issues/7729
@Aaron-Pool has thought about this problem quite a bit and might be able to give pointers.
Yeah, so there's two major parts of trying to get this feature implemented
1) making it work 2) making the developer experience not feel horrible
As for 1, I think we could use Babel macros to run just the contents of a story block (with a comment to trigger the macro, or something similar) through the Vue sfc compiler and Vue Babel preset.
2 is much trickier. And would be editor dependent. But for vscode, at least, I'm pretty confident it would be possible to write an extension that could detect .vue
as an "embedded language" within story blocks marked with a Vue indicator. I dunno how well code complete and intellisense would work, but the syntax highlighting would probably be fine, at least.
Thanks for the insights @Aaron-Pool
Regarding 1: Do you already have plans to work on that? If not, I would be glad to help implementing it.
Regarding 2: At least for syntax highlighting in VS Code the silvenon.mdx
extension already does a pretty good job with stories written in this syntax.
@visualjerk I've had "plans" to work on it for a while now, but my day job has been eating up most of coding creative energy as of late. I'd be glad to offer guidance and help with any technical hurdles if you started a PR though 👌
And glad to know the current MDX plugin does a decent job already, I though I remembered it not knowing how to handle a non-root script
tag, but I'm glad to be wrong 🤙
It sounds a great idea.
@visualjerk But if you want a awesome developer experience maybe the best is to use SFC (single file component) and the CSF (component story format).
// ButtonNormal.story.vue
<template>
<Button>Normal Button</Button>
</template>
<script>
import Button from '../src/components/Button.vue';
export default {
name: 'ButtonNormal',
components: { Button },
};
</script>
// Button.stories.ts
import ButtonNormalStory from './ButtonNormal.story.vue';
export default {
title: 'Button',
};
export const normalButton = () => ButtonNormalStory;
See this article for more details https://dev.to/josephuspaye/using-storybook-with-vue-single-file-components-2od
Note: The cons is the story source is not pertinent (displayed via story-source / docs addon). The article explains how to load the SFC vue story but i have tried without success.
@rdhainaut will try to get an addon-docs
Source
fix for this scenario in 6.0 .. 🤞
@rdhainaut thanks for pointing out that solution. Maybe we will give it a try and see how it works with multiple stories in one docs file.
@Aaron-Pool After working on the feature request for a while I can give a quick update:
The current implementation uses babel parser, which unfortunately does not support custom plugins: https://babeljs.io/docs/en/babel-parser#will-the-babel-parser-support-a-plugin-system
So it looks like we need a solution that handles SFC stories before babel parser does.
A possible approach would be to detect SFC stories and transform them to JSX stories. Afterwards babel can handle them just fine.
Additionally we would have to keep the original SFC story code and put it into the mdxSource
later on, so docs can show it in the code preview.
I was wrong 😄 While the mdx extension mentioned above works fine for simple code snippets, it breaks with things like :rounded="true
inside the template tag.
However we could provide a different syntax for SFC stories that works like a charm:
<Story name="to storybook" height="300px">
```vue
<template>
<button>click me {{ name }}</button>
</template>
<script>
export default {
data() {
return {
name: 'foo'
}
}
}
</script>
If anybody here hasn't seen it, I recently made source snippet customizable, which could be used to improve the Source
block for these SFC stories (which are awesome!!!). Here's the PR: https://github.com/storybookjs/storybook/pull/10089
@visualjerk Is there any standard/precedent for this ```vue ...
string? It looks a bit odd to me. It might make syntax highlighting difficult? I wonder if we should coordinate with @mdx-js/vue-loader to add this feature there first.
@graup thanks for the reference. I think https://github.com/mdx-js/mdx/tree/master/packages/vue-loader is the converse of what we want. It takes MDX and loads it into a Vue component. We want to load MDX into a React component since Storybook Docs is implemented in React. I'm also not sure whether @visualjerk 's approach is the best one, but it seems very reasonable at first glance. Definitely open for discussion.
@shilman you're right, I confused the purpose of @mdx-js/vue-loader.
Here's another idea instead of <Story> ```vue
: how about something like <Story lang="vue">
? That would feel pretty familiar to vue developers and be just as easily regex-able.
@graup I think the reason he went with the notated template string was because this gives us a lot of the DX provided by the markdown syntax highlighter for free (in most code editors, at least) 🤷♂️
If we want a different syntax, and a decent developer experience, we're going to need to work the MDX syntax highlighting extension and add some additional functionality to detect story block lang
attribute.
@graup Initially I was aiming for a solution similar to the one you proposed. But as @Aaron-Pool pointed out the syntax highlighting with this approach was not great. Having the fenced code block with vue
around it lets markdown syntax highlighter recognize it as Vue SFC code.
Any news on this?
The current approach of wirting the template in inline string template: "<div></div>"
has some flaws:
template: vue`<my-component :title="123"/>` (this highlights for me in Vetur)
Right now as shown above, it's possible to export a .vue file in .stories.js like
import MyVueStoryForButtonWithOverflowingContent from '@/button/stories/OverflowingContent.vue`
export const ButtonWithOverflowingContent = () => MyVueStoryForButtonWithOverflowingContent;
Assuming that right now you use the inline template string approach, you probably have one template, but then you reuse it eg. 5 times to show different scenarios eg.
const Template = (args, {argTypes}) => {
props: Object.keys(argTypes),
template: `<my-button v-bind="$props">{{text}}</my-button>
}
export const Basic = Template.bind({}).args = {text: 'Hello', disabled: false}
export const Disabled = Template.bind({}).args = {text: 'Hello', disabled: true}
//add some 5 more cases here
with the way current .story.vue components work, you can't really reuse the template from above (say you've created an SFC that just contains <my-button v-bind="$props">
in the same way. You need to create a new story.vue component for each case
Sure you can do {components: MyStoryComponent, template: "<my-story-component/>"}
but that's also just adding another layer on top
It'd be perfect if it was possible to write export default (args, {argTypes}) => {props: {prop1: String}, computed() {}}
in SFC <script>
and still have the text from attach, but that probably requires a custom loader (right now vue-docgen fails to parse)
I was also getting a bit frustrated by the missing support for native vue stories and thus wrote a simple storybook addon that enables idiomatic vue stories. The package is still in an early stage, but please feel free to play around with it: https://github.com/tobiasdiez/storybook-vue-addon
<script setup lang="ts">
import Button from './Button.vue'
</script>
<template>
<Stories
title="Stories in Vue format 😍"
:component="Button"
>
<Story title="Example">
<Button
background="#ff0"
label="😄👍😍💯"
/>
</Story>
</Stories>
</template>
@tobiasdiez Omg, that looks so good. That (or something like it) should become the official way to do Storybook stories in Vue. The .vue
format is much more versatile and a better DX.
@tobiasdiez honestly massive props for this addon! ❤️ both the TS & MDX format felt so clunky and boilerplate'y and not at all Vuey... Oh, but this chef's kiss
I was also getting a bit frustrated by the missing support for native vue stories and thus wrote a simple storybook addon that enables idiomatic vue stories. The package is still in an early stage, but please feel free to play around with it: https://github.com/tobiasdiez/storybook-vue-addon
<script setup lang="ts"> import Button from './Button.vue' </script> <template> <Stories title="Stories in Vue format 😍" :component="Button" > <Story title="Example"> <Button background="#ff0" label="😄👍😍💯" /> </Story> </Stories> </template>
This should be the default way to write Vue stories in Storybook!
If anyone is looking for an alternative to string template, you can use JSX in a separated file and just import your stories in the main story file.
Use vitejs/plugin-vue-jsx to support .jsx files.
Here's an example:
import { defineComponent } from "vue";
import AppButton from "@/components/button/AppButton.vue";
const ButtonDefault = defineComponent({
render ({ $attrs }) {
// Here we can use JSX as we want, way better DX than string template
return <AppButton {...args} />
}
})
// OR
const ButtonDefault = defineComponent({
setup (_, { attrs }) {
return () => <AppButton {...attrs} />
}
})
// Here you'll use the default story structure
export default {
render: (args) => ({
components: {
ButtonDefault
},
setup () {
return { args }
},
// Then just use the .jsx component here.
template: '<ButtonDefault v-bind="args" />'
})
}
// Now import the file
import ButtonDefault from './ButtonDefault.tsx'
// ...your meta story here...
export const Default = ButtonDefault
Since @floroz lib looks deprecated. There's a fork of his lib also, but in the current date, it seems to be in early stages: https://storybook.js.org/addons/storybook-vue-csf-addon
Is your feature request related to a problem? Please describe. When writing a story for Vue components inside mdx files, it differs from the way one would write the code inside a .vue file.
Also when reading the story source inside storybook, the code differs from the code one would normally put inside a .vue file.
So there is this mental switch developers have to do, when using storybook as their component documentation. This is especially troublesome for new developers, which in many teams are the main target group of the documentation.
Describe the solution you'd like In addition to writing a Vue story like this:
It would be nice to add support for stories in this format:
Are you able to assist bring the feature to reality? yes