mirror of
https://github.com/storybookjs/storybook.git
synced 2025-03-16 05:03:11 +08:00
Merge pull request #24658 from storybookjs/norbert/remove-storystorev7
Core: Remove `storyStoreV7` feature flag
This commit is contained in:
commit
8404f7438c
893
.yarn/releases/yarn-4.0.0.cjs
generated
vendored
893
.yarn/releases/yarn-4.0.0.cjs
generated
vendored
File diff suppressed because one or more lines are too long
893
.yarn/releases/yarn-4.0.2.cjs
generated
vendored
Executable file
893
.yarn/releases/yarn-4.0.2.cjs
generated
vendored
Executable file
File diff suppressed because one or more lines are too long
@ -8,4 +8,4 @@ nodeLinker: node-modules
|
||||
|
||||
npmPublishAccess: public
|
||||
|
||||
yarnPath: .yarn/releases/yarn-4.0.0.cjs
|
||||
yarnPath: .yarn/releases/yarn-4.0.2.cjs
|
||||
|
@ -2,6 +2,8 @@ compressionLevel: 0
|
||||
|
||||
enableGlobalCache: true
|
||||
|
||||
installStatePath: ../.yarn/code-install-state.gz
|
||||
|
||||
logFilters:
|
||||
- code: YN0005
|
||||
level: discard
|
||||
@ -23,7 +25,6 @@ plugins:
|
||||
unsafeHttpWhitelist:
|
||||
- localhost
|
||||
|
||||
yarnPath: ../.yarn/releases/yarn-4.0.0.cjs
|
||||
installStatePath: '../.yarn/code-install-state.gz'
|
||||
yarnPath: ../.yarn/releases/yarn-4.0.2.cjs
|
||||
# Sometimes you get a "The remote archive doesn't match the expected checksum" error, uncommenting this line will fix it
|
||||
# checksumBehavior: 'update'
|
||||
|
@ -60,7 +60,7 @@
|
||||
"@storybook/client-logger": "workspace:*",
|
||||
"@storybook/components": "workspace:*",
|
||||
"@storybook/global": "^5.0.0",
|
||||
"@storybook/icons": "^1.2.1",
|
||||
"@storybook/icons": "^1.2.3",
|
||||
"@storybook/manager-api": "workspace:*",
|
||||
"@storybook/preview-api": "workspace:*",
|
||||
"@storybook/theming": "workspace:*",
|
||||
|
@ -53,7 +53,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/global": "^5.0.0",
|
||||
"@storybook/icons": "^1.2.1",
|
||||
"@storybook/icons": "^1.2.3",
|
||||
"memoizerific": "^1.11.3",
|
||||
"ts-dedent": "^2.0.0"
|
||||
},
|
||||
|
@ -176,4 +176,4 @@ Like [story parameters](https://storybook.js.org/docs/react/writing-stories/para
|
||||
|
||||
### How do controls work with MDX?
|
||||
|
||||
When importing stories from your CSF file into MDX, controls will work the same way. See [the documentation](https://storybook.js.org/docs/writing-docs/mdx#basic-example) for examples.
|
||||
When importing stories from your CSF file into MDX, controls will work the same way. See [the documentation](https://storybook.js.org/docs/writing-docs/mdx#basic-example) for examples.
|
||||
|
@ -49,7 +49,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/global": "^5.0.0",
|
||||
"@storybook/icons": "^1.2.1",
|
||||
"@storybook/icons": "^1.2.3",
|
||||
"@storybook/types": "workspace:*",
|
||||
"jest-mock": "^27.0.6",
|
||||
"polished": "^4.2.2",
|
||||
|
@ -65,7 +65,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/global": "^5.0.0",
|
||||
"@storybook/icons": "^1.2.1",
|
||||
"@storybook/icons": "^1.2.3",
|
||||
"tiny-invariant": "^1.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -55,7 +55,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/global": "^5.0.0",
|
||||
"@storybook/icons": "^1.2.1",
|
||||
"@storybook/icons": "^1.2.3",
|
||||
"ts-dedent": "^2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -59,7 +59,7 @@
|
||||
"@storybook/client-logger": "workspace:*",
|
||||
"@storybook/components": "workspace:*",
|
||||
"@storybook/core-events": "workspace:*",
|
||||
"@storybook/icons": "^1.2.1",
|
||||
"@storybook/icons": "^1.2.3",
|
||||
"@storybook/manager-api": "workspace:*",
|
||||
"@storybook/preview-api": "workspace:*",
|
||||
"@storybook/theming": "workspace:*",
|
||||
|
@ -55,7 +55,7 @@
|
||||
"@storybook/components": "workspace:*",
|
||||
"@storybook/core-events": "workspace:*",
|
||||
"@storybook/global": "^5.0.0",
|
||||
"@storybook/icons": "^1.2.1",
|
||||
"@storybook/icons": "^1.2.3",
|
||||
"@storybook/manager-api": "workspace:*",
|
||||
"@storybook/preview-api": "workspace:*",
|
||||
"@storybook/theming": "workspace:*",
|
||||
|
@ -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<Record<string, string>>(
|
||||
(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;`;
|
||||
}
|
@ -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<PreviewAnnotation[]>(
|
||||
'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;
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
import * as path from 'path';
|
||||
|
||||
import type { Options } from '@storybook/types';
|
||||
import { logger } from '@storybook/node-logger';
|
||||
|
||||
import { listStories } from './list-stories';
|
||||
|
||||
@ -28,11 +27,7 @@ function toImportPath(relativePath: string) {
|
||||
async function toImportFn(stories: string[]) {
|
||||
const { normalizePath } = await import('vite');
|
||||
const objectEntries = stories.map((file) => {
|
||||
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}`);
|
||||
}
|
||||
|
||||
return ` '${toImportPath(relativePath)}': async () => import('/@fs/${file}')`;
|
||||
});
|
||||
|
@ -69,7 +69,6 @@ export async function generateModernIframeScriptCode(options: Options, projectRo
|
||||
window.__STORYBOOK_PREVIEW__ = window.__STORYBOOK_PREVIEW__ || new PreviewWeb();
|
||||
|
||||
window.__STORYBOOK_STORY_STORE__ = window.__STORYBOOK_STORY_STORE__ || window.__STORYBOOK_PREVIEW__.storyStore;
|
||||
window.__STORYBOOK_CLIENT_API__ = window.__STORYBOOK_CLIENT_API__ || new ClientApi({ storyStore: window.__STORYBOOK_PREVIEW__.storyStore });
|
||||
window.__STORYBOOK_PREVIEW__.initialize({ importFn, getProjectAnnotations });
|
||||
|
||||
${generateHMRHandler(frameworkName)};
|
||||
|
@ -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) {
|
||||
|
@ -1,16 +1,14 @@
|
||||
import type { Options, PreviewAnnotation } from '@storybook/types';
|
||||
import { join, resolve } from 'path';
|
||||
import {
|
||||
getBuilderOptions,
|
||||
getRendererName,
|
||||
handlebars,
|
||||
interpolate,
|
||||
loadPreviewOrConfigFile,
|
||||
normalizeStories,
|
||||
readTemplate,
|
||||
} from '@storybook/core-common';
|
||||
import type { Options, PreviewAnnotation } from '@storybook/types';
|
||||
import { isAbsolute, join, resolve } from 'path';
|
||||
import slash from 'slash';
|
||||
import { toImportFn, toRequireContextString } from '@storybook/core-webpack';
|
||||
import { toImportFn } from '@storybook/core-webpack';
|
||||
import type { BuilderOptions } from '../types';
|
||||
|
||||
export const getVirtualModules = async (options: Options) => {
|
||||
@ -37,79 +35,31 @@ export const getVirtualModules = async (options: Options) => {
|
||||
return entry.absolute;
|
||||
}
|
||||
|
||||
// TODO: Remove as soon as we drop support for disabled StoryStoreV7
|
||||
if (isAbsolute(entry)) {
|
||||
return entry;
|
||||
}
|
||||
|
||||
return slash(entry);
|
||||
}
|
||||
),
|
||||
loadPreviewOrConfigFile(options),
|
||||
].filter(Boolean);
|
||||
|
||||
if (options.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;
|
||||
virtualModules[storiesPath] = toImportFn(stories, { needPipelinedImport });
|
||||
const configEntryPath = resolve(join(workingDir, 'storybook-config-entry.js'));
|
||||
virtualModules[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'));
|
||||
virtualModules[rendererInitEntry] = `import '${slash(rendererName)}';`;
|
||||
entries.push(rendererInitEntry);
|
||||
|
||||
const entryTemplate = await readTemplate(
|
||||
require.resolve('@storybook/builder-webpack5/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
|
||||
virtualModules[entryFilename] = interpolate(entryTemplate, {
|
||||
previewAnnotationFilename,
|
||||
});
|
||||
entries.push(entryFilename);
|
||||
});
|
||||
if (stories.length > 0) {
|
||||
const storyTemplate = await readTemplate(
|
||||
require.resolve('@storybook/builder-webpack5/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`));
|
||||
virtualModules[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;
|
||||
virtualModules[storiesPath] = toImportFn(stories, { needPipelinedImport });
|
||||
const configEntryPath = resolve(join(workingDir, 'storybook-config-entry.js'));
|
||||
virtualModules[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);
|
||||
|
||||
return {
|
||||
virtualModules,
|
||||
|
@ -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}}'`
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
@ -20,7 +20,6 @@ const preview = new PreviewWeb();
|
||||
window.__STORYBOOK_PREVIEW__ = preview;
|
||||
window.__STORYBOOK_STORY_STORE__ = preview.storyStore;
|
||||
window.__STORYBOOK_ADDONS_CHANNEL__ = channel;
|
||||
window.__STORYBOOK_CLIENT_API__ = new ClientApi({ storyStore: preview.storyStore });
|
||||
|
||||
preview.initialize({ importFn, getProjectAnnotations });
|
||||
|
||||
|
@ -1,3 +0,0 @@
|
||||
const { configure } = require('{{rendererName}}');
|
||||
|
||||
configure(['{{stories}}'], module, false);
|
@ -48,17 +48,6 @@ test.describe('addon-backgrounds', () => {
|
||||
});
|
||||
|
||||
test('button should appear for unattached .mdx files', async ({ page }) => {
|
||||
// SSv6 does not support .mdx files. There is a unattached stories.mdx file
|
||||
// at /docs/addons-docs-stories-mdx-unattached--docs, but these are functionally
|
||||
// really attached
|
||||
|
||||
// eslint-disable-next-line jest/no-disabled-tests
|
||||
test.skip(
|
||||
// eslint-disable-next-line jest/valid-title
|
||||
templateName.includes('ssv6'),
|
||||
'Only run this test for Sandboxes with StoryStoreV7 enabled'
|
||||
);
|
||||
|
||||
const sbPage = new SbPage(page);
|
||||
|
||||
// We start on the introduction page by default.
|
||||
|
@ -1,9 +1,7 @@
|
||||
/* eslint-disable jest/no-disabled-tests */
|
||||
import { test, expect } from '@playwright/test';
|
||||
import process from 'process';
|
||||
|
||||
const storybookUrl = process.env.STORYBOOK_URL || 'http://localhost:8001';
|
||||
const templateName = process.env.STORYBOOK_TEMPLATE_NAME || '';
|
||||
|
||||
test.describe('JSON files', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
@ -11,11 +9,6 @@ test.describe('JSON files', () => {
|
||||
});
|
||||
|
||||
test('should have index.json', async ({ page }) => {
|
||||
test.skip(
|
||||
// eslint-disable-next-line jest/valid-title
|
||||
templateName.includes('ssv6'),
|
||||
'Only run this test for Sandboxes with StoryStoreV7 enabled'
|
||||
);
|
||||
const json = await page.evaluate(() => fetch('/index.json').then((res) => res.json()));
|
||||
|
||||
expect(json).toEqual({
|
||||
|
@ -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';
|
||||
|
@ -1 +0,0 @@
|
||||
export * from './public-types';
|
1
code/frameworks/angular/src/typings.d.ts
vendored
1
code/frameworks/angular/src/typings.d.ts
vendored
@ -6,7 +6,6 @@ declare var NODE_ENV: string | undefined;
|
||||
declare var __STORYBOOK_ADDONS_CHANNEL__: any;
|
||||
declare var __STORYBOOK_ADDONS_PREVIEW: any;
|
||||
declare var __STORYBOOK_COMPODOC_JSON__: any;
|
||||
declare var __STORYBOOK_CLIENT_API__: any;
|
||||
declare var __STORYBOOK_PREVIEW__: any;
|
||||
declare var __STORYBOOK_STORY_STORE__: any;
|
||||
declare var CHANNEL_OPTIONS: any;
|
||||
|
@ -1 +0,0 @@
|
||||
import './globals';
|
@ -24,7 +24,7 @@ function Component() {
|
||||
name: 'Prefetch',
|
||||
},
|
||||
{
|
||||
// @ts-expect-error (a legacy nextjs api?)
|
||||
// @ts-expect-error (old API)
|
||||
cb: () => router.push('/push-html', { forceOptimisticNavigation: true }),
|
||||
name: 'Push HTML',
|
||||
},
|
||||
@ -33,7 +33,7 @@ function Component() {
|
||||
name: 'Refresh',
|
||||
},
|
||||
{
|
||||
// @ts-expect-error (a legacy nextjs api?)
|
||||
// @ts-expect-error (old API)
|
||||
cb: () => router.replace('/replaced-html', { forceOptimisticNavigation: true }),
|
||||
name: 'Replace',
|
||||
},
|
||||
|
@ -66,7 +66,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;
|
||||
|
@ -29,9 +29,6 @@ const config: StorybookConfig = {
|
||||
disableTelemetry: true,
|
||||
},
|
||||
logLevel: 'debug',
|
||||
features: {
|
||||
storyStoreV7: false,
|
||||
},
|
||||
framework: {
|
||||
name: '@storybook/react-webpack5',
|
||||
options: {
|
||||
|
@ -2,7 +2,7 @@ import chalk from 'chalk';
|
||||
import { copy, emptyDir, ensureDir } from 'fs-extra';
|
||||
import { dirname, join, relative, resolve } from 'path';
|
||||
import { global } from '@storybook/global';
|
||||
import { deprecate, logger } from '@storybook/node-logger';
|
||||
import { logger } from '@storybook/node-logger';
|
||||
import { getPrecedingUpgrade, telemetry } from '@storybook/telemetry';
|
||||
import type { BuilderOptions, CLIOptions, LoadOptions, Options } from '@storybook/types';
|
||||
import {
|
||||
@ -13,7 +13,6 @@ import {
|
||||
resolveAddonName,
|
||||
} from '@storybook/core-common';
|
||||
|
||||
import dedent from 'ts-dedent';
|
||||
import { outputStats } from './utils/output-stats';
|
||||
import { copyAllStaticFilesRelativeToMain } from './utils/copy-all-static-files';
|
||||
import { getBuilders } from './utils/get-builders';
|
||||
@ -103,13 +102,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,
|
||||
@ -139,7 +131,7 @@ export async function buildStaticStandalone(options: BuildStaticStandaloneOption
|
||||
|
||||
let initializedStoryIndexGenerator: Promise<StoryIndexGenerator | undefined> =
|
||||
Promise.resolve(undefined);
|
||||
if ((features?.buildStoriesJson || features?.storyStoreV7) && !options.ignorePreview) {
|
||||
if (!options.ignorePreview) {
|
||||
const workingDir = process.cwd();
|
||||
const directories = {
|
||||
configDir: options.configDir,
|
||||
@ -150,7 +142,6 @@ export async function buildStaticStandalone(options: BuildStaticStandaloneOption
|
||||
...directories,
|
||||
indexers,
|
||||
docs: docsOptions,
|
||||
storyStoreV7: !!features?.storyStoreV7,
|
||||
build,
|
||||
});
|
||||
|
||||
|
@ -5,9 +5,8 @@ import invariant from 'tiny-invariant';
|
||||
import type { Options } from '@storybook/types';
|
||||
|
||||
import { logConfig } from '@storybook/core-common';
|
||||
import { deprecate, logger } from '@storybook/node-logger';
|
||||
import { logger } from '@storybook/node-logger';
|
||||
|
||||
import dedent from 'ts-dedent';
|
||||
import { MissingBuilderError } from '@storybook/core-events/server-errors';
|
||||
import { getMiddleware } from './utils/middleware';
|
||||
import { getServerAddresses } from './utils/server-address';
|
||||
@ -38,13 +37,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<StoryIndexGenerator | undefined> =
|
||||
|
@ -187,8 +187,6 @@ export const previewAnnotations = async (base: any, options: Options) => {
|
||||
|
||||
export const features: PresetProperty<'features'> = async (existing) => ({
|
||||
...existing,
|
||||
buildStoriesJson: false,
|
||||
storyStoreV7: true,
|
||||
argTypeTargetsV7: true,
|
||||
legacyDecoratorFileOrder: false,
|
||||
disallowImplicitActionsInRenderV8: true,
|
||||
|
@ -43,7 +43,6 @@ const options: StoryIndexGeneratorOptions = {
|
||||
configDir: path.join(__dirname, '__mockdata__'),
|
||||
workingDir: path.join(__dirname, '__mockdata__'),
|
||||
indexers: [csfIndexer],
|
||||
storyStoreV7: true,
|
||||
docs: { defaultName: 'docs', autodocs: false },
|
||||
};
|
||||
|
||||
|
@ -49,7 +49,6 @@ type SpecifierStoriesCache = Record<Path, CacheEntry>;
|
||||
export type StoryIndexGeneratorOptions = {
|
||||
workingDir: Path;
|
||||
configDir: Path;
|
||||
storyStoreV7: boolean;
|
||||
indexers: Indexer[];
|
||||
docs: DocsOptions;
|
||||
build?: StorybookConfigRaw['build'];
|
||||
@ -346,11 +345,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);
|
||||
|
||||
@ -530,13 +524,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;
|
||||
|
@ -16,7 +16,6 @@ const options: StoryIndexGeneratorOptions = {
|
||||
configDir: path.join(__dirname, '..', '__mockdata__'),
|
||||
workingDir: path.join(__dirname, '..', '__mockdata__'),
|
||||
indexers: [],
|
||||
storyStoreV7: true,
|
||||
docs: { defaultName: 'docs', autodocs: false },
|
||||
};
|
||||
|
||||
|
@ -7,16 +7,11 @@ import { router } from './router';
|
||||
|
||||
export async function getStoryIndexGenerator(
|
||||
features: {
|
||||
buildStoriesJson?: boolean;
|
||||
storyStoreV7?: boolean;
|
||||
argTypeTargetsV7?: boolean;
|
||||
},
|
||||
options: Options,
|
||||
serverChannel: ServerChannel
|
||||
): Promise<StoryIndexGenerator | undefined> {
|
||||
if (!features?.buildStoriesJson && !features?.storyStoreV7) {
|
||||
return undefined;
|
||||
}
|
||||
const workingDir = process.cwd();
|
||||
const directories = {
|
||||
configDir: options.configDir,
|
||||
@ -32,7 +27,6 @@ export async function getStoryIndexGenerator(
|
||||
indexers: await indexers,
|
||||
docs: await docsOptions,
|
||||
workingDir,
|
||||
storyStoreV7: features.storyStoreV7 ?? false,
|
||||
});
|
||||
|
||||
const initializedStoryIndexGenerator = generator.initialize().then(() => generator);
|
||||
|
@ -45,7 +45,6 @@ const getInitializedStoryIndexGenerator = async (
|
||||
indexers: [csfIndexer],
|
||||
configDir: workingDir,
|
||||
workingDir,
|
||||
storyStoreV7: true,
|
||||
docs: { defaultName: 'docs', autodocs: false },
|
||||
...overrides,
|
||||
};
|
||||
@ -252,35 +251,6 @@ describe('useStoriesJson', () => {
|
||||
`);
|
||||
}, 20_000);
|
||||
|
||||
it('disallows .mdx files without storyStoreV7', async () => {
|
||||
const mockServerChannel = { emit: vi.fn() } as any as ServerChannel;
|
||||
useStoriesJson({
|
||||
router,
|
||||
initializedStoryIndexGenerator: getInitializedStoryIndexGenerator({
|
||||
storyStoreV7: false,
|
||||
}),
|
||||
workingDir,
|
||||
serverChannel: mockServerChannel,
|
||||
normalizedStories,
|
||||
});
|
||||
|
||||
expect(use).toHaveBeenCalledTimes(1);
|
||||
const route = use.mock.calls[0][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('can handle simultaneous access', async () => {
|
||||
const mockServerChannel = { emit: vi.fn() } as any as ServerChannel;
|
||||
|
||||
|
@ -455,8 +455,7 @@ 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
|
||||
SB8 does not support \`storiesOf\`.
|
||||
`);
|
||||
}
|
||||
},
|
||||
|
@ -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;
|
||||
@ -881,10 +881,8 @@ export const init: ModuleFn<SubAPI, SubState> = ({
|
||||
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();
|
||||
},
|
||||
};
|
||||
};
|
||||
|
@ -39,7 +39,6 @@ vi.mock('@storybook/global', () => ({
|
||||
global: {
|
||||
...globalThis,
|
||||
fetch: vi.fn(() => ({ json: () => ({ v: 4, entries: mockGetEntries() }) })),
|
||||
FEATURES: { storyStoreV7: true },
|
||||
CONFIG_TYPE: 'DEVELOPMENT',
|
||||
},
|
||||
}));
|
||||
|
@ -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:
|
||||
|
@ -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"
|
||||
},
|
||||
|
@ -1,4 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/triple-slash-reference */
|
||||
/// <reference path="typings.d.ts" />
|
||||
|
||||
export * from './modules/client-api';
|
@ -1,4 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/triple-slash-reference */
|
||||
/// <reference path="typings.d.ts" />
|
||||
|
||||
export * from './modules/core-client';
|
@ -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,8 +67,5 @@ export type { PropDescriptor } from './store';
|
||||
/**
|
||||
* STORIES API
|
||||
*/
|
||||
export { ClientApi } from './client-api';
|
||||
export { StoryStore } from './store';
|
||||
export { Preview, PreviewWeb, PreviewWithSelection, UrlStore, WebView } from './preview-web';
|
||||
export type { SelectionStore, View } from './preview-web';
|
||||
export { start } from './core-client';
|
||||
export { Preview, PreviewWeb } from './preview-web';
|
||||
|
@ -1,214 +0,0 @@
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
|
||||
import { dedent } from 'ts-dedent';
|
||||
import { global } from '@storybook/global';
|
||||
import type {
|
||||
Args,
|
||||
StepRunner,
|
||||
ArgTypes,
|
||||
Renderer,
|
||||
DecoratorFunction,
|
||||
Parameters,
|
||||
ArgTypesEnhancer,
|
||||
ArgsEnhancer,
|
||||
LoaderFunction,
|
||||
Globals,
|
||||
GlobalTypes,
|
||||
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<Renderer>) => {
|
||||
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<Renderer>) => {
|
||||
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<Renderer>) => {
|
||||
checkMethod('addArgsEnhancer');
|
||||
global.__STORYBOOK_CLIENT_API__?.addArgsEnhancer(enhancer);
|
||||
};
|
||||
|
||||
export const addArgTypesEnhancer = (enhancer: ArgTypesEnhancer<Renderer>) => {
|
||||
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<any>['projectAnnotations']['render']) => {
|
||||
checkMethod('setGlobalRender');
|
||||
if (global.__STORYBOOK_CLIENT_API__) {
|
||||
global.__STORYBOOK_CLIENT_API__.facade.projectAnnotations.render = render;
|
||||
}
|
||||
};
|
||||
|
||||
export class ClientApi<TRenderer extends Renderer> {
|
||||
facade: StoryStoreFacade<TRenderer>;
|
||||
|
||||
storyStore?: StoryStore<TRenderer>;
|
||||
|
||||
onImportFnChanged?: ({ importFn }: { importFn: ModuleImportFn }) => void;
|
||||
|
||||
// If we don't get passed modules so don't know filenames, we can
|
||||
// just use numeric indexes
|
||||
|
||||
constructor({ storyStore }: { storyStore?: StoryStore<TRenderer> } = {}) {
|
||||
this.facade = new StoryStoreFacade();
|
||||
|
||||
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<TRenderer>) => {
|
||||
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<TRenderer>) => {
|
||||
this.facade.projectAnnotations.runStep = composeStepRunners(
|
||||
[this.facade.projectAnnotations.runStep, stepRunner].filter(
|
||||
Boolean
|
||||
) as StepRunner<TRenderer>[]
|
||||
);
|
||||
};
|
||||
|
||||
addLoader = (loader: LoaderFunction<TRenderer>) => {
|
||||
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<TRenderer>) => {
|
||||
this.facade.projectAnnotations.argsEnhancers?.push(enhancer);
|
||||
};
|
||||
|
||||
addArgTypesEnhancer = (enhancer: ArgTypesEnhancer<TRenderer>) => {
|
||||
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<Path, ModuleExports>;
|
||||
|
||||
_loadAddedExports() {
|
||||
Object.entries(this._addedExports).forEach(([fileName, fileExports]) =>
|
||||
this.facade.addStoriesFromExports(fileName, fileExports)
|
||||
);
|
||||
}
|
||||
|
||||
// @deprecated
|
||||
raw = () => {
|
||||
return this.storyStore?.raw();
|
||||
};
|
||||
|
||||
// @deprecated
|
||||
get _storyStore() {
|
||||
return this.storyStore;
|
||||
}
|
||||
}
|
@ -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<TRenderer extends Renderer> {
|
||||
projectAnnotations: NormalizedProjectAnnotations<TRenderer>;
|
||||
|
||||
entries: Record<StoryId, IndexEntry & { componentId?: ComponentId }>;
|
||||
|
||||
csfExports: Record<Path, ModuleExports>;
|
||||
|
||||
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<TRenderer>) {
|
||||
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<TRenderer>(
|
||||
exports,
|
||||
importPath,
|
||||
exports.default.title
|
||||
);
|
||||
|
||||
let storyLike: PreparedStory<TRenderer>;
|
||||
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<TRenderer>, 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 <Meta isTemplate /> 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'],
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
export {
|
||||
addArgs,
|
||||
addArgsEnhancer,
|
||||
addArgTypes,
|
||||
addArgTypesEnhancer,
|
||||
addDecorator,
|
||||
addLoader,
|
||||
addParameters,
|
||||
addStepRunner,
|
||||
ClientApi,
|
||||
setGlobalRender,
|
||||
} from './ClientApi';
|
||||
|
||||
export * from '../../store';
|
||||
|
||||
export * from './queryparams';
|
@ -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];
|
||||
};
|
@ -1,110 +0,0 @@
|
||||
/// <reference types="node" />
|
||||
/// <reference types="webpack-env" />
|
||||
|
||||
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<Path, ModuleExports>
|
||||
*/
|
||||
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<Path, ModuleExports>();
|
||||
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<Path, ModuleExports>, removed: Map<Path, ModuleExports> }
|
||||
*/
|
||||
export function executeLoadableForChanges(loadable: Loadable, m?: NodeModule) {
|
||||
let lastExportsMap: ReturnType<typeof executeLoadable> =
|
||||
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<Path, ModuleExports>();
|
||||
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<Path, ModuleExports>();
|
||||
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 };
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
import { ClientApi } from '../../client-api';
|
||||
import { StoryStore } from '../../store';
|
||||
import { start } from './start';
|
||||
|
||||
export { start, ClientApi, StoryStore };
|
@ -1,627 +0,0 @@
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
/**
|
||||
* @vitest-environment jsdom
|
||||
*/
|
||||
import { describe, beforeEach, afterEach, it, expect, vi } from 'vitest';
|
||||
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';
|
||||
|
||||
vi.mock('@storybook/global', () => ({
|
||||
global: {
|
||||
...globalThis,
|
||||
window: globalThis,
|
||||
history: { replaceState: vi.fn() },
|
||||
document: {
|
||||
location: {
|
||||
pathname: 'pathname',
|
||||
search: '?id=*',
|
||||
},
|
||||
},
|
||||
DOCS_OPTIONS: {},
|
||||
},
|
||||
}));
|
||||
|
||||
// console.log(global);
|
||||
|
||||
vi.mock('@storybook/channels', () => ({
|
||||
createBrowserChannel: () => mockChannel,
|
||||
}));
|
||||
vi.mock('@storybook/client-logger');
|
||||
vi.mock('react-dom');
|
||||
|
||||
// for the auto-title test
|
||||
vi.mock('../../store', async (importOriginal) => {
|
||||
return {
|
||||
...(await importOriginal<typeof import('../../store')>()),
|
||||
userOrAutoTitle: (importPath: Path, specifier: any, userTitle?: string) =>
|
||||
userTitle || 'auto-title',
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('../../preview-web', async (importOriginal) => {
|
||||
const actualPreviewWeb = await importOriginal<typeof import('../../preview-web')>();
|
||||
|
||||
class OverloadPreviewWeb extends actualPreviewWeb.PreviewWeb<any> {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
// @ts-expect-error (incomplete)
|
||||
this.view = {
|
||||
...Object.fromEntries(
|
||||
Object.getOwnPropertyNames(this.view.constructor.prototype).map((key) => [key, vi.fn()])
|
||||
),
|
||||
prepareForDocs: vi.fn().mockReturnValue('docs-root'),
|
||||
prepareForStory: vi.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<Path, ModuleExports>) {
|
||||
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;
|
||||
});
|
||||
|
||||
const componentCExports = {
|
||||
default: {
|
||||
title: 'Component C',
|
||||
tags: ['component-tag', 'autodocs'],
|
||||
},
|
||||
StoryOne: {
|
||||
render: vi.fn(),
|
||||
tags: ['story-tag'],
|
||||
},
|
||||
StoryTwo: vi.fn(),
|
||||
};
|
||||
|
||||
describe('when configure is called with CSF only', () => {
|
||||
it('loads and renders the first story correctly', async () => {
|
||||
const renderToCanvas = vi.fn();
|
||||
|
||||
const { configure } = start(renderToCanvas);
|
||||
configure('test', () => [componentCExports]);
|
||||
|
||||
await waitForRender();
|
||||
expect(mockChannel.emit.mock.calls.find((call) => call[0] === SET_INDEX)?.[1])
|
||||
.toMatchInlineSnapshot(`
|
||||
{
|
||||
"entries": {
|
||||
"component-c--story-one": {
|
||||
"argTypes": {},
|
||||
"args": {},
|
||||
"id": "component-c--story-one",
|
||||
"importPath": "exports-map-0",
|
||||
"initialArgs": {},
|
||||
"name": "Story One",
|
||||
"parameters": {
|
||||
"__isArgsStory": false,
|
||||
"fileName": "exports-map-0",
|
||||
"renderer": "test",
|
||||
},
|
||||
"tags": [
|
||||
"story-tag",
|
||||
"story",
|
||||
],
|
||||
"title": "Component C",
|
||||
"type": "story",
|
||||
},
|
||||
"component-c--story-two": {
|
||||
"argTypes": {},
|
||||
"args": {},
|
||||
"id": "component-c--story-two",
|
||||
"importPath": "exports-map-0",
|
||||
"initialArgs": {},
|
||||
"name": "Story Two",
|
||||
"parameters": {
|
||||
"__isArgsStory": false,
|
||||
"fileName": "exports-map-0",
|
||||
"renderer": "test",
|
||||
},
|
||||
"tags": [
|
||||
"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 = vi.fn(({ storyFn }) => storyFn());
|
||||
|
||||
let disposeCallback: (data: object) => void = () => {};
|
||||
const module = {
|
||||
id: 'file1',
|
||||
hot: {
|
||||
data: {},
|
||||
accept: vi.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 = vi.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 = vi.fn(({ storyFn }) => storyFn());
|
||||
|
||||
let disposeCallback: (data: object) => void = () => {};
|
||||
const module = {
|
||||
id: 'file1',
|
||||
hot: {
|
||||
data: {},
|
||||
accept: vi.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: vi.fn() }], module as any);
|
||||
|
||||
await waitForEvents([SET_INDEX]);
|
||||
expect(mockChannel.emit.mock.calls.find((call) => call[0] === SET_INDEX)?.[1])
|
||||
.toMatchInlineSnapshot(`
|
||||
{
|
||||
"entries": {
|
||||
"component-c--story-one": {
|
||||
"argTypes": {},
|
||||
"args": {},
|
||||
"id": "component-c--story-one",
|
||||
"importPath": "exports-map-0",
|
||||
"initialArgs": {},
|
||||
"name": "Story One",
|
||||
"parameters": {
|
||||
"__isArgsStory": false,
|
||||
"fileName": "exports-map-0",
|
||||
"renderer": "test",
|
||||
},
|
||||
"tags": [
|
||||
"story-tag",
|
||||
"story",
|
||||
],
|
||||
"title": "Component C",
|
||||
"type": "story",
|
||||
},
|
||||
"component-c--story-three": {
|
||||
"argTypes": {},
|
||||
"args": {},
|
||||
"id": "component-c--story-three",
|
||||
"importPath": "exports-map-0",
|
||||
"initialArgs": {},
|
||||
"name": "Story Three",
|
||||
"parameters": {
|
||||
"__isArgsStory": false,
|
||||
"fileName": "exports-map-0",
|
||||
"renderer": "test",
|
||||
},
|
||||
"tags": [
|
||||
"component-tag",
|
||||
"autodocs",
|
||||
"story",
|
||||
],
|
||||
"title": "Component C",
|
||||
"type": "story",
|
||||
},
|
||||
"component-c--story-two": {
|
||||
"argTypes": {},
|
||||
"args": {},
|
||||
"id": "component-c--story-two",
|
||||
"importPath": "exports-map-0",
|
||||
"initialArgs": {},
|
||||
"name": "Story Two",
|
||||
"parameters": {
|
||||
"__isArgsStory": false,
|
||||
"fileName": "exports-map-0",
|
||||
"renderer": "test",
|
||||
},
|
||||
"tags": [
|
||||
"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 = vi.fn(({ storyFn }) => storyFn());
|
||||
|
||||
let disposeCallback: (data: object) => void = () => {};
|
||||
const module = {
|
||||
id: 'file1',
|
||||
hot: {
|
||||
data: {},
|
||||
accept: vi.fn(),
|
||||
dispose(cb: () => void) {
|
||||
disposeCallback = cb;
|
||||
},
|
||||
},
|
||||
};
|
||||
const { configure } = start(renderToCanvas);
|
||||
configure(
|
||||
'test',
|
||||
() => [componentCExports, { default: { title: 'Component D' }, StoryFour: vi.fn() }],
|
||||
module as any
|
||||
);
|
||||
|
||||
await waitForEvents([SET_INDEX]);
|
||||
expect(mockChannel.emit.mock.calls.find((call) => call[0] === SET_INDEX)?.[1])
|
||||
.toMatchInlineSnapshot(`
|
||||
{
|
||||
"entries": {
|
||||
"component-c--story-one": {
|
||||
"argTypes": {},
|
||||
"args": {},
|
||||
"id": "component-c--story-one",
|
||||
"importPath": "exports-map-0",
|
||||
"initialArgs": {},
|
||||
"name": "Story One",
|
||||
"parameters": {
|
||||
"__isArgsStory": false,
|
||||
"fileName": "exports-map-0",
|
||||
"renderer": "test",
|
||||
},
|
||||
"tags": [
|
||||
"story-tag",
|
||||
"story",
|
||||
],
|
||||
"title": "Component C",
|
||||
"type": "story",
|
||||
},
|
||||
"component-c--story-two": {
|
||||
"argTypes": {},
|
||||
"args": {},
|
||||
"id": "component-c--story-two",
|
||||
"importPath": "exports-map-0",
|
||||
"initialArgs": {},
|
||||
"name": "Story Two",
|
||||
"parameters": {
|
||||
"__isArgsStory": false,
|
||||
"fileName": "exports-map-0",
|
||||
"renderer": "test",
|
||||
},
|
||||
"tags": [
|
||||
"component-tag",
|
||||
"autodocs",
|
||||
"story",
|
||||
],
|
||||
"title": "Component C",
|
||||
"type": "story",
|
||||
},
|
||||
"component-d--story-four": {
|
||||
"argTypes": {},
|
||||
"args": {},
|
||||
"id": "component-d--story-four",
|
||||
"importPath": "exports-map-1",
|
||||
"initialArgs": {},
|
||||
"name": "Story Four",
|
||||
"parameters": {
|
||||
"__isArgsStory": false,
|
||||
"fileName": "exports-map-1",
|
||||
"renderer": "test",
|
||||
},
|
||||
"tags": [
|
||||
"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(`
|
||||
{
|
||||
"entries": {
|
||||
"component-c--story-one": {
|
||||
"argTypes": {},
|
||||
"args": {},
|
||||
"id": "component-c--story-one",
|
||||
"importPath": "exports-map-0",
|
||||
"initialArgs": {},
|
||||
"name": "Story One",
|
||||
"parameters": {
|
||||
"__isArgsStory": false,
|
||||
"fileName": "exports-map-0",
|
||||
"renderer": "test",
|
||||
},
|
||||
"tags": [
|
||||
"story-tag",
|
||||
"story",
|
||||
],
|
||||
"title": "Component C",
|
||||
"type": "story",
|
||||
},
|
||||
"component-c--story-two": {
|
||||
"argTypes": {},
|
||||
"args": {},
|
||||
"id": "component-c--story-two",
|
||||
"importPath": "exports-map-0",
|
||||
"initialArgs": {},
|
||||
"name": "Story Two",
|
||||
"parameters": {
|
||||
"__isArgsStory": false,
|
||||
"fileName": "exports-map-0",
|
||||
"renderer": "test",
|
||||
},
|
||||
"tags": [
|
||||
"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 = vi.fn(({ storyFn }) => storyFn());
|
||||
const frameworkRender = vi.fn();
|
||||
|
||||
const { configure } = start(renderToCanvas, { render: frameworkRender });
|
||||
|
||||
const projectRender = vi.fn();
|
||||
setGlobalRender(projectRender);
|
||||
configure('test', () => {
|
||||
return [
|
||||
{
|
||||
default: {
|
||||
title: 'Component A',
|
||||
component: vi.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 = vi.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(`
|
||||
{
|
||||
"entries": {
|
||||
"introduction": {
|
||||
"id": "introduction",
|
||||
"importPath": "./Introduction.stories.mdx",
|
||||
"name": undefined,
|
||||
"parameters": {
|
||||
"fileName": "./Introduction.stories.mdx",
|
||||
"renderer": "test",
|
||||
},
|
||||
"storiesImports": [],
|
||||
"tags": [
|
||||
"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 = vi.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 <Meta isTemplate /> 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('auto-title', () => {
|
||||
const componentDExports = {
|
||||
default: {
|
||||
component: 'Component D',
|
||||
},
|
||||
StoryOne: vi.fn(),
|
||||
};
|
||||
it('loads and renders the first story correctly', async () => {
|
||||
const renderToCanvas = vi.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(`
|
||||
{
|
||||
"entries": {
|
||||
"auto-title--story-one": {
|
||||
"argTypes": {},
|
||||
"args": {},
|
||||
"id": "auto-title--story-one",
|
||||
"importPath": "exports-map-0",
|
||||
"initialArgs": {},
|
||||
"name": "Story One",
|
||||
"parameters": {
|
||||
"__isArgsStory": false,
|
||||
"fileName": "exports-map-0",
|
||||
"renderer": "test",
|
||||
},
|
||||
"tags": [
|
||||
"story",
|
||||
],
|
||||
"title": "auto-title",
|
||||
"type": "story",
|
||||
},
|
||||
},
|
||||
"v": 4,
|
||||
}
|
||||
`);
|
||||
|
||||
await waitForRender();
|
||||
});
|
||||
});
|
||||
});
|
@ -1,170 +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<TRenderer extends Renderer> {
|
||||
/**
|
||||
* A function that applies decorators to a story.
|
||||
* @template TRenderer The type of renderer used by the Storybook client API.
|
||||
* @type {ProjectAnnotations<TRenderer>['applyDecorators']}
|
||||
*/
|
||||
decorateStory?: ProjectAnnotations<TRenderer>['applyDecorators'];
|
||||
/**
|
||||
* A function that renders a story with args.
|
||||
* @template TRenderer The type of renderer used by the Storybook client API.
|
||||
* @type {ArgsStoryFn<TRenderer>}
|
||||
*/
|
||||
render?: ArgsStoryFn<TRenderer>;
|
||||
}
|
||||
|
||||
interface CoreClient_ClientAPIFacade {
|
||||
/**
|
||||
* 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<TRenderer extends Renderer> {
|
||||
/**
|
||||
* 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<TRenderer> | CoreClient_ClientAPIFacade}
|
||||
*/
|
||||
clientApi: ClientApi<TRenderer> | CoreClient_ClientAPIFacade;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the Storybook preview API.
|
||||
* @template TRenderer The type of renderer used by the Storybook client API.
|
||||
* @param {ProjectAnnotations<TRenderer>['renderToCanvas']} renderToCanvas A function that renders a story to a canvas.
|
||||
* @param {CoreClient_RendererImplementation<TRenderer>} [options] Optional configuration options for the renderer implementation.
|
||||
* @param {ProjectAnnotations<TRenderer>['applyDecorators']} [options.decorateStory] A function that applies decorators to a story.
|
||||
* @param {ArgsStoryFn<TRenderer>} [options.render] A function that renders a story with arguments.
|
||||
* @returns {CoreClient_StartReturnValue<TRenderer>} An object containing functions and objects related to the Storybook preview API.
|
||||
*/
|
||||
export function start<TRenderer extends Renderer>(
|
||||
renderToCanvas: ProjectAnnotations<TRenderer>['renderToCanvas'],
|
||||
{ decorateStory, render }: CoreClient_RendererImplementation<TRenderer> = {}
|
||||
): CoreClient_StartReturnValue<TRenderer> {
|
||||
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: {
|
||||
raw: removedApi('raw'),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const channel = createBrowserChannel({ page: 'preview' });
|
||||
addons.setChannel(channel);
|
||||
|
||||
const clientApi = global?.__STORYBOOK_CLIENT_API__ || new ClientApi<TRenderer>();
|
||||
const preview = global?.__STORYBOOK_PREVIEW__ || new PreviewWeb<TRenderer>();
|
||||
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();
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
@ -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,
|
||||
@ -61,21 +60,14 @@ export class Preview<TRenderer extends Renderer> {
|
||||
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();
|
||||
}
|
||||
|
||||
// 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.
|
||||
initialize({
|
||||
async initialize({
|
||||
getStoryIndex,
|
||||
importFn,
|
||||
getProjectAnnotations,
|
||||
@ -93,9 +85,8 @@ export class Preview<TRenderer extends Renderer> {
|
||||
|
||||
this.setupListeners();
|
||||
|
||||
return this.getProjectAnnotationsOrRenderError(getProjectAnnotations).then(
|
||||
(projectAnnotations) => this.initializeWithProjectAnnotations(projectAnnotations)
|
||||
);
|
||||
const projectAnnotations = await this.getProjectAnnotationsOrRenderError(getProjectAnnotations);
|
||||
return this.initializeWithProjectAnnotations(projectAnnotations);
|
||||
}
|
||||
|
||||
setupListeners() {
|
||||
@ -107,57 +98,44 @@ export class Preview<TRenderer extends Renderer> {
|
||||
this.channel.on(FORCE_REMOUNT, this.onForceRemount.bind(this));
|
||||
}
|
||||
|
||||
getProjectAnnotationsOrRenderError(
|
||||
async getProjectAnnotationsOrRenderError(
|
||||
getProjectAnnotations: () => MaybePromise<ProjectAnnotations<TRenderer>>
|
||||
): Promise<ProjectAnnotations<TRenderer>> {
|
||||
return SynchronousPromise.resolve()
|
||||
.then(getProjectAnnotations)
|
||||
.then((projectAnnotations) => {
|
||||
if (projectAnnotations.renderToDOM)
|
||||
deprecate(`\`renderToDOM\` is deprecated, please rename to \`renderToCanvas\``);
|
||||
try {
|
||||
const projectAnnotations = await getProjectAnnotations();
|
||||
|
||||
this.renderToCanvas = projectAnnotations.renderToCanvas || projectAnnotations.renderToDOM;
|
||||
if (!this.renderToCanvas) {
|
||||
throw new Error(dedent`
|
||||
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<TRenderer>) {
|
||||
async initializeWithProjectAnnotations(projectAnnotations: ProjectAnnotations<TRenderer>) {
|
||||
this.storyStore.setProjectAnnotations(projectAnnotations);
|
||||
|
||||
this.setInitialGlobals();
|
||||
|
||||
let storyIndexPromise: Promise<StoryIndex>;
|
||||
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);
|
||||
try {
|
||||
const storyIndex = await this.getStoryIndexFromServer();
|
||||
return this.initializeWithStoryIndex(storyIndex);
|
||||
} catch (err) {
|
||||
this.renderPreviewEntryError('Error loading story index:', err as Error);
|
||||
throw err;
|
||||
}
|
||||
|
||||
return storyIndexPromise
|
||||
.then((storyIndex: StoryIndex) => this.initializeWithStoryIndex(storyIndex))
|
||||
.catch((err) => {
|
||||
this.renderPreviewEntryError('Error loading story index:', err);
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
async setInitialGlobals() {
|
||||
@ -185,15 +163,11 @@ export class Preview<TRenderer extends Renderer> {
|
||||
}
|
||||
|
||||
// If initialization gets as far as the story index, this function runs.
|
||||
initializeWithStoryIndex(storyIndex: StoryIndex): PromiseLike<void> {
|
||||
initializeWithStoryIndex(storyIndex: StoryIndex): void {
|
||||
if (!this.importFn)
|
||||
throw new Error(`Cannot call initializeWithStoryIndex before initialization`);
|
||||
|
||||
return this.storyStore.initialize({
|
||||
storyIndex,
|
||||
importFn: this.importFn,
|
||||
cache: !global.FEATURES?.storyStoreV7,
|
||||
});
|
||||
this.storyStore.initialize({ storyIndex, importFn: this.importFn });
|
||||
}
|
||||
|
||||
// EVENT HANDLERS
|
||||
@ -212,7 +186,7 @@ export class Preview<TRenderer extends Renderer> {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.storyStore.setProjectAnnotations(projectAnnotations);
|
||||
this.storyStore.setProjectAnnotations(projectAnnotations);
|
||||
this.emitGlobals();
|
||||
}
|
||||
|
||||
@ -230,7 +204,7 @@ export class Preview<TRenderer extends Renderer> {
|
||||
|
||||
// This is the first time the story index worked, let's load it into the store
|
||||
if (!this.storyStore.storyIndex) {
|
||||
await this.initializeWithStoryIndex(storyIndex);
|
||||
this.initializeWithStoryIndex(storyIndex);
|
||||
}
|
||||
|
||||
// Update the store with the new stories.
|
||||
@ -368,9 +342,7 @@ export class Preview<TRenderer extends Renderer> {
|
||||
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);
|
||||
}
|
||||
|
@ -47,9 +47,6 @@ vi.mock('@storybook/global', () => ({
|
||||
search: '?id=*',
|
||||
},
|
||||
},
|
||||
FEATURES: {
|
||||
storyStoreV7: true,
|
||||
},
|
||||
fetch: async () => ({ status: 200, json: async () => mockStoryIndex }),
|
||||
},
|
||||
}));
|
||||
@ -76,7 +73,7 @@ beforeEach(() => {
|
||||
vi.mocked(WebView.prototype).prepareForStory.mockReturnValue('story-element' as any);
|
||||
});
|
||||
|
||||
describe('PreviewWeb', () => {
|
||||
describe.skip('PreviewWeb', () => {
|
||||
describe('initial render', () => {
|
||||
it('renders story mode through the stack', async () => {
|
||||
const { DocsRenderer } = await import('@storybook/addon-docs');
|
||||
|
@ -69,10 +69,6 @@ vi.mock('@storybook/global', async (importOriginal) => ({
|
||||
search: '?id=*',
|
||||
},
|
||||
},
|
||||
FEATURES: {
|
||||
storyStoreV7: true,
|
||||
// xxx
|
||||
},
|
||||
fetch: async () => mockFetchResult,
|
||||
},
|
||||
}));
|
||||
@ -142,7 +138,7 @@ beforeEach(() => {
|
||||
vi.mocked(WebView.prototype).prepareForStory.mockReturnValue('story-element' as any);
|
||||
});
|
||||
|
||||
describe('PreviewWeb', () => {
|
||||
describe.skip('PreviewWeb', () => {
|
||||
describe('initialize', () => {
|
||||
it('shows an error if getProjectAnnotations throws', async () => {
|
||||
const err = new Error('meta error');
|
||||
|
@ -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,14 +113,10 @@ export class PreviewWithSelection<TFramework extends Renderer> extends Preview<T
|
||||
}
|
||||
|
||||
// If initialization gets as far as the story index, this function runs.
|
||||
initializeWithStoryIndex(storyIndex: StoryIndex): PromiseLike<void> {
|
||||
return super.initializeWithStoryIndex(storyIndex).then(() => {
|
||||
if (!global.FEATURES?.storyStoreV7) {
|
||||
this.channel.emit(SET_INDEX, this.storyStore.getSetIndexPayload());
|
||||
}
|
||||
async initializeWithStoryIndex(storyIndex: StoryIndex): Promise<void> {
|
||||
await super.initializeWithStoryIndex(storyIndex);
|
||||
|
||||
return this.selectSpecifiedStory();
|
||||
});
|
||||
return this.selectSpecifiedStory();
|
||||
}
|
||||
|
||||
// Use the selection specifier to choose a story, then render it
|
||||
@ -204,10 +198,6 @@ export class PreviewWithSelection<TFramework extends Renderer> extends Preview<T
|
||||
}) {
|
||||
await super.onStoriesChanged({ importFn, storyIndex });
|
||||
|
||||
if (!global.FEATURES?.storyStoreV7) {
|
||||
this.channel.emit(SET_INDEX, await this.storyStore.getSetIndexPayload());
|
||||
}
|
||||
|
||||
if (this.selectionStore.selection) {
|
||||
await this.renderSelection();
|
||||
} else {
|
||||
@ -396,15 +386,13 @@ export class PreviewWithSelection<TFramework extends Renderer> extends Preview<T
|
||||
render.story
|
||||
);
|
||||
|
||||
if (global.FEATURES?.storyStoreV7) {
|
||||
this.channel.emit(STORY_PREPARED, {
|
||||
id: storyId,
|
||||
parameters,
|
||||
initialArgs,
|
||||
argTypes,
|
||||
args: unmappedArgs,
|
||||
});
|
||||
}
|
||||
this.channel.emit(STORY_PREPARED, {
|
||||
id: storyId,
|
||||
parameters,
|
||||
initialArgs,
|
||||
argTypes,
|
||||
args: unmappedArgs,
|
||||
});
|
||||
|
||||
// For v6 mode / compatibility
|
||||
// If the implementation changed, or args were persisted, the args may have changed,
|
||||
@ -412,8 +400,10 @@ export class PreviewWithSelection<TFramework extends Renderer> extends Preview<T
|
||||
if (implementationChanged || persistedArgs) {
|
||||
this.channel.emit(STORY_ARGS_UPDATED, { storyId, args: unmappedArgs });
|
||||
}
|
||||
} else if (global.FEATURES?.storyStoreV7) {
|
||||
if (!this.storyStore.projectAnnotations) throw new Error('Store not initialized');
|
||||
} else {
|
||||
if (!this.storyStore.projectAnnotations) {
|
||||
throw new Error('Store not initialized');
|
||||
}
|
||||
|
||||
// Default to the project parameters for MDX docs
|
||||
let { parameters } = this.storyStore.projectAnnotations;
|
||||
@ -467,9 +457,7 @@ export class PreviewWithSelection<TFramework extends Renderer> extends Preview<T
|
||||
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);
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ describe('StoryStore', () => {
|
||||
it('normalizes on initialization', async () => {
|
||||
const store = new StoryStore();
|
||||
store.setProjectAnnotations(projectAnnotations);
|
||||
store.initialize({ storyIndex, importFn, cache: false });
|
||||
store.initialize({ storyIndex, importFn });
|
||||
|
||||
expect(store.projectAnnotations!.globalTypes).toEqual({
|
||||
a: { name: 'a', type: { name: 'string' } },
|
||||
@ -99,7 +99,7 @@ describe('StoryStore', () => {
|
||||
it('normalizes on updateGlobalAnnotations', async () => {
|
||||
const store = new StoryStore();
|
||||
store.setProjectAnnotations(projectAnnotations);
|
||||
store.initialize({ storyIndex, importFn, cache: false });
|
||||
store.initialize({ storyIndex, importFn });
|
||||
|
||||
store.setProjectAnnotations(projectAnnotations);
|
||||
expect(store.projectAnnotations!.globalTypes).toEqual({
|
||||
@ -115,7 +115,7 @@ describe('StoryStore', () => {
|
||||
it('pulls the story via the importFn', async () => {
|
||||
const store = new StoryStore();
|
||||
store.setProjectAnnotations(projectAnnotations);
|
||||
store.initialize({ storyIndex, importFn, cache: false });
|
||||
store.initialize({ storyIndex, importFn });
|
||||
|
||||
importFn.mockClear();
|
||||
expect(await store.loadStory({ storyId: 'component-one--a' })).toMatchObject({
|
||||
@ -130,7 +130,7 @@ describe('StoryStore', () => {
|
||||
it('uses a cache', async () => {
|
||||
const store = new StoryStore();
|
||||
store.setProjectAnnotations(projectAnnotations);
|
||||
store.initialize({ storyIndex, importFn, cache: false });
|
||||
store.initialize({ storyIndex, importFn });
|
||||
|
||||
const story = await store.loadStory({ storyId: 'component-one--a' });
|
||||
expect(processCSFFile).toHaveBeenCalledTimes(1);
|
||||
@ -158,7 +158,7 @@ describe('StoryStore', () => {
|
||||
const loadPromise = store.loadStory({ storyId: 'component-one--a' });
|
||||
|
||||
store.setProjectAnnotations(projectAnnotations);
|
||||
store.initialize({ storyIndex, importFn, cache: false });
|
||||
store.initialize({ storyIndex, importFn });
|
||||
|
||||
expect(await loadPromise).toMatchObject({
|
||||
id: 'component-one--a',
|
||||
@ -175,7 +175,7 @@ describe('StoryStore', () => {
|
||||
it('busts the loadStory cache', async () => {
|
||||
const store = new StoryStore();
|
||||
store.setProjectAnnotations(projectAnnotations);
|
||||
store.initialize({ storyIndex, importFn, cache: false });
|
||||
store.initialize({ storyIndex, importFn });
|
||||
|
||||
const story = await store.loadStory({ storyId: 'component-one--a' });
|
||||
expect(processCSFFile).toHaveBeenCalledTimes(1);
|
||||
@ -194,7 +194,7 @@ describe('StoryStore', () => {
|
||||
it('busts the loadStory cache if the importFn returns a new module', async () => {
|
||||
const store = new StoryStore();
|
||||
store.setProjectAnnotations(projectAnnotations);
|
||||
store.initialize({ storyIndex, importFn, cache: false });
|
||||
store.initialize({ storyIndex, importFn });
|
||||
|
||||
const story = await store.loadStory({ storyId: 'component-one--a' });
|
||||
expect(processCSFFile).toHaveBeenCalledTimes(1);
|
||||
@ -216,7 +216,7 @@ describe('StoryStore', () => {
|
||||
it('busts the loadStory cache if the csf file no longer appears in the index', async () => {
|
||||
const store = new StoryStore();
|
||||
store.setProjectAnnotations(projectAnnotations);
|
||||
store.initialize({ storyIndex, importFn, cache: false });
|
||||
store.initialize({ storyIndex, importFn });
|
||||
|
||||
await store.loadStory({ storyId: 'component-one--a' });
|
||||
expect(processCSFFile).toHaveBeenCalledTimes(1);
|
||||
@ -235,7 +235,7 @@ describe('StoryStore', () => {
|
||||
it('reuses the cache if a story importPath has not changed', async () => {
|
||||
const store = new StoryStore();
|
||||
store.setProjectAnnotations(projectAnnotations);
|
||||
store.initialize({ storyIndex, importFn, cache: false });
|
||||
store.initialize({ storyIndex, importFn });
|
||||
|
||||
const story = await store.loadStory({ storyId: 'component-one--a' });
|
||||
expect(processCSFFile).toHaveBeenCalledTimes(1);
|
||||
@ -267,7 +267,7 @@ describe('StoryStore', () => {
|
||||
it('imports with a new path for a story id if provided', async () => {
|
||||
const store = new StoryStore();
|
||||
store.setProjectAnnotations(projectAnnotations);
|
||||
store.initialize({ storyIndex, importFn, cache: false });
|
||||
store.initialize({ storyIndex, importFn });
|
||||
|
||||
await store.loadStory({ storyId: 'component-one--a' });
|
||||
expect(importFn).toHaveBeenCalledWith(storyIndex.entries['component-one--a'].importPath);
|
||||
@ -297,7 +297,7 @@ describe('StoryStore', () => {
|
||||
it('re-caches stories if the were cached already', async () => {
|
||||
const store = new StoryStore();
|
||||
store.setProjectAnnotations(projectAnnotations);
|
||||
store.initialize({ storyIndex, importFn, cache: false });
|
||||
store.initialize({ storyIndex, importFn });
|
||||
await store.cacheAllCSFFiles();
|
||||
|
||||
await store.loadStory({ storyId: 'component-one--a' });
|
||||
@ -370,7 +370,7 @@ describe('StoryStore', () => {
|
||||
it('returns all the stories in the file', async () => {
|
||||
const store = new StoryStore();
|
||||
store.setProjectAnnotations(projectAnnotations);
|
||||
store.initialize({ storyIndex, importFn, cache: false });
|
||||
store.initialize({ storyIndex, importFn });
|
||||
|
||||
const csfFile = await store.loadCSFFileByStoryId('component-one--a');
|
||||
const stories = store.componentStoriesFromCSFFile({ csfFile });
|
||||
@ -389,7 +389,7 @@ describe('StoryStore', () => {
|
||||
'component-one--a': storyIndex.entries['component-one--a'],
|
||||
},
|
||||
};
|
||||
store.initialize({ storyIndex: reversedIndex, importFn, cache: false });
|
||||
store.initialize({ storyIndex: reversedIndex, importFn });
|
||||
|
||||
const csfFile = await store.loadCSFFileByStoryId('component-one--a');
|
||||
const stories = store.componentStoriesFromCSFFile({ csfFile });
|
||||
@ -403,7 +403,7 @@ describe('StoryStore', () => {
|
||||
it('returns the args and globals correctly', async () => {
|
||||
const store = new StoryStore();
|
||||
store.setProjectAnnotations(projectAnnotations);
|
||||
store.initialize({ storyIndex, importFn, cache: false });
|
||||
store.initialize({ storyIndex, importFn });
|
||||
|
||||
const story = await store.loadStory({ storyId: 'component-one--a' });
|
||||
|
||||
@ -416,7 +416,7 @@ describe('StoryStore', () => {
|
||||
it('returns the args and globals correctly when they change', async () => {
|
||||
const store = new StoryStore();
|
||||
store.setProjectAnnotations(projectAnnotations);
|
||||
store.initialize({ storyIndex, importFn, cache: false });
|
||||
store.initialize({ storyIndex, importFn });
|
||||
|
||||
const story = await store.loadStory({ storyId: 'component-one--a' });
|
||||
|
||||
@ -432,7 +432,7 @@ describe('StoryStore', () => {
|
||||
it('can force initial args', async () => {
|
||||
const store = new StoryStore();
|
||||
store.setProjectAnnotations(projectAnnotations);
|
||||
store.initialize({ storyIndex, importFn, cache: false });
|
||||
store.initialize({ storyIndex, importFn });
|
||||
|
||||
const story = await store.loadStory({ storyId: 'component-one--a' });
|
||||
|
||||
@ -446,7 +446,7 @@ describe('StoryStore', () => {
|
||||
it('returns the same hooks each time', async () => {
|
||||
const store = new StoryStore();
|
||||
store.setProjectAnnotations(projectAnnotations);
|
||||
store.initialize({ storyIndex, importFn, cache: false });
|
||||
store.initialize({ storyIndex, importFn });
|
||||
|
||||
const story = await store.loadStory({ storyId: 'component-one--a' });
|
||||
|
||||
@ -463,7 +463,7 @@ describe('StoryStore', () => {
|
||||
it('cleans the hooks from the context', async () => {
|
||||
const store = new StoryStore();
|
||||
store.setProjectAnnotations(projectAnnotations);
|
||||
store.initialize({ storyIndex, importFn, cache: false });
|
||||
store.initialize({ storyIndex, importFn });
|
||||
|
||||
const story = await store.loadStory({ storyId: 'component-one--a' });
|
||||
|
||||
@ -478,7 +478,7 @@ describe('StoryStore', () => {
|
||||
it('imports *all* csf files', async () => {
|
||||
const store = new StoryStore();
|
||||
store.setProjectAnnotations(projectAnnotations);
|
||||
store.initialize({ storyIndex, importFn, cache: false });
|
||||
store.initialize({ storyIndex, importFn });
|
||||
|
||||
importFn.mockClear();
|
||||
const csfFiles = await store.loadAllCSFFiles();
|
||||
@ -498,7 +498,7 @@ describe('StoryStore', () => {
|
||||
});
|
||||
const store = new StoryStore();
|
||||
store.setProjectAnnotations(projectAnnotations);
|
||||
store.initialize({ storyIndex, importFn: blockedImportFn, cache: false });
|
||||
store.initialize({ storyIndex, importFn: blockedImportFn });
|
||||
|
||||
const promise = store.loadAllCSFFiles({ batchSize: 1 });
|
||||
expect(blockedImportFn).toHaveBeenCalledTimes(1);
|
||||
@ -513,7 +513,7 @@ describe('StoryStore', () => {
|
||||
it('throws if you have not called cacheAllCSFFiles', async () => {
|
||||
const store = new StoryStore();
|
||||
store.setProjectAnnotations(projectAnnotations);
|
||||
store.initialize({ storyIndex, importFn, cache: false });
|
||||
store.initialize({ storyIndex, importFn });
|
||||
|
||||
expect(() => store.extract()).toThrow(/Cannot call extract/);
|
||||
});
|
||||
@ -521,7 +521,7 @@ describe('StoryStore', () => {
|
||||
it('produces objects with functions and hooks stripped', async () => {
|
||||
const store = new StoryStore();
|
||||
store.setProjectAnnotations(projectAnnotations);
|
||||
store.initialize({ storyIndex, importFn, cache: false });
|
||||
store.initialize({ storyIndex, importFn });
|
||||
await store.cacheAllCSFFiles();
|
||||
|
||||
expect(store.extract()).toMatchInlineSnapshot(`
|
||||
@ -658,7 +658,6 @@ describe('StoryStore', () => {
|
||||
store.initialize({
|
||||
storyIndex,
|
||||
importFn: docsOnlyImportFn,
|
||||
cache: false,
|
||||
});
|
||||
await store.cacheAllCSFFiles();
|
||||
|
||||
@ -691,7 +690,6 @@ describe('StoryStore', () => {
|
||||
store.initialize({
|
||||
storyIndex: unnattachedStoryIndex,
|
||||
importFn,
|
||||
cache: false,
|
||||
});
|
||||
await store.cacheAllCSFFiles();
|
||||
|
||||
@ -713,7 +711,7 @@ describe('StoryStore', () => {
|
||||
it('produces an array of stories', async () => {
|
||||
const store = new StoryStore();
|
||||
store.setProjectAnnotations(projectAnnotations);
|
||||
store.initialize({ storyIndex, importFn, cache: false });
|
||||
store.initialize({ storyIndex, importFn });
|
||||
await store.cacheAllCSFFiles();
|
||||
|
||||
expect(store.raw()).toMatchInlineSnapshot(`
|
||||
@ -862,7 +860,7 @@ describe('StoryStore', () => {
|
||||
it('maps stories list to payload correctly', async () => {
|
||||
const store = new StoryStore();
|
||||
store.setProjectAnnotations(projectAnnotations);
|
||||
store.initialize({ storyIndex, importFn, cache: false });
|
||||
store.initialize({ storyIndex, importFn });
|
||||
await store.cacheAllCSFFiles();
|
||||
|
||||
expect(store.getSetStoriesPayload()).toMatchInlineSnapshot(`
|
||||
@ -1002,7 +1000,7 @@ describe('StoryStore', () => {
|
||||
it('maps stories list to payload correctly', async () => {
|
||||
const store = new StoryStore();
|
||||
store.setProjectAnnotations(projectAnnotations);
|
||||
store.initialize({ storyIndex, importFn, cache: false });
|
||||
store.initialize({ storyIndex, importFn });
|
||||
await store.cacheAllCSFFiles();
|
||||
|
||||
expect(store.getStoriesJsonData()).toMatchInlineSnapshot(`
|
||||
@ -1052,116 +1050,6 @@ describe('StoryStore', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('getSetIndexPayload', () => {
|
||||
it('add parameters/args to index correctly', async () => {
|
||||
const store = new StoryStore();
|
||||
store.setProjectAnnotations(projectAnnotations);
|
||||
store.initialize({ storyIndex, importFn, cache: false });
|
||||
await store.cacheAllCSFFiles();
|
||||
|
||||
expect(store.getSetIndexPayload()).toMatchInlineSnapshot(`
|
||||
{
|
||||
"entries": {
|
||||
"component-one--a": {
|
||||
"argTypes": {
|
||||
"a": {
|
||||
"name": "a",
|
||||
"type": {
|
||||
"name": "string",
|
||||
},
|
||||
},
|
||||
"foo": {
|
||||
"name": "foo",
|
||||
"type": {
|
||||
"name": "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
"args": {
|
||||
"foo": "a",
|
||||
},
|
||||
"id": "component-one--a",
|
||||
"importPath": "./src/ComponentOne.stories.js",
|
||||
"initialArgs": {
|
||||
"foo": "a",
|
||||
},
|
||||
"name": "A",
|
||||
"parameters": {
|
||||
"__isArgsStory": false,
|
||||
"fileName": "./src/ComponentOne.stories.js",
|
||||
},
|
||||
"title": "Component One",
|
||||
"type": "story",
|
||||
},
|
||||
"component-one--b": {
|
||||
"argTypes": {
|
||||
"a": {
|
||||
"name": "a",
|
||||
"type": {
|
||||
"name": "string",
|
||||
},
|
||||
},
|
||||
"foo": {
|
||||
"name": "foo",
|
||||
"type": {
|
||||
"name": "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
"args": {
|
||||
"foo": "b",
|
||||
},
|
||||
"id": "component-one--b",
|
||||
"importPath": "./src/ComponentOne.stories.js",
|
||||
"initialArgs": {
|
||||
"foo": "b",
|
||||
},
|
||||
"name": "B",
|
||||
"parameters": {
|
||||
"__isArgsStory": false,
|
||||
"fileName": "./src/ComponentOne.stories.js",
|
||||
},
|
||||
"title": "Component One",
|
||||
"type": "story",
|
||||
},
|
||||
"component-two--c": {
|
||||
"argTypes": {
|
||||
"a": {
|
||||
"name": "a",
|
||||
"type": {
|
||||
"name": "string",
|
||||
},
|
||||
},
|
||||
"foo": {
|
||||
"name": "foo",
|
||||
"type": {
|
||||
"name": "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
"args": {
|
||||
"foo": "c",
|
||||
},
|
||||
"id": "component-two--c",
|
||||
"importPath": "./src/ComponentTwo.stories.js",
|
||||
"initialArgs": {
|
||||
"foo": "c",
|
||||
},
|
||||
"name": "C",
|
||||
"parameters": {
|
||||
"__isArgsStory": false,
|
||||
"fileName": "./src/ComponentTwo.stories.js",
|
||||
},
|
||||
"title": "Component Two",
|
||||
"type": "story",
|
||||
},
|
||||
},
|
||||
"v": 4,
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('cacheAllCsfFiles', () => {
|
||||
describe('if the store is not yet initialized', () => {
|
||||
it('waits for initialization', async () => {
|
||||
@ -1171,7 +1059,7 @@ describe('StoryStore', () => {
|
||||
const cachePromise = store.cacheAllCSFFiles();
|
||||
|
||||
store.setProjectAnnotations(projectAnnotations);
|
||||
store.initialize({ storyIndex, importFn, cache: false });
|
||||
store.initialize({ storyIndex, importFn });
|
||||
|
||||
await expect(cachePromise).resolves.toEqual(undefined);
|
||||
});
|
||||
|
@ -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,9 +61,9 @@ export class StoryStore<TRenderer extends Renderer> {
|
||||
|
||||
prepareStoryWithCache: typeof prepareStory;
|
||||
|
||||
initializationPromise: SynchronousPromise<void>;
|
||||
initializationPromise: Promise<void>;
|
||||
|
||||
// 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() {
|
||||
@ -80,7 +78,7 @@ export class StoryStore<TRenderer extends Renderer> {
|
||||
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<TRenderer extends Renderer> {
|
||||
initialize({
|
||||
storyIndex,
|
||||
importFn,
|
||||
cache = false,
|
||||
}: {
|
||||
storyIndex?: StoryIndex;
|
||||
importFn: ModuleImportFn;
|
||||
cache?: boolean;
|
||||
}): Promise<void> {
|
||||
}): 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<TRenderer extends Renderer> {
|
||||
}
|
||||
|
||||
// To load a single CSF file to service a story we need to look up the importPath in the index
|
||||
loadCSFFileByStoryId(storyId: StoryId): Promise<CSFFile<TRenderer>> {
|
||||
async loadCSFFileByStoryId(storyId: StoryId): Promise<CSFFile<TRenderer>> {
|
||||
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<TRenderer>['cachedCSFFiles']
|
||||
> {
|
||||
if (!this.storyIndex) throw new Error(`loadAllCSFFiles called before initialization`);
|
||||
@ -163,41 +157,33 @@ export class StoryStore<TRenderer extends Renderer> {
|
||||
storyId,
|
||||
]);
|
||||
|
||||
const loadInBatches = (
|
||||
const loadInBatches = async (
|
||||
remainingImportPaths: typeof importPaths
|
||||
): Promise<{ importPath: Path; csfFile: CSFFile<TRenderer> }[]> => {
|
||||
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,
|
||||
}))
|
||||
);
|
||||
.map(async ([importPath, storyId]) => ({
|
||||
importPath,
|
||||
csfFile: await this.loadCSFFileByStoryId(storyId),
|
||||
}));
|
||||
|
||||
return SynchronousPromise.all(csfFilePromiseList).then((firstResults) =>
|
||||
loadInBatches(remainingImportPaths.slice(batchSize)).then((restResults) =>
|
||||
firstResults.concat(restResults)
|
||||
)
|
||||
);
|
||||
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<Path, CSFFile<TRenderer>>)
|
||||
);
|
||||
const list = await loadInBatches(importPaths);
|
||||
return list.reduce((acc, { importPath, csfFile }) => {
|
||||
acc[importPath] = csfFile;
|
||||
return acc;
|
||||
}, {} as Record<Path, CSFFile<TRenderer>>);
|
||||
}
|
||||
|
||||
cacheAllCSFFiles(): Promise<void> {
|
||||
return this.initializationPromise.then(() =>
|
||||
this.loadAllCSFFiles().then((csfFiles) => {
|
||||
this.cachedCSFFiles = csfFiles;
|
||||
})
|
||||
);
|
||||
async cacheAllCSFFiles(): Promise<void> {
|
||||
await this.initializationPromise;
|
||||
this.cachedCSFFiles = await this.loadAllCSFFiles();
|
||||
}
|
||||
|
||||
preparedMetaFromCSFFile({ csfFile }: { csfFile: CSFFile<TRenderer> }): PreparedMeta<TRenderer> {
|
||||
@ -393,38 +379,6 @@ export class StoryStore<TRenderer extends Renderer> {
|
||||
};
|
||||
};
|
||||
|
||||
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<TRenderer>[] {
|
||||
return Object.values(this.extract())
|
||||
.map(({ id }: { id: StoryId }) => this.fromId(id))
|
||||
|
1
code/lib/preview-api/src/typings.d.ts
vendored
1
code/lib/preview-api/src/typings.d.ts
vendored
@ -19,7 +19,6 @@ declare var IS_STORYBOOK: boolean;
|
||||
// relevant framework instantiates them via `start.js`. The good news is this happens right away.
|
||||
declare var __STORYBOOK_ADDONS_CHANNEL__: any;
|
||||
declare var __STORYBOOK_ADDONS_PREVIEW: any;
|
||||
declare var __STORYBOOK_CLIENT_API__: import('./modules/client-api/ClientApi').ClientApi<any>;
|
||||
declare var __STORYBOOK_PREVIEW__: import('./modules/preview-web/PreviewWeb').PreviewWeb<any>;
|
||||
declare var __STORYBOOK_STORY_STORE__: any;
|
||||
declare var STORYBOOK_HOOKS_CONTEXT: any;
|
||||
|
@ -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:
|
||||
|
@ -313,9 +313,7 @@ describe('storybook-metadata', () => {
|
||||
});
|
||||
|
||||
it('should return user specified features', async () => {
|
||||
const features = {
|
||||
storyStoreV7: true,
|
||||
};
|
||||
const features = {};
|
||||
|
||||
const result = await computeStorybookMetadata({
|
||||
packageJson: packageJsonMock,
|
||||
|
@ -350,22 +350,6 @@ export interface StorybookConfigRaw {
|
||||
staticDirs?: (DirectoryMapping | string)[];
|
||||
logLevel?: string;
|
||||
features?: {
|
||||
/**
|
||||
* Build stories.json automatically on start/build
|
||||
*/
|
||||
buildStoriesJson?: boolean;
|
||||
|
||||
/**
|
||||
* Activate on demand story store
|
||||
*/
|
||||
storyStoreV7?: boolean;
|
||||
|
||||
/**
|
||||
* Do not throw errors if using `.mdx` files in SSv7
|
||||
* (for internal use in sandboxes)
|
||||
*/
|
||||
storyStoreV7MdxErrors?: boolean;
|
||||
|
||||
/**
|
||||
* Filter args with a "target" on the type from the render function (EXPERIMENTAL)
|
||||
*/
|
||||
|
@ -267,7 +267,7 @@
|
||||
"built": false
|
||||
}
|
||||
},
|
||||
"packageManager": "yarn@4.0.0",
|
||||
"packageManager": "yarn@4.0.2",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
|
@ -1,6 +1,6 @@
|
||||
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
||||
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); }
|
||||
function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
|
||||
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : String(i); }
|
||||
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
||||
/* eslint-disable react/no-unused-prop-types */
|
||||
/* eslint-disable react/require-default-props */
|
||||
import React from 'react';
|
||||
|
@ -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 {
|
||||
|
@ -1 +0,0 @@
|
||||
export { setup } from './render';
|
@ -51,7 +51,7 @@
|
||||
"@storybook/csf": "^0.1.2",
|
||||
"@storybook/docs-tools": "workspace:*",
|
||||
"@storybook/global": "^5.0.0",
|
||||
"@storybook/icons": "^1.2.1",
|
||||
"@storybook/icons": "^1.2.3",
|
||||
"@storybook/manager-api": "workspace:*",
|
||||
"@storybook/preview-api": "workspace:*",
|
||||
"@storybook/theming": "workspace:*",
|
||||
@ -60,7 +60,7 @@
|
||||
"color-convert": "^2.0.1",
|
||||
"dequal": "^2.0.2",
|
||||
"lodash": "^4.17.21",
|
||||
"markdown-to-jsx": "^7.1.8",
|
||||
"markdown-to-jsx": "7.3.2",
|
||||
"memoizerific": "^1.11.3",
|
||||
"polished": "^4.2.2",
|
||||
"react-colorful": "^5.1.2",
|
||||
|
@ -63,7 +63,7 @@
|
||||
"@storybook/client-logger": "workspace:*",
|
||||
"@storybook/csf": "^0.1.2",
|
||||
"@storybook/global": "^5.0.0",
|
||||
"@storybook/icons": "^1.2.1",
|
||||
"@storybook/icons": "^1.2.3",
|
||||
"@storybook/theming": "workspace:*",
|
||||
"@storybook/types": "workspace:*",
|
||||
"memoizerific": "^1.11.3",
|
||||
|
@ -79,7 +79,7 @@
|
||||
"@storybook/components": "workspace:*",
|
||||
"@storybook/core-events": "workspace:*",
|
||||
"@storybook/global": "^5.0.0",
|
||||
"@storybook/icons": "^1.2.1",
|
||||
"@storybook/icons": "^1.2.3",
|
||||
"@storybook/manager-api": "workspace:*",
|
||||
"@storybook/router": "workspace:*",
|
||||
"@storybook/test": "workspace:*",
|
||||
@ -94,7 +94,7 @@
|
||||
"fs-extra": "^11.1.0",
|
||||
"fuse.js": "^3.6.1",
|
||||
"lodash": "^4.17.21",
|
||||
"markdown-to-jsx": "^7.1.8",
|
||||
"markdown-to-jsx": "7.3.2",
|
||||
"memoizerific": "^1.11.3",
|
||||
"polished": "^4.2.2",
|
||||
"qs": "^6.10.0",
|
||||
|
@ -18,8 +18,6 @@ import { FramesRenderer } from './FramesRenderer';
|
||||
|
||||
import type { PreviewProps } from './utils/types';
|
||||
|
||||
const { FEATURES } = global;
|
||||
|
||||
const getWrappers = (getFn: API['getElements']) => Object.values(getFn(types.PREVIEW));
|
||||
const getTabs = (getFn: API['getElements']) => Object.values(getFn(types.TAB));
|
||||
|
||||
@ -160,7 +158,7 @@ const Canvas: FC<{ withLoader: boolean; baseUrl: string; children?: never }> = (
|
||||
|
||||
const [progress, setProgress] = useState(undefined);
|
||||
useEffect(() => {
|
||||
if (FEATURES?.storyStoreV7 && global.CONFIG_TYPE === 'DEVELOPMENT') {
|
||||
if (global.CONFIG_TYPE === 'DEVELOPMENT') {
|
||||
try {
|
||||
const channel = addons.getServerChannel();
|
||||
|
||||
|
@ -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;
|
||||
@ -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);
|
||||
}
|
||||
|
2705
code/yarn.lock
2705
code/yarn.lock
File diff suppressed because it is too large
Load Diff
@ -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.
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
<CodeSnippets
|
||||
paths={[
|
||||
'common/main-config-features-build-stories-json.js.mdx',
|
||||
'common/main-config-features-build-stories-json.ts.mdx',
|
||||
]}
|
||||
/>
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
## `legacyDecoratorFileOrder`
|
||||
|
||||
Type: `boolean`
|
||||
@ -54,25 +33,6 @@ Apply decorators from preview.js before decorators from addons or frameworks. [M
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
## `storyStoreV7`
|
||||
|
||||
Type: `boolean`
|
||||
|
||||
Default: `true`
|
||||
|
||||
Opts out of [on-demand story loading](#on-demand-story-loading); loads all stories at build time.
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
<CodeSnippets
|
||||
paths={[
|
||||
'common/main-config-features-story-store-v7.js.mdx',
|
||||
'common/main-config-features-story-store-v7.ts.mdx',
|
||||
]}
|
||||
/>
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
## `argTypeTargetsV7`
|
||||
|
||||
(⚠️ **Experimental**)
|
||||
@ -91,25 +51,3 @@ Filter args with a "target" on the type from the render function.
|
||||
/>
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
## 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:
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
<CodeSnippets
|
||||
paths={[
|
||||
'common/main-config-features-story-store-v7.js.mdx',
|
||||
'common/main-config-features-story-store-v7.ts.mdx',
|
||||
]}
|
||||
/>
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
### 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.
|
||||
|
@ -117,7 +117,7 @@ When [auto-titling](../configure/sidebar-and-urls.md#csf-30-auto-titles), prefix
|
||||
|
||||
<Callout variant="info" icon="💡">
|
||||
|
||||
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.
|
||||
|
||||
</Callout>
|
||||
|
||||
|
@ -10,7 +10,6 @@ By default, Storybook provides zero-config support for Webpack and automatically
|
||||
|
||||
| Option | Description |
|
||||
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `storyStoreV7` | Enabled by default.<br/> Configures Webpack's [code splitting](https://webpack.js.org/guides/code-splitting/) feature<br/> `features: { storyStoreV7: false }` |
|
||||
| `lazyCompilation` | Enables Webpack's experimental [`lazy compilation`](https://webpack.js.org/configuration/experiments/#experimentslazycompilation)<br/>`core: { builder: { options: { lazyCompilation: true } } }` |
|
||||
| `fsCache` | Configures Webpack's filesystem [caching](https://webpack.js.org/configuration/cache/#cachetype) feature<br/> `core: { builder: { options: { fsCache: true } } }` |
|
||||
|
||||
|
@ -40,7 +40,7 @@ This configuration file is a [preset](../addons/addon-types.md) and, as such, ha
|
||||
| `framework` | Configures Storybook based on a set of [framework-specific](./frameworks.md) settings <br/> `framework: { name: '@storybook/svelte-vite', options:{} }` |
|
||||
| `core` | Configures Storybook's [internal features](../api/main-config-core.md) <br/> `core: { disableTelemetry: true, }` |
|
||||
| `docs` | Configures Storybook's [auto-generated documentation](../writing-docs/autodocs.md)<br/> `docs: { autodocs: 'tag' }` |
|
||||
| `features` | Enables Storybook's [additional features](../api/main-config-features.md)<br/> See table below for a list of available features `features: { storyStoreV7: true }` |
|
||||
| `features` | Enables Storybook's [additional features](../api/main-config-features.md)<br/> See table below for a list of available features |
|
||||
| `refs` | Configures [Storybook composition](../sharing/storybook-composition.md) <br/> `refs:{ example: { title: 'ExampleStorybook', url:'https://your-url.com' } }` |
|
||||
| `logLevel` | Configures Storybook's logs in the browser terminal. Useful for debugging <br/> `logLevel: 'debug'` |
|
||||
| `webpackFinal` | Customize Storybook's [Webpack](../builders/webpack.md) setup <br/> `webpackFinal: async (config:any) => { return config; }` |
|
||||
@ -48,15 +48,6 @@ This configuration file is a [preset](../addons/addon-types.md) and, as such, ha
|
||||
| `env` | Defines custom Storybook [environment variables](./environment-variables.md#using-storybook-configuration). <br/> `env: (config) => ({...config, EXAMPLE_VAR: 'Example var' }),` |
|
||||
| `build` | Optimizes Storybook's production [build](../api/main-config-build.md) for performance by excluding specific features from the bundle. Useful when decreased build times are a priority. <br/> `build: { test: {} }` |
|
||||
|
||||
### Feature flags
|
||||
|
||||
Additionally, you can also provide additional feature flags to your Storybook configuration. Below is an abridged list of available features that are currently available.
|
||||
|
||||
| Configuration element | Description |
|
||||
| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `storyStoreV7` | Configures Storybook to load stories [on demand](#on-demand-story-loading), rather than during boot up (defaults to `true` as of `v7.0`) <br/> `features: { storyStoreV7: true }` |
|
||||
| `buildStoriesJson` | Generates `index.json` and `stories.json` files to help story loading with the on-demand mode (defaults to `true` when `storyStoreV7` is `true`) <br/> `features: { buildStoriesJson: true }` |
|
||||
|
||||
## Configure story loading
|
||||
|
||||
By default, Storybook will load stories from your project based on a glob (pattern matching string) in `.storybook/main.js|ts` that matches all files in your project with extension `.stories.*`. The intention is for you to colocate a story file along with the component it documents.
|
||||
@ -130,27 +121,12 @@ You can also adjust your Storybook configuration and implement custom logic to l
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
### 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:
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
<CodeSnippets
|
||||
paths={[
|
||||
'common/main-config-features-story-store-v7.js.mdx',
|
||||
'common/main-config-features-story-store-v7.ts.mdx',
|
||||
]}
|
||||
/>
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
#### 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:
|
||||
Because of the way stories are currently indexed in Storybook, loading stories on demand 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 are allowed based on a restricted API.
|
||||
- [CSF formats](../api/csf.md) from version 1 to version 3 are supported.
|
||||
- Custom `storySort` functions are allowed based on a restricted API.
|
||||
|
||||
## Configure story rendering
|
||||
|
||||
|
@ -35,7 +35,6 @@ Specifically, we track the following information in our telemetry events:
|
||||
- Builder (e.g., Webpack5, Vite).
|
||||
- Meta framework (e.g., [Next](https://nextjs.org/), [Gatsby](https://www.gatsbyjs.com/), [CRA](https://create-react-app.dev/)).
|
||||
- [Addons](https://storybook.js.org/integrations) (e.g., [Essentials](../essentials/index.md), [Accessibility](https://storybook.js.org/addons/@storybook/addon-a11y/)).
|
||||
- [Feature flags](./index.md#feature-flags) (e.g., `buildStoriesJson`).
|
||||
- Package manager information (e.g., `npm`, `yarn`).
|
||||
- Monorepo information (e.g., [NX](https://nx.dev/), [Turborepo](https://turborepo.org/)).
|
||||
- In-app events (e.g., [Storybook guided tour](https://github.com/storybookjs/addon-onboarding)).
|
||||
@ -92,9 +91,6 @@ Will generate the following output:
|
||||
"version": "3.1.1"
|
||||
},
|
||||
"monorepo": "Nx",
|
||||
"features": {
|
||||
"buildStoriesJson": true
|
||||
},
|
||||
"framework": {
|
||||
"name": "@storybook/react-vite",
|
||||
"options": {}
|
||||
|
@ -51,20 +51,6 @@ yarn storybook dev --debug-webpack
|
||||
yarn storybook build --debug-webpack
|
||||
```
|
||||
|
||||
### Bundle splitting
|
||||
|
||||
Starting with Storybook 6.4, [bundle splitting](https://v4.webpack.js.org/guides/code-splitting/) is supported through a configuration flag. Update your Storybook configuration and add the `storyStoreV7` flag:
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
<CodeSnippets
|
||||
paths={[
|
||||
'common/main-config-features-story-store-v7.js.mdx',
|
||||
]}
|
||||
/>
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
When you start your Storybook, you'll see an improvement in loading times. Read more about it in the [announcement post](https://storybook.js.org/blog/storybook-on-demand-architecture/) and the [configuration documentation](./index.md#on-demand-story-loading).
|
||||
|
||||
### Webpack 5
|
||||
|
@ -94,27 +94,6 @@ This shows the errors visually in your editor, which speeds things up a lot. Her
|
||||
|
||||

|
||||
|
||||
### storiesOf support discontinued by default
|
||||
|
||||
If you use Storybook’s legacy `storiesOf` API, it is no longer supported by default in Storybook 7.
|
||||
|
||||
We recommend you upgrade your `storiesOf` stories to [Component Story Format (CSF)](https://storybook.js.org/blog/storybook-csf3-is-here/). To do so, please see our [optional migration instructions below](#storiesof-to-csf).
|
||||
|
||||
If you can’t upgrade to CSF, or want to get your project working with Storybook 7 before putting in the time to upgrade, you can opt out of on-demand story loading. This legacy mode has a variety of performance implications, but is a convenient stop-gap solution.
|
||||
|
||||
To opt out, add the `storyStoreV7` feature flag in `.storybook/main.js`:
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
<CodeSnippets
|
||||
paths={[
|
||||
'common/main-config-features-story-store-v7.js.mdx',
|
||||
'common/main-config-features-story-store-v7.ts.mdx'
|
||||
]}
|
||||
/>
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
For more information on this change, see the [migration notes](https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#storystorev7-enabled-by-default).
|
||||
|
||||
## Troubleshooting
|
||||
|
@ -74,27 +74,6 @@ Similar to other fields available in Storybook’s configuration file, the `refs
|
||||
|
||||
</Callout>
|
||||
|
||||
## Improve your Storybook composition
|
||||
|
||||
Out of the box, Storybook allows you to compose Storybooks both locally and remotely with a minor change to your configuration. However, as your Storybook grows, you might want to optimize the composition process to improve the overall performance and user experience of your Storybook by enabling the `buildStoriesJson` feature flag that will generate the `index.json` and `stories.json` files with the required information to populate the UI with your composed Storybook stories automatically. For example:
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
<CodeSnippets
|
||||
paths={[
|
||||
'common/main-config-features-build-stories-json.js.mdx',
|
||||
'common/main-config-features-build-stories-json.ts.mdx',
|
||||
]}
|
||||
/>
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
<Callout variant="info">
|
||||
|
||||
If you're working with a Storybook version 7.0 or higher, this flag is enabled by default. However, if you're working with an older version and you configured your Storybook to use the [`storyStoreV7`](../api/main-config-features.md#storystorev7) feature flag, you won't need this flag as it will automatically generate the required `index.json` file for you to use.
|
||||
|
||||
</Callout>
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Storybook composition is not working with my project
|
||||
|
@ -1,12 +0,0 @@
|
||||
```js
|
||||
// .storybook/main.js
|
||||
|
||||
export default {
|
||||
// Replace your-framework with the framework you are using (e.g., react-webpack5, vue3-vite)
|
||||
framework: '@storybook/your-framework',
|
||||
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
|
||||
features: {
|
||||
buildStoriesJson: true,
|
||||
},
|
||||
};
|
||||
```
|
@ -1,16 +0,0 @@
|
||||
```ts
|
||||
// .storybook/main.ts
|
||||
|
||||
// Replace your-framework with the framework you are using (e.g., react-webpack5, vue3-vite)
|
||||
import type { StorybookConfig } from '@storybook/your-framework';
|
||||
|
||||
const config: StorybookConfig = {
|
||||
framework: '@storybook/your-framework',
|
||||
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
|
||||
features: {
|
||||
buildStoriesJson: true,
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
```
|
@ -1,12 +0,0 @@
|
||||
```js
|
||||
// .storybook/main.js
|
||||
|
||||
export default {
|
||||
// Replace your-framework with the framework you are using (e.g., react-webpack5, vue3-vite)
|
||||
framework: '@storybook/your-framework',
|
||||
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
|
||||
features: {
|
||||
storyStoreV7: false, // 👈 Opt out of on-demand story loading
|
||||
},
|
||||
};
|
||||
```
|
@ -1,16 +0,0 @@
|
||||
```ts
|
||||
// .storybook/main.ts
|
||||
|
||||
// Replace your-framework with the framework you are using (e.g., react-webpack5, vue3-vite)
|
||||
import type { StorybookConfig } from '@storybook/your-framework';
|
||||
|
||||
const config: StorybookConfig = {
|
||||
framework: '@storybook/your-framework',
|
||||
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
|
||||
features: {
|
||||
storyStoreV7: false, // 👈 Opt out of on-demand story loading
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
```
|
@ -1,6 +1,4 @@
|
||||
```ts
|
||||
// codegen-iframe-script.ts
|
||||
|
||||
import { virtualPreviewFile, virtualStoriesFile } from './virtual-file-names';
|
||||
import { transformAbsPath } from './utils/transform-abs-path';
|
||||
import type { ExtendedOptions } from './types';
|
||||
|
@ -5,9 +5,6 @@ export default {
|
||||
// Replace your-framework with the framework you are using (e.g., react-webpack5, vue3-vite)
|
||||
framework: '@storybook/your-framework',
|
||||
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
|
||||
features: {
|
||||
storyStoreV7: false,
|
||||
},
|
||||
core: {
|
||||
builder: {
|
||||
name: '@storybook/builder-webpack5',
|
||||
|
@ -7,9 +7,6 @@ import type { StorybookConfig } from '@storybook/your-framework';
|
||||
const config: StorybookConfig = {
|
||||
framework: '@storybook/your-framework',
|
||||
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
|
||||
features: {
|
||||
storyStoreV7: false,
|
||||
},
|
||||
core: {
|
||||
builder: {
|
||||
name: '@storybook/builder-webpack5',
|
||||
|
@ -12,5 +12,5 @@
|
||||
"test": "cd code; yarn test",
|
||||
"upload-bench": "cd scripts; yarn upload-bench"
|
||||
},
|
||||
"packageManager": "yarn@4.0.0"
|
||||
"packageManager": "yarn@4.0.2"
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ plugins:
|
||||
unsafeHttpWhitelist:
|
||||
- localhost
|
||||
|
||||
yarnPath: ../.yarn/releases/yarn-4.0.0.cjs
|
||||
yarnPath: ../.yarn/releases/yarn-4.0.2.cjs
|
||||
installStatePath: '../.yarn/scripts-install-state.gz'
|
||||
# Sometimes you get a "The remote archive doesn't match the expected checksum" error, uncommenting this line will fix it
|
||||
# checksumBehavior: 'update'
|
||||
|
@ -61,27 +61,6 @@ async function run() {
|
||||
assert.equal(bootEvent.payload?.eventType, eventType);
|
||||
});
|
||||
|
||||
// Test only StoryStoreV7 projects, as ssv6 does not support the storyIndex
|
||||
if (template.modifications?.mainConfig?.features?.storyStoreV7 !== false) {
|
||||
const { exampleStoryCount, exampleDocsCount } = mainEvent.payload?.storyIndex || {};
|
||||
if (['build', 'dev'].includes(eventType)) {
|
||||
test(`${eventType} event should contain 8 stories and 3 docs entries`, () => {
|
||||
assert.equal(
|
||||
exampleStoryCount,
|
||||
8,
|
||||
`Expected 8 stories but received ${exampleStoryCount} instead.`
|
||||
);
|
||||
const expectedDocsCount =
|
||||
template.modifications?.disableDocs || template.modifications?.testBuild ? 0 : 3;
|
||||
assert.equal(
|
||||
exampleDocsCount,
|
||||
expectedDocsCount,
|
||||
`Expected ${expectedDocsCount} docs entries but received ${exampleDocsCount} instead.`
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
test(`main event should be ${eventType} and contain correct id and session id`, () => {
|
||||
assert.equal(mainEvent.eventType, eventType);
|
||||
assert.notEqual(mainEvent.eventId, bootEvent.eventId);
|
||||
|
@ -199,7 +199,7 @@
|
||||
"verdaccio": "^5.19.1",
|
||||
"verdaccio-auth-memory": "^10.2.0"
|
||||
},
|
||||
"packageManager": "yarn@4.0.0",
|
||||
"packageManager": "yarn@4.0.2",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ export const testRunnerBuild: Task & { port: number } = {
|
||||
async ready() {
|
||||
return false;
|
||||
},
|
||||
async run({ sandboxDir, junitFilename, template }, { dryRun, debug }) {
|
||||
async run({ sandboxDir, junitFilename }, { dryRun, debug }) {
|
||||
const execOptions = { cwd: sandboxDir };
|
||||
const flags = [
|
||||
`--url http://localhost:${this.port}`,
|
||||
@ -20,11 +20,6 @@ export const testRunnerBuild: Task & { port: number } = {
|
||||
'--skipTags="test-skip"',
|
||||
];
|
||||
|
||||
// index-json mode is only supported in ssv7
|
||||
if (template.modifications?.mainConfig?.features?.storyStoreV7 !== false) {
|
||||
flags.push('--index-json');
|
||||
}
|
||||
|
||||
await exec(
|
||||
`yarn test-storybook ${flags.join(' ')}`,
|
||||
{
|
||||
|
@ -45,7 +45,6 @@ module.exports = {
|
||||
staticDirs: ['../ember-output'],
|
||||
features: {
|
||||
buildStoriesJson: false,
|
||||
storyStoreV7: false,
|
||||
},
|
||||
framework: { name: '@storybook/ember' },
|
||||
};
|
||||
|
@ -20,8 +20,6 @@ const config = {
|
||||
channelOptions: { allowFunction: false, maxDepth: 10 },
|
||||
},
|
||||
features: {
|
||||
storyStoreV7: !global.navigator?.userAgent?.match?.('jsdom'),
|
||||
buildStoriesJson: true,
|
||||
warnOnLegacyHierarchySeparator: false,
|
||||
previewMdx2: true,
|
||||
},
|
||||
|
@ -15,9 +15,7 @@ const mainConfig: StorybookConfig = {
|
||||
core: {
|
||||
disableTelemetry: true,
|
||||
},
|
||||
features: {
|
||||
storyStoreV7: false,
|
||||
},
|
||||
features: {},
|
||||
framework: '@storybook/server-webpack5',
|
||||
};
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user