1
+ /* eslint-disable no-underscore-dangle */
1
2
import path from 'node:path' ;
2
3
import chalk from 'chalk' ;
3
4
import fs from 'fs-extra' ;
@@ -17,6 +18,7 @@ import type {
17
18
StoryIndex ,
18
19
Indexer ,
19
20
StorybookConfigRaw ,
21
+ IndexInputStats ,
20
22
} from '@storybook/core/types' ;
21
23
import { userOrAutoTitleFromSpecifier , sortStoriesV7 } from '@storybook/core/preview-api' ;
22
24
import { commonGlobOptions , normalizeStoryPath } from '@storybook/core/common' ;
@@ -27,14 +29,17 @@ import { analyze } from '@storybook/docs-mdx';
27
29
import { dedent } from 'ts-dedent' ;
28
30
import { autoName } from './autoName' ;
29
31
import { IndexingError , MultipleIndexingError } from './IndexingError' ;
32
+ import { addStats , type IndexStatsSummary } from './summarizeStats' ;
30
33
31
34
// Extended type to keep track of the csf meta id so we know the component id when referencing docs in `extractDocs`
32
- type StoryIndexEntryWithMetaId = StoryIndexEntry & { metaId ?: string } ;
35
+ type StoryIndexEntryWithExtra = StoryIndexEntry & {
36
+ extra : { metaId ?: string ; stats : IndexInputStats } ;
37
+ } ;
33
38
/** A .mdx file will produce a docs entry */
34
39
type DocsCacheEntry = DocsIndexEntry ;
35
40
/** A *.stories.* file will produce a list of stories and possibly a docs entry */
36
41
type StoriesCacheEntry = {
37
- entries : ( StoryIndexEntryWithMetaId | DocsIndexEntry ) [ ] ;
42
+ entries : ( StoryIndexEntryWithExtra | DocsIndexEntry ) [ ] ;
38
43
dependents : Path [ ] ;
39
44
type : 'stories' ;
40
45
} ;
@@ -104,6 +109,9 @@ export class StoryIndexGenerator {
104
109
// - the preview changes [not yet implemented]
105
110
private lastIndex ?: StoryIndex | null ;
106
111
112
+ // Cache the last value stats calculation, mirroring lastIndex
113
+ private lastStats ?: IndexStatsSummary ;
114
+
107
115
// Same as the above but for the error case
108
116
private lastError ?: Error | null ;
109
117
@@ -222,7 +230,7 @@ export class StoryIndexGenerator {
222
230
projectTags,
223
231
} : {
224
232
projectTags ?: Tag [ ] ;
225
- } ) : Promise < ( IndexEntry | ErrorEntry ) [ ] > {
233
+ } ) : Promise < { entries : ( IndexEntry | ErrorEntry ) [ ] ; stats : IndexStatsSummary } > {
226
234
// First process all the story files. Then, in a second pass,
227
235
// process the docs files. The reason for this is that the docs
228
236
// files may use the `<Meta of={XStories} />` syntax, which requires
@@ -237,7 +245,8 @@ export class StoryIndexGenerator {
237
245
this . extractDocs ( specifier , absolutePath , projectTags )
238
246
) ;
239
247
240
- return this . specifiers . flatMap ( ( specifier ) => {
248
+ const statsSummary = { } as IndexStatsSummary ;
249
+ const entries = this . specifiers . flatMap ( ( specifier ) => {
241
250
const cache = this . specifierToCache . get ( specifier ) ;
242
251
invariant (
243
252
cache ,
@@ -252,12 +261,17 @@ export class StoryIndexGenerator {
252
261
253
262
return entry . entries . map ( ( item ) => {
254
263
if ( item . type === 'docs' ) return item ;
255
- // Drop the meta id as it isn't part of the index, we just used it for record keeping in `extractDocs`
256
- const { metaId, ...existing } = item ;
264
+
265
+ addStats ( item . extra . stats , statsSummary ) ;
266
+
267
+ // Drop extra data used for internal bookkeeping
268
+ const { extra, ...existing } = item ;
257
269
return existing ;
258
270
} ) ;
259
271
} ) ;
260
272
} ) ;
273
+
274
+ return { entries, stats : statsSummary } ;
261
275
}
262
276
263
277
findDependencies ( absoluteImports : Path [ ] ) {
@@ -341,22 +355,24 @@ export class StoryIndexGenerator {
341
355
] ) ;
342
356
}
343
357
344
- const entries : ( ( StoryIndexEntryWithMetaId | DocsCacheEntry ) & { tags : Tag [ ] } ) [ ] =
358
+ const entries : ( ( StoryIndexEntryWithExtra | DocsCacheEntry ) & { tags : Tag [ ] } ) [ ] =
345
359
indexInputs . map ( ( input ) => {
346
360
const name = input . name ?? storyNameFromExport ( input . exportName ) ;
347
361
const componentPath =
348
362
input . rawComponentPath &&
349
363
this . resolveComponentPath ( input . rawComponentPath , absolutePath , matchPath ) ;
350
364
const title = input . title ?? defaultMakeTitle ( ) ;
351
365
352
- // eslint-disable-next-line no-underscore-dangle
353
366
const id = input . __id ?? toId ( input . metaId ?? title , storyNameFromExport ( input . exportName ) ) ;
354
367
const tags = combineTags ( ...projectTags , ...( input . tags ?? [ ] ) ) ;
355
368
356
369
return {
357
370
type : 'story' ,
358
371
id,
359
- metaId : input . metaId ,
372
+ extra : {
373
+ metaId : input . metaId ,
374
+ stats : input . __stats ?? { } ,
375
+ } ,
360
376
name,
361
377
title,
362
378
importPath,
@@ -428,12 +444,12 @@ export class StoryIndexGenerator {
428
444
429
445
// Also, if `result.of` is set, it means that we're using the `<Meta of={XStories} />` syntax,
430
446
// so find the `title` defined the file that `meta` points to.
431
- let csfEntry : StoryIndexEntryWithMetaId | undefined ;
447
+ let csfEntry : StoryIndexEntryWithExtra | undefined ;
432
448
if ( result . of ) {
433
449
const absoluteOf = makeAbsolute ( result . of , normalizedPath , this . options . workingDir ) ;
434
450
dependencies . forEach ( ( dep ) => {
435
451
if ( dep . entries . length > 0 ) {
436
- const first = dep . entries . find ( ( e ) => e . type !== 'docs' ) as StoryIndexEntryWithMetaId ;
452
+ const first = dep . entries . find ( ( e ) => e . type !== 'docs' ) as StoryIndexEntryWithExtra ;
437
453
438
454
if (
439
455
path
@@ -475,7 +491,7 @@ export class StoryIndexGenerator {
475
491
result . name ||
476
492
( csfEntry ? autoName ( importPath , csfEntry . importPath , defaultName ) : defaultName ) ;
477
493
478
- const id = toId ( csfEntry ?. metaId || title , name ) ;
494
+ const id = toId ( csfEntry ?. extra . metaId || title , name ) ;
479
495
480
496
const tags = combineTags (
481
497
...projectTags ,
@@ -598,15 +614,20 @@ export class StoryIndexGenerator {
598
614
}
599
615
600
616
async getIndex ( ) {
601
- if ( this . lastIndex ) return this . lastIndex ;
617
+ return ( await this . getIndexAndStats ( ) ) . storyIndex ;
618
+ }
619
+
620
+ async getIndexAndStats ( ) : Promise < { storyIndex : StoryIndex ; stats : IndexStatsSummary } > {
621
+ if ( this . lastIndex && this . lastStats )
622
+ return { storyIndex : this . lastIndex , stats : this . lastStats } ;
602
623
if ( this . lastError ) throw this . lastError ;
603
624
604
625
const previewCode = await this . getPreviewCode ( ) ;
605
626
const projectTags = this . getProjectTags ( previewCode ) ;
606
627
607
628
// Extract any entries that are currently missing
608
629
// Pull out each file's stories into a list of stories, to be composed and sorted
609
- const storiesList = await this . ensureExtracted ( { projectTags } ) ;
630
+ const { entries : storiesList , stats } = await this . ensureExtracted ( { projectTags } ) ;
610
631
611
632
try {
612
633
const errorEntries = storiesList . filter ( ( entry ) => entry . type === 'error' ) ;
@@ -635,12 +656,13 @@ export class StoryIndexGenerator {
635
656
previewCode && getStorySortParameter ( previewCode )
636
657
) ;
637
658
659
+ this . lastStats = stats ;
638
660
this . lastIndex = {
639
661
v : 5 ,
640
662
entries : sorted ,
641
663
} ;
642
664
643
- return this . lastIndex ;
665
+ return { storyIndex : this . lastIndex , stats : this . lastStats } ;
644
666
} catch ( err ) {
645
667
this . lastError = err == null || err instanceof Error ? err : undefined ;
646
668
invariant ( this . lastError ) ;
0 commit comments