Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
ce88eed
Webpack: Improve performance of module-mocking plugins
valentinpalkovic Nov 25, 2025
b08bd00
Merge branch 'next' into valentin/fix-hmr-for-webpack-users
valentinpalkovic Nov 26, 2025
5c0c277
Prevent duplicate asset emission and avoid rebuild loops in Webpack c…
valentinpalkovic Nov 26, 2025
180015d
Revert
valentinpalkovic Nov 27, 2025
a2eea61
Merge remote-tracking branch 'origin/next' into valentin/fix-hmr-for-…
valentinpalkovic Jan 21, 2026
f59138d
Revert "Revert"
valentinpalkovic Jan 21, 2026
7b0a781
Revert
valentinpalkovic Jan 21, 2026
8cabf98
Remove comment
valentinpalkovic Jan 21, 2026
5d31c5e
Only log mock message when necessary
valentinpalkovic Jan 21, 2026
a1fe2b7
Merge branch 'next' into valentin/fix-hmr-for-webpack-users
valentinpalkovic Jan 22, 2026
a0e3f49
Merge branch 'next' into valentin/fix-hmr-for-webpack-users
valentinpalkovic Feb 3, 2026
d7d669f
Revamp Vite dep optimization: add preview annotations as entries, rem…
Copilot Feb 19, 2026
9537a3e
Optimize deps for react and builder-vite
valentinpalkovic Feb 19, 2026
13849a6
Add regression tests for virtual module imports and dynamic import op…
Copilot Feb 19, 2026
0e11b0e
Revert
valentinpalkovic Feb 20, 2026
e96bd64
fix optimized deps for preact and docs and enable vitest on more sand…
yannbf Feb 20, 2026
f98bb1d
Add '@storybook/react-dom-shim' to optimizeViteDeps for improved depe…
yannbf Feb 20, 2026
c92f3f4
Remove unnecessary test
valentinpalkovic Feb 20, 2026
086dbce
Addon-Vitest: Fix postinstall a11y installation
valentinpalkovic Feb 20, 2026
dd04e13
Fix #33845: make React Native Web Page story template consistent with…
Feb 21, 2026
815e9d1
Merge branch 'next' into copilot/revamp-vite-vitest-optimization-again
valentinpalkovic Feb 23, 2026
0d8b963
Revert "Revert"
yannbf Feb 23, 2026
d09605b
add server config for linked mode sandboxes
yannbf Feb 23, 2026
12fa278
fix formatting
yannbf Feb 23, 2026
2d1cd26
fix link override
yannbf Feb 23, 2026
681bfa2
fix and improve mcp addon e2e test
yannbf Feb 23, 2026
3487711
Add optimizeDeps configuration for react-dom/client in viteFinal
valentinpalkovic Feb 23, 2026
56f9268
fix preact optimization issue
yannbf Feb 23, 2026
f7649ed
Merge pull request #33888 from storybookjs/valentin/fix-addon-vitest-…
valentinpalkovic Feb 23, 2026
da5a88b
fix react deps optimization
yannbf Feb 23, 2026
39ccbc4
Merge branch 'next' into copilot/revamp-vite-vitest-optimization-again
yannbf Feb 23, 2026
ca461fa
fix flake
yannbf Feb 23, 2026
20bf512
Merge pull request #33169 from storybookjs/valentin/fix-hmr-for-webpa…
valentinpalkovic Feb 23, 2026
d869970
Merge branch 'next' into fix/issue-33845
jonniebigodes Feb 23, 2026
d4cba11
Update CHANGELOG.md for v10.2.11 [skip ci]
storybook-bot Feb 23, 2026
9ac3db5
Merge branch 'next' into fix/issue-33845
jonniebigodes Feb 23, 2026
5a2c607
Merge pull request #33891 from danielalanbates/fix/issue-33845
jonniebigodes Feb 23, 2026
4556340
Merge pull request #33875 from storybookjs/copilot/revamp-vite-vitest…
valentinpalkovic Feb 24, 2026
8011372
Write changelog for 10.3.0-alpha.10 [skip ci]
storybook-bot Feb 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## 10.2.11

