diff --git a/code/builders/builder-vite/src/plugins/storybook-optimize-deps-plugin.test.ts b/code/builders/builder-vite/src/plugins/storybook-optimize-deps-plugin.test.ts index f42d94a5fe12..b9f1062943ba 100644 --- a/code/builders/builder-vite/src/plugins/storybook-optimize-deps-plugin.test.ts +++ b/code/builders/builder-vite/src/plugins/storybook-optimize-deps-plugin.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; -import { escapeGlobPath } from './storybook-optimize-deps-plugin'; +import { escapeGlobPath, getMockRedirectIncludeEntries } from './storybook-optimize-deps-plugin'; describe('escapeGlobPath', () => { it('should not modify a plain path without special characters', () => { @@ -42,3 +42,35 @@ describe('escapeGlobPath', () => { ); }); }); + +describe('getMockRedirectIncludeEntries', () => { + it('should include only manual mock redirect paths', () => { + expect( + getMockRedirectIncludeEntries([ + { redirectPath: '/project/src/lib/__mocks__/db.ts' }, + { redirectPath: null }, + ]) + ).toEqual(['/project/src/lib/__mocks__/db.ts']); + }); + + it('should escape special glob characters in redirect paths', () => { + expect( + getMockRedirectIncludeEntries([ + { redirectPath: '/project/src/(group)/__mocks__/db.ts' }, + { redirectPath: '/project/src/[id]/__mocks__/db.ts' }, + ]) + ).toEqual([ + '/project/src/\\(group\\)/__mocks__/db.ts', + '/project/src/\\[id\\]/__mocks__/db.ts', + ]); + }); + + it('should dedupe redirect paths', () => { + expect( + getMockRedirectIncludeEntries([ + { redirectPath: '/project/src/lib/__mocks__/db.ts' }, + { redirectPath: '/project/src/lib/__mocks__/db.ts' }, + ]) + ).toEqual(['/project/src/lib/__mocks__/db.ts']); + }); +}); diff --git a/code/builders/builder-vite/src/plugins/storybook-optimize-deps-plugin.ts b/code/builders/builder-vite/src/plugins/storybook-optimize-deps-plugin.ts index e75ab293dff1..cf3e2e8cc3f0 100644 --- a/code/builders/builder-vite/src/plugins/storybook-optimize-deps-plugin.ts +++ b/code/builders/builder-vite/src/plugins/storybook-optimize-deps-plugin.ts @@ -1,5 +1,6 @@ import { loadPreviewOrConfigFile } from 'storybook/internal/common'; import type { StoryIndexGenerator } from 'storybook/internal/core-server'; +import { babelParser, extractMockCalls, findMockRedirect } from 'storybook/internal/mocking-utils'; import type { Options, PreviewAnnotation, StoryIndex } from 'storybook/internal/types'; import { resolve } from 'pathe'; @@ -18,6 +19,20 @@ export function escapeGlobPath(filePath: string): string { return filePath.replace(/[()[\]{}!*?|+@]/g, '\\$&'); } +/** Converts extracted sb.mock calls into optimizeDeps include entries for manual **mocks** files. */ +export function getMockRedirectIncludeEntries( + mockCalls: Array<{ redirectPath: string | null }> +): string[] { + return Array.from( + new Set( + mockCalls + .map((mockCall) => mockCall.redirectPath) + .filter((redirectPath): redirectPath is string => redirectPath !== null) + .map(escapeGlobPath) + ) + ); +} + /** A Vite plugin that configures dependency optimization for Storybook's dev server. */ export function storybookOptimizeDepsPlugin(options: Options): Plugin { return { @@ -41,6 +56,22 @@ export function storybookOptimizeDepsPlugin(options: Options): Plugin { // 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 mockRedirectIncludeEntries = previewOrConfigFile + ? getMockRedirectIncludeEntries( + extractMockCalls( + { + previewConfigPath: previewOrConfigFile, + coreOptions: { disableTelemetry: true }, + configDir: options.configDir, + }, + babelParser, + projectRoot, + findMockRedirect + ) + ) + : []; + const previewAnnotationEntries = [...previewAnnotations, previewOrConfigFile] .filter((path): path is PreviewAnnotation => path !== undefined) .map((path) => processPreviewAnnotation(path, projectRoot)); @@ -56,6 +87,7 @@ export function storybookOptimizeDepsPlugin(options: Options): Plugin { ...(typeof config.optimizeDeps?.entries === 'string' ? [config.optimizeDeps.entries] : (config.optimizeDeps?.entries ?? [])), + ...mockRedirectIncludeEntries, ...getUniqueImportPaths(index).map(escapeGlobPath), ...previewAnnotationEntries.map(escapeGlobPath), ],