-
-
Notifications
You must be signed in to change notification settings - Fork 9.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Indexer: Deprecate storyIndexers
API, add foundation for indexers
#23582
Conversation
storiesV2Compatibility: false, | ||
storyStoreV7: true, | ||
docs: { defaultName: 'docs', autodocs: false }, | ||
}; | ||
|
||
describe('StoryIndexGenerator', () => { | ||
describe('StoryIndexGenerator with deprecated indexer API', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also notice the file rename here.
code/lib/csf-tools/src/CsfFile.ts
Outdated
// TODO: is there a better way to type this correctly? | ||
parse(): CsfFile & { meta: StaticMeta; stories: IndexedStory[] } { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would like some input here @kasperpeulen @tmeasday
There's no new implementation, I just added types to our existing indexers which surfaced this type error.
The problem is, that:
- Most indexers return the result
CsfFile.parse()
as the result of the indexer. - An indexer needs to return an
IndexedCsfFile
CsfFile.parse()
returnsCsfFile
, notIndexedCsfFile
- The difference between the two is that:
meta
can beundefined
inCsfFile
, it can't inIndexedCsfFile
stories.*.name
can beundefined
inCsfFile
(StaticStory
), it can't inIndexedCsfFile
(IndexedStory
)
- The implementation of
CsfFile.parse()
ensures that both of these constraints are met, but TypeScript of course doesn't know that because 3.
My solution here makes TypeScript happy, but it doesn't feel like a type-safe way to solve this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason you didn't just change it to return an IndexedCSFFile
(I assume that's what you meant?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IndexedCSFFile
has less properties than CsfFile
, so other callsites that uses parse()
won't accept that. But I can change it to CsfFile & IndexedCSFFile
which should be okay I guess.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't really know the difference between the two types but is another option to just add the fields to CsfFile
if that's what's returned by the CsfFile
library?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The issue is that CsfFile
already have meta
and stories
, but they can be undefined
because they are not set during construction, only after parse()
is called.
export type IndexEntry = StoryIndexEntry | DocsIndexEntry; | ||
export interface NewBaseIndexEntry { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's annoying that we have to temporarily introduce this new type, but it's the only non-breaking way I can think of. We'll replace IndexEntry
with NewIndexEntry
in 8.0.
If users migrate now, they'll migrate to NewIndexEntry
, only to migrate back to IndexEntry
in 8.0, which must be annoying.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this type isn't an IndexEntry
. And index entry is what ends up in index.json
, so should for instance always include an id
.
I think this type is like an IndexEntryInput
or something?
PS -- tell me more about key
. For CSF files we don't need that because it's expected that the loader will produce a CSFFile
which is keyed on storyId
. That is the loader responsible for mapping the pure ESM module exports to an object keyed on storyId
, so we can look up the story annotations only knowing the storyId
.
Are we expecting something different in this case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this type isn't an
IndexEntry
. And index entry is what ends up inindex.json
, so should for instance always include anid
.I think this type is like an
IndexEntryInput
or something?
I see what you mean, of course! I've changed it to that, then IndexEntry
also won't be deprecated.
PS -- tell me more about
key
. For CSF files we don't need that because it's expected that the loader will produce aCSFFile
which is keyed onstoryId
. That is the loader responsible for mapping the pure ESM module exports to an object keyed onstoryId
, so we can look up the story annotations only knowing thestoryId
.Are we expecting something different in this case?
This type is taken directly from the Indexer RFC, which also shows example indexers that makes use of the key
property.
I'm assuming that this is added to make indexers and story definitions more flexible, in that the export doesn't need to match the story ID, but I'd like @shilman to elaborate here.
Are you saying that if we introduce this, we need to make the annotation lookup smarter, because then it can't just rely on the id
, it also needs to know the key
, which I guess would mean we need the key
in the index as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess would mean we need the key in the index as well?
Yes, if the plan is to read the export named key
in the browser when constructing the NormalizedCsfFile
. However, I'm not sure if that makes sense because I'm pretty sure you are going to need to transform the raw module exports (of the indexed file) to get something that looks like the module exports of a "true" CSF file anyway (either in a loader or a runtime transformer ala this comment).
If you are already transforming the file into CSF, why wouldn't you transform the export names to mean this key
business wasn't required (ie. so they match up with the story ids)? Alternatively, why wouldn't you just write the story ids in the first place to match with the export names?
Perhaps I am missing a use case though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's been awhile since I wrote the RFC but I think the rationale was twofold.
First, the consumer of the data needs to have the export name to be able to reference the story:
export default { title: 'Button' };
export const Primary = {};
This would generate a story with ID button--primary
. But we don't know if the export key is Primary
, primary
, or something else. I'm not sure how this is handled today, but I felt that providing the key directly would be more straightforward.
Second, it is more convenient to provide the key and have the calling code generate the story ID automatically from the export. And if you provide the ID that can take precedence.
If it's causing problems, I'm fine with using something closer to the current interface instead. But I'd like to know how it actually works currently, since it seems like it would be a bit convoluted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, let me try again. Currently, the indexer must produce an id
.
Separately, the code that loads the story computes its own id
here (key
is called exportName
here):
const id = parameters.__id || toId(meta.id, exportName); |
It's convoluted because the system currently assumes that the ID generation is identical across both codepaths, and if not then it will fail.
The minimal change would be that the Indexer can return a key
, but that the code that calls the indexer computes the id
from the title
and key
(unless an id
is also provided), but that the key
is not stored in the index.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That seems simple enough and uncontroversial. I'm not sure how it interacts with auto title but suspect in a good way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like a good design. To make sure I understood it correctly, let's continue the example above: The vue indexer would handle the story export const vueStoryDefault
by returning id = default
and key = vueStoryDefault
. Then the url would use the id, but the loader the key, right?
In that context, it would be nice if the indexer doesn't need to handle id/key conflicts but instead that this would be handled in a central place. For example, the svelte indexer currently keeps track of all ids its encounters and prevents id collisions (e.g. https://github.com/storybookjs/addon-svelte-csf/blob/da2523a272716884667eb6107d6a5d95ab38d706/src/parser/extract-id.ts#L13-L37). In my opinion, this adds unnecessary complications for the indexer (every indexer would need to implement a similar feature) and doesn't work if multiple indexers are used (an indexer doesn't know about the ids that another indexer creates).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The minimal change would be that the Indexer can return a key, but that the code that calls the indexer computes the id from the title and key (unless an id is also provided), but that the key is not stored in the index.
Now that I've implemented this and thought about it some more, this still doesn't quite make sense to me @shilman.
with this solution you're still computing the export name from the id
(because that is all there is in the index), so the id
must directly match the export name. so if you input a key
that matches the export, but write a custom id
like my-pretty-id
, then SB will throw because it can't find the MyPrettyId
export in the CSF.
I'm not sure what the point is of the indexer being able to specify the id
, since it must match the same ID generation as you pointed out.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In that context, it would be nice if the indexer doesn't need to handle id/key conflicts but instead that this would be handled in a central place. ... In my opinion, this adds unnecessary complications for the indexer (every indexer would need to implement a similar feature) and doesn't work if multiple indexers are used (an indexer doesn't know about the ids that another indexer creates).
Agree @tobiasdiez, this makes sense to me too. Stretch goal @shilman ?
storyIndexers
API, add foundation for indexers
storyIndexers
API, add foundation for indexers
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lgtm
Works on #23457
What I did
storyIndexers
in favor ofindexers
as the new entry for the new APIindexers
yet, they are ignored. Which is also why this doesn't targetnext
- we don't want to deprecate the API before the replacement is ready.StoryIndexGenerator
toStoryIndexGenerator.deprecate.test.ts
. The plan is to copy the file toStoryIndexGenerator.test.ts
that will have the same tests but with the new API instead (plus a bunch more)Follow-up PR here will implement support for the new
IndexEntry
inStoryIndexGenerator
, which should be enough to make this releasable. Actually changing our internal indexers can be done afterwards.How to test
Checklist
MIGRATION.MD
Maintainers
ci:normal
,ci:merged
orci:daily
GH label to it to run a specific set of sandboxes. The particular set of sandboxes can be found incode/lib/cli/src/sandbox-templates.ts
["cleanup", "BREAKING CHANGE", "feature request", "bug", "build", "documentation", "maintenance", "dependencies", "other"]