Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -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', () => {
Expand Down Expand Up @@ -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']);
});
});
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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 {
Expand All @@ -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));
Expand All @@ -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),
],
Expand Down
Loading