diff --git a/.circleci/config.yml b/.circleci/config.yml index b7266a42c698..af3484641b13 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -507,7 +507,7 @@ workflows: requires: - create-sandboxes - chromatic-sandboxes: - parallelism: 9 + parallelism: 11 requires: - build-sandboxes - e2e-production: @@ -563,7 +563,7 @@ workflows: requires: - create-sandboxes - chromatic-sandboxes: - parallelism: 18 + parallelism: 20 requires: - build-sandboxes - e2e-production: @@ -620,7 +620,7 @@ workflows: requires: - create-sandboxes - chromatic-sandboxes: - parallelism: 33 + parallelism: 35 requires: - build-sandboxes - e2e-production: diff --git a/code/.eslintrc.js b/code/.eslintrc.js index e967113bb8eb..35086680644d 100644 --- a/code/.eslintrc.js +++ b/code/.eslintrc.js @@ -68,6 +68,13 @@ module.exports = { '@typescript-eslint/ban-types': 'warn', // should become error, in the future }, }, + { + // this package depends on a lot of peerDependencies we don't want to specify, because npm would install them + files: ['**/builder-vite/**/*.html'], + rules: { + '@typescript-eslint/no-unused-expressions': 'off', // should become error, in the future + }, + }, { // these packages use pre-bundling, dependencies will be bundled, and will be in devDepenencies files: [ diff --git a/code/builders/builder-vite/input/iframe.html b/code/builders/builder-vite/input/iframe.html index 436864580ab6..867a16a4a223 100644 --- a/code/builders/builder-vite/input/iframe.html +++ b/code/builders/builder-vite/input/iframe.html @@ -22,6 +22,8 @@ window.STORIES = '[STORIES HERE]'; window.DOCS_OPTIONS = '[DOCS_OPTIONS HERE]'; + ('OTHER_GLOBLALS HERE'); + // We do this so that "module && module.hot" etc. in Storybook source code // doesn't fail (it will simply be disabled) window.module = undefined; diff --git a/code/builders/builder-vite/src/transform-iframe-html.ts b/code/builders/builder-vite/src/transform-iframe-html.ts index c0bc245e30cc..d3437d5c1460 100644 --- a/code/builders/builder-vite/src/transform-iframe-html.ts +++ b/code/builders/builder-vite/src/transform-iframe-html.ts @@ -5,6 +5,7 @@ export type PreviewHtml = string | undefined; export async function transformIframeHtml(html: string, options: Options) { const { configType, features, presets } = options; + const build = await presets.apply('build'); const frameworkOptions = await presets.apply | null>('frameworkOptions'); const headHtmlSnippet = await presets.apply('previewHead'); const bodyHtmlSnippet = await presets.apply('previewBody'); @@ -20,10 +21,20 @@ export async function transformIframeHtml(html: string, options: Options) { importPathMatcher: specifier.importPathMatcher.source, })); + const otherGlobals = { + ...(build?.test?.emptyBlocks ? { __STORYBOOK_BLOCKS_EMPTY_MODULE__: {} } : {}), + }; + return html .replace('[CONFIG_TYPE HERE]', configType || '') .replace('[LOGLEVEL HERE]', logLevel || '') .replace(`'[FRAMEWORK_OPTIONS HERE]'`, JSON.stringify(frameworkOptions)) + .replace( + `('OTHER_GLOBLALS HERE');`, + Object.entries(otherGlobals) + .map(([k, v]) => `window["${k}"] = ${JSON.stringify(v)};`) + .join('') + ) .replace( `'[CHANNEL_OPTIONS HERE]'`, JSON.stringify(coreOptions && coreOptions.channelOptions ? coreOptions.channelOptions : {}) diff --git a/code/builders/builder-vite/src/vite-config.ts b/code/builders/builder-vite/src/vite-config.ts index 24db49249909..5274248afa80 100644 --- a/code/builders/builder-vite/src/vite-config.ts +++ b/code/builders/builder-vite/src/vite-config.ts @@ -77,6 +77,11 @@ export async function commonConfig( export async function pluginConfig(options: Options) { const frameworkName = await getFrameworkName(options); + const build = await options.presets.apply('build'); + + if (build?.test?.emptyBlocks) { + globals['@storybook/blocks'] = '__STORYBOOK_BLOCKS_EMPTY_MODULE__'; + } const plugins = [ codeGeneratorPlugin(options), diff --git a/code/builders/builder-webpack5/src/preview/iframe-webpack.config.ts b/code/builders/builder-webpack5/src/preview/iframe-webpack.config.ts index 01b294f48fb0..e34cb0d70ef1 100644 --- a/code/builders/builder-webpack5/src/preview/iframe-webpack.config.ts +++ b/code/builders/builder-webpack5/src/preview/iframe-webpack.config.ts @@ -90,6 +90,7 @@ export default async ( entries, nonNormalizedStories, modulesCount = 1000, + build, ] = await Promise.all([ presets.apply('core'), presets.apply('frameworkOptions'), @@ -102,6 +103,7 @@ export default async ( presets.apply('entries', []), presets.apply('stories', []), options.cache?.get('modulesCount').catch(() => {}), + options.presets.apply('build'), ]); const stories = normalizeStories(nonNormalizedStories, { @@ -217,6 +219,10 @@ export default async ( `); } + if (build?.test?.emptyBlocks) { + globals['@storybook/blocks'] = '__STORYBOOK_BLOCKS_EMPTY_MODULE__'; + } + return { name: 'preview', mode: isProd ? 'production' : 'development', @@ -269,6 +275,7 @@ export default async ( importPathMatcher: specifier.importPathMatcher.source, })), DOCS_OPTIONS: docsOptions, + ...(build?.test?.emptyBlocks ? { __STORYBOOK_BLOCKS_EMPTY_MODULE__: {} } : {}), }, headHtmlSnippet, bodyHtmlSnippet, diff --git a/code/frameworks/nextjs/src/preset.ts b/code/frameworks/nextjs/src/preset.ts index db1c276da9b4..fd726835293c 100644 --- a/code/frameworks/nextjs/src/preset.ts +++ b/code/frameworks/nextjs/src/preset.ts @@ -20,7 +20,6 @@ import { configureAliasing } from './dependency-map'; export const addons: PresetProperty<'addons', StorybookConfig> = [ dirname(require.resolve(join('@storybook/preset-react-webpack', 'package.json'))), - dirname(require.resolve(join('@storybook/builder-webpack5', 'package.json'))), ]; const defaultFrameworkOptions: FrameworkOptions = {}; diff --git a/code/lib/cli/src/sandbox-templates.ts b/code/lib/cli/src/sandbox-templates.ts index 2dd4edb620f9..7047dc138e16 100644 --- a/code/lib/cli/src/sandbox-templates.ts +++ b/code/lib/cli/src/sandbox-templates.ts @@ -568,7 +568,7 @@ const benchTemplates = { skipTemplateStories: true, testBuild: true, }, - skipTasks: ['e2e-tests-dev', 'test-runner', 'test-runner-dev', 'e2e-tests', 'chromatic'], + skipTasks: ['e2e-tests-dev', 'test-runner', 'test-runner-dev', 'e2e-tests'], }, 'bench/react-webpack-18-ts-test-build': { ...baseTemplates['react-webpack/18-ts'], @@ -578,7 +578,7 @@ const benchTemplates = { skipTemplateStories: true, testBuild: true, }, - skipTasks: ['e2e-tests-dev', 'test-runner', 'test-runner-dev', 'e2e-tests', 'chromatic'], + skipTasks: ['e2e-tests-dev', 'test-runner', 'test-runner-dev', 'e2e-tests'], }, } satisfies Record; diff --git a/code/lib/core-server/src/presets/common-preset.ts b/code/lib/core-server/src/presets/common-preset.ts index 8d2ac3b7e2d4..6049d81a4f4e 100644 --- a/code/lib/core-server/src/presets/common-preset.ts +++ b/code/lib/core-server/src/presets/common-preset.ts @@ -185,6 +185,10 @@ export const previewAnnotations = async (base: any, options: Options) => { return [...config, ...base]; }; +const testBuildFeatures = (value: boolean) => ({ + emptyBlocks: value, +}); + export const features = async ( existing: StorybookConfig['features'] ): Promise => ({ @@ -196,6 +200,16 @@ export const features = async ( legacyDecoratorFileOrder: false, }); +export const build = async (value: StorybookConfig['build'], options: Options) => { + return { + ...value, + test: { + ...testBuildFeatures(!!options.test), + ...value?.test, + }, + }; +}; + export const csfIndexer: Indexer = { test: /(stories|story)\.(m?js|ts)x?$/, createIndex: async (fileName, options) => (await readCsf(fileName, options)).parse().indexInputs, diff --git a/code/lib/preview/src/globals/types.ts b/code/lib/preview/src/globals/types.ts index 40ed603cd598..176d4582833d 100644 --- a/code/lib/preview/src/globals/types.ts +++ b/code/lib/preview/src/globals/types.ts @@ -1,5 +1,6 @@ // Here we map the name of a module to their NAME in the global scope. -export const globals = { +// eslint-disable-next-line @typescript-eslint/naming-convention,no-underscore-dangle +const _globals = { '@storybook/addons': '__STORYBOOK_MODULE_ADDONS__', '@storybook/global': '__STORYBOOK_MODULE_GLOBAL__', '@storybook/channel-postmessage': '__STORYBOOK_MODULE_CHANNEL_POSTMESSAGE__', // @deprecated: remove in 8.0 @@ -13,3 +14,5 @@ export const globals = { '@storybook/preview-api': '__STORYBOOK_MODULE_PREVIEW_API__', '@storybook/store': '__STORYBOOK_MODULE_STORE__', }; + +export const globals: typeof _globals & Record = _globals; diff --git a/code/lib/types/src/modules/core-common.ts b/code/lib/types/src/modules/core-common.ts index 0f2a9f54c271..69362b192896 100644 --- a/code/lib/types/src/modules/core-common.ts +++ b/code/lib/types/src/modules/core-common.ts @@ -76,6 +76,7 @@ export interface Presets { apply(extension: 'managerEntries', config: [], args?: any): Promise; apply(extension: 'refs', config?: [], args?: any): Promise; apply(extension: 'core', config?: {}, args?: any): Promise; + apply(extension: 'build', config?: {}, args?: any): Promise; apply(extension: string, config?: T, args?: unknown): Promise; } @@ -156,6 +157,7 @@ export interface CLIOptions { quiet?: boolean; versionUpdates?: boolean; docs?: boolean; + test?: boolean; debugWebpack?: boolean; webpackStatsJson?: string | boolean; outputDir?: string; @@ -312,6 +314,15 @@ export interface StorybookConfig { legacyDecoratorFileOrder?: boolean; }; + build?: { + test?: { + /** + * Globalize @storybook/blocks + */ + emptyBlocks?: boolean; + }; + }; + /** * Tells Storybook where to find stories. *