From 534e49cfd0ba4c8bb778249638b92c6f49e70ef7 Mon Sep 17 00:00:00 2001 From: Tom Coleman Date: Mon, 30 Oct 2023 16:47:24 +1100 Subject: [PATCH 1/8] Initial run-through deleting all ssv6 code --- .../builder-vite/src/codegen-entries.ts | 48 - .../builder-vite/src/codegen-iframe-script.ts | 116 -- .../src/codegen-importfn-script.ts | 2 +- .../src/plugins/code-generator-plugin.ts | 17 +- .../src/preview/iframe-webpack.config.ts | 77 +- .../templates/virtualModuleEntry.template.js | 65 - .../templates/virtualModuleStory.template.js | 3 - .../core-server/src/__for-testing__/main.ts | 3 - code/lib/core-server/src/build-static.ts | 13 +- code/lib/core-server/src/dev-server.ts | 7 - .../core-server/src/presets/common-preset.ts | 1 - .../src/utils/StoryIndexGenerator.ts | 15 +- .../utils/__tests__/index-extraction.test.ts | 1 - .../src/utils/getStoryIndexGenerator.ts | 9 +- .../src/utils/stories-json.test.ts | 175 -- code/lib/csf-tools/src/CsfFile.ts | 4 +- code/lib/manager-api/src/modules/stories.ts | 6 +- .../lib/manager-api/src/tests/stories.test.ts | 1 - code/lib/preview-api/README-preview-web.md | 20 - code/lib/preview-api/src/client-api.ts | 4 - code/lib/preview-api/src/core-client.ts | 4 - code/lib/preview-api/src/index.ts | 18 - .../src/modules/client-api/ClientApi.test.ts | 55 - .../src/modules/client-api/ClientApi.ts | 375 ----- .../modules/client-api/StoryStoreFacade.ts | 256 --- .../src/modules/client-api/index.ts | 16 - .../src/modules/client-api/queryparams.ts | 17 - .../modules/core-client/executeLoadable.ts | 110 -- .../src/modules/core-client/index.ts | 5 - .../src/modules/core-client/start.test.ts | 1436 ----------------- .../src/modules/core-client/start.ts | 176 -- .../src/modules/preview-web/Preview.tsx | 14 +- .../PreviewWeb.integration.test.ts | 4 +- .../modules/preview-web/PreviewWeb.test.ts | 4 - .../preview-web/PreviewWithSelection.tsx | 30 +- code/lib/preview/README.md | 20 - code/lib/types/src/modules/core-common.ts | 10 - .../src/components/preview/Preview.tsx | 2 +- code/ui/manager/src/runtime.ts | 2 +- docs/api/main-config-features.md | 64 +- docs/api/main-config-stories.md | 2 +- docs/builders/webpack.md | 1 - 42 files changed, 49 insertions(+), 3159 deletions(-) delete mode 100644 code/builders/builder-vite/src/codegen-entries.ts delete mode 100644 code/builders/builder-vite/src/codegen-iframe-script.ts delete mode 100644 code/builders/builder-webpack5/templates/virtualModuleEntry.template.js delete mode 100644 code/builders/builder-webpack5/templates/virtualModuleStory.template.js delete mode 100644 code/lib/preview-api/src/client-api.ts delete mode 100644 code/lib/preview-api/src/core-client.ts delete mode 100644 code/lib/preview-api/src/modules/client-api/ClientApi.test.ts delete mode 100644 code/lib/preview-api/src/modules/client-api/ClientApi.ts delete mode 100644 code/lib/preview-api/src/modules/client-api/StoryStoreFacade.ts delete mode 100644 code/lib/preview-api/src/modules/client-api/index.ts delete mode 100644 code/lib/preview-api/src/modules/client-api/queryparams.ts delete mode 100644 code/lib/preview-api/src/modules/core-client/executeLoadable.ts delete mode 100644 code/lib/preview-api/src/modules/core-client/index.ts delete mode 100644 code/lib/preview-api/src/modules/core-client/start.test.ts delete mode 100644 code/lib/preview-api/src/modules/core-client/start.ts diff --git a/code/builders/builder-vite/src/codegen-entries.ts b/code/builders/builder-vite/src/codegen-entries.ts deleted file mode 100644 index 44c3163b1def..000000000000 --- a/code/builders/builder-vite/src/codegen-entries.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { loadPreviewOrConfigFile } from '@storybook/core-common'; -import type { Options } from '@storybook/types'; -import slash from 'slash'; -import { listStories } from './list-stories'; - -const absoluteFilesToImport = async ( - files: string[], - name: string, - normalizePath: (id: string) => string -) => - files - .map((el, i) => `import ${name ? `* as ${name}_${i} from ` : ''}'/@fs/${normalizePath(el)}'`) - .join('\n'); - -export async function generateVirtualStoryEntryCode(options: Options) { - const { normalizePath } = await import('vite'); - const storyEntries = await listStories(options); - const resolveMap = storyEntries.reduce>( - (prev, entry) => ({ ...prev, [entry]: entry.replace(slash(process.cwd()), '.') }), - {} - ); - const modules = storyEntries.map((entry, i) => `${JSON.stringify(entry)}: story_${i}`).join(','); - - return ` - ${await absoluteFilesToImport(storyEntries, 'story', normalizePath)} - - function loadable(key) { - return {${modules}}[key]; - } - - Object.assign(loadable, { - keys: () => (${JSON.stringify(Object.keys(resolveMap))}), - resolve: (key) => (${JSON.stringify(resolveMap)}[key]) - }); - - export function configStories(configure) { - configure(loadable, { hot: import.meta.hot }, false); - } - `.trim(); -} - -export async function generatePreviewEntryCode({ configDir }: Options) { - const previewFile = loadPreviewOrConfigFile({ configDir }); - if (!previewFile) return ''; - - return `import * as preview from '${slash(previewFile)}'; - export default preview;`; -} diff --git a/code/builders/builder-vite/src/codegen-iframe-script.ts b/code/builders/builder-vite/src/codegen-iframe-script.ts deleted file mode 100644 index 94400f57a017..000000000000 --- a/code/builders/builder-vite/src/codegen-iframe-script.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { getRendererName } from '@storybook/core-common'; -import type { Options, PreviewAnnotation } from '@storybook/types'; -import { virtualPreviewFile, virtualStoriesFile } from './virtual-file-names'; -import { processPreviewAnnotation } from './utils/process-preview-annotation'; - -export async function generateIframeScriptCode(options: Options, projectRoot: string) { - const { presets } = options; - const rendererName = await getRendererName(options); - - const previewAnnotations = await presets.apply( - 'previewAnnotations', - [], - options - ); - const configEntries = [...previewAnnotations] - .filter(Boolean) - .map((path) => processPreviewAnnotation(path, projectRoot)); - - const filesToImport = (files: string[], name: string) => - files.map((el, i) => `import ${name ? `* as ${name}_${i} from ` : ''}'${el}'`).join('\n'); - - const importArray = (name: string, length: number) => - new Array(length).fill(0).map((_, i) => `${name}_${i}`); - - // noinspection UnnecessaryLocalVariableJS - /** @todo Inline variable and remove `noinspection` */ - // language=JavaScript - const code = ` - // Ensure that the client API is initialized by the framework before any other iframe code - // is loaded. That way our client-apis can assume the existence of the API+store - import { configure } from '${rendererName}'; - - import { logger } from '@storybook/client-logger'; - import * as previewApi from "@storybook/preview-api"; - ${filesToImport(configEntries, 'config')} - - import * as preview from '${virtualPreviewFile}'; - import { configStories } from '${virtualStoriesFile}'; - - const { - addDecorator, - addParameters, - addLoader, - addArgs, - addArgTypes, - addStepRunner, - addArgTypesEnhancer, - addArgsEnhancer, - setGlobalRender, - } = previewApi; - - const configs = [${importArray('config', configEntries.length) - .concat('preview.default') - .join(',')}].filter(Boolean) - - configs.map(config => config.default ? config.default : config).forEach(config => { - Object.keys(config).forEach((key) => { - const value = config[key]; - switch (key) { - case 'args': { - return addArgs(value); - } - case 'argTypes': { - return addArgTypes(value); - } - case 'decorators': { - return value.forEach((decorator) => addDecorator(decorator, false)); - } - case 'loaders': { - return value.forEach((loader) => addLoader(loader, false)); - } - case 'parameters': { - return addParameters({ ...value }, false); - } - case 'argTypesEnhancers': { - return value.forEach((enhancer) => addArgTypesEnhancer(enhancer)); - } - case 'argsEnhancers': { - return value.forEach((enhancer) => addArgsEnhancer(enhancer)) - } - case 'render': { - return setGlobalRender(value) - } - case 'globals': - case 'globalTypes': { - const v = {}; - v[key] = value; - return addParameters(v, false); - } - case 'decorateStory': - case 'applyDecorators': - case 'renderToDOM': // deprecated - case 'renderToCanvas': { - return null; // This key is not handled directly in v6 mode. - } - case 'runStep': { - return addStepRunner(value); - } - default: { - // eslint-disable-next-line prefer-template - return console.log(key + ' was not supported :( !'); - } - } - }); - }) - - /* TODO: not quite sure what to do with this, to fix HMR - if (import.meta.hot) { - import.meta.hot.accept(); - } - */ - - configStories(configure); - `.trim(); - return code; -} diff --git a/code/builders/builder-vite/src/codegen-importfn-script.ts b/code/builders/builder-vite/src/codegen-importfn-script.ts index 5df14d875f25..85db4dc2954c 100644 --- a/code/builders/builder-vite/src/codegen-importfn-script.ts +++ b/code/builders/builder-vite/src/codegen-importfn-script.ts @@ -31,7 +31,7 @@ async function toImportFn(stories: string[]) { const ext = path.extname(file); const relativePath = normalizePath(path.relative(process.cwd(), file)); if (!['.js', '.jsx', '.ts', '.tsx', '.mdx', '.svelte', '.vue'].includes(ext)) { - logger.warn(`Cannot process ${ext} file with storyStoreV7: ${relativePath}`); + logger.warn(`Cannot process ${ext} file: ${relativePath}`); } return ` '${toImportPath(relativePath)}': async () => import('/@fs/${file}')`; diff --git a/code/builders/builder-vite/src/plugins/code-generator-plugin.ts b/code/builders/builder-vite/src/plugins/code-generator-plugin.ts index c27d7c73ea4a..2aa64057592b 100644 --- a/code/builders/builder-vite/src/plugins/code-generator-plugin.ts +++ b/code/builders/builder-vite/src/plugins/code-generator-plugin.ts @@ -4,10 +4,8 @@ import * as fs from 'fs'; import type { Plugin } from 'vite'; import type { Options } from '@storybook/types'; import { transformIframeHtml } from '../transform-iframe-html'; -import { generateIframeScriptCode } from '../codegen-iframe-script'; import { generateModernIframeScriptCode } from '../codegen-modern-iframe-script'; import { generateImportFnScriptCode } from '../codegen-importfn-script'; -import { generateVirtualStoryEntryCode, generatePreviewEntryCode } from '../codegen-entries'; import { generateAddonSetupCode } from '../codegen-set-addon-channel'; import { @@ -90,27 +88,16 @@ export function codeGeneratorPlugin(options: Options): Plugin { return undefined; }, async load(id, config) { - const storyStoreV7 = options.features?.storyStoreV7; if (id === virtualStoriesFile) { - if (storyStoreV7) { - return generateImportFnScriptCode(options); - } - return generateVirtualStoryEntryCode(options); + return generateImportFnScriptCode(options); } if (id === virtualAddonSetupFile) { return generateAddonSetupCode(); } - if (id === virtualPreviewFile && !storyStoreV7) { - return generatePreviewEntryCode(options); - } - if (id === virtualFileId) { - if (storyStoreV7) { - return generateModernIframeScriptCode(options, projectRoot); - } - return generateIframeScriptCode(options, projectRoot); + return generateModernIframeScriptCode(options, projectRoot); } if (id === iframeId) { diff --git a/code/builders/builder-webpack5/src/preview/iframe-webpack.config.ts b/code/builders/builder-webpack5/src/preview/iframe-webpack.config.ts index 55e0d733aa9e..003839dccd40 100644 --- a/code/builders/builder-webpack5/src/preview/iframe-webpack.config.ts +++ b/code/builders/builder-webpack5/src/preview/iframe-webpack.config.ts @@ -132,68 +132,25 @@ export default async ( ].filter(Boolean); const virtualModuleMapping: Record = {}; - if (features?.storyStoreV7) { - const storiesFilename = 'storybook-stories.js'; - const storiesPath = resolve(join(workingDir, storiesFilename)); + const storiesFilename = 'storybook-stories.js'; + const storiesPath = resolve(join(workingDir, storiesFilename)); - const needPipelinedImport = !!builderOptions.lazyCompilation && !isProd; - virtualModuleMapping[storiesPath] = toImportFn(stories, { needPipelinedImport }); - const configEntryPath = resolve(join(workingDir, 'storybook-config-entry.js')); - virtualModuleMapping[configEntryPath] = handlebars( - await readTemplate( - require.resolve( - '@storybook/builder-webpack5/templates/virtualModuleModernEntry.js.handlebars' - ) - ), - { - storiesFilename, - previewAnnotations, - } - // We need to double escape `\` for webpack. We may have some in windows paths - ).replace(/\\/g, '\\\\'); - entries.push(configEntryPath); - } else { - const rendererName = await getRendererName(options); - - const rendererInitEntry = resolve(join(workingDir, 'storybook-init-renderer-entry.js')); - virtualModuleMapping[rendererInitEntry] = `import '${slash(rendererName)}';`; - entries.push(rendererInitEntry); - - const entryTemplate = await readTemplate( - join(__dirname, '..', '..', 'templates', 'virtualModuleEntry.template.js') - ); - - previewAnnotations.forEach((previewAnnotationFilename: string | undefined) => { - if (!previewAnnotationFilename) return; - - // Ensure that relative paths end up mapped to a filename in the cwd, so a later import - // of the `previewAnnotationFilename` in the template works. - const entryFilename = previewAnnotationFilename.startsWith('.') - ? `${previewAnnotationFilename.replace(/(\w)(\/|\\)/g, '$1-')}-generated-config-entry.js` - : `${previewAnnotationFilename}-generated-config-entry.js`; - // NOTE: although this file is also from the `dist/cjs` directory, it is actually a ESM - // file, see https://github.com/storybookjs/storybook/pull/16727#issuecomment-986485173 - virtualModuleMapping[entryFilename] = interpolate(entryTemplate, { - previewAnnotationFilename, - }); - entries.push(entryFilename); - }); - if (stories.length > 0) { - const storyTemplate = await readTemplate( - join(__dirname, '..', '..', 'templates', 'virtualModuleStory.template.js') - ); - // NOTE: this file has a `.cjs` extension as it is a CJS file (from `dist/cjs`) and runs - // in the user's webpack mode, which may be strict about the use of require/import. - // See https://github.com/storybookjs/storybook/issues/14877 - const storiesFilename = resolve(join(workingDir, `generated-stories-entry.cjs`)); - virtualModuleMapping[storiesFilename] = interpolate(storyTemplate, { - rendererName, - }) - // Make sure we also replace quotes for this one - .replace("'{{stories}}'", stories.map(toRequireContextString).join(',')); - entries.push(storiesFilename); + const needPipelinedImport = !!builderOptions.lazyCompilation && !isProd; + virtualModuleMapping[storiesPath] = toImportFn(stories, { needPipelinedImport }); + const configEntryPath = resolve(join(workingDir, 'storybook-config-entry.js')); + virtualModuleMapping[configEntryPath] = handlebars( + await readTemplate( + require.resolve( + '@storybook/builder-webpack5/templates/virtualModuleModernEntry.js.handlebars' + ) + ), + { + storiesFilename, + previewAnnotations, } - } + // We need to double escape `\` for webpack. We may have some in windows paths + ).replace(/\\/g, '\\\\'); + entries.push(configEntryPath); const shouldCheckTs = typescriptOptions.check && !typescriptOptions.skipBabel; const tsCheckOptions = typescriptOptions.checkOptions || {}; diff --git a/code/builders/builder-webpack5/templates/virtualModuleEntry.template.js b/code/builders/builder-webpack5/templates/virtualModuleEntry.template.js deleted file mode 100644 index 083a3c5f16c4..000000000000 --- a/code/builders/builder-webpack5/templates/virtualModuleEntry.template.js +++ /dev/null @@ -1,65 +0,0 @@ -/* eslint-disable import/no-unresolved */ -import { - addDecorator, - addParameters, - addLoader, - addArgs, - addArgTypes, - addStepRunner, - addArgsEnhancer, - addArgTypesEnhancer, - setGlobalRender, -} from '@storybook/preview-api'; -import * as previewAnnotations from '{{previewAnnotationFilename}}'; - -const config = previewAnnotations.default ?? previewAnnotations; - -Object.keys(config).forEach((key) => { - const value = config[key]; - switch (key) { - case 'args': { - return addArgs(value); - } - case 'argTypes': { - return addArgTypes(value); - } - case 'decorators': { - return value.forEach((decorator) => addDecorator(decorator, false)); - } - case 'loaders': { - return value.forEach((loader) => addLoader(loader, false)); - } - case 'parameters': { - return addParameters({ ...value }, false); - } - case 'argTypesEnhancers': { - return value.forEach((enhancer) => addArgTypesEnhancer(enhancer)); - } - case 'argsEnhancers': { - return value.forEach((enhancer) => addArgsEnhancer(enhancer)); - } - case 'render': { - return setGlobalRender(value); - } - case 'globals': - case 'globalTypes': { - const v = {}; - v[key] = value; - return addParameters(v, false); - } - case '__namedExportsOrder': - case 'decorateStory': - case 'renderToDOM': // deprecated - case 'renderToCanvas': { - return null; // This key is not handled directly in v6 mode. - } - case 'runStep': { - return addStepRunner(value); - } - default: { - return console.log( - `Unknown key '${key}' exported by preview annotation file '{{previewAnnotationFilename}}'` - ); - } - } -}); diff --git a/code/builders/builder-webpack5/templates/virtualModuleStory.template.js b/code/builders/builder-webpack5/templates/virtualModuleStory.template.js deleted file mode 100644 index f7a668cab55f..000000000000 --- a/code/builders/builder-webpack5/templates/virtualModuleStory.template.js +++ /dev/null @@ -1,3 +0,0 @@ -const { configure } = require('{{rendererName}}'); - -configure(['{{stories}}'], module, false); diff --git a/code/lib/core-server/src/__for-testing__/main.ts b/code/lib/core-server/src/__for-testing__/main.ts index 988f5ba319fd..2643a4582bd5 100644 --- a/code/lib/core-server/src/__for-testing__/main.ts +++ b/code/lib/core-server/src/__for-testing__/main.ts @@ -29,9 +29,6 @@ const config: StorybookConfig = { disableTelemetry: true, }, logLevel: 'debug', - features: { - storyStoreV7: false, - }, framework: { name: '@storybook/react-webpack5', options: { diff --git a/code/lib/core-server/src/build-static.ts b/code/lib/core-server/src/build-static.ts index 55dc59fbaac1..26ad52d47626 100644 --- a/code/lib/core-server/src/build-static.ts +++ b/code/lib/core-server/src/build-static.ts @@ -124,13 +124,6 @@ export async function buildStaticStandalone(options: BuildStaticStandaloneOption presets.apply('docs', {}), ]); - if (features?.storyStoreV7 === false) { - deprecate( - dedent`storyStoreV6 is deprecated, please migrate to storyStoreV7 instead. - - Refer to the migration guide at https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#storystorev6-and-storiesof-is-deprecated` - ); - } - const fullOptions: Options = { ...options, presets, @@ -164,7 +157,7 @@ export async function buildStaticStandalone(options: BuildStaticStandaloneOption let initializedStoryIndexGenerator: Promise = Promise.resolve(undefined); - if ((features?.buildStoriesJson || features?.storyStoreV7) && !options.ignorePreview) { + if (!options.ignorePreview) { const workingDir = process.cwd(); const directories = { configDir: options.configDir, @@ -176,8 +169,8 @@ export async function buildStaticStandalone(options: BuildStaticStandaloneOption storyIndexers: deprecatedStoryIndexers, indexers, docs: docsOptions, - storiesV2Compatibility: !features?.storyStoreV7, - storyStoreV7: !!features?.storyStoreV7, + storiesV2Compatibility: false, + storyStoreV7: true, }); initializedStoryIndexGenerator = generator.initialize().then(() => generator); diff --git a/code/lib/core-server/src/dev-server.ts b/code/lib/core-server/src/dev-server.ts index 1e8de546880e..97993a1dcc17 100644 --- a/code/lib/core-server/src/dev-server.ts +++ b/code/lib/core-server/src/dev-server.ts @@ -38,13 +38,6 @@ export async function storybookDevServer(options: Options) { getServerChannel(server) ); - if (features?.storyStoreV7 === false) { - deprecate( - dedent`storyStoreV6 is deprecated, please migrate to storyStoreV7 instead. - - Refer to the migration guide at https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#storystorev6-and-storiesof-is-deprecated` - ); - } - let indexError: Error | undefined; // try get index generator, if failed, send telemetry without storyCount, then rethrow the error const initializedStoryIndexGenerator: Promise = diff --git a/code/lib/core-server/src/presets/common-preset.ts b/code/lib/core-server/src/presets/common-preset.ts index 347713e03aaf..5e50b89defff 100644 --- a/code/lib/core-server/src/presets/common-preset.ts +++ b/code/lib/core-server/src/presets/common-preset.ts @@ -190,7 +190,6 @@ export const features = async ( ...existing, warnOnLegacyHierarchySeparator: true, buildStoriesJson: false, - storyStoreV7: true, argTypeTargetsV7: true, legacyDecoratorFileOrder: false, }); diff --git a/code/lib/core-server/src/utils/StoryIndexGenerator.ts b/code/lib/core-server/src/utils/StoryIndexGenerator.ts index 0f847b8917a1..4ed00b9ebb09 100644 --- a/code/lib/core-server/src/utils/StoryIndexGenerator.ts +++ b/code/lib/core-server/src/utils/StoryIndexGenerator.ts @@ -429,11 +429,6 @@ export class StoryIndexGenerator { async extractDocs(specifier: NormalizedStoriesSpecifier, absolutePath: Path) { const relativePath = path.relative(this.options.workingDir, absolutePath); try { - invariant( - this.options.storyStoreV7, - `You cannot use \`.mdx\` files without using \`storyStoreV7\`.` - ); - const normalizedPath = normalizeStoryPath(relativePath); const importPath = slash(normalizedPath); @@ -613,13 +608,9 @@ export class StoryIndexGenerator { async sortStories(entries: StoryIndex['entries']) { const sortableStories = Object.values(entries); - // Skip sorting if we're in v6 mode because we don't have - // all the info we need here - if (this.options.storyStoreV7) { - const storySortParameter = await this.getStorySortParameter(); - const fileNameOrder = this.storyFileNames(); - sortStoriesV7(sortableStories, storySortParameter, fileNameOrder); - } + const storySortParameter = await this.getStorySortParameter(); + const fileNameOrder = this.storyFileNames(); + sortStoriesV7(sortableStories, storySortParameter, fileNameOrder); return sortableStories.reduce((acc, item) => { acc[item.id] = item; diff --git a/code/lib/core-server/src/utils/__tests__/index-extraction.test.ts b/code/lib/core-server/src/utils/__tests__/index-extraction.test.ts index 39820b3e2c17..84ff37e2219e 100644 --- a/code/lib/core-server/src/utils/__tests__/index-extraction.test.ts +++ b/code/lib/core-server/src/utils/__tests__/index-extraction.test.ts @@ -19,7 +19,6 @@ const options: StoryIndexGeneratorOptions = { storyIndexers: [], indexers: [], storiesV2Compatibility: false, - storyStoreV7: true, docs: { defaultName: 'docs', autodocs: false }, }; diff --git a/code/lib/core-server/src/utils/getStoryIndexGenerator.ts b/code/lib/core-server/src/utils/getStoryIndexGenerator.ts index 952edb507824..9153fb0a5bb0 100644 --- a/code/lib/core-server/src/utils/getStoryIndexGenerator.ts +++ b/code/lib/core-server/src/utils/getStoryIndexGenerator.ts @@ -7,17 +7,12 @@ import { router } from './router'; export async function getStoryIndexGenerator( features: { - buildStoriesJson?: boolean; - storyStoreV7?: boolean; argTypeTargetsV7?: boolean; warnOnLegacyHierarchySeparator?: boolean; }, options: Options, serverChannel: ServerChannel ): Promise { - if (!features?.buildStoriesJson && !features?.storyStoreV7) { - return undefined; - } const workingDir = process.cwd(); const directories = { configDir: options.configDir, @@ -35,8 +30,8 @@ export async function getStoryIndexGenerator( indexers: await indexers, docs: await docsOptions, workingDir, - storiesV2Compatibility: !features?.storyStoreV7, - storyStoreV7: features.storyStoreV7 ?? false, + storiesV2Compatibility: false, // FIXME -- drop this + storyStoreV7: true, // FIXME -- drop this }); const initializedStoryIndexGenerator = generator.initialize().then(() => generator); diff --git a/code/lib/core-server/src/utils/stories-json.test.ts b/code/lib/core-server/src/utils/stories-json.test.ts index e1f0b1f6d613..ad5f6064f158 100644 --- a/code/lib/core-server/src/utils/stories-json.test.ts +++ b/code/lib/core-server/src/utils/stories-json.test.ts @@ -710,181 +710,6 @@ describe('useStoriesJson', () => { `); }); - it('disallows .mdx files without storyStoreV7', async () => { - const mockServerChannel = { emit: jest.fn() } as any as ServerChannel; - useStoriesJson({ - router, - initializedStoryIndexGenerator: getInitializedStoryIndexGenerator({ - storyStoreV7: false, - }), - workingDir, - serverChannel: mockServerChannel, - normalizedStories, - }); - - expect(use).toHaveBeenCalledTimes(2); - const route = use.mock.calls[1][1]; - - await route(request, response); - - expect(send).toHaveBeenCalledTimes(1); - expect(send.mock.calls[0][0]).toMatchInlineSnapshot(` - "Unable to index files: - - ./src/docs2/ComponentReference.mdx: Invariant failed: You cannot use \`.mdx\` files without using \`storyStoreV7\`. - - ./src/docs2/MetaOf.mdx: Invariant failed: You cannot use \`.mdx\` files without using \`storyStoreV7\`. - - ./src/docs2/NoTitle.mdx: Invariant failed: You cannot use \`.mdx\` files without using \`storyStoreV7\`. - - ./src/docs2/SecondMetaOf.mdx: Invariant failed: You cannot use \`.mdx\` files without using \`storyStoreV7\`. - - ./src/docs2/Template.mdx: Invariant failed: You cannot use \`.mdx\` files without using \`storyStoreV7\`. - - ./src/docs2/Title.mdx: Invariant failed: You cannot use \`.mdx\` files without using \`storyStoreV7\`." - `); - }); - - it('allows disabling storyStoreV7 if no .mdx files are used', async () => { - const mockServerChannel = { emit: jest.fn() } as any as ServerChannel; - useStoriesJson({ - router, - initializedStoryIndexGenerator: getInitializedStoryIndexGenerator( - { storyStoreV7: false }, - normalizedStories.slice(0, 1) - ), - workingDir, - serverChannel: mockServerChannel, - normalizedStories, - }); - - expect(use).toHaveBeenCalledTimes(2); - const route = use.mock.calls[1][1]; - - await route(request, response); - - expect(send).toHaveBeenCalledTimes(1); - expect(JSON.parse(send.mock.calls[0][0])).toMatchInlineSnapshot(` - Object { - "stories": Object { - "a--story-one": Object { - "id": "a--story-one", - "importPath": "./src/A.stories.js", - "kind": "A", - "name": "Story One", - "parameters": Object { - "__id": "a--story-one", - "docsOnly": false, - "fileName": "./src/A.stories.js", - }, - "story": "Story One", - "tags": Array [ - "component-tag", - "story-tag", - "story", - ], - "title": "A", - }, - "b--story-one": Object { - "id": "b--story-one", - "importPath": "./src/B.stories.ts", - "kind": "B", - "name": "Story One", - "parameters": Object { - "__id": "b--story-one", - "docsOnly": false, - "fileName": "./src/B.stories.ts", - }, - "story": "Story One", - "tags": Array [ - "autodocs", - "story", - ], - "title": "B", - }, - "d--story-one": Object { - "id": "d--story-one", - "importPath": "./src/D.stories.jsx", - "kind": "D", - "name": "Story One", - "parameters": Object { - "__id": "d--story-one", - "docsOnly": false, - "fileName": "./src/D.stories.jsx", - }, - "story": "Story One", - "tags": Array [ - "autodocs", - "story", - ], - "title": "D", - }, - "first-nested-deeply-f--story-one": Object { - "id": "first-nested-deeply-f--story-one", - "importPath": "./src/first-nested/deeply/F.stories.js", - "kind": "first-nested/deeply/F", - "name": "Story One", - "parameters": Object { - "__id": "first-nested-deeply-f--story-one", - "docsOnly": false, - "fileName": "./src/first-nested/deeply/F.stories.js", - }, - "story": "Story One", - "tags": Array [ - "story", - ], - "title": "first-nested/deeply/F", - }, - "h--story-one": Object { - "id": "h--story-one", - "importPath": "./src/H.stories.mjs", - "kind": "H", - "name": "Story One", - "parameters": Object { - "__id": "h--story-one", - "docsOnly": false, - "fileName": "./src/H.stories.mjs", - }, - "story": "Story One", - "tags": Array [ - "autodocs", - "story", - ], - "title": "H", - }, - "nested-button--story-one": Object { - "id": "nested-button--story-one", - "importPath": "./src/nested/Button.stories.ts", - "kind": "nested/Button", - "name": "Story One", - "parameters": Object { - "__id": "nested-button--story-one", - "docsOnly": false, - "fileName": "./src/nested/Button.stories.ts", - }, - "story": "Story One", - "tags": Array [ - "component-tag", - "story", - ], - "title": "nested/Button", - }, - "second-nested-g--story-one": Object { - "id": "second-nested-g--story-one", - "importPath": "./src/second-nested/G.stories.ts", - "kind": "second-nested/G", - "name": "Story One", - "parameters": Object { - "__id": "second-nested-g--story-one", - "docsOnly": false, - "fileName": "./src/second-nested/G.stories.ts", - }, - "story": "Story One", - "tags": Array [ - "story", - ], - "title": "second-nested/G", - }, - }, - "v": 3, - } - `); - }); - it('can handle simultaneous access', async () => { const mockServerChannel = { emit: jest.fn() } as any as ServerChannel; diff --git a/code/lib/csf-tools/src/CsfFile.ts b/code/lib/csf-tools/src/CsfFile.ts index 15ab39abdc45..a8aa2aed903d 100644 --- a/code/lib/csf-tools/src/CsfFile.ts +++ b/code/lib/csf-tools/src/CsfFile.ts @@ -455,8 +455,8 @@ export class CsfFile { throw new Error(dedent` Unexpected \`storiesOf\` usage: ${formatLocation(node, self._fileName)}. - In SB7, we use the next-generation \`storyStoreV7\` by default, which does not support \`storiesOf\`. - More info, with details about how to opt-out here: https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#storystorev7-enabled-by-default + In SB8, we no longer support \`storiesOf\`. + More info, with details about how to migrate here: https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#FIXME `); } }, diff --git a/code/lib/manager-api/src/modules/stories.ts b/code/lib/manager-api/src/modules/stories.ts index 07d6fb702d30..08019e849bef 100644 --- a/code/lib/manager-api/src/modules/stories.ts +++ b/code/lib/manager-api/src/modules/stories.ts @@ -814,10 +814,8 @@ export const init: ModuleFn = ({ filters: config?.sidebar?.filters || {}, }, init: async () => { - if (FEATURES?.storyStoreV7) { - provider.channel.on(STORY_INDEX_INVALIDATED, () => api.fetchIndex()); - await api.fetchIndex(); - } + provider.channel.on(STORY_INDEX_INVALIDATED, () => api.fetchIndex()); + await api.fetchIndex(); }, }; }; diff --git a/code/lib/manager-api/src/tests/stories.test.ts b/code/lib/manager-api/src/tests/stories.test.ts index a6617bce9f41..c84d80044734 100644 --- a/code/lib/manager-api/src/tests/stories.test.ts +++ b/code/lib/manager-api/src/tests/stories.test.ts @@ -40,7 +40,6 @@ jest.mock('@storybook/global', () => ({ global: { ...globalThis, fetch: jest.fn(() => ({ json: () => ({ v: 4, entries: mockGetEntries() }) })), - FEATURES: { storyStoreV7: true }, CONFIG_TYPE: 'DEVELOPMENT', }, })); diff --git a/code/lib/preview-api/README-preview-web.md b/code/lib/preview-api/README-preview-web.md index cb44e3b4b07e..e5c237775ce0 100644 --- a/code/lib/preview-api/README-preview-web.md +++ b/code/lib/preview-api/README-preview-web.md @@ -10,34 +10,14 @@ The preview's job is: 3. Render the current selection to the web view in either story or docs mode. -## V7 Store vs Legacy (V6) - -The story store is designed to load stories 'on demand', and will operate in this fashion if the `storyStoreV7` feature is enabled. - -However, for back-compat reasons, in v6 mode, we need to load all stories, synchronously on bootup, emitting the `SET_STORIES` event. - -In V7 mode we do not emit that event, instead preferring the `STORY_PREPARED` event, with the data for the single story being rendered. - ## Initialization -The preview is `initialized` in two ways. - -### V7 Mode: - - `importFn` - is an async `import()` function - `getProjectAnnotations` - is a simple function that evaluations `preview.js` and addon config files and combines them. If it errors, the Preview will show the error. - No `getStoryIndex` function is passed, instead the preview creates a `StoryIndexClient` that pulls `stories.json` from node and watches the event stream for invalidation events. -### V6 Mode - -- `importFn` - is a simulated `import()` function, that is synchronous, see `client-api` for details. -- `getProjectAnnotations` - also evaluates `preview.js` et al, but watches for calls to `setStories`, and passes them to the `ClientApi` -- `getStoryIndex` is a local function (that must be called _after_ `getProjectAnnotations`) that gets the list of stories added. - -See `client-api` for more details on this process. - ## Story Rendering and interruptions The Preview is split into three parts responsible for state management: diff --git a/code/lib/preview-api/src/client-api.ts b/code/lib/preview-api/src/client-api.ts deleted file mode 100644 index c133a47e3570..000000000000 --- a/code/lib/preview-api/src/client-api.ts +++ /dev/null @@ -1,4 +0,0 @@ -/* eslint-disable @typescript-eslint/triple-slash-reference */ -/// - -export * from './modules/client-api'; diff --git a/code/lib/preview-api/src/core-client.ts b/code/lib/preview-api/src/core-client.ts deleted file mode 100644 index 18db05321a65..000000000000 --- a/code/lib/preview-api/src/core-client.ts +++ /dev/null @@ -1,4 +0,0 @@ -/* eslint-disable @typescript-eslint/triple-slash-reference */ -/// - -export * from './modules/core-client'; diff --git a/code/lib/preview-api/src/index.ts b/code/lib/preview-api/src/index.ts index db1f05d29870..2abcec8de9e1 100644 --- a/code/lib/preview-api/src/index.ts +++ b/code/lib/preview-api/src/index.ts @@ -41,22 +41,6 @@ export { DocsContext } from './preview-web'; */ export { simulatePageLoad, simulateDOMContentLoaded } from './preview-web'; -/** - * STORIES API - */ -export { - addArgTypes, - addArgTypesEnhancer, - addArgs, - addArgsEnhancer, - addDecorator, - addLoader, - addParameters, - addStepRunner, -} from './client-api'; -export { getQueryParam, getQueryParams } from './client-api'; -export { setGlobalRender } from './client-api'; - export { combineArgs, combineParameters, @@ -83,7 +67,5 @@ export type { PropDescriptor } from './store'; /** * STORIES API */ -export { ClientApi } from './client-api'; export { StoryStore } from './store'; export { Preview, PreviewWeb } from './preview-web'; -export { start } from './core-client'; diff --git a/code/lib/preview-api/src/modules/client-api/ClientApi.test.ts b/code/lib/preview-api/src/modules/client-api/ClientApi.test.ts deleted file mode 100644 index 65f4d718a9ed..000000000000 --- a/code/lib/preview-api/src/modules/client-api/ClientApi.test.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { addons, mockChannel } from '../addons'; -import { ClientApi } from './ClientApi'; - -beforeEach(() => { - addons.setChannel(mockChannel()); -}); - -describe('ClientApi', () => { - describe('getStoryIndex', () => { - it('should remember the order that files were added in', async () => { - const clientApi = new ClientApi(); - const store = { - processCSFFileWithCache: jest.fn(() => ({ meta: { title: 'title' } })), - storyFromCSFFile: jest.fn(({ storyId }) => ({ - id: storyId, - parameters: { fileName: storyId.split('-')[0].replace('kind', 'file') }, - })), - }; - clientApi.storyStore = store as any; - - let disposeCallback: () => void = () => {}; - const module1 = { - id: 'file1', - hot: { - data: {}, - accept: jest.fn(), - dispose(cb: () => void) { - disposeCallback = cb; - }, - }, - }; - const module2 = { - id: 'file2', - }; - clientApi.storiesOf('kind1', module1 as unknown as NodeModule).add('story1', jest.fn()); - clientApi.storiesOf('kind2', module2 as unknown as NodeModule).add('story2', jest.fn()); - // This gets called by configure - // eslint-disable-next-line no-underscore-dangle - clientApi._loadAddedExports(); - - expect(Object.keys(clientApi.getStoryIndex().entries)).toEqual([ - 'kind1--story1', - 'kind2--story2', - ]); - - disposeCallback(); - clientApi.storiesOf('kind1', module1 as unknown as NodeModule).add('story1', jest.fn()); - await new Promise((r) => setTimeout(r, 0)); - expect(Object.keys(clientApi.getStoryIndex().entries)).toEqual([ - 'kind1--story1', - 'kind2--story2', - ]); - }); - }); -}); diff --git a/code/lib/preview-api/src/modules/client-api/ClientApi.ts b/code/lib/preview-api/src/modules/client-api/ClientApi.ts deleted file mode 100644 index 83a2ba0cd6b0..000000000000 --- a/code/lib/preview-api/src/modules/client-api/ClientApi.ts +++ /dev/null @@ -1,375 +0,0 @@ -/* eslint-disable no-underscore-dangle */ - -import { dedent } from 'ts-dedent'; -import { global } from '@storybook/global'; -import { logger } from '@storybook/client-logger'; -import { toId, sanitize } from '@storybook/csf'; -import type { - Args, - StepRunner, - ArgTypes, - Renderer, - DecoratorFunction, - Parameters, - ArgTypesEnhancer, - ArgsEnhancer, - LoaderFunction, - StoryFn, - Globals, - GlobalTypes, - Addon_ClientApiAddons, - Addon_StoryApi, - NormalizedComponentAnnotations, - Path, - ModuleImportFn, - ModuleExports, -} from '@storybook/types'; -import type { StoryStore } from '../../store'; -import { combineParameters, composeStepRunners, normalizeInputTypes } from '../../store'; - -import { StoryStoreFacade } from './StoryStoreFacade'; - -const warningAlternatives = { - addDecorator: `Instead, use \`export const decorators = [];\` in your \`preview.js\`.`, - addParameters: `Instead, use \`export const parameters = {};\` in your \`preview.js\`.`, - addLoader: `Instead, use \`export const loaders = [];\` in your \`preview.js\`.`, - addArgs: '', - addArgTypes: '', - addArgsEnhancer: '', - addArgTypesEnhancer: '', - addStepRunner: '', - getGlobalRender: '', - setGlobalRender: '', -}; - -const checkMethod = (method: keyof typeof warningAlternatives) => { - if (global.FEATURES?.storyStoreV7) { - throw new Error( - dedent`You cannot use \`${method}\` with the new Story Store. - - ${warningAlternatives[method]}` - ); - } - - if (!global.__STORYBOOK_CLIENT_API__) { - throw new Error(`Singleton client API not yet initialized, cannot call \`${method}\`.`); - } -}; - -export const addDecorator = (decorator: DecoratorFunction) => { - checkMethod('addDecorator'); - global.__STORYBOOK_CLIENT_API__?.addDecorator(decorator); -}; - -export const addParameters = (parameters: Parameters) => { - checkMethod('addParameters'); - global.__STORYBOOK_CLIENT_API__?.addParameters(parameters); -}; - -export const addLoader = (loader: LoaderFunction) => { - checkMethod('addLoader'); - global.__STORYBOOK_CLIENT_API__?.addLoader(loader); -}; - -export const addArgs = (args: Args) => { - checkMethod('addArgs'); - global.__STORYBOOK_CLIENT_API__?.addArgs(args); -}; - -export const addArgTypes = (argTypes: ArgTypes) => { - checkMethod('addArgTypes'); - global.__STORYBOOK_CLIENT_API__?.addArgTypes(argTypes); -}; - -export const addArgsEnhancer = (enhancer: ArgsEnhancer) => { - checkMethod('addArgsEnhancer'); - global.__STORYBOOK_CLIENT_API__?.addArgsEnhancer(enhancer); -}; - -export const addArgTypesEnhancer = (enhancer: ArgTypesEnhancer) => { - checkMethod('addArgTypesEnhancer'); - global.__STORYBOOK_CLIENT_API__?.addArgTypesEnhancer(enhancer); -}; - -export const addStepRunner = (stepRunner: StepRunner) => { - checkMethod('addStepRunner'); - global.__STORYBOOK_CLIENT_API__?.addStepRunner(stepRunner); -}; - -export const getGlobalRender = () => { - checkMethod('getGlobalRender'); - return global.__STORYBOOK_CLIENT_API__?.facade.projectAnnotations.render; -}; - -export const setGlobalRender = (render: StoryStoreFacade['projectAnnotations']['render']) => { - checkMethod('setGlobalRender'); - if (global.__STORYBOOK_CLIENT_API__) { - global.__STORYBOOK_CLIENT_API__.facade.projectAnnotations.render = render; - } -}; - -const invalidStoryTypes = new Set(['string', 'number', 'boolean', 'symbol']); -export class ClientApi { - facade: StoryStoreFacade; - - storyStore?: StoryStore; - - private addons: Addon_ClientApiAddons; - - onImportFnChanged?: ({ importFn }: { importFn: ModuleImportFn }) => void; - - // If we don't get passed modules so don't know filenames, we can - // just use numeric indexes - private lastFileName = 0; - - constructor({ storyStore }: { storyStore?: StoryStore } = {}) { - this.facade = new StoryStoreFacade(); - - this.addons = {}; - - this.storyStore = storyStore; - } - - importFn(path: Path) { - return this.facade.importFn(path); - } - - getStoryIndex() { - if (!this.storyStore) { - throw new Error('Cannot get story index before setting storyStore'); - } - return this.facade.getStoryIndex(this.storyStore); - } - - addDecorator = (decorator: DecoratorFunction) => { - this.facade.projectAnnotations.decorators?.push(decorator); - }; - - addParameters = ({ - globals, - globalTypes, - ...parameters - }: Parameters & { globals?: Globals; globalTypes?: GlobalTypes }) => { - this.facade.projectAnnotations.parameters = combineParameters( - this.facade.projectAnnotations.parameters, - parameters - ); - if (globals) { - this.facade.projectAnnotations.globals = { - ...this.facade.projectAnnotations.globals, - ...globals, - }; - } - if (globalTypes) { - this.facade.projectAnnotations.globalTypes = { - ...this.facade.projectAnnotations.globalTypes, - ...normalizeInputTypes(globalTypes), - }; - } - }; - - addStepRunner = (stepRunner: StepRunner) => { - this.facade.projectAnnotations.runStep = composeStepRunners( - [this.facade.projectAnnotations.runStep, stepRunner].filter(Boolean) as StepRunner[] - ); - }; - - addLoader = (loader: LoaderFunction) => { - this.facade.projectAnnotations.loaders?.push(loader); - }; - - addArgs = (args: Args) => { - this.facade.projectAnnotations.args = { - ...this.facade.projectAnnotations.args, - ...args, - }; - }; - - addArgTypes = (argTypes: ArgTypes) => { - this.facade.projectAnnotations.argTypes = { - ...this.facade.projectAnnotations.argTypes, - ...normalizeInputTypes(argTypes), - }; - }; - - addArgsEnhancer = (enhancer: ArgsEnhancer) => { - this.facade.projectAnnotations.argsEnhancers?.push(enhancer); - }; - - addArgTypesEnhancer = (enhancer: ArgTypesEnhancer) => { - this.facade.projectAnnotations.argTypesEnhancers?.push(enhancer); - }; - - // Because of the API of `storiesOf().add()` we don't have a good "end" call for a - // storiesOf file to finish adding stories, and us to load it into the facade as a - // single psuedo-CSF file. So instead we just keep collecting the CSF files and load - // them all into the facade at the end. - _addedExports = {} as Record; - - _loadAddedExports() { - Object.entries(this._addedExports).forEach(([fileName, fileExports]) => - this.facade.addStoriesFromExports(fileName, fileExports) - ); - } - - // what are the occasions that "m" is a boolean vs an obj - storiesOf = (kind: string, m?: NodeModule): Addon_StoryApi => { - if (!kind && typeof kind !== 'string') { - throw new Error('Invalid or missing kind provided for stories, should be a string'); - } - - if (!m) { - logger.warn( - `Missing 'module' parameter for story with a kind of '${kind}'. It will break your HMR` - ); - } - - if (m) { - const proto = Object.getPrototypeOf(m); - if (proto.exports && proto.exports.default) { - // FIXME: throw an error in SB6.0 - logger.error( - `Illegal mix of CSF default export and storiesOf calls in a single file: ${proto.i}` - ); - } - } - - // eslint-disable-next-line no-plusplus - const baseFilename = m && m.id ? `${m.id}` : (this.lastFileName++).toString(); - let fileName = baseFilename; - let i = 1; - // Deal with `storiesOf()` being called twice in the same file. - // On HMR, we clear _addedExports[fileName] below. - - while (this._addedExports[fileName]) { - i += 1; - fileName = `${baseFilename}-${i}`; - } - - if (m && m.hot && m.hot.accept) { - // This module used storiesOf(), so when it re-runs on HMR, it will reload - // itself automatically without us needing to look at our imports - m.hot.accept(); - m.hot.dispose(() => { - this.facade.clearFilenameExports(fileName); - - delete this._addedExports[fileName]; - - // We need to update the importFn as soon as the module re-evaluates - // (and calls storiesOf() again, etc). We could call `onImportFnChanged()` - // at the end of every setStories call (somehow), but then we'd need to - // debounce it somehow for initial startup. Instead, we'll take advantage of - // the fact that the evaluation of the module happens immediately in the same tick - setTimeout(() => { - this._loadAddedExports(); - this.onImportFnChanged?.({ importFn: this.importFn.bind(this) }); - }, 0); - }); - } - - let hasAdded = false; - const api: Addon_StoryApi = { - kind: kind.toString(), - add: () => api, - addDecorator: () => api, - addLoader: () => api, - addParameters: () => api, - }; - - // apply addons - Object.keys(this.addons).forEach((name) => { - const addon = this.addons[name]; - api[name] = (...args: any[]) => { - addon.apply(api, args); - return api; - }; - }); - - const meta: NormalizedComponentAnnotations = { - id: sanitize(kind), - title: kind, - decorators: [], - loaders: [], - parameters: {}, - }; - // We map these back to a simple default export, even though we have type guarantees at this point - - this._addedExports[fileName] = { default: meta }; - - let counter = 0; - api.add = (storyName: string, storyFn: StoryFn, parameters: Parameters = {}) => { - hasAdded = true; - - if (typeof storyName !== 'string') { - throw new Error(`Invalid or missing storyName provided for a "${kind}" story.`); - } - - if (!storyFn || Array.isArray(storyFn) || invalidStoryTypes.has(typeof storyFn)) { - throw new Error( - `Cannot load story "${storyName}" in "${kind}" due to invalid format. Storybook expected a function/object but received ${typeof storyFn} instead.` - ); - } - - const { decorators, loaders, component, args, argTypes, ...storyParameters } = parameters; - - const storyId = parameters.__id || toId(kind, storyName); - - const csfExports = this._addedExports[fileName]; - // Whack a _ on the front incase it is "default" - csfExports[`story${counter}`] = { - name: storyName, - parameters: { fileName, __id: storyId, ...storyParameters }, - decorators, - loaders, - args, - argTypes, - component, - render: storyFn, - }; - counter += 1; - - return api; - }; - - api.addDecorator = (decorator: DecoratorFunction) => { - if (hasAdded) - throw new Error(`You cannot add a decorator after the first story for a kind. -Read more here: https://github.com/storybookjs/storybook/blob/master/MIGRATION.md#can-no-longer-add-decoratorsparameters-after-stories`); - - meta.decorators?.push(decorator); - return api; - }; - - api.addLoader = (loader: LoaderFunction) => { - if (hasAdded) throw new Error(`You cannot add a loader after the first story for a kind.`); - - meta.loaders?.push(loader); - return api; - }; - - api.addParameters = ({ component, args, argTypes, tags, ...parameters }: Parameters) => { - if (hasAdded) - throw new Error(`You cannot add parameters after the first story for a kind. -Read more here: https://github.com/storybookjs/storybook/blob/master/MIGRATION.md#can-no-longer-add-decoratorsparameters-after-stories`); - - meta.parameters = combineParameters(meta.parameters, parameters); - if (component) meta.component = component; - if (args) meta.args = { ...meta.args, ...args }; - if (argTypes) meta.argTypes = { ...meta.argTypes, ...argTypes }; - if (tags) meta.tags = tags; - return api; - }; - - return api; - }; - - // @deprecated - raw = () => { - return this.storyStore?.raw(); - }; - - // @deprecated - get _storyStore() { - return this.storyStore; - } -} diff --git a/code/lib/preview-api/src/modules/client-api/StoryStoreFacade.ts b/code/lib/preview-api/src/modules/client-api/StoryStoreFacade.ts deleted file mode 100644 index ccd95120b45c..000000000000 --- a/code/lib/preview-api/src/modules/client-api/StoryStoreFacade.ts +++ /dev/null @@ -1,256 +0,0 @@ -/* eslint-disable no-underscore-dangle */ -import { global } from '@storybook/global'; -import { dedent } from 'ts-dedent'; -import { SynchronousPromise } from 'synchronous-promise'; -import { toId, isExportStory, storyNameFromExport } from '@storybook/csf'; -import type { - IndexEntry, - Renderer, - ComponentId, - DocsOptions, - Parameters, - Path, - ModuleExports, - NormalizedProjectAnnotations, - NormalizedStoriesSpecifier, - PreparedStory, - StoryIndex, - StoryId, -} from '@storybook/types'; -import { logger } from '@storybook/client-logger'; -import type { StoryStore } from '../../store'; -import { userOrAutoTitle, sortStoriesV6 } from '../../store'; - -export const AUTODOCS_TAG = 'autodocs'; -export const STORIES_MDX_TAG = 'stories-mdx'; - -export class StoryStoreFacade { - projectAnnotations: NormalizedProjectAnnotations; - - entries: Record; - - csfExports: Record; - - constructor() { - this.projectAnnotations = { - loaders: [], - decorators: [], - parameters: {}, - argsEnhancers: [], - argTypesEnhancers: [], - args: {}, - argTypes: {}, - }; - - this.entries = {}; - - this.csfExports = {}; - } - - // This doesn't actually import anything because the client-api loads fully - // on startup, but this is a shim after all. - importFn(path: Path) { - return SynchronousPromise.resolve().then(() => { - const moduleExports = this.csfExports[path]; - if (!moduleExports) throw new Error(`Unknown path: ${path}`); - return moduleExports; - }); - } - - getStoryIndex(store: StoryStore) { - const fileNameOrder = Object.keys(this.csfExports); - const storySortParameter = this.projectAnnotations.parameters?.options?.storySort; - - const storyEntries = Object.entries(this.entries); - // Add the kind parameters and global parameters to each entry - const sortableV6 = storyEntries.map(([storyId, { type, importPath, ...entry }]) => { - const exports = this.csfExports[importPath]; - const csfFile = store.processCSFFileWithCache( - exports, - importPath, - exports.default.title - ); - - let storyLike: PreparedStory; - if (type === 'story') { - storyLike = store.storyFromCSFFile({ storyId, csfFile }); - } else { - storyLike = { - ...entry, - story: entry.name, - kind: entry.title, - componentId: toId(entry.componentId || entry.title), - parameters: { fileName: importPath }, - } as any; - } - return [ - storyId, - storyLike, - csfFile.meta.parameters, - this.projectAnnotations.parameters || {}, - ] as [StoryId, PreparedStory, Parameters, Parameters]; - }); - - // NOTE: the sortStoriesV6 version returns the v7 data format. confusing but more convenient! - let sortedV7: IndexEntry[]; - - try { - sortedV7 = sortStoriesV6(sortableV6, storySortParameter, fileNameOrder); - } catch (err: any) { - if (typeof storySortParameter === 'function') { - throw new Error(dedent` - Error sorting stories with sort parameter ${storySortParameter}: - - > ${err.message} - - Are you using a V7-style sort function in V6 compatibility mode? - - More info: https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#v7-style-story-sort - `); - } - throw err; - } - const entries = sortedV7.reduce((acc, s) => { - // We use the original entry we stored in `this.stories` because it is possible that the CSF file itself - // exports a `parameters.fileName` which can be different and mess up our `importFn`. - // NOTE: this doesn't actually change the story object, just the index. - acc[s.id] = this.entries[s.id]; - return acc; - }, {} as StoryIndex['entries']); - - return { v: 4, entries }; - } - - clearFilenameExports(fileName: Path) { - if (!this.csfExports[fileName]) { - return; - } - - // Clear this module's stories from the storyList and existing exports - Object.entries(this.entries).forEach(([id, { importPath }]) => { - if (importPath === fileName) { - delete this.entries[id]; - } - }); - - // We keep this as an empty record so we can use it to maintain component order - this.csfExports[fileName] = {}; - } - - // NOTE: we could potentially share some of this code with the stories.json generation - addStoriesFromExports(fileName: Path, fileExports: ModuleExports) { - if (fileName.match(/\.mdx$/) && !fileName.match(/\.stories\.mdx$/)) { - if (global.FEATURES?.storyStoreV7MdxErrors !== false) { - throw new Error(dedent` - Cannot index \`.mdx\` file (\`${fileName}\`) in \`storyStoreV7: false\` mode. - - The legacy story store does not support new-style \`.mdx\` files. If the file above - is not intended to be indexed (i.e. displayed as an entry in the sidebar), either - exclude it from your \`stories\` glob, or add to it. - - If you wanted to index the file, you'll need to name it \`stories.mdx\` and stick to the - legacy (6.x) MDX API, or use the new store.`); - } - } - - // if the export haven't changed since last time we added them, this is a no-op - if (this.csfExports[fileName] === fileExports) { - return; - } - // OTOH, if they have changed, let's clear them out first - this.clearFilenameExports(fileName); - - // eslint-disable-next-line @typescript-eslint/naming-convention - const { default: defaultExport, __namedExportsOrder, ...namedExports } = fileExports; - // eslint-disable-next-line prefer-const - let { id: componentId, title, tags: componentTags = [] } = defaultExport || {}; - - const specifiers = (global.STORIES || []).map( - (specifier: NormalizedStoriesSpecifier & { importPathMatcher: string }) => ({ - ...specifier, - importPathMatcher: new RegExp(specifier.importPathMatcher), - }) - ); - - title = userOrAutoTitle(fileName, specifiers, title); - - if (!title) { - logger.info( - `Unexpected default export without title in '${fileName}': ${JSON.stringify( - fileExports.default - )}` - ); - return; - } - - this.csfExports[fileName] = { - ...fileExports, - default: { ...defaultExport, title }, - }; - - let sortedExports = namedExports; - - // prefer a user/loader provided `__namedExportsOrder` array if supplied - // we do this as es module exports are always ordered alphabetically - // see https://github.com/storybookjs/storybook/issues/9136 - if (Array.isArray(__namedExportsOrder)) { - sortedExports = {}; - __namedExportsOrder.forEach((name) => { - const namedExport = namedExports[name]; - if (namedExport) sortedExports[name] = namedExport; - }); - } - - const storyExports = Object.entries(sortedExports).filter(([key]) => - isExportStory(key, defaultExport) - ); - - // NOTE: this logic is equivalent to the `extractStories` function of `StoryIndexGenerator` - const docsOptions = (global.DOCS_OPTIONS || {}) as DocsOptions; - const { autodocs } = docsOptions; - const componentAutodocs = componentTags.includes(AUTODOCS_TAG); - const autodocsOptedIn = autodocs === true || (autodocs === 'tag' && componentAutodocs); - if (storyExports.length) { - if (componentTags.includes(STORIES_MDX_TAG) || autodocsOptedIn) { - const name = docsOptions.defaultName; - const docsId = toId(componentId || title, name); - this.entries[docsId] = { - type: 'docs', - id: docsId, - title, - name, - importPath: fileName, - ...(componentId && { componentId }), - tags: [ - ...componentTags, - 'docs', - ...(autodocsOptedIn && !componentAutodocs ? [AUTODOCS_TAG] : []), - ], - storiesImports: [], - }; - } - } - - storyExports.forEach(([key, storyExport]: [string, any]) => { - const exportName = storyNameFromExport(key); - const id = storyExport.parameters?.__id || toId(componentId || title, exportName); - const name = - (typeof storyExport !== 'function' && storyExport.name) || - storyExport.storyName || - storyExport.story?.name || - exportName; - - if (!storyExport.parameters?.docsOnly) { - this.entries[id] = { - type: 'story', - id, - name, - title, - importPath: fileName, - ...(componentId && { componentId }), - tags: [...(storyExport.tags || componentTags), 'story'], - }; - } - }); - } -} diff --git a/code/lib/preview-api/src/modules/client-api/index.ts b/code/lib/preview-api/src/modules/client-api/index.ts deleted file mode 100644 index 075075214e9c..000000000000 --- a/code/lib/preview-api/src/modules/client-api/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -export { - addArgs, - addArgsEnhancer, - addArgTypes, - addArgTypesEnhancer, - addDecorator, - addLoader, - addParameters, - addStepRunner, - ClientApi, - setGlobalRender, -} from './ClientApi'; - -export * from '../../store'; - -export * from './queryparams'; diff --git a/code/lib/preview-api/src/modules/client-api/queryparams.ts b/code/lib/preview-api/src/modules/client-api/queryparams.ts deleted file mode 100644 index ccaa7193e407..000000000000 --- a/code/lib/preview-api/src/modules/client-api/queryparams.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { global } from '@storybook/global'; -import { parse } from 'qs'; - -export const getQueryParams = () => { - const { document } = global; - // document.location is not defined in react-native - if (document && document.location && document.location.search) { - return parse(document.location.search, { ignoreQueryPrefix: true }); - } - return {}; -}; - -export const getQueryParam = (key: string) => { - const params = getQueryParams(); - - return params[key]; -}; diff --git a/code/lib/preview-api/src/modules/core-client/executeLoadable.ts b/code/lib/preview-api/src/modules/core-client/executeLoadable.ts deleted file mode 100644 index 9c831f1ab610..000000000000 --- a/code/lib/preview-api/src/modules/core-client/executeLoadable.ts +++ /dev/null @@ -1,110 +0,0 @@ -/// -/// - -import { logger } from '@storybook/client-logger'; -import type { Path, ModuleExports } from '@storybook/types'; - -export interface RequireContext { - keys: () => string[]; - (id: string): any; - resolve(id: string): string; -} - -export type LoaderFunction = () => void | any[]; - -export type Loadable = RequireContext | RequireContext[] | LoaderFunction; - -/** - * Executes a Loadable (function that returns exports or require context(s)) - * and returns a map of filename => module exports - * - * @param loadable Loadable - * @returns Map - */ -export function executeLoadable(loadable: Loadable) { - let reqs = null; - // todo discuss / improve type check - if (Array.isArray(loadable)) { - reqs = loadable; - } else if ((loadable as RequireContext).keys) { - reqs = [loadable as RequireContext]; - } - - let exportsMap = new Map(); - if (reqs) { - reqs.forEach((req) => { - req.keys().forEach((filename: string) => { - try { - const fileExports = req(filename) as ModuleExports; - exportsMap.set( - typeof req.resolve === 'function' ? req.resolve(filename) : filename, - fileExports - ); - } catch (error: any) { - const errorString = - error.message && error.stack ? `${error.message}\n ${error.stack}` : error.toString(); - logger.error(`Unexpected error while loading ${filename}: ${errorString}`); - } - }); - }); - } else { - const exported = (loadable as LoaderFunction)(); - if (Array.isArray(exported) && exported.every((obj) => obj.default != null)) { - exportsMap = new Map( - exported.map((fileExports, index) => [`exports-map-${index}`, fileExports]) - ); - } else if (exported) { - logger.warn( - `Loader function passed to 'configure' should return void or an array of module exports that all contain a 'default' export. Received: ${JSON.stringify( - exported - )}` - ); - } - } - - return exportsMap; -} - -/** - * Executes a Loadable (function that returns exports or require context(s)) - * and compares it's output to the last time it was run (as stored on a node module) - * - * @param loadable Loadable - * @param m NodeModule - * @returns { added: Map, removed: Map } - */ -export function executeLoadableForChanges(loadable: Loadable, m?: NodeModule) { - let lastExportsMap: ReturnType = - m?.hot?.data?.lastExportsMap || new Map(); - if (m?.hot?.dispose) { - m.hot.accept(); - m.hot.dispose((data) => { - // eslint-disable-next-line no-param-reassign - data.lastExportsMap = lastExportsMap; - }); - } - - const exportsMap = executeLoadable(loadable); - const added = new Map(); - Array.from(exportsMap.entries()) - // Ignore files that do not have a default export - .filter(([, fileExports]) => !!fileExports.default) - // Ignore exports that are equal (by reference) to last time, this means the file hasn't changed - .filter(([fileName, fileExports]) => lastExportsMap.get(fileName) !== fileExports) - .forEach(([fileName, fileExports]) => added.set(fileName, fileExports)); - - const removed = new Map(); - Array.from(lastExportsMap.keys()) - .filter((fileName) => !exportsMap.has(fileName)) - .forEach((fileName) => { - const value = lastExportsMap.get(fileName); - if (value) { - removed.set(fileName, value); - } - }); - - // Save the value for the dispose() call above - lastExportsMap = exportsMap; - - return { added, removed }; -} diff --git a/code/lib/preview-api/src/modules/core-client/index.ts b/code/lib/preview-api/src/modules/core-client/index.ts deleted file mode 100644 index 72d57e5f9d76..000000000000 --- a/code/lib/preview-api/src/modules/core-client/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { ClientApi } from '../../client-api'; -import { StoryStore } from '../../store'; -import { start } from './start'; - -export { start, ClientApi, StoryStore }; diff --git a/code/lib/preview-api/src/modules/core-client/start.test.ts b/code/lib/preview-api/src/modules/core-client/start.test.ts deleted file mode 100644 index b114e640f50a..000000000000 --- a/code/lib/preview-api/src/modules/core-client/start.test.ts +++ /dev/null @@ -1,1436 +0,0 @@ -/* eslint-disable no-underscore-dangle */ -/** - * @jest-environment jsdom - */ - -// import { describe, it, beforeAll, beforeEach, afterAll, afterEach, jest } from '@jest/globals'; -import { STORY_RENDERED, STORY_UNCHANGED, SET_INDEX, CONFIG_ERROR } from '@storybook/core-events'; - -import type { ModuleExports, Path } from '@storybook/types'; -import { global } from '@storybook/global'; -import { setGlobalRender } from '../../client-api'; -import { - waitForRender, - waitForEvents, - waitForQuiescence, - emitter, - mockChannel, -} from '../preview-web/PreviewWeb.mockdata'; - -import { start as realStart } from './start'; -import type { Loadable } from './executeLoadable'; - -jest.mock('@storybook/global', () => ({ - global: { - ...globalThis, - window: globalThis, - history: { replaceState: jest.fn() }, - document: { - location: { - pathname: 'pathname', - search: '?id=*', - }, - }, - DOCS_OPTIONS: {}, - }, -})); - -// console.log(global); - -jest.mock('@storybook/channels', () => ({ - createBrowserChannel: () => mockChannel, -})); -jest.mock('@storybook/client-logger'); -jest.mock('react-dom'); - -// for the auto-title test -jest.mock('../../store', () => { - const actualStore = jest.requireActual('../../store'); - return { - ...actualStore, - userOrAutoTitle: (importPath: Path, specifier: any, userTitle?: string) => - userTitle || 'auto-title', - }; -}); - -jest.mock('../../preview-web', () => { - const actualPreviewWeb = jest.requireActual('../../preview-web'); - - class OverloadPreviewWeb extends actualPreviewWeb.PreviewWeb { - constructor() { - super(); - - this.view = { - ...Object.fromEntries( - Object.getOwnPropertyNames(this.view.constructor.prototype).map((key) => [key, jest.fn()]) - ), - prepareForDocs: jest.fn().mockReturnValue('docs-root'), - prepareForStory: jest.fn().mockReturnValue('story-root'), - }; - } - } - return { - ...actualPreviewWeb, - PreviewWeb: OverloadPreviewWeb, - }; -}); - -beforeEach(() => { - mockChannel.emit.mockClear(); - // Preview doesn't clean itself up as it isn't designed to ever be stopped :shrug: - emitter.removeAllListeners(); -}); - -const start: typeof realStart = (...args) => { - const result = realStart(...args); - - const configure: typeof result['configure'] = ( - framework: string, - loadable: Loadable, - m?: NodeModule, - disableBackwardCompatibility = false - ) => result.configure(framework, loadable, m, disableBackwardCompatibility); - - return { - ...result, - configure, - }; -}; -afterEach(() => { - // I'm not sure why this is required (it seems just afterEach is required really) - mockChannel.emit.mockClear(); -}); - -function makeRequireContext(importMap: Record) { - const req = (path: Path) => importMap[path]; - req.keys = () => Object.keys(importMap); - return req; -} - -describe('start', () => { - beforeEach(() => { - global.DOCS_OPTIONS = {}; - // @ts-expect-error (setting this to undefined is indeed what we want to do) - global.__STORYBOOK_CLIENT_API__ = undefined; - // @ts-expect-error (setting this to undefined is indeed what we want to do) - global.__STORYBOOK_PREVIEW__ = undefined; - // @ts-expect-error (setting this to undefined is indeed what we want to do) - global.IS_STORYBOOK = undefined; - }); - describe('when configure is called with storiesOf only', () => { - it('loads and renders the first story correctly', async () => { - const renderToCanvas = jest.fn(); - - const { configure, clientApi } = start(renderToCanvas); - - configure('test', () => { - clientApi - .storiesOf('Component A', { id: 'file1' } as NodeModule) - .add('Story One', jest.fn()) - .add('Story Two', jest.fn()); - - clientApi - .storiesOf('Component B', { id: 'file2' } as NodeModule) - .add('Story Three', jest.fn()); - }); - - await waitForRender(); - - expect(mockChannel.emit.mock.calls.find((call) => call[0] === SET_INDEX)?.[1]) - .toMatchInlineSnapshot(` - Object { - "entries": Object { - "component-a--story-one": Object { - "argTypes": Object {}, - "args": Object {}, - "componentId": "component-a", - "id": "component-a--story-one", - "importPath": "file1", - "initialArgs": Object {}, - "name": "Story One", - "parameters": Object { - "__id": "component-a--story-one", - "__isArgsStory": false, - "fileName": "file1", - "renderer": "test", - }, - "tags": Array [ - "story", - ], - "title": "Component A", - "type": "story", - }, - "component-a--story-two": Object { - "argTypes": Object {}, - "args": Object {}, - "componentId": "component-a", - "id": "component-a--story-two", - "importPath": "file1", - "initialArgs": Object {}, - "name": "Story Two", - "parameters": Object { - "__id": "component-a--story-two", - "__isArgsStory": false, - "fileName": "file1", - "renderer": "test", - }, - "tags": Array [ - "story", - ], - "title": "Component A", - "type": "story", - }, - "component-b--story-three": Object { - "argTypes": Object {}, - "args": Object {}, - "componentId": "component-b", - "id": "component-b--story-three", - "importPath": "file2", - "initialArgs": Object {}, - "name": "Story Three", - "parameters": Object { - "__id": "component-b--story-three", - "__isArgsStory": false, - "fileName": "file2", - "renderer": "test", - }, - "tags": Array [ - "story", - ], - "title": "Component B", - "type": "story", - }, - }, - "v": 4, - } - `); - - await waitForRender(); - expect(mockChannel.emit).toHaveBeenCalledWith(STORY_RENDERED, 'component-a--story-one'); - - expect(renderToCanvas).toHaveBeenCalledWith( - expect.objectContaining({ - id: 'component-a--story-one', - }), - 'story-root' - ); - }); - - it('deals with stories with "default" name', async () => { - const renderToCanvas = jest.fn(); - - const { configure, clientApi } = start(renderToCanvas); - - configure('test', () => { - clientApi.storiesOf('Component A', { id: 'file1' } as NodeModule).add('default', jest.fn()); - }); - - await waitForRender(); - - expect(mockChannel.emit).toHaveBeenCalledWith(STORY_RENDERED, 'component-a--default'); - }); - - it('deals with stories with camel-cased names', async () => { - const renderToCanvas = jest.fn(); - - const { configure, clientApi } = start(renderToCanvas); - - configure('test', () => { - clientApi - .storiesOf('Component A', { id: 'file1' } as NodeModule) - .add('storyOne', jest.fn()); - }); - - await waitForRender(); - - expect(mockChannel.emit).toHaveBeenCalledWith(STORY_RENDERED, 'component-a--storyone'); - }); - - it('deals with stories with spaces in the name', async () => { - const renderToCanvas = jest.fn(); - - const { configure, clientApi } = start(renderToCanvas); - - configure('test', () => { - clientApi - .storiesOf('Component A', { id: 'file1' } as NodeModule) - .add('Story One', jest.fn()); - }); - - await waitForRender(); - - expect(mockChannel.emit).toHaveBeenCalledWith(STORY_RENDERED, 'component-a--story-one'); - }); - - // https://github.com/storybookjs/storybook/issues/16303 - it('deals with stories with numeric names', async () => { - const renderToCanvas = jest.fn(); - - const { configure, clientApi } = start(renderToCanvas); - - configure('test', () => { - clientApi.storiesOf('Component A', { id: 'file1' } as NodeModule).add('story0', jest.fn()); - }); - - await waitForRender(); - - expect(mockChannel.emit).toHaveBeenCalledWith(STORY_RENDERED, 'component-a--story0'); - }); - - it('deals with storiesOf from the same file twice', async () => { - const renderToCanvas = jest.fn(); - - const { configure, clientApi } = start(renderToCanvas); - - configure('test', () => { - clientApi.storiesOf('Component A', { id: 'file1' } as NodeModule).add('default', jest.fn()); - clientApi.storiesOf('Component B', { id: 'file1' } as NodeModule).add('default', jest.fn()); - clientApi.storiesOf('Component C', { id: 'file1' } as NodeModule).add('default', jest.fn()); - }); - - await waitForRender(); - expect(mockChannel.emit).toHaveBeenCalledWith(STORY_RENDERED, 'component-a--default'); - - const storiesOfData = mockChannel.emit.mock.calls.find((call) => call[0] === SET_INDEX)?.[1]; - expect(Object.values(storiesOfData.entries).map((s: any) => s.parameters.fileName)).toEqual([ - 'file1', - 'file1-2', - 'file1-3', - ]); - }); - - it('allows setting compomnent/args/argTypes via a parameter', async () => { - const renderToCanvas = jest.fn(({ storyFn }) => storyFn()); - - const { configure, clientApi } = start(renderToCanvas); - - const component = {}; - configure('test', () => { - clientApi - .storiesOf('Component A', { id: 'file1' } as NodeModule) - .addParameters({ - component, - args: { a: 'a' }, - argTypes: { a: { type: 'string' } }, - }) - .add('default', jest.fn(), { - args: { b: 'b' }, - argTypes: { b: { type: 'string' } }, - }); - }); - - await waitForRender(); - - expect(renderToCanvas).toHaveBeenCalledWith( - expect.objectContaining({ - storyContext: expect.objectContaining({ - component, - args: { a: 'a', b: 'b' }, - argTypes: { - a: { name: 'a', type: { name: 'string' } }, - b: { name: 'b', type: { name: 'string' } }, - }, - }), - }), - 'story-root' - ); - - expect(global.IS_STORYBOOK).toBe(true); - }); - - it('supports forceRerender()', async () => { - const renderToCanvas = jest.fn(({ storyFn }) => storyFn()); - - const { configure, clientApi, forceReRender } = start(renderToCanvas); - - configure('test', () => { - clientApi.storiesOf('Component A', { id: 'file1' } as NodeModule).add('default', jest.fn()); - }); - - await waitForRender(); - expect(mockChannel.emit).toHaveBeenCalledWith(STORY_RENDERED, 'component-a--default'); - - mockChannel.emit.mockClear(); - forceReRender(); - - await waitForRender(); - expect(mockChannel.emit).toHaveBeenCalledWith(STORY_RENDERED, 'component-a--default'); - }); - - it('supports HMR when a story file changes', async () => { - const renderToCanvas = jest.fn(({ storyFn }) => storyFn()); - - const { configure, clientApi } = start(renderToCanvas); - - let disposeCallback: () => void = () => {}; - const module = { - id: 'file1', - hot: { - accept: jest.fn(), - dispose(cb: () => void) { - disposeCallback = cb; - }, - }, - }; - const firstImplementation = jest.fn(); - configure('test', () => { - clientApi.storiesOf('Component A', module as any).add('default', firstImplementation); - }); - - await waitForRender(); - expect(mockChannel.emit).toHaveBeenCalledWith(STORY_RENDERED, 'component-a--default'); - expect(firstImplementation).toHaveBeenCalled(); - expect(module.hot.accept).toHaveBeenCalled(); - expect(disposeCallback).toBeDefined(); - - mockChannel.emit.mockClear(); - disposeCallback(); - const secondImplementation = jest.fn(); - clientApi.storiesOf('Component A', module as any).add('default', secondImplementation); - - await waitForRender(); - expect(mockChannel.emit).toHaveBeenCalledWith(STORY_RENDERED, 'component-a--default'); - expect(secondImplementation).toHaveBeenCalled(); - }); - - it('re-emits SET_INDEX when a story is added', async () => { - const renderToCanvas = jest.fn(({ storyFn }) => storyFn()); - - const { configure, clientApi } = start(renderToCanvas); - - let disposeCallback: () => void = () => {}; - const module = { - id: 'file1', - hot: { - accept: jest.fn(), - dispose(cb: () => void) { - disposeCallback = cb; - }, - }, - }; - configure('test', () => { - clientApi.storiesOf('Component A', module as any).add('default', jest.fn()); - }); - - await waitForRender(); - - mockChannel.emit.mockClear(); - disposeCallback(); - clientApi - .storiesOf('Component A', module as any) - .add('default', jest.fn()) - .add('new', jest.fn()); - - await waitForEvents([SET_INDEX]); - expect(mockChannel.emit.mock.calls.find((call) => call[0] === SET_INDEX)?.[1]) - .toMatchInlineSnapshot(` - Object { - "entries": Object { - "component-a--default": Object { - "argTypes": Object {}, - "args": Object {}, - "componentId": "component-a", - "id": "component-a--default", - "importPath": "file1", - "initialArgs": Object {}, - "name": "default", - "parameters": Object { - "__id": "component-a--default", - "__isArgsStory": false, - "fileName": "file1", - "renderer": "test", - }, - "tags": Array [ - "story", - ], - "title": "Component A", - "type": "story", - }, - "component-a--new": Object { - "argTypes": Object {}, - "args": Object {}, - "componentId": "component-a", - "id": "component-a--new", - "importPath": "file1", - "initialArgs": Object {}, - "name": "new", - "parameters": Object { - "__id": "component-a--new", - "__isArgsStory": false, - "fileName": "file1", - "renderer": "test", - }, - "tags": Array [ - "story", - ], - "title": "Component A", - "type": "story", - }, - }, - "v": 4, - } - `); - }); - - it('re-emits SET_INDEX when a story file is removed', async () => { - const renderToCanvas = jest.fn(({ storyFn }) => storyFn()); - - const { configure, clientApi } = start(renderToCanvas); - - let disposeCallback: () => void = () => {}; - const moduleB = { - id: 'file2', - hot: { - accept: jest.fn(), - dispose(cb: () => void) { - disposeCallback = cb; - }, - }, - }; - configure('test', () => { - clientApi.storiesOf('Component A', { id: 'file1' } as any).add('default', jest.fn()); - clientApi.storiesOf('Component B', moduleB as any).add('default', jest.fn()); - }); - - await waitForEvents([SET_INDEX]); - expect(mockChannel.emit.mock.calls.find((call) => call[0] === SET_INDEX)?.[1]) - .toMatchInlineSnapshot(` - Object { - "entries": Object { - "component-a--default": Object { - "argTypes": Object {}, - "args": Object {}, - "componentId": "component-a", - "id": "component-a--default", - "importPath": "file1", - "initialArgs": Object {}, - "name": "default", - "parameters": Object { - "__id": "component-a--default", - "__isArgsStory": false, - "fileName": "file1", - "renderer": "test", - }, - "tags": Array [ - "story", - ], - "title": "Component A", - "type": "story", - }, - "component-b--default": Object { - "argTypes": Object {}, - "args": Object {}, - "componentId": "component-b", - "id": "component-b--default", - "importPath": "file2", - "initialArgs": Object {}, - "name": "default", - "parameters": Object { - "__id": "component-b--default", - "__isArgsStory": false, - "fileName": "file2", - "renderer": "test", - }, - "tags": Array [ - "story", - ], - "title": "Component B", - "type": "story", - }, - }, - "v": 4, - } - `); - mockChannel.emit.mockClear(); - disposeCallback(); - - await waitForEvents([SET_INDEX]); - expect(mockChannel.emit.mock.calls.find((call) => call[0] === SET_INDEX)?.[1]) - .toMatchInlineSnapshot(` - Object { - "entries": Object { - "component-a--default": Object { - "argTypes": Object {}, - "args": Object {}, - "componentId": "component-a", - "id": "component-a--default", - "importPath": "file1", - "initialArgs": Object {}, - "name": "default", - "parameters": Object { - "__id": "component-a--default", - "__isArgsStory": false, - "fileName": "file1", - "renderer": "test", - }, - "tags": Array [ - "story", - ], - "title": "Component A", - "type": "story", - }, - }, - "v": 4, - } - `); - }); - }); - - const componentCExports = { - default: { - title: 'Component C', - tags: ['component-tag', 'autodocs'], - }, - StoryOne: { - render: jest.fn(), - tags: ['story-tag'], - }, - StoryTwo: jest.fn(), - }; - - describe('when configure is called with CSF only', () => { - it('loads and renders the first story correctly', async () => { - const renderToCanvas = jest.fn(); - - const { configure } = start(renderToCanvas); - configure('test', () => [componentCExports]); - - await waitForRender(); - expect(mockChannel.emit.mock.calls.find((call) => call[0] === SET_INDEX)?.[1]) - .toMatchInlineSnapshot(` - Object { - "entries": Object { - "component-c--story-one": Object { - "argTypes": Object {}, - "args": Object {}, - "id": "component-c--story-one", - "importPath": "exports-map-0", - "initialArgs": Object {}, - "name": "Story One", - "parameters": Object { - "__isArgsStory": false, - "fileName": "exports-map-0", - "renderer": "test", - }, - "tags": Array [ - "story-tag", - "story", - ], - "title": "Component C", - "type": "story", - }, - "component-c--story-two": Object { - "argTypes": Object {}, - "args": Object {}, - "id": "component-c--story-two", - "importPath": "exports-map-0", - "initialArgs": Object {}, - "name": "Story Two", - "parameters": Object { - "__isArgsStory": false, - "fileName": "exports-map-0", - "renderer": "test", - }, - "tags": Array [ - "component-tag", - "autodocs", - "story", - ], - "title": "Component C", - "type": "story", - }, - }, - "v": 4, - } - `); - - await waitForRender(); - expect(mockChannel.emit).toHaveBeenCalledWith(STORY_RENDERED, 'component-c--story-one'); - - expect(renderToCanvas).toHaveBeenCalledWith( - expect.objectContaining({ - id: 'component-c--story-one', - }), - 'story-root' - ); - }); - - it('supports HMR when a story file changes', async () => { - const renderToCanvas = jest.fn(({ storyFn }) => storyFn()); - - let disposeCallback: (data: object) => void = () => {}; - const module = { - id: 'file1', - hot: { - data: {}, - accept: jest.fn(), - dispose(cb: () => void) { - disposeCallback = cb; - }, - }, - }; - - const { configure } = start(renderToCanvas); - configure('test', () => [componentCExports], module as any); - - await waitForRender(); - expect(mockChannel.emit).toHaveBeenCalledWith(STORY_RENDERED, 'component-c--story-one'); - expect(componentCExports.StoryOne.render).toHaveBeenCalled(); - expect(module.hot.accept).toHaveBeenCalled(); - expect(disposeCallback).toBeDefined(); - - mockChannel.emit.mockClear(); - disposeCallback(module.hot.data); - const secondImplementation = jest.fn(); - configure( - 'test', - () => [{ ...componentCExports, StoryOne: secondImplementation }], - module as any - ); - - await waitForRender(); - expect(mockChannel.emit).toHaveBeenCalledWith(STORY_RENDERED, 'component-c--story-one'); - expect(secondImplementation).toHaveBeenCalled(); - }); - - it('re-emits SET_INDEX when a story is added', async () => { - const renderToCanvas = jest.fn(({ storyFn }) => storyFn()); - - let disposeCallback: (data: object) => void = () => {}; - const module = { - id: 'file1', - hot: { - data: {}, - accept: jest.fn(), - dispose(cb: () => void) { - disposeCallback = cb; - }, - }, - }; - const { configure } = start(renderToCanvas); - configure('test', () => [componentCExports], module as any); - - await waitForRender(); - - mockChannel.emit.mockClear(); - disposeCallback(module.hot.data); - configure('test', () => [{ ...componentCExports, StoryThree: jest.fn() }], module as any); - - await waitForEvents([SET_INDEX]); - expect(mockChannel.emit.mock.calls.find((call) => call[0] === SET_INDEX)?.[1]) - .toMatchInlineSnapshot(` - Object { - "entries": Object { - "component-c--story-one": Object { - "argTypes": Object {}, - "args": Object {}, - "id": "component-c--story-one", - "importPath": "exports-map-0", - "initialArgs": Object {}, - "name": "Story One", - "parameters": Object { - "__isArgsStory": false, - "fileName": "exports-map-0", - "renderer": "test", - }, - "tags": Array [ - "story-tag", - "story", - ], - "title": "Component C", - "type": "story", - }, - "component-c--story-three": Object { - "argTypes": Object {}, - "args": Object {}, - "id": "component-c--story-three", - "importPath": "exports-map-0", - "initialArgs": Object {}, - "name": "Story Three", - "parameters": Object { - "__isArgsStory": false, - "fileName": "exports-map-0", - "renderer": "test", - }, - "tags": Array [ - "component-tag", - "autodocs", - "story", - ], - "title": "Component C", - "type": "story", - }, - "component-c--story-two": Object { - "argTypes": Object {}, - "args": Object {}, - "id": "component-c--story-two", - "importPath": "exports-map-0", - "initialArgs": Object {}, - "name": "Story Two", - "parameters": Object { - "__isArgsStory": false, - "fileName": "exports-map-0", - "renderer": "test", - }, - "tags": Array [ - "component-tag", - "autodocs", - "story", - ], - "title": "Component C", - "type": "story", - }, - }, - "v": 4, - } - `); - }); - - it('re-emits SET_INDEX when a story file is removed', async () => { - const renderToCanvas = jest.fn(({ storyFn }) => storyFn()); - - let disposeCallback: (data: object) => void = () => {}; - const module = { - id: 'file1', - hot: { - data: {}, - accept: jest.fn(), - dispose(cb: () => void) { - disposeCallback = cb; - }, - }, - }; - const { configure } = start(renderToCanvas); - configure( - 'test', - () => [componentCExports, { default: { title: 'Component D' }, StoryFour: jest.fn() }], - module as any - ); - - await waitForEvents([SET_INDEX]); - expect(mockChannel.emit.mock.calls.find((call) => call[0] === SET_INDEX)?.[1]) - .toMatchInlineSnapshot(` - Object { - "entries": Object { - "component-c--story-one": Object { - "argTypes": Object {}, - "args": Object {}, - "id": "component-c--story-one", - "importPath": "exports-map-0", - "initialArgs": Object {}, - "name": "Story One", - "parameters": Object { - "__isArgsStory": false, - "fileName": "exports-map-0", - "renderer": "test", - }, - "tags": Array [ - "story-tag", - "story", - ], - "title": "Component C", - "type": "story", - }, - "component-c--story-two": Object { - "argTypes": Object {}, - "args": Object {}, - "id": "component-c--story-two", - "importPath": "exports-map-0", - "initialArgs": Object {}, - "name": "Story Two", - "parameters": Object { - "__isArgsStory": false, - "fileName": "exports-map-0", - "renderer": "test", - }, - "tags": Array [ - "component-tag", - "autodocs", - "story", - ], - "title": "Component C", - "type": "story", - }, - "component-d--story-four": Object { - "argTypes": Object {}, - "args": Object {}, - "id": "component-d--story-four", - "importPath": "exports-map-1", - "initialArgs": Object {}, - "name": "Story Four", - "parameters": Object { - "__isArgsStory": false, - "fileName": "exports-map-1", - "renderer": "test", - }, - "tags": Array [ - "story", - ], - "title": "Component D", - "type": "story", - }, - }, - "v": 4, - } - `); - await waitForRender(); - - mockChannel.emit.mockClear(); - disposeCallback(module.hot.data); - configure('test', () => [componentCExports], module as any); - - await waitForEvents([SET_INDEX]); - expect(mockChannel.emit.mock.calls.find((call) => call[0] === SET_INDEX)?.[1]) - .toMatchInlineSnapshot(` - Object { - "entries": Object { - "component-c--story-one": Object { - "argTypes": Object {}, - "args": Object {}, - "id": "component-c--story-one", - "importPath": "exports-map-0", - "initialArgs": Object {}, - "name": "Story One", - "parameters": Object { - "__isArgsStory": false, - "fileName": "exports-map-0", - "renderer": "test", - }, - "tags": Array [ - "story-tag", - "story", - ], - "title": "Component C", - "type": "story", - }, - "component-c--story-two": Object { - "argTypes": Object {}, - "args": Object {}, - "id": "component-c--story-two", - "importPath": "exports-map-0", - "initialArgs": Object {}, - "name": "Story Two", - "parameters": Object { - "__isArgsStory": false, - "fileName": "exports-map-0", - "renderer": "test", - }, - "tags": Array [ - "component-tag", - "autodocs", - "story", - ], - "title": "Component C", - "type": "story", - }, - }, - "v": 4, - } - `); - - await waitForEvents([STORY_UNCHANGED]); - }); - - it('allows you to override the render function in project annotations', async () => { - const renderToCanvas = jest.fn(({ storyFn }) => storyFn()); - const frameworkRender = jest.fn(); - - const { configure } = start(renderToCanvas, { render: frameworkRender }); - - const projectRender = jest.fn(); - setGlobalRender(projectRender); - configure('test', () => { - return [ - { - default: { - title: 'Component A', - component: jest.fn(), - }, - StoryOne: {}, - }, - ]; - }); - - await waitForRender(); - expect(mockChannel.emit).toHaveBeenCalledWith(STORY_RENDERED, 'component-a--story-one'); - - expect(frameworkRender).not.toHaveBeenCalled(); - expect(projectRender).toHaveBeenCalled(); - }); - - describe('docs', () => { - beforeEach(() => { - global.DOCS_OPTIONS = {}; - }); - - // NOTE: MDX files are only ever passed as CSF - it('sends over docs only stories as entries', async () => { - const renderToCanvas = jest.fn(); - - const { configure } = start(renderToCanvas); - - configure( - 'test', - makeRequireContext({ - './Introduction.stories.mdx': { - default: { title: 'Introduction', tags: ['stories-mdx'] }, - _Page: { name: 'Page', parameters: { docsOnly: true } }, - }, - }) - ); - - await waitForEvents([SET_INDEX]); - expect(mockChannel.emit.mock.calls.find((call) => call[0] === SET_INDEX)?.[1]) - .toMatchInlineSnapshot(` - Object { - "entries": Object { - "introduction": Object { - "id": "introduction", - "importPath": "./Introduction.stories.mdx", - "name": undefined, - "parameters": Object { - "fileName": "./Introduction.stories.mdx", - "renderer": "test", - }, - "storiesImports": Array [], - "tags": Array [ - "stories-mdx", - "docs", - ], - "title": "Introduction", - "type": "docs", - }, - }, - "v": 4, - } - `); - - // Wait a second to let the docs "render" finish (and maybe throw) - await waitForQuiescence(); - }); - - it('errors on .mdx files', async () => { - const renderToCanvas = jest.fn(); - - const { configure } = start(renderToCanvas); - - configure( - 'test', - makeRequireContext({ - './Introduction.mdx': { - default: () => 'some mdx function', - }, - }) - ); - - await waitForEvents([CONFIG_ERROR]); - expect(mockChannel.emit.mock.calls.find((call) => call[0] === CONFIG_ERROR)?.[1]) - .toMatchInlineSnapshot(` - [Error: Cannot index \`.mdx\` file (\`./Introduction.mdx\`) in \`storyStoreV7: false\` mode. - - The legacy story store does not support new-style \`.mdx\` files. If the file above - is not intended to be indexed (i.e. displayed as an entry in the sidebar), either - exclude it from your \`stories\` glob, or add to it. - - If you wanted to index the file, you'll need to name it \`stories.mdx\` and stick to the - legacy (6.x) MDX API, or use the new store.] - `); - }); - }); - }); - - describe('when configure is called with a combination', () => { - it('loads and renders the first story correctly', async () => { - const renderToCanvas = jest.fn(); - - const { configure, clientApi } = start(renderToCanvas); - configure('test', () => { - clientApi - .storiesOf('Component A', { id: 'file1' } as NodeModule) - .add('Story One', jest.fn()) - .add('Story Two', jest.fn()); - - clientApi - .storiesOf('Component B', { id: 'file2' } as NodeModule) - .add('Story Three', jest.fn()); - - return [componentCExports]; - }); - - await waitForRender(); - expect(mockChannel.emit.mock.calls.find((call) => call[0] === SET_INDEX)?.[1]) - .toMatchInlineSnapshot(` - Object { - "entries": Object { - "component-a--story-one": Object { - "argTypes": Object {}, - "args": Object {}, - "componentId": "component-a", - "id": "component-a--story-one", - "importPath": "file1", - "initialArgs": Object {}, - "name": "Story One", - "parameters": Object { - "__id": "component-a--story-one", - "__isArgsStory": false, - "fileName": "file1", - "renderer": "test", - }, - "tags": Array [ - "story", - ], - "title": "Component A", - "type": "story", - }, - "component-a--story-two": Object { - "argTypes": Object {}, - "args": Object {}, - "componentId": "component-a", - "id": "component-a--story-two", - "importPath": "file1", - "initialArgs": Object {}, - "name": "Story Two", - "parameters": Object { - "__id": "component-a--story-two", - "__isArgsStory": false, - "fileName": "file1", - "renderer": "test", - }, - "tags": Array [ - "story", - ], - "title": "Component A", - "type": "story", - }, - "component-b--story-three": Object { - "argTypes": Object {}, - "args": Object {}, - "componentId": "component-b", - "id": "component-b--story-three", - "importPath": "file2", - "initialArgs": Object {}, - "name": "Story Three", - "parameters": Object { - "__id": "component-b--story-three", - "__isArgsStory": false, - "fileName": "file2", - "renderer": "test", - }, - "tags": Array [ - "story", - ], - "title": "Component B", - "type": "story", - }, - "component-c--story-one": Object { - "argTypes": Object {}, - "args": Object {}, - "id": "component-c--story-one", - "importPath": "exports-map-0", - "initialArgs": Object {}, - "name": "Story One", - "parameters": Object { - "__isArgsStory": false, - "fileName": "exports-map-0", - "renderer": "test", - }, - "tags": Array [ - "story-tag", - "story", - ], - "title": "Component C", - "type": "story", - }, - "component-c--story-two": Object { - "argTypes": Object {}, - "args": Object {}, - "id": "component-c--story-two", - "importPath": "exports-map-0", - "initialArgs": Object {}, - "name": "Story Two", - "parameters": Object { - "__isArgsStory": false, - "fileName": "exports-map-0", - "renderer": "test", - }, - "tags": Array [ - "component-tag", - "autodocs", - "story", - ], - "title": "Component C", - "type": "story", - }, - }, - "v": 4, - } - `); - - await waitForRender(); - expect(mockChannel.emit).toHaveBeenCalledWith(STORY_RENDERED, 'component-a--story-one'); - - expect(renderToCanvas).toHaveBeenCalledWith( - expect.objectContaining({ - id: 'component-a--story-one', - }), - 'story-root' - ); - }); - - describe('autodocs', () => { - beforeEach(() => { - global.DOCS_OPTIONS = { autodocs: 'tag', defaultName: 'Docs' }; - }); - - it('adds stories for each component with autodocs tag', async () => { - const renderToCanvas = jest.fn(); - - const { configure, clientApi } = start(renderToCanvas); - configure('test', () => { - clientApi - .storiesOf('Component A', { id: 'file1' } as NodeModule) - .add('Story One', jest.fn()) - .add('Story Two', jest.fn()); - - clientApi - .storiesOf('Component B', { id: 'file2' } as NodeModule) - .addParameters({ tags: ['autodocs'] }) - .add('Story Three', jest.fn()); - - return [componentCExports]; - }); - - await waitForRender(); - expect(mockChannel.emit.mock.calls.find((call) => call[0] === SET_INDEX)?.[1]) - .toMatchInlineSnapshot(` - Object { - "entries": Object { - "component-a--story-one": Object { - "argTypes": Object {}, - "args": Object {}, - "componentId": "component-a", - "id": "component-a--story-one", - "importPath": "file1", - "initialArgs": Object {}, - "name": "Story One", - "parameters": Object { - "__id": "component-a--story-one", - "__isArgsStory": false, - "fileName": "file1", - "renderer": "test", - }, - "tags": Array [ - "story", - ], - "title": "Component A", - "type": "story", - }, - "component-a--story-two": Object { - "argTypes": Object {}, - "args": Object {}, - "componentId": "component-a", - "id": "component-a--story-two", - "importPath": "file1", - "initialArgs": Object {}, - "name": "Story Two", - "parameters": Object { - "__id": "component-a--story-two", - "__isArgsStory": false, - "fileName": "file1", - "renderer": "test", - }, - "tags": Array [ - "story", - ], - "title": "Component A", - "type": "story", - }, - "component-b--docs": Object { - "componentId": "component-b", - "id": "component-b--docs", - "importPath": "file2", - "name": "Docs", - "parameters": Object { - "fileName": "file2", - "renderer": "test", - }, - "storiesImports": Array [], - "tags": Array [ - "autodocs", - "docs", - ], - "title": "Component B", - "type": "docs", - }, - "component-b--story-three": Object { - "argTypes": Object {}, - "args": Object {}, - "componentId": "component-b", - "id": "component-b--story-three", - "importPath": "file2", - "initialArgs": Object {}, - "name": "Story Three", - "parameters": Object { - "__id": "component-b--story-three", - "__isArgsStory": false, - "fileName": "file2", - "renderer": "test", - }, - "tags": Array [ - "autodocs", - "story", - ], - "title": "Component B", - "type": "story", - }, - "component-c--docs": Object { - "id": "component-c--docs", - "importPath": "exports-map-0", - "name": "Docs", - "parameters": Object { - "fileName": "exports-map-0", - "renderer": "test", - }, - "storiesImports": Array [], - "tags": Array [ - "component-tag", - "autodocs", - "docs", - ], - "title": "Component C", - "type": "docs", - }, - "component-c--story-one": Object { - "argTypes": Object {}, - "args": Object {}, - "id": "component-c--story-one", - "importPath": "exports-map-0", - "initialArgs": Object {}, - "name": "Story One", - "parameters": Object { - "__isArgsStory": false, - "fileName": "exports-map-0", - "renderer": "test", - }, - "tags": Array [ - "story-tag", - "story", - ], - "title": "Component C", - "type": "story", - }, - "component-c--story-two": Object { - "argTypes": Object {}, - "args": Object {}, - "id": "component-c--story-two", - "importPath": "exports-map-0", - "initialArgs": Object {}, - "name": "Story Two", - "parameters": Object { - "__isArgsStory": false, - "fileName": "exports-map-0", - "renderer": "test", - }, - "tags": Array [ - "component-tag", - "autodocs", - "story", - ], - "title": "Component C", - "type": "story", - }, - }, - "v": 4, - } - `); - }); - }); - describe('when docsOptions.autodocs = true', () => { - beforeEach(() => { - global.DOCS_OPTIONS = { autodocs: true, defaultName: 'Docs' }; - }); - - it('adds stories for each component with autodocs tag', async () => { - const renderToDOM = jest.fn(); - - const { configure, clientApi } = start(renderToDOM); - configure('test', () => { - (clientApi as any).addParameters({ - docs: { renderer: () => ({ render: jest.fn((_, _2, _3, d) => d()) }) }, - }); - clientApi - .storiesOf('Component A', { id: 'file1' } as NodeModule) - .add('Story One', jest.fn()) - .add('Story Two', jest.fn()); - - clientApi - .storiesOf('Component B', { id: 'file2' } as NodeModule) - .addParameters({ tags: ['autodocs'] }) - .add('Story Three', jest.fn()); - - return [componentCExports]; - }); - - await waitForRender(); - const setIndexData = mockChannel.emit.mock.calls.find((call) => call[0] === SET_INDEX)?.[1]; - expect(Object.keys(setIndexData.entries)).toMatchInlineSnapshot(` - Array [ - "component-a--docs", - "component-a--story-one", - "component-a--story-two", - "component-b--docs", - "component-b--story-three", - "component-c--docs", - "component-c--story-one", - "component-c--story-two", - ] - `); - }); - }); - }); - - describe('auto-title', () => { - const componentDExports = { - default: { - component: 'Component D', - }, - StoryOne: jest.fn(), - }; - it('loads and renders the first story correctly', async () => { - const renderToCanvas = jest.fn(); - - const { configure } = start(renderToCanvas); - configure('test', () => [componentDExports]); - - await waitForEvents([SET_INDEX]); - expect(mockChannel.emit.mock.calls.find((call) => call[0] === SET_INDEX)?.[1]) - .toMatchInlineSnapshot(` - Object { - "entries": Object { - "auto-title--story-one": Object { - "argTypes": Object {}, - "args": Object {}, - "id": "auto-title--story-one", - "importPath": "exports-map-0", - "initialArgs": Object {}, - "name": "Story One", - "parameters": Object { - "__isArgsStory": false, - "fileName": "exports-map-0", - "renderer": "test", - }, - "tags": Array [ - "story", - ], - "title": "auto-title", - "type": "story", - }, - }, - "v": 4, - } - `); - - await waitForRender(); - }); - }); -}); diff --git a/code/lib/preview-api/src/modules/core-client/start.ts b/code/lib/preview-api/src/modules/core-client/start.ts deleted file mode 100644 index f9c389f71e8f..000000000000 --- a/code/lib/preview-api/src/modules/core-client/start.ts +++ /dev/null @@ -1,176 +0,0 @@ -/* eslint-disable no-underscore-dangle, @typescript-eslint/naming-convention */ -import { global } from '@storybook/global'; -import type { Renderer, ArgsStoryFn, Path, ProjectAnnotations } from '@storybook/types'; -import { createBrowserChannel } from '@storybook/channels'; -import { FORCE_RE_RENDER } from '@storybook/core-events'; -import { addons } from '../../addons'; -import { PreviewWeb } from '../../preview-web'; -import { ClientApi } from '../../client-api'; - -import { executeLoadableForChanges } from './executeLoadable'; -import type { Loadable } from './executeLoadable'; - -const { FEATURES } = global; - -const removedApi = (name: string) => () => { - throw new Error(`@storybook/client-api:${name} was removed in storyStoreV7.`); -}; - -interface CoreClient_RendererImplementation { - /** - * A function that applies decorators to a story. - * @template TRenderer The type of renderer used by the Storybook client API. - * @type {ProjectAnnotations['applyDecorators']} - */ - decorateStory?: ProjectAnnotations['applyDecorators']; - /** - * A function that renders a story with args. - * @template TRenderer The type of renderer used by the Storybook client API. - * @type {ArgsStoryFn} - */ - render?: ArgsStoryFn; -} - -interface CoreClient_ClientAPIFacade { - /** - * The old way of adding stories at runtime. - * @deprecated This method is deprecated and will be removed in a future version. - */ - storiesOf: (...args: any[]) => never; - /** - * The old way of retrieving the list of stories at runtime. - * @deprecated This method is deprecated and will be removed in a future version. - */ - raw: (...args: any[]) => never; -} - -interface CoreClient_StartReturnValue { - /** - * Forces a re-render of all stories in the Storybook preview. - * This function emits the `FORCE_RE_RENDER` event to the Storybook channel. - * @deprecated This method is deprecated and will be removed in a future version. - * @returns {void} - */ - forceReRender: () => void; - /** - * The old way of setting up storybook with runtime configuration. - * @deprecated This method is deprecated and will be removed in a future version. - * @returns {void} - */ - configure: any; - /** - * @deprecated This property is deprecated and will be removed in a future version. - * @type {ClientApi | CoreClient_ClientAPIFacade} - */ - clientApi: ClientApi | CoreClient_ClientAPIFacade; -} - -/** - * Initializes the Storybook preview API. - * @template TRenderer The type of renderer used by the Storybook client API. - * @param {ProjectAnnotations['renderToCanvas']} renderToCanvas A function that renders a story to a canvas. - * @param {CoreClient_RendererImplementation} [options] Optional configuration options for the renderer implementation. - * @param {ProjectAnnotations['applyDecorators']} [options.decorateStory] A function that applies decorators to a story. - * @param {ArgsStoryFn} [options.render] A function that renders a story with arguments. - * @returns {CoreClient_StartReturnValue} An object containing functions and objects related to the Storybook preview API. - */ -export function start( - renderToCanvas: ProjectAnnotations['renderToCanvas'], - { decorateStory, render }: CoreClient_RendererImplementation = {} -): CoreClient_StartReturnValue { - if (global) { - // To enable user code to detect if it is running in Storybook - global.IS_STORYBOOK = true; - } - - if (FEATURES?.storyStoreV7) { - return { - forceReRender: removedApi('forceReRender'), - configure: removedApi('configure'), - clientApi: { - storiesOf: removedApi('clientApi.storiesOf'), - raw: removedApi('raw'), - }, - }; - } - - const channel = createBrowserChannel({ page: 'preview' }); - addons.setChannel(channel); - - const clientApi = global?.__STORYBOOK_CLIENT_API__ || new ClientApi(); - const preview = global?.__STORYBOOK_PREVIEW__ || new PreviewWeb(); - let initialized = false; - - const importFn = (path: Path) => clientApi.importFn(path); - function onStoriesChanged() { - const storyIndex = clientApi.getStoryIndex(); - preview.onStoriesChanged({ storyIndex, importFn }); - } - - // These two bits are a bit ugly, but due to dependencies, `ClientApi` cannot have - // direct reference to `PreviewWeb`, so we need to patch in bits - clientApi.onImportFnChanged = onStoriesChanged; - clientApi.storyStore = preview.storyStore; - - if (global) { - global.__STORYBOOK_CLIENT_API__ = clientApi; - global.__STORYBOOK_ADDONS_CHANNEL__ = channel; - global.__STORYBOOK_PREVIEW__ = preview; - global.__STORYBOOK_STORY_STORE__ = preview.storyStore; - } - - return { - forceReRender: () => channel.emit(FORCE_RE_RENDER), - - clientApi, - // This gets called each time the user calls configure (i.e. once per HMR) - // The first time, it constructs the preview, subsequently it updates it - configure( - renderer: string, - loadable: Loadable, - m?: NodeModule, - disableBackwardCompatibility = true - ) { - if (disableBackwardCompatibility) { - throw new Error('unexpected configure() call'); - } - - clientApi.addParameters({ renderer }); - - // We need to run the `executeLoadableForChanges` function *inside* the `getProjectAnnotations - // function in case it throws. So we also need to process its output there also - const getProjectAnnotations = () => { - const { added, removed } = executeLoadableForChanges(loadable, m); - clientApi._loadAddedExports(); - - Array.from(added.entries()).forEach(([fileName, fileExports]) => - clientApi.facade.addStoriesFromExports(fileName, fileExports) - ); - - Array.from(removed.entries()).forEach(([fileName]) => - clientApi.facade.clearFilenameExports(fileName) - ); - - return { - render, - ...clientApi.facade.projectAnnotations, - renderToCanvas, - applyDecorators: decorateStory, - }; - }; - - if (!initialized) { - preview.initialize({ - getStoryIndex: () => clientApi.getStoryIndex(), - importFn, - getProjectAnnotations, - }); - initialized = true; - } else { - // TODO -- why don't we care about the new annotations? - getProjectAnnotations(); - onStoriesChanged(); - } - }, - }; -} diff --git a/code/lib/preview-api/src/modules/preview-web/Preview.tsx b/code/lib/preview-api/src/modules/preview-web/Preview.tsx index 638cf8126b61..d98bd7feaf05 100644 --- a/code/lib/preview-api/src/modules/preview-web/Preview.tsx +++ b/code/lib/preview-api/src/modules/preview-web/Preview.tsx @@ -61,7 +61,7 @@ export class Preview { previewEntryError?: Error; constructor(protected channel: Channel = addons.getChannel()) { - if (global.FEATURES?.storyStoreV7 && addons.hasServerChannel()) { + if (addons.hasServerChannel()) { this.serverChannel = addons.getServerChannel(); } this.storyStore = new StoryStore(); @@ -142,15 +142,7 @@ export class Preview { this.setInitialGlobals(); - let storyIndexPromise: Promise; - if (global.FEATURES?.storyStoreV7) { - storyIndexPromise = this.getStoryIndexFromServer(); - } else { - if (!this.getStoryIndex) { - throw new Error('No `getStoryIndex` passed defined in v6 mode'); - } - storyIndexPromise = SynchronousPromise.resolve().then(this.getStoryIndex); - } + const storyIndexPromise = this.getStoryIndexFromServer(); return storyIndexPromise .then((storyIndex: StoryIndex) => this.initializeWithStoryIndex(storyIndex)) @@ -192,7 +184,7 @@ export class Preview { return this.storyStore.initialize({ storyIndex, importFn: this.importFn, - cache: !global.FEATURES?.storyStoreV7, + cache: false, // FIXME -- drop this option }); } diff --git a/code/lib/preview-api/src/modules/preview-web/PreviewWeb.integration.test.ts b/code/lib/preview-api/src/modules/preview-web/PreviewWeb.integration.test.ts index 91d2cd35ae3e..556f7d2d2ecc 100644 --- a/code/lib/preview-api/src/modules/preview-web/PreviewWeb.integration.test.ts +++ b/code/lib/preview-api/src/modules/preview-web/PreviewWeb.integration.test.ts @@ -44,9 +44,7 @@ jest.mock('@storybook/global', () => ({ search: '?id=*', }, }, - FEATURES: { - storyStoreV7: true, - }, + fetch: async () => ({ status: 200, json: async () => mockStoryIndex }), }, })); diff --git a/code/lib/preview-api/src/modules/preview-web/PreviewWeb.test.ts b/code/lib/preview-api/src/modules/preview-web/PreviewWeb.test.ts index 8123020b86bb..a132deee8f56 100644 --- a/code/lib/preview-api/src/modules/preview-web/PreviewWeb.test.ts +++ b/code/lib/preview-api/src/modules/preview-web/PreviewWeb.test.ts @@ -69,10 +69,6 @@ jest.mock('@storybook/global', () => ({ search: '?id=*', }, }, - FEATURES: { - storyStoreV7: true, - // xxx - }, fetch: async () => mockFetchResult, }, })); diff --git a/code/lib/preview-api/src/modules/preview-web/PreviewWithSelection.tsx b/code/lib/preview-api/src/modules/preview-web/PreviewWithSelection.tsx index 73b58cc70ead..4535e4201a7f 100644 --- a/code/lib/preview-api/src/modules/preview-web/PreviewWithSelection.tsx +++ b/code/lib/preview-api/src/modules/preview-web/PreviewWithSelection.tsx @@ -117,10 +117,6 @@ export class PreviewWithSelection extends Preview { return super.initializeWithStoryIndex(storyIndex).then(() => { - if (!global.FEATURES?.storyStoreV7) { - this.channel.emit(SET_INDEX, this.storyStore.getSetIndexPayload()); - } - return this.selectSpecifiedStory(); }); } @@ -204,10 +200,6 @@ export class PreviewWithSelection extends Preview extends Preview extends Preview extends Preview = ( const [progress, setProgress] = useState(undefined); useEffect(() => { - if (FEATURES?.storyStoreV7 && global.CONFIG_TYPE === 'DEVELOPMENT') { + if (global.CONFIG_TYPE === 'DEVELOPMENT') { try { const channel = addons.getServerChannel(); diff --git a/code/ui/manager/src/runtime.ts b/code/ui/manager/src/runtime.ts index 861fc3b88fcb..8c565be5b1c8 100644 --- a/code/ui/manager/src/runtime.ts +++ b/code/ui/manager/src/runtime.ts @@ -34,7 +34,7 @@ class ReactProvider extends Provider { this.channel = channel; global.__STORYBOOK_ADDONS_CHANNEL__ = channel; - if (FEATURES?.storyStoreV7 && CONFIG_TYPE === 'DEVELOPMENT') { + if (CONFIG_TYPE === 'DEVELOPMENT') { this.serverChannel = this.channel; addons.setServerChannel(this.serverChannel); } diff --git a/docs/api/main-config-features.md b/docs/api/main-config-features.md index 81a80502a7bc..e0bb12df4b12 100644 --- a/docs/api/main-config-features.md +++ b/docs/api/main-config-features.md @@ -9,34 +9,13 @@ Type: ```ts { argTypeTargetsV7?: boolean; - buildStoriesJson?: boolean; legacyDecoratorFileOrder?: boolean; legacyMdx1?: boolean; - storyStoreV7?: boolean; } ``` Enables Storybook's additional features. -## `buildStoriesJson` - -Type: `boolean` - -Default: `true`, when [`storyStoreV7`](#storystorev7) is `true` - -Generates a `index.json` and `stories.json` files to help story loading with the on-demand mode. - - - - - - - ## `legacyDecoratorFileOrder` Type: `boolean` @@ -71,25 +50,6 @@ Enables support for MDX version 1 as a fallback. Requires [@storybook/mdx1-csf]( -## `storyStoreV7` - -Type: `boolean` - -Default: `true` - -Opts out of [on-demand story loading](#on-demand-story-loading); loads all stories at build time. - - - - - - - ## `argTypeTargetsV7` (⚠️ **Experimental**) @@ -107,26 +67,4 @@ Filter args with a "target" on the type from the render function. ]} /> - - -## On-demand story loading - -As your Storybook grows, it gets challenging to load all of your stories performantly, slowing down the loading times and yielding a large bundle. Out of the box, Storybook loads your stories on demand rather than during boot-up to improve the performance of your Storybook. If you need to load all of your stories during boot-up, you can disable this feature by setting the `storyStoreV7` feature flag to `false` in your configuration as follows: - - - - - - - -### Known limitations - -Because of the way stories are currently indexed in Storybook, loading stories on demand with `storyStoreV7` has a couple of minor limitations at the moment: - -- [CSF formats](../api/csf.md) from version 1 to version 3 are supported. The `storiesOf` construct is not. -- Custom [`storySort` functions](../writing-stories/naming-components-and-hierarchy.md#sorting-stories) receive more limited arguments. + \ No newline at end of file diff --git a/docs/api/main-config-stories.md b/docs/api/main-config-stories.md index 9eefa1360565..eeffbe428ece 100644 --- a/docs/api/main-config-stories.md +++ b/docs/api/main-config-stories.md @@ -117,7 +117,7 @@ When [auto-titling](../configure/sidebar-and-urls.md#csf-30-auto-titles), prefix
-💡 With [`storyStoreV7`](./main-config-features.md#storystorev7) (the default in Storybook 7), Storybook now statically analyzes the configuration file to improve performance. Loading stories with a custom implementation may de-optimize or break this ability. +💡 Storybook now statically analyzes the configuration file to improve performance. Loading stories with a custom implementation may de-optimize or break this ability.
diff --git a/docs/builders/webpack.md b/docs/builders/webpack.md index 130ac9bd56e1..5c9ddf93ebb1 100644 --- a/docs/builders/webpack.md +++ b/docs/builders/webpack.md @@ -10,7 +10,6 @@ By default, Storybook provides zero-config support for Webpack and automatically | Option | Description | | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `storyStoreV7` | Enabled by default.
Configures Webpack's [code splitting](https://webpack.js.org/guides/code-splitting/) feature
`features: { storyStoreV7: false }` | | `lazyCompilation` | Enables Webpack's experimental [`lazy compilation`](https://webpack.js.org/configuration/experiments/#experimentslazycompilation)
`core: { builder: { options: { lazyCompilation: true } } }` | | `fsCache` | Configures Webpack's filesystem [caching](https://webpack.js.org/configuration/cache/#cachetype) feature
`core: { builder: { options: { fsCache: true } } }` | From 01836ba60461601dd4f93b104ebe6f1823d83651 Mon Sep 17 00:00:00 2001 From: Tom Coleman Date: Mon, 30 Oct 2023 16:47:47 +1100 Subject: [PATCH 2/8] Convert all Preview Web code to async/await --- code/lib/preview-api/package.json | 1 - .../src/modules/preview-web/Preview.tsx | 73 ++++++------- .../preview-web/PreviewWithSelection.tsx | 10 +- .../src/modules/store/StoryStore.ts | 100 +++++------------- code/yarn.lock | 8 -- 5 files changed, 61 insertions(+), 131 deletions(-) diff --git a/code/lib/preview-api/package.json b/code/lib/preview-api/package.json index 93eebf7dc43f..3fe3fad4768c 100644 --- a/code/lib/preview-api/package.json +++ b/code/lib/preview-api/package.json @@ -54,7 +54,6 @@ "lodash": "^4.17.21", "memoizerific": "^1.11.3", "qs": "^6.10.0", - "synchronous-promise": "^2.0.15", "ts-dedent": "^2.0.0", "util-deprecate": "^1.0.2" }, diff --git a/code/lib/preview-api/src/modules/preview-web/Preview.tsx b/code/lib/preview-api/src/modules/preview-web/Preview.tsx index d98bd7feaf05..8e36bcf89eaf 100644 --- a/code/lib/preview-api/src/modules/preview-web/Preview.tsx +++ b/code/lib/preview-api/src/modules/preview-web/Preview.tsx @@ -1,6 +1,5 @@ import { dedent } from 'ts-dedent'; import { global } from '@storybook/global'; -import { SynchronousPromise } from 'synchronous-promise'; import { CONFIG_ERROR, FORCE_REMOUNT, @@ -13,7 +12,7 @@ import { UPDATE_GLOBALS, UPDATE_STORY_ARGS, } from '@storybook/core-events'; -import { logger, deprecate } from '@storybook/client-logger'; +import { logger } from '@storybook/client-logger'; import type { Channel } from '@storybook/channels'; import type { Renderer, @@ -75,7 +74,7 @@ export class Preview { // (Even simple things like `Promise.resolve()` and `await` involve the callback happening // in the next promise "tick"). // See the comment in `storyshots-core/src/api/index.ts` for more detail. - initialize({ + async initialize({ getStoryIndex, importFn, getProjectAnnotations, @@ -93,9 +92,8 @@ export class Preview { this.setupListeners(); - return this.getProjectAnnotationsOrRenderError(getProjectAnnotations).then( - (projectAnnotations) => this.initializeWithProjectAnnotations(projectAnnotations) - ); + const projectAnnotations = await this.getProjectAnnotationsOrRenderError(getProjectAnnotations); + return this.initializeWithProjectAnnotations(projectAnnotations); } setupListeners() { @@ -107,49 +105,44 @@ export class Preview { this.channel.on(FORCE_REMOUNT, this.onForceRemount.bind(this)); } - getProjectAnnotationsOrRenderError( + async getProjectAnnotationsOrRenderError( getProjectAnnotations: () => MaybePromise> ): Promise> { - return SynchronousPromise.resolve() - .then(getProjectAnnotations) - .then((projectAnnotations) => { - if (projectAnnotations.renderToDOM) - deprecate(`\`renderToDOM\` is deprecated, please rename to \`renderToCanvas\``); - - this.renderToCanvas = projectAnnotations.renderToCanvas || projectAnnotations.renderToDOM; - if (!this.renderToCanvas) { - throw new Error(dedent` + try { + const projectAnnotations = await getProjectAnnotations(); + + this.renderToCanvas = projectAnnotations.renderToCanvas; + if (!this.renderToCanvas) { + throw new Error(dedent` Expected your framework's preset to export a \`renderToCanvas\` field. Perhaps it needs to be upgraded for Storybook 6.4? More info: https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#mainjs-framework-field `); - } - return projectAnnotations; - }) - .catch((err) => { - // This is an error extracting the projectAnnotations (i.e. evaluating the previewEntries) and - // needs to be show to the user as a simple error - this.renderPreviewEntryError('Error reading preview.js:', err); - throw err; - }); + } + return projectAnnotations; + } catch (err) { + // This is an error extracting the projectAnnotations (i.e. evaluating the previewEntries) and + // needs to be show to the user as a simple error + this.renderPreviewEntryError('Error reading preview.js:', err as Error); + throw err; + } } // If initialization gets as far as project annotations, this function runs. - initializeWithProjectAnnotations(projectAnnotations: ProjectAnnotations) { + async initializeWithProjectAnnotations(projectAnnotations: ProjectAnnotations) { this.storyStore.setProjectAnnotations(projectAnnotations); this.setInitialGlobals(); - const storyIndexPromise = this.getStoryIndexFromServer(); - - return storyIndexPromise - .then((storyIndex: StoryIndex) => this.initializeWithStoryIndex(storyIndex)) - .catch((err) => { - this.renderPreviewEntryError('Error loading story index:', err); - throw err; - }); + try { + const storyIndex = await this.getStoryIndexFromServer(); + return this.initializeWithStoryIndex(storyIndex); + } catch (err) { + this.renderPreviewEntryError('Error loading story index:', err as Error); + throw err; + } } async setInitialGlobals() { @@ -177,15 +170,11 @@ export class Preview { } // If initialization gets as far as the story index, this function runs. - initializeWithStoryIndex(storyIndex: StoryIndex): PromiseLike { + initializeWithStoryIndex(storyIndex: StoryIndex): void { if (!this.importFn) throw new Error(`Cannot call initializeWithStoryIndex before initialization`); - return this.storyStore.initialize({ - storyIndex, - importFn: this.importFn, - cache: false, // FIXME -- drop this option - }); + this.storyStore.initialize({ storyIndex, importFn: this.importFn }); } // EVENT HANDLERS @@ -360,9 +349,7 @@ export class Preview { Do you have an error in your \`preview.js\`? Check your Storybook's browser console for errors.`); } - if (global.FEATURES?.storyStoreV7) { - await this.storyStore.cacheAllCSFFiles(); - } + await this.storyStore.cacheAllCSFFiles(); return this.storyStore.extract(options); } diff --git a/code/lib/preview-api/src/modules/preview-web/PreviewWithSelection.tsx b/code/lib/preview-api/src/modules/preview-web/PreviewWithSelection.tsx index 4535e4201a7f..04178c5ff9af 100644 --- a/code/lib/preview-api/src/modules/preview-web/PreviewWithSelection.tsx +++ b/code/lib/preview-api/src/modules/preview-web/PreviewWithSelection.tsx @@ -1,12 +1,10 @@ import { dedent } from 'ts-dedent'; -import { global } from '@storybook/global'; import { CURRENT_STORY_WAS_SET, DOCS_PREPARED, PRELOAD_ENTRIES, PREVIEW_KEYDOWN, SET_CURRENT_STORY, - SET_INDEX, STORY_ARGS_UPDATED, STORY_CHANGED, STORY_ERRORED, @@ -115,10 +113,10 @@ export class PreviewWithSelection extends Preview { - return super.initializeWithStoryIndex(storyIndex).then(() => { - return this.selectSpecifiedStory(); - }); + async initializeWithStoryIndex(storyIndex: StoryIndex): Promise { + await super.initializeWithStoryIndex(storyIndex); + + return this.selectSpecifiedStory(); } // Use the selection specifier to choose a story, then render it diff --git a/code/lib/preview-api/src/modules/store/StoryStore.ts b/code/lib/preview-api/src/modules/store/StoryStore.ts index 1fc0fa547d2a..19d21c8d6fc6 100644 --- a/code/lib/preview-api/src/modules/store/StoryStore.ts +++ b/code/lib/preview-api/src/modules/store/StoryStore.ts @@ -2,7 +2,6 @@ import memoize from 'memoizerific'; import type { IndexEntry, Renderer, - API_PreparedStoryIndex, ComponentTitle, Parameters, Path, @@ -24,7 +23,6 @@ import type { } from '@storybook/types'; import mapValues from 'lodash/mapValues.js'; import pick from 'lodash/pick.js'; -import { SynchronousPromise } from 'synchronous-promise'; import { HooksContext } from '../addons'; import { StoryIndexStore } from './StoryIndexStore'; @@ -63,7 +61,7 @@ export class StoryStore { prepareStoryWithCache: typeof prepareStory; - initializationPromise: SynchronousPromise; + initializationPromise: Promise; // This *does* get set in the constructor but the semantics of `new SynchronousPromise` trip up TS resolveInitializationPromise!: () => void; @@ -80,7 +78,7 @@ export class StoryStore { this.prepareStoryWithCache = memoize(STORY_CACHE_SIZE)(prepareStory) as typeof prepareStory; // We cannot call `loadStory()` until we've been initialized properly. But we can wait for it. - this.initializationPromise = new SynchronousPromise((resolve) => { + this.initializationPromise = new Promise((resolve) => { this.resolveInitializationPromise = resolve; }); } @@ -100,19 +98,15 @@ export class StoryStore { initialize({ storyIndex, importFn, - cache = false, }: { storyIndex?: StoryIndex; importFn: ModuleImportFn; - cache?: boolean; - }): Promise { + }): void { this.storyIndex = new StoryIndexStore(storyIndex); this.importFn = importFn; // We don't need the cache to be loaded to call `loadStory`, we just need the index ready this.resolveInitializationPromise(); - - return cache ? this.cacheAllCSFFiles() : SynchronousPromise.resolve(); } // This means that one of the CSF files has changed. @@ -142,18 +136,18 @@ export class StoryStore { } // To load a single CSF file to service a story we need to look up the importPath in the index - loadCSFFileByStoryId(storyId: StoryId): Promise> { + async loadCSFFileByStoryId(storyId: StoryId): Promise> { if (!this.storyIndex || !this.importFn) throw new Error(`loadCSFFileByStoryId called before initialization`); const { importPath, title } = this.storyIndex.storyIdToEntry(storyId); - return this.importFn(importPath).then((moduleExports) => - // We pass the title in here as it may have been generated by autoTitle on the server. - this.processCSFFileWithCache(moduleExports, importPath, title) - ); + const moduleExports = await this.importFn(importPath); + + // We pass the title in here as it may have been generated by autoTitle on the server. + return this.processCSFFileWithCache(moduleExports, importPath, title); } - loadAllCSFFiles({ batchSize = EXTRACT_BATCH_SIZE } = {}): Promise< + async loadAllCSFFiles({ batchSize = EXTRACT_BATCH_SIZE } = {}): Promise< StoryStore['cachedCSFFiles'] > { if (!this.storyIndex) throw new Error(`loadAllCSFFiles called before initialization`); @@ -163,41 +157,33 @@ export class StoryStore { storyId, ]); - const loadInBatches = ( + const loadInBatches = async ( remainingImportPaths: typeof importPaths ): Promise<{ importPath: Path; csfFile: CSFFile }[]> => { - if (remainingImportPaths.length === 0) return SynchronousPromise.resolve([]); + if (remainingImportPaths.length === 0) return Promise.resolve([]); const csfFilePromiseList = remainingImportPaths .slice(0, batchSize) - .map(([importPath, storyId]) => - this.loadCSFFileByStoryId(storyId).then((csfFile) => ({ - importPath, - csfFile, - })) - ); - - return SynchronousPromise.all(csfFilePromiseList).then((firstResults) => - loadInBatches(remainingImportPaths.slice(batchSize)).then((restResults) => - firstResults.concat(restResults) - ) - ); + .map(async ([importPath, storyId]) => ({ + importPath, + csfFile: await this.loadCSFFileByStoryId(storyId), + })); + + const firstResults = await Promise.all(csfFilePromiseList); + const restResults = await loadInBatches(remainingImportPaths.slice(batchSize)); + return firstResults.concat(restResults); }; - return loadInBatches(importPaths).then((list) => - list.reduce((acc, { importPath, csfFile }) => { - acc[importPath] = csfFile; - return acc; - }, {} as Record>) - ); + const list = await loadInBatches(importPaths); + return list.reduce((acc, { importPath, csfFile }) => { + acc[importPath] = csfFile; + return acc; + }, {} as Record>); } - cacheAllCSFFiles(): Promise { - return this.initializationPromise.then(() => - this.loadAllCSFFiles().then((csfFiles) => { - this.cachedCSFFiles = csfFiles; - }) - ); + async cacheAllCSFFiles(): Promise { + await this.initializationPromise; + this.cachedCSFFiles = await this.loadAllCSFFiles(); } preparedMetaFromCSFFile({ csfFile }: { csfFile: CSFFile }): PreparedMeta { @@ -393,38 +379,6 @@ export class StoryStore { }; }; - getSetIndexPayload(): API_PreparedStoryIndex { - if (!this.storyIndex) throw new Error('getSetIndexPayload called before initialization'); - if (!this.cachedCSFFiles) - throw new Error('Cannot call getSetIndexPayload() unless you call cacheAllCSFFiles() first'); - const { cachedCSFFiles } = this; - - const stories = this.extract({ includeDocsOnly: true }); - - return { - v: 4, - entries: Object.fromEntries( - Object.entries(this.storyIndex.entries).map(([id, entry]) => [ - id, - stories[id] - ? { - ...entry, - args: stories[id].initialArgs, - initialArgs: stories[id].initialArgs, - argTypes: stories[id].argTypes, - parameters: stories[id].parameters, - } - : { - ...entry, - parameters: this.preparedMetaFromCSFFile({ - csfFile: cachedCSFFiles[entry.importPath], - }).parameters, - }, - ]) - ), - }; - } - raw(): BoundStory[] { return Object.values(this.extract()) .map(({ id }: { id: StoryId }) => this.fromId(id)) diff --git a/code/yarn.lock b/code/yarn.lock index 6ca7f63f7d0a..1c97619cf949 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -6940,7 +6940,6 @@ __metadata: qs: ^6.10.0 react: ^18.2.0 slash: ^5.0.0 - synchronous-promise: ^2.0.15 ts-dedent: ^2.0.0 util-deprecate: ^1.0.2 languageName: unknown @@ -28863,13 +28862,6 @@ __metadata: languageName: node linkType: hard -"synchronous-promise@npm:^2.0.15": - version: 2.0.17 - resolution: "synchronous-promise@npm:2.0.17" - checksum: 1babe643d8417789ef6e5a2f3d4b8abcda2de236acd09bbe2c98f6be82c0a2c92ed21a6e4f934845fa8de18b1435a9cba1e8c3d945032e8a532f076224c024b1 - languageName: node - linkType: hard - "tapable@npm:^2.0.0, tapable@npm:^2.1.1, tapable@npm:^2.2.0, tapable@npm:^2.2.1": version: 2.2.1 resolution: "tapable@npm:2.2.1" From 7e1e2b69596f1c34983743b73f530f7d0b8a53af Mon Sep 17 00:00:00 2001 From: Tom Coleman Date: Mon, 30 Oct 2023 16:54:40 +1100 Subject: [PATCH 3/8] Small fixups --- code/lib/preview-api/src/modules/preview-web/Preview.tsx | 7 ------- code/lib/preview-api/src/modules/store/StoryStore.ts | 2 +- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/code/lib/preview-api/src/modules/preview-web/Preview.tsx b/code/lib/preview-api/src/modules/preview-web/Preview.tsx index 8e36bcf89eaf..c5964131fad0 100644 --- a/code/lib/preview-api/src/modules/preview-web/Preview.tsx +++ b/code/lib/preview-api/src/modules/preview-web/Preview.tsx @@ -67,13 +67,6 @@ export class Preview { } // INITIALIZATION - - // NOTE: the reason that the preview and store's initialization code is written in a promise - // style and not `async-await`, and the use of `SynchronousPromise`s is in order to allow - // storyshots to immediately call `raw()` on the store without waiting for a later tick. - // (Even simple things like `Promise.resolve()` and `await` involve the callback happening - // in the next promise "tick"). - // See the comment in `storyshots-core/src/api/index.ts` for more detail. async initialize({ getStoryIndex, importFn, diff --git a/code/lib/preview-api/src/modules/store/StoryStore.ts b/code/lib/preview-api/src/modules/store/StoryStore.ts index 19d21c8d6fc6..c16f353d7c04 100644 --- a/code/lib/preview-api/src/modules/store/StoryStore.ts +++ b/code/lib/preview-api/src/modules/store/StoryStore.ts @@ -63,7 +63,7 @@ export class StoryStore { initializationPromise: Promise; - // This *does* get set in the constructor but the semantics of `new SynchronousPromise` trip up TS + // This *does* get set in the constructor but the semantics of `new Promise` trip up TS resolveInitializationPromise!: () => void; constructor() { From d21b6b4d0cfbde391f3abff9098e00c8e496d52c Mon Sep 17 00:00:00 2001 From: Tom Coleman Date: Tue, 31 Oct 2023 11:19:36 +1100 Subject: [PATCH 4/8] Remove public APIs from packages --- code/frameworks/angular/src/client/index.ts | 3 -- .../angular/src/client/public-api.ts | 29 ----------------- .../ember/src/client/preview/index.ts | 16 ---------- code/renderers/html/src/index.ts | 1 - code/renderers/html/src/public-api.ts | 26 ---------------- code/renderers/preact/src/index.ts | 1 - code/renderers/preact/src/public-api.ts | 26 ---------------- code/renderers/react/src/index.ts | 1 - code/renderers/react/src/public-api.tsx | 25 --------------- code/renderers/server/src/index.ts | 1 - code/renderers/server/src/public-api.ts | 26 ---------------- code/renderers/svelte/src/index.ts | 1 - code/renderers/svelte/src/public-api.ts | 30 ------------------ code/renderers/vue/src/index.ts | 1 - code/renderers/vue/src/public-api.ts | 28 ----------------- code/renderers/vue3/src/index.ts | 2 +- code/renderers/vue3/src/public-api.ts | 31 ------------------- code/renderers/web-components/src/index.ts | 1 - .../web-components/src/public-api.ts | 26 ---------------- 19 files changed, 1 insertion(+), 274 deletions(-) delete mode 100644 code/frameworks/angular/src/client/public-api.ts delete mode 100644 code/frameworks/ember/src/client/preview/index.ts delete mode 100644 code/renderers/html/src/public-api.ts delete mode 100644 code/renderers/preact/src/public-api.ts delete mode 100644 code/renderers/react/src/public-api.tsx delete mode 100644 code/renderers/server/src/public-api.ts delete mode 100644 code/renderers/svelte/src/public-api.ts delete mode 100644 code/renderers/vue/src/public-api.ts delete mode 100644 code/renderers/vue3/src/public-api.ts delete mode 100644 code/renderers/web-components/src/public-api.ts diff --git a/code/frameworks/angular/src/client/index.ts b/code/frameworks/angular/src/client/index.ts index 2377678bda2e..8f2e0a4172c0 100644 --- a/code/frameworks/angular/src/client/index.ts +++ b/code/frameworks/angular/src/client/index.ts @@ -2,9 +2,6 @@ import './globals'; -// eslint-disable-next-line import/export -export * from './public-api'; -// eslint-disable-next-line import/export export * from './public-types'; export type { StoryFnAngularReturnType as IStory } from './types'; diff --git a/code/frameworks/angular/src/client/public-api.ts b/code/frameworks/angular/src/client/public-api.ts deleted file mode 100644 index 3f91276a044b..000000000000 --- a/code/frameworks/angular/src/client/public-api.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* eslint-disable prefer-destructuring */ -import { Addon_ClientStoryApi, Addon_Loadable } from '@storybook/types'; -import { start } from '@storybook/preview-api'; -import { renderToCanvas, render } from './render'; -import decorateStory from './decorateStory'; -import { AngularRenderer } from './types'; - -export * from './public-types'; - -const RENDERER = 'angular'; - -interface ClientApi extends Addon_ClientStoryApi { - configure(loader: Addon_Loadable, module: NodeModule): void; - forceReRender(): void; - raw: () => any; // todo add type - load: (...args: any[]) => void; -} - -const api = start(renderToCanvas, { decorateStory, render }); - -export const storiesOf: ClientApi['storiesOf'] = (kind, m) => { - return (api.clientApi.storiesOf(kind, m) as ReturnType).addParameters({ - renderer: RENDERER, - }); -}; - -export const configure: ClientApi['configure'] = (...args) => api.configure(RENDERER, ...args); -export const forceReRender: ClientApi['forceReRender'] = api.forceReRender; -export const raw: ClientApi['raw'] = api.clientApi.raw; diff --git a/code/frameworks/ember/src/client/preview/index.ts b/code/frameworks/ember/src/client/preview/index.ts deleted file mode 100644 index 7b77a8755219..000000000000 --- a/code/frameworks/ember/src/client/preview/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { start } from '@storybook/preview-api'; - -import './globals'; -import type { EmberRenderer } from './types'; -import { renderToCanvas } from './render'; - -const { configure: coreConfigure, clientApi, forceReRender } = start(renderToCanvas); - -export const { raw } = clientApi; - -const RENDERER = 'ember'; -export const storiesOf = (kind: string, m: any) => - clientApi.storiesOf(kind, m).addParameters({ renderer: RENDERER }); -export const configure = (...args: any[]) => coreConfigure(RENDERER, ...args); - -export { forceReRender }; diff --git a/code/renderers/html/src/index.ts b/code/renderers/html/src/index.ts index 0c37ede8d826..145fbf2612ea 100644 --- a/code/renderers/html/src/index.ts +++ b/code/renderers/html/src/index.ts @@ -2,7 +2,6 @@ import './globals'; -export * from './public-api'; export * from './public-types'; // optimization: stop HMR propagation in webpack diff --git a/code/renderers/html/src/public-api.ts b/code/renderers/html/src/public-api.ts deleted file mode 100644 index 542eb1ca7b65..000000000000 --- a/code/renderers/html/src/public-api.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* eslint-disable prefer-destructuring */ -import type { Addon_ClientStoryApi, Addon_Loadable } from '@storybook/types'; -import { start } from '@storybook/preview-api'; -import type { HtmlRenderer } from './types'; - -import { renderToCanvas, render } from './render'; - -const RENDERER = 'html'; - -interface ClientApi extends Addon_ClientStoryApi { - configure(loader: Addon_Loadable, module: NodeModule): void; - forceReRender(): void; - raw: () => any; // todo add type -} - -const api = start(renderToCanvas, { render }); - -export const storiesOf: ClientApi['storiesOf'] = (kind, m) => { - return (api.clientApi.storiesOf(kind, m) as ReturnType).addParameters({ - renderer: RENDERER, - }); -}; - -export const configure: ClientApi['configure'] = (...args) => api.configure(RENDERER, ...args); -export const forceReRender: ClientApi['forceReRender'] = api.forceReRender; -export const raw: ClientApi['raw'] = api.clientApi.raw; diff --git a/code/renderers/preact/src/index.ts b/code/renderers/preact/src/index.ts index 0c37ede8d826..145fbf2612ea 100644 --- a/code/renderers/preact/src/index.ts +++ b/code/renderers/preact/src/index.ts @@ -2,7 +2,6 @@ import './globals'; -export * from './public-api'; export * from './public-types'; // optimization: stop HMR propagation in webpack diff --git a/code/renderers/preact/src/public-api.ts b/code/renderers/preact/src/public-api.ts deleted file mode 100644 index 7a5451bb9ae8..000000000000 --- a/code/renderers/preact/src/public-api.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* eslint-disable prefer-destructuring */ -import type { Addon_ClientStoryApi, Addon_Loadable } from '@storybook/types'; -import { start } from '@storybook/preview-api'; - -import { renderToCanvas } from './render'; -import type { PreactRenderer } from './types'; - -export interface ClientApi extends Addon_ClientStoryApi { - configure(loader: Addon_Loadable, module: NodeModule): void; - forceReRender(): void; - raw: () => any; // todo add type - load: (...args: any[]) => void; -} - -const RENDERER = 'preact'; -const api = start(renderToCanvas); - -export const storiesOf: ClientApi['storiesOf'] = (kind, m) => { - return (api.clientApi.storiesOf(kind, m) as ReturnType).addParameters({ - renderer: RENDERER, - }); -}; - -export const configure: ClientApi['configure'] = (...args) => api.configure(RENDERER, ...args); -export const forceReRender: ClientApi['forceReRender'] = api.forceReRender; -export const raw: ClientApi['raw'] = api.clientApi.raw; diff --git a/code/renderers/react/src/index.ts b/code/renderers/react/src/index.ts index ae032e4f06b8..07a118109716 100644 --- a/code/renderers/react/src/index.ts +++ b/code/renderers/react/src/index.ts @@ -2,7 +2,6 @@ import './globals'; -export * from './public-api'; export * from './public-types'; export * from './testing-api'; diff --git a/code/renderers/react/src/public-api.tsx b/code/renderers/react/src/public-api.tsx deleted file mode 100644 index aaef6d65ddd2..000000000000 --- a/code/renderers/react/src/public-api.tsx +++ /dev/null @@ -1,25 +0,0 @@ -/* eslint-disable prefer-destructuring */ -import { start } from '@storybook/preview-api'; -import type { Addon_ClientStoryApi, Addon_Loadable } from '@storybook/types'; - -import { render, renderToCanvas } from './render'; -import type { ReactRenderer } from './types'; - -interface ClientApi extends Addon_ClientStoryApi { - configure(loader: Addon_Loadable, module: NodeModule): void; - forceReRender(): void; - raw: () => any; // todo add type -} -const RENDERER = 'react'; - -const api = start(renderToCanvas, { render }); - -export const storiesOf: ClientApi['storiesOf'] = (kind, m) => { - return (api.clientApi.storiesOf(kind, m) as ReturnType).addParameters({ - renderer: RENDERER, - }); -}; - -export const configure: ClientApi['configure'] = (...args) => api.configure(RENDERER, ...args); -export const forceReRender: ClientApi['forceReRender'] = api.forceReRender; -export const raw: ClientApi['raw'] = api.clientApi.raw; diff --git a/code/renderers/server/src/index.ts b/code/renderers/server/src/index.ts index 0c37ede8d826..145fbf2612ea 100644 --- a/code/renderers/server/src/index.ts +++ b/code/renderers/server/src/index.ts @@ -2,7 +2,6 @@ import './globals'; -export * from './public-api'; export * from './public-types'; // optimization: stop HMR propagation in webpack diff --git a/code/renderers/server/src/public-api.ts b/code/renderers/server/src/public-api.ts deleted file mode 100644 index e830f96a8918..000000000000 --- a/code/renderers/server/src/public-api.ts +++ /dev/null @@ -1,26 +0,0 @@ -import type { Addon_ClientStoryApi, Addon_Loadable } from '@storybook/types'; -import { start } from '@storybook/preview-api'; - -import { renderToCanvas, render } from './render'; -import type { ServerRenderer } from './types'; - -const RENDERER = 'server'; - -interface ClientApi extends Addon_ClientStoryApi { - configure(loader: Addon_Loadable, module: NodeModule): void; - forceReRender(): void; - raw: () => any; // todo add type -} - -const api = start(renderToCanvas, { render }); - -export const storiesOf: ClientApi['storiesOf'] = (kind, m) => { - return (api.clientApi.storiesOf(kind, m) as ReturnType).addParameters({ - renderer: RENDERER, - }); -}; - -export const configure: ClientApi['configure'] = (...args) => api.configure(RENDERER, ...args); -export const { raw } = api.clientApi; - -export const { forceReRender } = api; diff --git a/code/renderers/svelte/src/index.ts b/code/renderers/svelte/src/index.ts index 0c37ede8d826..145fbf2612ea 100644 --- a/code/renderers/svelte/src/index.ts +++ b/code/renderers/svelte/src/index.ts @@ -2,7 +2,6 @@ import './globals'; -export * from './public-api'; export * from './public-types'; // optimization: stop HMR propagation in webpack diff --git a/code/renderers/svelte/src/public-api.ts b/code/renderers/svelte/src/public-api.ts deleted file mode 100644 index fde627e6a20e..000000000000 --- a/code/renderers/svelte/src/public-api.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* eslint-disable prefer-destructuring */ -import { start } from '@storybook/preview-api'; -import type { Addon_ClientStoryApi, Addon_Loadable } from '@storybook/types'; -import { decorateStory } from './decorators'; - -import type { SvelteRenderer } from './types'; -import { render, renderToCanvas } from './render'; - -const RENDERER = 'svelte'; - -interface ClientApi extends Addon_ClientStoryApi { - configure(loader: Addon_Loadable, module: NodeModule): void; - forceReRender(): void; - raw: () => any; // todo add type -} - -const api = start(renderToCanvas, { - decorateStory, - render, -}); - -export const storiesOf: ClientApi['storiesOf'] = (kind, m) => { - return (api.clientApi.storiesOf(kind, m) as ReturnType).addParameters({ - renderer: RENDERER, - }); -}; - -export const configure: ClientApi['configure'] = (...args) => api.configure(RENDERER, ...args); -export const forceReRender: ClientApi['forceReRender'] = api.forceReRender; -export const raw: ClientApi['raw'] = api.clientApi.raw; diff --git a/code/renderers/vue/src/index.ts b/code/renderers/vue/src/index.ts index 0c37ede8d826..145fbf2612ea 100644 --- a/code/renderers/vue/src/index.ts +++ b/code/renderers/vue/src/index.ts @@ -2,7 +2,6 @@ import './globals'; -export * from './public-api'; export * from './public-types'; // optimization: stop HMR propagation in webpack diff --git a/code/renderers/vue/src/public-api.ts b/code/renderers/vue/src/public-api.ts deleted file mode 100644 index 708e4e2b4014..000000000000 --- a/code/renderers/vue/src/public-api.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* eslint-disable prefer-destructuring */ -import type { Addon_ClientStoryApi, Addon_Loadable } from '@storybook/types'; -import { start } from '@storybook/preview-api'; - -import type { VueRenderer } from './types'; -import { renderToCanvas, render } from './render'; -import { decorateStory } from './decorateStory'; - -const RENDERER = 'vue'; - -interface ClientApi extends Addon_ClientStoryApi { - configure(loader: Addon_Loadable, module: NodeModule): void; - forceReRender(): void; - raw: () => any; // todo add type - load: (...args: any[]) => void; -} - -const api = start(renderToCanvas, { decorateStory, render }); - -export const storiesOf: ClientApi['storiesOf'] = (kind, m) => { - return (api.clientApi.storiesOf(kind, m) as ReturnType).addParameters({ - renderer: RENDERER, - }); -}; - -export const configure: ClientApi['configure'] = (...args) => api.configure(RENDERER, ...args); -export const forceReRender: ClientApi['forceReRender'] = api.forceReRender; -export const raw: ClientApi['raw'] = api.clientApi.raw; diff --git a/code/renderers/vue3/src/index.ts b/code/renderers/vue3/src/index.ts index 6987cefb0c8e..83d5e6cdd65b 100644 --- a/code/renderers/vue3/src/index.ts +++ b/code/renderers/vue3/src/index.ts @@ -2,8 +2,8 @@ import './globals'; -export * from './public-api'; export * from './public-types'; +export { setup } from './render'; // optimization: stop HMR propagation in webpack try { diff --git a/code/renderers/vue3/src/public-api.ts b/code/renderers/vue3/src/public-api.ts deleted file mode 100644 index a6daddc418ac..000000000000 --- a/code/renderers/vue3/src/public-api.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { Addon_ClientStoryApi, Addon_Loadable } from '@storybook/types'; -import type { App } from 'vue'; -import { start } from '@storybook/preview-api'; - -import type { VueRenderer } from './types'; -import { decorateStory } from './decorateStory'; - -import { render, renderToCanvas } from './render'; - -const RENDERER = 'vue3'; - -interface ClientApi extends Addon_ClientStoryApi { - configure(loader: Addon_Loadable, module: NodeModule): void; - forceReRender(): void; - raw: () => any; // todo add type - load: (...args: any[]) => void; - app: App; -} - -const api = start(renderToCanvas, { decorateStory, render }); - -export const storiesOf: ClientApi['storiesOf'] = (kind, m) => { - return (api.clientApi.storiesOf(kind, m) as ReturnType).addParameters({ - renderer: RENDERER, - }); -}; - -export const configure: ClientApi['configure'] = (...args) => api.configure(RENDERER, ...args); -export const { forceReRender } = api; -export const { raw } = api.clientApi; -export { setup } from './render'; diff --git a/code/renderers/web-components/src/index.ts b/code/renderers/web-components/src/index.ts index 044e7875f24c..dfea3afb488d 100644 --- a/code/renderers/web-components/src/index.ts +++ b/code/renderers/web-components/src/index.ts @@ -7,7 +7,6 @@ import './globals'; const { window, EventSource } = global; export * from './public-types'; -export * from './public-api'; export * from './framework-api'; // TODO: disable HMR and do full page loads because of customElements.define diff --git a/code/renderers/web-components/src/public-api.ts b/code/renderers/web-components/src/public-api.ts deleted file mode 100644 index b8e1b24bb107..000000000000 --- a/code/renderers/web-components/src/public-api.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* eslint-disable prefer-destructuring */ -import type { Addon_ClientStoryApi, Addon_Loadable } from '@storybook/types'; -import { start } from '@storybook/preview-api'; - -import { renderToCanvas } from './render'; -import type { WebComponentsRenderer } from './types'; - -const RENDERER = 'web-components'; - -interface ClientApi extends Addon_ClientStoryApi { - configure(loader: Addon_Loadable, module: NodeModule): void; - forceReRender(): void; - raw: () => any; // todo add type -} - -const api = start(renderToCanvas); - -export const storiesOf: ClientApi['storiesOf'] = (kind, m) => { - return (api.clientApi.storiesOf(kind, m) as ReturnType).addParameters({ - renderer: RENDERER, - }); -}; - -export const configure: ClientApi['configure'] = (...args) => api.configure(RENDERER, ...args); -export const forceReRender: ClientApi['forceReRender'] = api.forceReRender; -export const raw: ClientApi['raw'] = api.clientApi.raw; From 36f2199c27b20b5c6b99bc3186975bde7e6863a6 Mon Sep 17 00:00:00 2001 From: Tom Coleman Date: Wed, 1 Nov 2023 08:02:55 +1100 Subject: [PATCH 5/8] Drop unused vars --- code/lib/cli/src/sandbox-templates.ts | 2 +- code/lib/manager-api/src/modules/stories.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/code/lib/cli/src/sandbox-templates.ts b/code/lib/cli/src/sandbox-templates.ts index abca1b7fcb98..7a39209b2d4a 100644 --- a/code/lib/cli/src/sandbox-templates.ts +++ b/code/lib/cli/src/sandbox-templates.ts @@ -62,7 +62,7 @@ export type Template = { inDevelopment?: boolean; /** * Some sandboxes might need extra modifications in the initialized Storybook, - * such as extend main.js, for setting specific feature flags like storyStoreV7, etc. + * such as extend main.js, for setting specific feature flags. */ modifications?: { skipTemplateStories?: boolean; diff --git a/code/lib/manager-api/src/modules/stories.ts b/code/lib/manager-api/src/modules/stories.ts index 08019e849bef..a81f35d48b7b 100644 --- a/code/lib/manager-api/src/modules/stories.ts +++ b/code/lib/manager-api/src/modules/stories.ts @@ -58,7 +58,7 @@ import { import type { ComposedRef } from '../index'; import type { ModuleFn } from '../lib/types'; -const { FEATURES, fetch } = global; +const { fetch } = global; const STORY_INDEX_PATH = './index.json'; type Direction = -1 | 1; From b3dcefe368a2308de7ed76bb14bccc7d11b17d73 Mon Sep 17 00:00:00 2001 From: Tom Coleman Date: Wed, 1 Nov 2023 14:22:07 +1100 Subject: [PATCH 6/8] Remove unused var --- code/ui/manager/src/runtime.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/ui/manager/src/runtime.ts b/code/ui/manager/src/runtime.ts index 8c565be5b1c8..4a24fa6a7209 100644 --- a/code/ui/manager/src/runtime.ts +++ b/code/ui/manager/src/runtime.ts @@ -9,7 +9,7 @@ import { CHANNEL_CREATED } from '@storybook/core-events'; import Provider from './provider'; import { renderStorybookUI } from './index'; -const { FEATURES, CONFIG_TYPE } = global; +const { CONFIG_TYPE } = global; class ReactProvider extends Provider { private addons: AddonStore; From 4b9abd6dcfe93f5aa0e3b6d8aa0addf0e356cfe3 Mon Sep 17 00:00:00 2001 From: Tom Coleman Date: Wed, 1 Nov 2023 14:29:47 +1100 Subject: [PATCH 7/8] Drop `buildStoriesJson` flag --- code/lib/core-server/src/presets/common-preset.ts | 1 - test-storybooks/external-docs/.storybook/main.cjs | 2 -- 2 files changed, 3 deletions(-) diff --git a/code/lib/core-server/src/presets/common-preset.ts b/code/lib/core-server/src/presets/common-preset.ts index 5e50b89defff..93195bb1c96f 100644 --- a/code/lib/core-server/src/presets/common-preset.ts +++ b/code/lib/core-server/src/presets/common-preset.ts @@ -189,7 +189,6 @@ export const features = async ( ): Promise => ({ ...existing, warnOnLegacyHierarchySeparator: true, - buildStoriesJson: false, argTypeTargetsV7: true, legacyDecoratorFileOrder: false, }); diff --git a/test-storybooks/external-docs/.storybook/main.cjs b/test-storybooks/external-docs/.storybook/main.cjs index fbe15b8582ac..d82b62915774 100644 --- a/test-storybooks/external-docs/.storybook/main.cjs +++ b/test-storybooks/external-docs/.storybook/main.cjs @@ -20,8 +20,6 @@ const config = { channelOptions: { allowFunction: false, maxDepth: 10 }, }, features: { - storyStoreV7: !global.navigator?.userAgent?.match?.('jsdom'), - buildStoriesJson: true, warnOnLegacyHierarchySeparator: false, previewMdx2: true, }, From fc2236c0ccabac3079833cca30315b4304b80f9f Mon Sep 17 00:00:00 2001 From: Tom Coleman Date: Wed, 1 Nov 2023 14:48:34 +1100 Subject: [PATCH 8/8] Remove unused imports --- code/lib/core-server/src/build-static.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/code/lib/core-server/src/build-static.ts b/code/lib/core-server/src/build-static.ts index 26ad52d47626..be715b49534a 100644 --- a/code/lib/core-server/src/build-static.ts +++ b/code/lib/core-server/src/build-static.ts @@ -2,7 +2,7 @@ import chalk from 'chalk'; import { copy, emptyDir, ensureDir } from 'fs-extra'; import { dirname, isAbsolute, join, resolve } from 'path'; import { global } from '@storybook/global'; -import { deprecate, logger } from '@storybook/node-logger'; +import { logger } from '@storybook/node-logger'; import { telemetry, getPrecedingUpgrade } from '@storybook/telemetry'; import type { BuilderOptions, @@ -23,7 +23,6 @@ import { import { ConflictingStaticDirConfigError } from '@storybook/core-events/server-errors'; import isEqual from 'lodash/isEqual.js'; -import dedent from 'ts-dedent'; import { outputStats } from './utils/output-stats'; import { copyAllStaticFiles,