Replies: 1 comment 1 reply
-
|
Paraphrased feedback from someone:
|
Beta Was this translation helpful? Give feedback.
1 reply
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
While working with the addon recently, I discovered a few areas where our support for Svelte snippets is suboptimal, and I think we can improve them. I'm listing them all out here because I think they are all related, but in essence we could approve/decline and implement each idea individually if we want to.
I think doing these would truly make snippets first class citizens of Storybook, which would be a great win for the community.
Throughout the RFC we'll be working with this Layout component, which relies heavily on snippets:
Layout.svelteand in an application it would be used like this:
App.sveltePlayground: https://svelte.dev/playground/2dd31039c3144aa9b987072ea245b3de?version=5.28.2
Problem 1: Snippets can't be args
Args in stories is a great construct, because it allows you to easily set them per-story. Args are also what powers the Controls panel, making it easy for users to try out their components with different args.
The problem is, that Controls can only really work with primitive-like values - strings, numbers, objects, booleans, etc. - and snippets are functions. So while you can set an arg to be a snippet, the control for it would be a stringified function which is both confusing and unusable.
If you try and set a snippet like the
header-prop to just be a primitive arg like'My header content', Svelte will fail because it is expecting the prop to be a snippet-function, not a plain string.The workaround today is that users define a template that converts the plain-string arg to a snippet:
Proposal A: Convert args to snippets automatically
We could convert specific args to snippets automatically behind the scenes, so users wouldn't have to do it manually with a verbose template.
But how? We can't know upfront which args should be snippets, and which should be left as-is. You might be tempted to use our automatic argTypes inference to find all props typed as Snippet, but we should not let argTypes control how stories render. ArgTypes are purely for documentation and manager purposes, and a story should render the exact same if argTypes was defined or not.
This wasn't always the case, but we've seen that argTypes generation slows down building and rendering a lot, so we're moving towards an architecture where argTypes inference and handling is lazy, not blocking the story rendering pipeline.
This also rules out the user specifying which args should be treated as args in the
argTypesdefinition.Also, this is highly specific to Svelte, and the story annotations API (
title,args,argTypes,decorators, etc.) is framework agnostic, so we shouldn't expand that API with Svelte-specific stuff.I propose that we add a second
optionsargument todefineMeta, that is specific to Svelte CSF. Down the line this could be expanded to configure more things in Svelte CSF, but right now it could contain a singlesnippetize(naming feedback welcome), that users would use to tell Storybook which args to convert to snippets:snippetizecould default to being{ children: true }- and a customsnippetizeshould merge with that - becausechildrenis sort of a reserved word in Svelte: https://svelte.dev/docs/svelte/snippet#Passing-snippets-to-components-Implicit-children-snippetHowever you can override that by setting
{ children: false }. if you have a component that supports<MyComp children="plain string" />. We could also say we don't support that, because it is an anti-pattern and Svelte docs state:an alternative API would be an array,
snippetize: ['header', 'children', 'footer'], however the merging-heuristics for that is less obvious to me and to users.A full solution would look like this:
Two wins:
childrenandfooterto snippets, because they are already snippets in the{...args}spread.Unresolved questions
snippetizeisn't part of the story annotations, it won't automatically follow Storybook's inheritance-model withpreview.js->meta->story. However I doubt it makes sense to globally define which prop names to snippetize inpreview.js, as it would be a per-component thing. Similarly I doubt there is a use case for setting it at a story-level - you wouldn't have a component that supports a prop both being a snippet and a primitive at the same time, would you? Maybe I'm missing something.snippetize, it means that the arg you set indefineMetacan now be a primitive and doesn't need to match theSnippettype derived from the component's prop types. And when you define a custom template that takes inargs, it needs to understand that those args are now snippets, not primitives. A simpler solution would be to always convert allSnippet-types to beSnippet | AllThePrimitives(I tried this once but failed, I'm sure it's doable though).Proposal B: Automatically convert
childrenarg to snippetOnly and always* convert the
childrenarg to a snippet. An argument is that named snippets are niche, and so requiring a bit of gymnastics to make them work is okay. But we know thatchildrenshould always be a snippet, so we can convert that safely. This means we don't need thesnippetize-API at all, and in general makes this a lot easier to implement.*: Unless it is already a snippet. booleans probably also should not be converted, in reality only
stringandnumbermakes sense to snippetize.Problem 2: Story children cannot be used with templates
In #228 we introduced the feature that a
Story'schildrenwould be forwarded aschildrento the component:This made it easier to write stories with
childrensnippets, however it came with one limitation: you can't mix Storychildrenwithtemplate. This is not allowed:But it doesn't have to be that way, we can support this!
Proposal: Merge Story
childrenintoargsWhat we could do, is instead of making it a completely separate rendering case when
Storyhaschildren, we just merge theStory-component'schildreninto the args, overriding the existingargs.childrenif it's there.It's not a huge win, but it is actually easier to maintain for us, and it's one less thing to know as a user, it simplifies the mental model: "
childrenonStoryis a shortcut forargs.children, because writingargs.childrenmanually with a snippet is cumbersome".FYI if users were to do this manually today (which they shouldn't), they would achieve it like this:
Again, we need to ensure that the code snippet generation logic can support this case
Problem 3: Passing snippets requires a template
Continuing from the Problem 2 above, why isn't this supported for any snippets in a
Story? You can't pass any snippet to aStoryand expect it to be forwarded, onlychildren. You can't do this today:Although it seems like a natural mental model to have from a user perspective: "any snippet passed to
Story(including implicitchildren) will be set asargson the story, ie. forwarded to the component or template".You might think that this is unnecessary if Problem 1 is solved, however Problem 1 is about turning primitve args into snippets, while the use case for this problem is when you have complex snippets that can't be expressed with simple strings, ie. because they contain other components or Svelte constructs, so they need to be snippets.
Proposal: Merge any snippets passed to
StoryintoargsExpand the idea of Problem 2, to not just cover
children, but any snippet passed toStorywill merge intoargs, overriding any existing arg with the same name. However there are a few gotchas:Firstly, Story already accepts a
template-snippet. It is a reserved keyword in a sense, it won't be forwarded to args obviously. We also can't forward story annotations likeplayorbeforeEach, because you'd get props collisions if you try to define both a prop and a snippet with that name. Should you have a component that accepts a reserved keyword snippet itself, the workaround is to use that directly (as it is the case already today):Secondly, Svelte currently doesn't expose an API that allows us to check if something is a snippet or not (See sveltejs/svelte#14017, sveltejs/svelte#9774 and sveltejs/svelte#9903), it seems like the maintainers don't think the currently known use cases are valid or perhaps an anti-pattern. Maybe this will change that, I don't know. I've heard they are great people.
For us it means we can only check if any given prop to
Storyis a function, which doesn't necessarily mean it is a snippet. Many story annotations are functions, eg. this shouldn't become a snippet:<Story play={() => {...}} />. There are a few solutions to this:toString. This is fragile if the internals ever change in a Svelte version.const IGNORED_PROPS = ['play', 'beforeEach', 'decorators', ...]. This is fragile if Storybook decides to change story annotations in the future, ie. in the near futureexperimental_afterEachcould be renamed toafterEach, which we would need to handle. However this is within our control, so easier to maintain.If we implemented this proposal, it would make the above Just Work™:
Beta Was this translation helpful? Give feedback.
All reactions