- Addon-Vitest: Fix postinstall a11y installation - [#33888](https://github.com/storybookjs/storybook/pull/33888), thanks @valentinpalkovic!
- Manifests: Use correct story name - [#33709](https://github.com/storybookjs/storybook/pull/33709), thanks @JReinhold!
- Next.js: Handle legacyBehavior prop in Link mock component - [#33862](https://github.com/storybookjs/storybook/pull/33862), thanks @yatishgoel!
- React: Fix manifest stories empty when meta has no explicit title - [#33878](https://github.com/storybookjs/storybook/pull/33878), thanks @kasperpeulen!

## 10.2.10

- Core: Require token for websocket connections - [#33820](https://github.com/storybookjs/storybook/pull/33820), thanks @ghengeveld!
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.prerelease.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## 10.3.0-alpha.10

- Addon-Vitest: Fix postinstall a11y installation - [#33888](https://github.com/storybookjs/storybook/pull/33888), thanks @valentinpalkovic!
- Builder-Vite: Use preview annotations as entry points for optimizeDeps - [#33875](https://github.com/storybookjs/storybook/pull/33875), thanks @copilot-swe-agent!
- React Native Web: Fix inconsistent example stories - [#33891](https://github.com/storybookjs/storybook/pull/33891), thanks @danielalanbates!
- Webpack: Improve performance of module-mocking plugins - [#33169](https://github.com/storybookjs/storybook/pull/33169), thanks @valentinpalkovic!

## 10.3.0-alpha.9

- React: Add react-docgen-typescript to component manifest - [#33818](https://github.com/storybookjs/storybook/pull/33818), thanks @kasperpeulen!
Expand Down
3 changes: 3 additions & 0 deletions code/addons/docs/src/preset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,9 @@ const optimizeViteDeps = [
'@storybook/addon-docs',
'@storybook/addon-docs/blocks',
'@storybook/addon-docs > @mdx-js/react',
'@storybook/addon-docs > @storybook/react-dom-shim',
'react-dom/client',
'react/jsx-runtime',
];

export { webpackX as webpack, docsX as docs, optimizeViteDeps };
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { describe, expect, it } from 'vitest';

import { generateModernIframeScriptCodeFromPreviews } from './codegen-modern-iframe-script';
import { generateAddonSetupCode } from './codegen-set-addon-channel';
import { optimizeViteDeps } from './preset';

describe('generateModernIframeScriptCodeFromPreviews', () => {
it('handle one annotation', async () => {
Expand Down Expand Up @@ -131,3 +133,60 @@ describe('generateModernIframeScriptCodeFromPreviews', () => {
`);
});
});

/**
* Extract bare package import specifiers from a block of generated JavaScript/TypeScript code.
* Captures both `import ... from 'pkg'` and `import 'pkg'` forms, excluding:
*
* - Relative paths (start with `.`)
* - Virtual module IDs (start with `virtual:`)
* - Absolute paths (start with `/`)
*/
function extractPackageImports(code: string): string[] {
const importRegex = /import\s+(?:[^'"]*\s+from\s+)?['"]([^'"]+)['"]/g;
const specifiers = new Set<string>();
for (const match of code.matchAll(importRegex)) {
const specifier = match[1];
if (
!specifier.startsWith('.') &&
!specifier.startsWith('virtual:') &&
!specifier.startsWith('/')
) {
specifiers.add(specifier);
}
}
return [...specifiers];
}

describe('optimizeDeps coverage for virtual module imports', () => {
it('every package imported in virtual module code is either in optimizeViteDeps or known to be discovered via entry crawling', async () => {
// Collect all code generated for virtual modules — Vite's dep scanner never sees
// the contents of virtual modules, so any package imported there must be
// pre-bundled explicitly via optimizeViteDeps.
const iframeCode = await generateModernIframeScriptCodeFromPreviews({ frameworkName: 'test' });
const addonCode = await generateAddonSetupCode();
const allVirtualModuleCode = [iframeCode, addonCode].join('\n');

const packageImports = extractPackageImports(allVirtualModuleCode);

// These packages are also imported in real source files (preview annotations, renderer
// previews, addon previews) that ARE added as optimizeDeps entries, so Vite discovers
// them via entry crawling. No explicit optimizeViteDeps entry is required.
const discoveredViaEntries = new Set([
'storybook/preview-api', // Imported in many renderer/addon preview files
'storybook/internal/channels', // Imported in addon preview files
]);

const notCovered = packageImports.filter(
(pkg) => !discoveredViaEntries.has(pkg) && !optimizeViteDeps.includes(pkg)
);

expect(
notCovered,
`The following packages are imported in virtual module code but are NOT covered.\n` +
`They must be added to optimizeViteDeps in builder-vite/src/preset.ts, OR added to\n` +
`the discoveredViaEntries set in this test if they appear in real source entry files.\n` +
`Uncovered: ${notCovered.join(', ')}`
).toHaveLength(0);
});
});
118 changes: 0 additions & 118 deletions code/builders/builder-vite/src/constants.ts

This file was deleted.

30 changes: 0 additions & 30 deletions code/builders/builder-vite/src/optimizeDeps.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { loadPreviewOrConfigFile } from 'storybook/internal/common';
import type { StoryIndexGenerator } from 'storybook/internal/core-server';
import type { Options, StoryIndex } from 'storybook/internal/types';
import type { Options, PreviewAnnotation, StoryIndex } from 'storybook/internal/types';

import { resolve } from 'pathe';
import { type Plugin } from 'vite';

import { processPreviewAnnotation } from '../utils/process-preview-annotation';
import { getUniqueImportPaths } from '../utils/unique-import-paths';

/** A Vite plugin that configures dependency optimization for Storybook's dev server. */
Expand All @@ -15,25 +18,37 @@ export function storybookOptimizeDepsPlugin(options: Options): Plugin {
return;
}

const [extraOptimizeDeps, storyIndexGenerator] = await Promise.all([
options.presets.apply('optimizeViteDeps', []),
const projectRoot = resolve(options.configDir, '..');

const [extraOptimizeDeps, storyIndexGenerator, previewAnnotations] = await Promise.all([
options.presets.apply<string[]>('optimizeViteDeps', []),
options.presets.apply<StoryIndexGenerator>('storyIndexGenerator'),
options.presets.apply<PreviewAnnotation[]>('previewAnnotations', [], options),
]);

const index: StoryIndex = await storyIndexGenerator.getIndex();

// Include the user's preview file and all addon/framework/renderer preview annotations
// as optimizer entries so Vite can discover all transitive CJS dependencies automatically.
const previewOrConfigFile = loadPreviewOrConfigFile({ configDir: options.configDir });
const previewAnnotationEntries = [...previewAnnotations, previewOrConfigFile]
.filter((path): path is PreviewAnnotation => path !== undefined)
.map((path) => processPreviewAnnotation(path, projectRoot));

return {
optimizeDeps: {
// Story file paths as entry points for the optimizer
// Story files + preview annotation files as entry points for the dep optimizer.
// Vite will crawl these to discover all transitive CJS dependencies that need
// pre-bundling, removing the need for a hard-coded include list.
entries: [
...(typeof config.optimizeDeps?.entries === 'string'
? [config.optimizeDeps.entries]
: []),
: (config.optimizeDeps?.entries ?? [])),
...getUniqueImportPaths(index),
...previewAnnotationEntries,
],
// Known CJS dependencies that need to be pre-compiled to ESM,
// plus any extra deps from Storybook presets.
include: [...extraOptimizeDeps, ...(config.optimizeDeps?.include || [])],
// Extra deps explicitly included by Storybook presets (e.g. framework-specific packages).
include: [...extraOptimizeDeps, ...(config.optimizeDeps?.include ?? [])],
},
};
},
Expand Down
2 changes: 2 additions & 0 deletions code/builders/builder-vite/src/preset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { storybookSanitizeEnvs } from './plugins/storybook-runtime-plugin';
import { viteInjectMockerRuntime } from './plugins/vite-inject-mocker/plugin';
import { viteMockPlugin } from './plugins/vite-mock/plugin';

export const optimizeViteDeps: string[] = ['storybook/internal/preview/runtime'];

/**
* Preset that provides the core Storybook Vite plugins shared between `@storybook/builder-vite` and
* `@storybook/addon-vitest`.
Expand Down
8 changes: 0 additions & 8 deletions code/builders/builder-vite/src/vite-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,15 @@ import { dedent } from 'ts-dedent';
import type { InlineConfig, ServerOptions } from 'vite';

import { createViteLogger } from './logger';
import { getOptimizeDeps } from './optimizeDeps';
import { commonConfig } from './vite-config';

export async function createViteServer(options: Options, devServer: Server) {
const { presets } = options;

const commonCfg = await commonConfig(options, 'development');

const optimizeDeps = await getOptimizeDeps(commonCfg);

const config: InlineConfig & { server: ServerOptions } = {
...commonCfg,
// Set up dev server
optimizeDeps: {
...commonCfg.optimizeDeps,
include: [...(commonCfg.optimizeDeps?.include || []), ...optimizeDeps.include],
},
server: {
middlewareMode: true,
hmr: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const PLUGIN_NAME = 'WebpackInjectMockerRuntimePlugin';
* Storybook preview bundle, are executed.
*/
export class WebpackInjectMockerRuntimePlugin {
private cachedRuntime: string | null = null;
// We need to lazy-require HtmlWebpackPlugin because it's an optional peer dependency.
private getHtmlWebpackPlugin(compiler: Compiler): typeof HtmlWebpackPlugin | null {
try {
Expand Down Expand Up @@ -52,20 +53,23 @@ export class WebpackInjectMockerRuntimePlugin {
PLUGIN_NAME,
(data, cb) => {
try {
const runtimeScriptContent = getMockerRuntime();
const runtimeScriptContent =
this.cachedRuntime ?? (this.cachedRuntime = getMockerRuntime());
const runtimeAssetName = 'mocker-runtime-injected.js';

// Use the documented `emitAsset` method to add the pre-bundled runtime script
// to the compilation's assets. This is the standard Webpack way.
compilation.emitAsset(
runtimeAssetName,
new compiler.webpack.sources.RawSource(runtimeScriptContent)
);
if (!compilation.getAsset(runtimeAssetName)) {
compilation.emitAsset(
runtimeAssetName,
new compiler.webpack.sources.RawSource(runtimeScriptContent)
);
data.assets.js.unshift(runtimeAssetName);
}

// Prepend the name of our new asset to the list of JavaScript files.
// Prepend the name of our new asset to the list of JavaScript files, once.
// HtmlWebpackPlugin will automatically create a <script> tag for it
// and place it at the beginning of the body scripts.
data.assets.js.unshift(runtimeAssetName);
cb(null, data);
} catch (error) {
// In case of an error (e.g., file not found), pass it to Webpack's compilation.
Expand Down
Loading
Loading