diff --git a/code/core/package.json b/code/core/package.json index 52d490d49e5a..f11d38752a82 100644 --- a/code/core/package.json +++ b/code/core/package.json @@ -314,6 +314,7 @@ "react-textarea-autosize": "^8.3.0", "react-transition-group": "^4.4.5", "require-from-string": "^2.0.2", + "resolve": "^1.22.11", "resolve.exports": "^2.0.3", "sirv": "^2.0.4", "slash": "^5.0.0", diff --git a/code/core/src/common/index.ts b/code/core/src/common/index.ts index e0c73e661ab0..af3093d20c02 100644 --- a/code/core/src/common/index.ts +++ b/code/core/src/common/index.ts @@ -45,6 +45,7 @@ export * from './js-package-manager'; export * from './utils/scan-and-transform-files'; export * from './utils/transform-imports'; export * from '../shared/utils/module'; +export * from './utils/utils'; export { versions }; diff --git a/code/core/src/common/utils/interpret-files.ts b/code/core/src/common/utils/interpret-files.ts index 6f792473e8de..97930031ae4a 100644 --- a/code/core/src/common/utils/interpret-files.ts +++ b/code/core/src/common/utils/interpret-files.ts @@ -1,14 +1,19 @@ import { existsSync } from 'node:fs'; +import { extname } from 'node:path'; + +import resolve from 'resolve'; export const supportedExtensions = [ '.js', - '.mjs', - '.cjs', - '.jsx', '.ts', + '.jsx', + '.tsx', + '.mjs', '.mts', + '.mtsx', + '.cjs', '.cts', - '.tsx', + '.ctsx', ] as const; export function getInterpretedFile(pathToFile: string) { @@ -16,3 +21,38 @@ export function getInterpretedFile(pathToFile: string) { .map((ext) => (pathToFile.endsWith(ext) ? pathToFile : `${pathToFile}${ext}`)) .find((candidate) => existsSync(candidate)); } + +export function resolveImport(id: string, options: resolve.SyncOpts): string { + const mergedOptions: resolve.SyncOpts = { + extensions: supportedExtensions, + packageFilter(pkg) { + // Prefer 'module' over 'main' if available + if (pkg.module) { + pkg.main = pkg.module; + } + return pkg; + }, + ...options, + }; + + try { + return resolve.sync(id, { ...mergedOptions }); + } catch (error) { + const ext = extname(id); + + // if we try to import a JavaScript file it might be that we are actually pointing to + // a TypeScript file. This can happen in ES modules as TypeScript requires to import other + // TypeScript files with .js extensions + // https://www.typescriptlang.org/docs/handbook/esm-node.html#type-in-packagejson-and-new-extensions + const newId = ['.js', '.mjs', '.cjs'].includes(ext) + ? `${id.slice(0, -2)}ts` + : ext === '.jsx' + ? `${id.slice(0, -3)}tsx` + : null; + + if (!newId) { + throw error; + } + return resolve.sync(newId, { ...mergedOptions, extensions: [] }); + } +} diff --git a/code/core/src/common/utils/utils.ts b/code/core/src/common/utils/utils.ts new file mode 100644 index 000000000000..2aaccefe716e --- /dev/null +++ b/code/core/src/common/utils/utils.ts @@ -0,0 +1,26 @@ +// Object.groupBy polyfill +export const groupBy = ( + items: T[], + keySelector: (item: T, index: number) => K +) => { + return items.reduce>( + (acc, item, index) => { + const key = keySelector(item, index); + acc[key] ??= []; + acc[key].push(item); + return acc; + }, + {} as Record + ); +}; + +// This invariant allows for lazy evaluation of the message, which we need to avoid excessive computation. +export function invariant( + condition: unknown, + message?: string | (() => string) +): asserts condition { + if (condition) { + return; + } + throw new Error((typeof message === 'function' ? message() : message) ?? 'Invariant failed'); +} diff --git a/code/core/src/core-server/build-static.ts b/code/core/src/core-server/build-static.ts index 396915646d5f..ef1bc814288f 100644 --- a/code/core/src/core-server/build-static.ts +++ b/code/core/src/core-server/build-static.ts @@ -11,7 +11,7 @@ import { import { logger } from 'storybook/internal/node-logger'; import { getPrecedingUpgrade, telemetry } from 'storybook/internal/telemetry'; import type { BuilderOptions, CLIOptions, LoadOptions, Options } from 'storybook/internal/types'; -import { type ComponentManifestGenerator } from 'storybook/internal/types'; +import { type ComponentManifestGenerator, type ComponentsManifest } from 'storybook/internal/types'; import { global } from '@storybook/global'; @@ -19,6 +19,7 @@ import { join, relative, resolve } from 'pathe'; import picocolors from 'picocolors'; import { resolvePackageDir } from '../shared/utils/module'; +import { renderManifestComponentsPage } from './manifest'; import { StoryIndexGenerator } from './utils/StoryIndexGenerator'; import { buildOrThrow } from './utils/build-or-throw'; import { copyAllStaticFilesRelativeToMain } from './utils/copy-all-static-files'; @@ -180,6 +181,10 @@ export async function buildStaticStandalone(options: BuildStaticStandaloneOption join(options.outputDir, 'manifests', 'components.json'), JSON.stringify(manifests) ); + await writeFile( + join(options.outputDir, 'manifests', 'components.html'), + renderManifestComponentsPage(manifests) + ); } catch (e) { logger.error('Failed to generate manifests/components.json'); logger.error(e instanceof Error ? e : String(e)); diff --git a/code/core/src/core-server/dev-server.ts b/code/core/src/core-server/dev-server.ts index 8b25f5438d6a..b5ec0b04dc90 100644 --- a/code/core/src/core-server/dev-server.ts +++ b/code/core/src/core-server/dev-server.ts @@ -1,7 +1,7 @@ import { logConfig } from 'storybook/internal/common'; import { logger } from 'storybook/internal/node-logger'; import { MissingBuilderError } from 'storybook/internal/server-errors'; -import type { Options } from 'storybook/internal/types'; +import type { ComponentsManifest, Options } from 'storybook/internal/types'; import { type ComponentManifestGenerator } from 'storybook/internal/types'; import compression from '@polka/compression'; @@ -9,6 +9,7 @@ import polka from 'polka'; import invariant from 'tiny-invariant'; import { telemetry } from '../telemetry'; +import { renderManifestComponentsPage } from './manifest'; import { type StoryIndexGenerator } from './utils/StoryIndexGenerator'; import { doTelemetry } from './utils/doTelemetry'; import { getManagerBuilder, getPreviewBuilder } from './utils/get-builders'; @@ -165,6 +166,34 @@ export async function storybookDevServer(options: Options) { return; } }); + + app.get('/manifests/components.html', async (req, res) => { + try { + const componentManifestGenerator: ComponentManifestGenerator = await options.presets.apply( + 'experimental_componentManifestGenerator' + ); + const indexGenerator = await initializedStoryIndexGenerator; + + if (!componentManifestGenerator || !indexGenerator) { + res.statusCode = 400; + res.setHeader('Content-Type', 'text/html; charset=utf-8'); + res.end(`
No component manifest generator configured.
`); + return; + } + + const manifest = (await componentManifestGenerator( + indexGenerator as unknown as import('storybook/internal/core-server').StoryIndexGenerator + )) as ComponentsManifest; + + res.setHeader('Content-Type', 'text/html; charset=utf-8'); + res.end(renderManifestComponentsPage(manifest)); + } catch (e) { + // logger?.error?.(e instanceof Error ? e : String(e)); + res.statusCode = 500; + res.setHeader('Content-Type', 'text/html; charset=utf-8'); + res.end(`
${e instanceof Error ? e.toString() : String(e)}
`); + } + }); } // Now the preview has successfully started, we can count this as a 'dev' event. doTelemetry(app, core, initializedStoryIndexGenerator as Promise, options); diff --git a/code/core/src/core-server/manifest.ts b/code/core/src/core-server/manifest.ts new file mode 100644 index 000000000000..fb7f33677298 --- /dev/null +++ b/code/core/src/core-server/manifest.ts @@ -0,0 +1,710 @@ +import { groupBy } from 'storybook/internal/common'; + +import type { ComponentManifest, ComponentsManifest } from '../types'; + +// AI generated manifests/components.html page +// Only HTML/CSS no JS +export function renderManifestComponentsPage(manifest: ComponentsManifest) { + const entries = Object.entries(manifest?.components ?? {}).sort((a, b) => + (a[1].name || a[0]).localeCompare(b[1].name || b[0]) + ); + + const analyses = entries.map(([, c]) => analyzeComponent(c)); + const totals = { + components: entries.length, + componentsWithError: analyses.filter((a) => a.hasComponentError).length, + componentsWithWarnings: analyses.filter((a) => a.hasWarns).length, + examples: analyses.reduce((sum, a) => sum + a.totalExamples, 0), + exampleErrors: analyses.reduce((sum, a) => sum + a.exampleErrors, 0), + }; + + // Top filters (clickable), no tags; 1px active ring lives in CSS via :target + const allPill = `All`; + const compErrorsPill = + totals.componentsWithError > 0 + ? `${totals.componentsWithError}/${totals.components} component ${plural(totals.componentsWithError, 'error')}` + : `${totals.components} components ok`; + const compWarningsPill = + totals.componentsWithWarnings > 0 + ? `${totals.componentsWithWarnings}/${totals.components} component ${plural(totals.componentsWithWarnings, 'warning')}` + : ''; + const examplesPill = + totals.exampleErrors > 0 + ? `${totals.exampleErrors}/${totals.examples} example errors` + : `${totals.examples} examples ok`; + + const grid = entries.map(([key, c], idx) => renderComponentCard(key, c, `${idx}`)).join(''); + + const errorGroups = Object.entries( + groupBy( + entries.map(([, it]) => it).filter((it) => it.error), + (manifest) => manifest.error?.name ?? 'Error' + ) + ); + + const errorGroupsHTML = errorGroups + .map(([error, grouped]) => { + const id = error.toLowerCase().replace(/[^a-z0-9]+/g, '-'); + const headerText = `${esc(error)}`; + const cards = grouped + .map((manifest, id) => renderComponentCard(manifest.id, manifest, `error-${id}`)) + .join(''); + return ` +
+ + +
${cards}
+
+ `; + }) + .join(''); + + return ` + + + + + Components Manifest + + + + + + + + +
+
+

Components Manifest

+
${allPill}${compErrorsPill}${compWarningsPill}${examplesPill}
+
+
+
+
+
+ ${ + grid || + `
No components.
` + } +
+ ${ + errorGroups.length + ? `
${errorGroupsHTML}
` + : '' + } +
+
+ + `; +} + +const esc = (s: unknown) => + String(s ?? '').replace( + /[&<>"']/g, + (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[c] as string + ); +const plural = (n: number, one: string, many = `${one}s`) => (n === 1 ? one : many); + +function analyzeComponent(c: ComponentManifest) { + const hasComponentError = !!c.error; + const warns: string[] = []; + + if (!c.description?.trim()) { + warns.push( + 'No description found. Write a jsdoc comment such as /** Component description */ on your component or on your stories meta.' + ); + } + + if (!c.import?.trim()) { + warns.push( + `Specify an @import jsdoc tag on your component or your stories meta such as @import import { ${c.name} } from 'my-design-system';` + ); + } + + const totalExamples = c.examples?.length ?? 0; + const exampleErrors = (c.examples ?? []).filter((e) => !!e?.error).length; + const exampleOk = totalExamples - exampleErrors; + + const hasAnyError = hasComponentError || exampleErrors > 0; // for status dot (red if any errors) + + return { + hasComponentError, + hasAnyError, + hasWarns: warns.length > 0, + warns, + totalExamples, + exampleErrors, + exampleOk, + }; +} + +function note(title: string, bodyHTML: string, kind: 'warn' | 'err') { + return ` +
+
${esc(title)}
+
${bodyHTML}
+
`; +} + +function renderComponentCard(key: string, c: ComponentManifest, id: string) { + const a = analyzeComponent(c); + const statusDot = a.hasAnyError ? 'dot-err' : 'dot-ok'; + const errorExamples = (c.examples ?? []).filter((ex) => !!ex?.error); + + const slug = `c-${id}-${(c.id || key) + .toLowerCase() + .replace(/[^a-z0-9]+/g, '-') + .replace(/^-+|-+$/g, '')}`; + + const componentErrorBadge = a.hasComponentError + ? `` + : ''; + + const warningsBadge = a.hasWarns + ? `` + : ''; + + const examplesBadge = + a.exampleErrors > 0 + ? `` + : `${a.totalExamples} examples ok`; + + const tags = + c.jsDocTags && typeof c.jsDocTags === 'object' + ? Object.entries(c.jsDocTags) + .flatMap(([k, v]) => + (Array.isArray(v) ? v : [v]).map( + (val) => `${esc(k)}: ${esc(val)}` + ) + ) + .join('') + : ''; + + esc(c.error?.message || 'Unknown error'); + return ` +
+
+
+

${esc(c.name || key)}

+
+ ${componentErrorBadge} + ${warningsBadge} + ${examplesBadge} +
+
+
${esc(c.id)} · ${esc(c.path)}
+ ${c.summary ? `
${esc(c.summary)}
` : ''} + ${c.description ? `
${esc(c.description)}
` : ''} + ${tags ? `
${tags}
` : ''} +
+ + + ${a.hasComponentError ? `` : ''} + ${a.hasWarns ? `` : ''} + ${a.exampleErrors > 0 ? `` : ''} + +
+ ${ + a.hasComponentError + ? ` +
+ ${note('Component error', `
${esc(c.error?.message || 'Unknown error')}
`, 'err')} +
` + : '' + } + ${ + a.hasWarns + ? ` +
+ ${a.warns.map((w) => note('Warning', esc(w), 'warn')).join('')} +
` + : '' + } + ${ + a.exampleErrors > 0 + ? ` +
+ ${errorExamples + .map( + (ex, j) => ` +
+
+ + ${esc(ex?.name ?? `Example ${j + 1}`)} + example error +
+ ${ex?.snippet ? `
${esc(ex.snippet)}
` : ''} + ${ex?.error?.message ? `
${esc(ex.error.message)}
` : ''} +
` + ) + .join('')} +
` + : '' + } +
+
`; +} diff --git a/code/core/src/core-server/utils/StoryIndexGenerator.ts b/code/core/src/core-server/utils/StoryIndexGenerator.ts index 97849f187cc9..ce9689d65b36 100644 --- a/code/core/src/core-server/utils/StoryIndexGenerator.ts +++ b/code/core/src/core-server/utils/StoryIndexGenerator.ts @@ -29,6 +29,7 @@ import invariant from 'tiny-invariant'; import { dedent } from 'ts-dedent'; import * as TsconfigPaths from 'tsconfig-paths'; +import { resolveImport, supportedExtensions } from '../../common'; import { userOrAutoTitleFromSpecifier } from '../../preview-api/modules/store/autoTitle'; import { sortStoriesV7 } from '../../preview-api/modules/store/sortStories'; import { IndexingError, MultipleIndexingError } from './IndexingError'; @@ -378,21 +379,16 @@ export class StoryIndexGenerator { absolutePath: Path, matchPath: TsconfigPaths.MatchPath | undefined ) { - let rawPath = rawComponentPath; - if (matchPath) { - rawPath = matchPath(rawPath) ?? rawPath; - } - - const absoluteComponentPath = resolve(dirname(absolutePath), rawPath); - const existing = ['', '.js', '.ts', '.jsx', '.tsx', '.mjs', '.mts'] - .map((ext) => `${absoluteComponentPath}${ext}`) - .find((candidate) => existsSync(candidate)); - if (existing) { - const relativePath = relative(this.options.workingDir, existing); - return slash(normalizeStoryPath(relativePath)); + const matchedPath = + matchPath?.(rawComponentPath, undefined, undefined, supportedExtensions) ?? rawComponentPath; + let resolved; + try { + resolved = resolveImport(matchedPath, { basedir: dirname(absolutePath) }); + } catch (_) { + return matchedPath; } - - return rawComponentPath; + const relativePath = relative(this.options.workingDir, resolved); + return slash(normalizeStoryPath(relativePath)); } async extractStories( diff --git a/code/core/src/types/modules/core-common.ts b/code/core/src/types/modules/core-common.ts index 36063b164e73..4918d619c6c4 100644 --- a/code/core/src/types/modules/core-common.ts +++ b/code/core/src/types/modules/core-common.ts @@ -352,9 +352,9 @@ export interface ComponentManifest { description?: string; import?: string; summary?: string; - examples: { name: string; snippet?: string; error?: { message: string } }[]; + examples: { name: string; snippet?: string; error?: { name: string; message: string } }[]; jsDocTags: Record; - error?: { message: string }; + error?: { name: string; message: string }; } export interface ComponentsManifest { diff --git a/code/renderers/react/src/componentManifest/generateCodeSnippet.test.tsx b/code/renderers/react/src/componentManifest/generateCodeSnippet.test.tsx index 582343a7c0f5..2b4272a0a0c8 100644 --- a/code/renderers/react/src/componentManifest/generateCodeSnippet.test.tsx +++ b/code/renderers/react/src/componentManifest/generateCodeSnippet.test.tsx @@ -195,6 +195,17 @@ test('CSF2 - Template.bind', () => { ); }); +test('CSF2 - with args', () => { + const input = withCSF3(dedent` + const Template = (args) => ;"` + ); +}); + test('Custom Render', () => { const input = withCSF3(dedent` export const CustomRender: Story = { render: () => } diff --git a/code/renderers/react/src/componentManifest/generateCodeSnippet.ts b/code/renderers/react/src/componentManifest/generateCodeSnippet.ts index 583765a9b8d1..3afb9a1d62d9 100644 --- a/code/renderers/react/src/componentManifest/generateCodeSnippet.ts +++ b/code/renderers/react/src/componentManifest/generateCodeSnippet.ts @@ -139,7 +139,9 @@ export function getCodeSnippet( .map((p) => p.get('value')) .find((v) => v.isObjectExpression()); const storyArgs = argsRecordFromObjectPath(storyArgsPath); - const merged: Record = { ...metaArgs, ...storyArgs }; + const storyAssignedArgsPath = storyArgsAssignmentPath(csf._file.path, storyName); + const storyAssignedArgs = argsRecordFromObjectPath(storyAssignedArgsPath); + const merged: Record = { ...metaArgs, ...storyArgs, ...storyAssignedArgs }; // For no-function fallback const entries = Object.entries(merged).filter(([k]) => k !== 'children'); @@ -251,6 +253,32 @@ const argsRecordFromObjectPath = (objPath?: NodePath | null) ) : {}; +/** Find `StoryName.args = { ... }` assignment and return the right-hand ObjectExpression if present. */ +function storyArgsAssignmentPath( + program: NodePath, + storyName: string +): NodePath | null { + let found: NodePath | null = null; + program.traverse({ + AssignmentExpression(p) { + const left = p.get('left'); + const right = p.get('right'); + if (left.isMemberExpression()) { + const obj = left.get('object'); + const prop = left.get('property'); + const isStoryIdent = obj.isIdentifier() && obj.node.name === storyName; + const isArgsProp = + (prop.isIdentifier() && prop.node.name === 'args' && !left.node.computed) || + (t.isStringLiteral(prop.node) && left.node.computed && prop.node.value === 'args'); + if (isStoryIdent && isArgsProp && right.isObjectExpression()) { + found = right as NodePath; + } + } + }, + }); + return found; +} + const argsRecordFromObjectNode = (obj?: t.ObjectExpression | null) => obj ? Object.fromEntries( diff --git a/code/renderers/react/src/componentManifest/generator.test.ts b/code/renderers/react/src/componentManifest/generator.test.ts index 6196a2637956..c330f3c511fd 100644 --- a/code/renderers/react/src/componentManifest/generator.test.ts +++ b/code/renderers/react/src/componentManifest/generator.test.ts @@ -549,6 +549,7 @@ test('component exported from other file', async () => { 9 | > 10 | export { Primary } from './other-file'; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^", + "name": "SyntaxError", }, "name": "Primary", }, @@ -600,6 +601,7 @@ test('unknown expressions', async () => { 9 | > 10 | export const Primary = someWeirdExpression; | ^^^^^^^^^^^^^^^^^^^", + "name": "SyntaxError", }, "name": "Primary", }, diff --git a/code/renderers/react/src/componentManifest/generator.ts b/code/renderers/react/src/componentManifest/generator.ts index a08e63f1047f..63a7694af40a 100644 --- a/code/renderers/react/src/componentManifest/generator.ts +++ b/code/renderers/react/src/componentManifest/generator.ts @@ -50,6 +50,7 @@ export const componentManifestGenerator = async () => { return { name: storyName, error: { + name: e.name, message: e.message, }, }; @@ -66,12 +67,26 @@ export const componentManifestGenerator = async () => { } satisfies Partial; if (!entry.componentPath) { - const message = `No component file found for the "${name}" component.`; + const componentName = csf._meta?.component; + + const error = !componentName + ? { + name: 'No meta.component specified', + message: 'Specify meta.component for the component to be included in the manifest.', + } + : { + name: 'No component import found', + message: `No component file found for the "${componentName}" component.`, + }; return { ...base, name, examples, - error: { message }, + error: { + name: error.name, + message: + csf._metaStatementPath?.buildCodeFrameError(error.message).message ?? error.message, + }, }; } @@ -86,7 +101,8 @@ export const componentManifestGenerator = async () => { name, examples, error: { - message: `Could not read the component file located at ${entry.componentPath}`, + name: 'Component file could not be read', + message: `Could not read the component file located at "${entry.componentPath}".\nPrefer relative imports.`, }, }; } @@ -99,7 +115,13 @@ export const componentManifestGenerator = async () => { const error = !docgen ? { - message: `Could not parse props information for the located at ${entry.componentPath}`, + name: 'Docgen evaluation failed', + message: + `Could not parse props information for the component file located at "${entry.componentPath}"\n` + + `Avoid barrel files when importing your component file.\n` + + `Prefer relative imports if possible.\n` + + `Avoid pointing to transpiled files.\n` + + `You can debug your component file in this playground: https://react-docgen.dev/playground`, } : undefined; diff --git a/code/renderers/react/src/componentManifest/reactDocgen.ts b/code/renderers/react/src/componentManifest/reactDocgen.ts index 4770e958b1cb..8031d64e8a8b 100644 --- a/code/renderers/react/src/componentManifest/reactDocgen.ts +++ b/code/renderers/react/src/componentManifest/reactDocgen.ts @@ -3,6 +3,8 @@ import { sep } from 'node:path'; import { types as t } from 'storybook/internal/babel'; import { getProjectRoot } from 'storybook/internal/common'; +import { supportedExtensions } from 'storybook/internal/common'; +import { resolveImport } from 'storybook/internal/common'; import { type CsfFile } from 'storybook/internal/csf-tools'; import * as find from 'empathic/find'; @@ -16,11 +18,7 @@ import { import * as TsconfigPaths from 'tsconfig-paths'; import actualNameHandler from './reactDocgen/actualNameHandler'; -import { - RESOLVE_EXTENSIONS, - ReactDocgenResolveError, - defaultLookupModule, -} from './reactDocgen/docgenResolver'; +import { ReactDocgenResolveError } from './reactDocgen/docgenResolver'; import exportNameHandler from './reactDocgen/exportNameHandler'; export type DocObj = Documentation & { @@ -92,14 +90,14 @@ export function getReactDocgenImporter(matchPath: TsconfigPaths.MatchPath | unde return makeFsImporter((filename, basedir) => { const mappedFilenameByPaths = (() => { if (matchPath) { - const match = matchPath(filename); + const match = matchPath(filename, undefined, undefined, supportedExtensions); return match || filename; } else { return filename; } })(); - const result = defaultLookupModule(mappedFilenameByPaths, basedir); + const result = resolveImport(mappedFilenameByPaths, { basedir }); if (result.includes(`${sep}react-native${sep}index.js`)) { const replaced = result.replace( @@ -107,12 +105,12 @@ export function getReactDocgenImporter(matchPath: TsconfigPaths.MatchPath | unde `${sep}react-native-web${sep}dist${sep}index.js` ); if (existsSync(replaced)) { - if (RESOLVE_EXTENSIONS.find((ext) => result.endsWith(ext))) { + if (supportedExtensions.find((ext) => result.endsWith(ext))) { return replaced; } } } - if (RESOLVE_EXTENSIONS.find((ext) => result.endsWith(ext))) { + if (supportedExtensions.find((ext) => result.endsWith(ext))) { return result; } diff --git a/code/renderers/react/src/componentManifest/reactDocgen/docgenResolver.ts b/code/renderers/react/src/componentManifest/reactDocgen/docgenResolver.ts index f4ae37407c09..6b69ca55d7dd 100644 --- a/code/renderers/react/src/componentManifest/reactDocgen/docgenResolver.ts +++ b/code/renderers/react/src/componentManifest/reactDocgen/docgenResolver.ts @@ -1,5 +1,7 @@ import { extname } from 'node:path'; +import { supportedExtensions } from 'storybook/internal/common'; + import resolve from 'resolve'; export class ReactDocgenResolveError extends Error { @@ -14,30 +16,11 @@ export class ReactDocgenResolveError extends Error { /* The below code was copied from: * https://github.com/reactjs/react-docgen/blob/df2daa8b6f0af693ecc3c4dc49f2246f60552bcb/packages/react-docgen/src/importer/makeFsImporter.ts#L14-L63 * because it wasn't exported from the react-docgen package. - * watch out: when updating this code, also update the code in code/presets/react-webpack/src/loaders/docgen-resolver.ts */ - -// These extensions are sorted by priority -// resolve() will check for files in the order these extensions are sorted -export const RESOLVE_EXTENSIONS = [ - '.js', - '.cts', // These were originally not in the code, I added them - '.mts', // These were originally not in the code, I added them - '.ctsx', // These were originally not in the code, I added them - '.mtsx', // These were originally not in the code, I added them - '.ts', - '.tsx', - '.mjs', - '.cjs', - '.mts', - '.cts', - '.jsx', -]; - export function defaultLookupModule(filename: string, basedir: string): string { const resolveOptions = { basedir, - extensions: RESOLVE_EXTENSIONS, + extensions: supportedExtensions, // we do not need to check core modules as we cannot import them anyway includeCoreModules: false, }; diff --git a/code/yarn.lock b/code/yarn.lock index 6da9c97418cc..d6028e53f751 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -23100,6 +23100,19 @@ __metadata: languageName: node linkType: hard +"resolve@npm:^1.22.11": + version: 1.22.11 + resolution: "resolve@npm:1.22.11" + dependencies: + is-core-module: "npm:^2.16.1" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/f657191507530f2cbecb5815b1ee99b20741ea6ee02a59c57028e9ec4c2c8d7681afcc35febbd554ac0ded459db6f2d8153382c53a2f266cee2575e512674409 + languageName: node + linkType: hard + "resolve@npm:^2.0.0-next.5": version: 2.0.0-next.5 resolution: "resolve@npm:2.0.0-next.5" @@ -23126,6 +23139,19 @@ __metadata: languageName: node linkType: hard +"resolve@patch:resolve@npm%3A^1.22.11#optional!builtin": + version: 1.22.11 + resolution: "resolve@patch:resolve@npm%3A1.22.11#optional!builtin::version=1.22.11&hash=c3c19d" + dependencies: + is-core-module: "npm:^2.16.1" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/ee5b182f2e37cb1165465e58c6abc797fec0a80b5ba3231607beb4677db0c9291ac010c47cf092b6daa2b7f518d69a0e21888e7e2b633f68d501a874212a8c63 + languageName: node + linkType: hard + "resolve@patch:resolve@npm%3A^2.0.0-next.5#optional!builtin": version: 2.0.0-next.5 resolution: "resolve@patch:resolve@npm%3A2.0.0-next.5#optional!builtin::version=2.0.0-next.5&hash=c3c19d" @@ -24586,6 +24612,7 @@ __metadata: react-transition-group: "npm:^4.4.5" recast: "npm:^0.23.5" require-from-string: "npm:^2.0.2" + resolve: "npm:^1.22.11" resolve.exports: "npm:^2.0.3" semver: "npm:^7.6.2" sirv: "npm:^2.0.4"