diff --git a/code/builders/builder-vite/src/codegen-modern-iframe-script.ts b/code/builders/builder-vite/src/codegen-modern-iframe-script.ts index 2de92617befa..7ad50afec12f 100644 --- a/code/builders/builder-vite/src/codegen-modern-iframe-script.ts +++ b/code/builders/builder-vite/src/codegen-modern-iframe-script.ts @@ -2,7 +2,7 @@ import { getFrameworkName, loadPreviewOrConfigFile } from 'storybook/internal/co import type { Options, PreviewAnnotation } from 'storybook/internal/types'; import { processPreviewAnnotation } from './utils/process-preview-annotation'; -import { virtualAddonSetupFile, virtualStoriesFile } from './virtual-file-names'; +import { SB_VIRTUAL_FILES, getResolvedVirtualModuleId } from './virtual-file-names'; export async function generateModernIframeScriptCode(options: Options, projectRoot: string) { const { presets, configDir } = options; @@ -45,7 +45,7 @@ export async function generateModernIframeScriptCode(options: Options, projectRo return ` if (import.meta.hot) { - import.meta.hot.accept('${virtualStoriesFile}', (newModule) => { + import.meta.hot.accept('${getResolvedVirtualModuleId(SB_VIRTUAL_FILES.VIRTUAL_STORIES_FILE)}', (newModule) => { // importFn has changed so we need to patch the new one in window.__STORYBOOK_PREVIEW__.onStoriesChanged({ importFn: newModule.importFn }); }); @@ -68,8 +68,8 @@ export async function generateModernIframeScriptCode(options: Options, projectRo */ const code = ` import { composeConfigs, PreviewWeb, ClientApi } from 'storybook/internal/preview-api'; - import '${virtualAddonSetupFile}'; - import { importFn } from '${virtualStoriesFile}'; + import '${SB_VIRTUAL_FILES.VIRTUAL_ADDON_SETUP_FILE}'; + import { importFn } from '${SB_VIRTUAL_FILES.VIRTUAL_STORIES_FILE}'; ${getPreviewAnnotationsFunction} diff --git a/code/builders/builder-vite/src/plugins/code-generator-plugin.ts b/code/builders/builder-vite/src/plugins/code-generator-plugin.ts index 89f6430f7584..77509b97cf93 100644 --- a/code/builders/builder-vite/src/plugins/code-generator-plugin.ts +++ b/code/builders/builder-vite/src/plugins/code-generator-plugin.ts @@ -8,12 +8,7 @@ import { generateImportFnScriptCode } from '../codegen-importfn-script'; import { generateModernIframeScriptCode } from '../codegen-modern-iframe-script'; import { generateAddonSetupCode } from '../codegen-set-addon-channel'; import { transformIframeHtml } from '../transform-iframe-html'; -import { - virtualAddonSetupFile, - virtualFileId, - virtualPreviewFile, - virtualStoriesFile, -} from '../virtual-file-names'; +import { SB_VIRTUAL_FILES, getResolvedVirtualModuleId } from '../virtual-file-names'; export function codeGeneratorPlugin(options: Options): Plugin { const iframePath = require.resolve('@storybook/builder-vite/input/iframe.html'); @@ -28,11 +23,15 @@ export function codeGeneratorPlugin(options: Options): Plugin { // invalidate the whole vite-app.js script on every file change. // (this might be a little too aggressive?) server.watcher.on('change', () => { - const appModule = server.moduleGraph.getModuleById(virtualFileId); + const appModule = server.moduleGraph.getModuleById( + getResolvedVirtualModuleId(SB_VIRTUAL_FILES.VIRTUAL_APP_FILE) + ); if (appModule) { server.moduleGraph.invalidateModule(appModule); } - const storiesModule = server.moduleGraph.getModuleById(virtualStoriesFile); + const storiesModule = server.moduleGraph.getModuleById( + getResolvedVirtualModuleId(SB_VIRTUAL_FILES.VIRTUAL_STORIES_FILE) + ); if (storiesModule) { server.moduleGraph.invalidateModule(storiesModule); } @@ -45,7 +44,7 @@ export function codeGeneratorPlugin(options: Options): Plugin { // TODO maybe use the stories declaration in main if (/\.stories\.([tj])sx?$/.test(path) || /\.mdx$/.test(path)) { // We need to emit a change event to trigger HMR - server.watcher.emit('change', virtualStoriesFile); + server.watcher.emit('change', SB_VIRTUAL_FILES.VIRTUAL_STORIES_FILE); } }); }, @@ -69,34 +68,34 @@ export function codeGeneratorPlugin(options: Options): Plugin { iframeId = `${config.root}/iframe.html`; }, resolveId(source) { - if (source === virtualFileId) { - return `${virtualFileId}`; + if (source === SB_VIRTUAL_FILES.VIRTUAL_APP_FILE) { + return getResolvedVirtualModuleId(SB_VIRTUAL_FILES.VIRTUAL_APP_FILE); } if (source === iframePath) { return iframeId; } - if (source === virtualStoriesFile) { - return `${virtualStoriesFile}`; + if (source === SB_VIRTUAL_FILES.VIRTUAL_STORIES_FILE) { + return getResolvedVirtualModuleId(SB_VIRTUAL_FILES.VIRTUAL_STORIES_FILE); } - if (source === virtualPreviewFile) { - return virtualPreviewFile; + if (source === SB_VIRTUAL_FILES.VIRTUAL_PREVIEW_FILE) { + return getResolvedVirtualModuleId(SB_VIRTUAL_FILES.VIRTUAL_PREVIEW_FILE); } - if (source === virtualAddonSetupFile) { - return `${virtualAddonSetupFile}`; + if (source === SB_VIRTUAL_FILES.VIRTUAL_ADDON_SETUP_FILE) { + return getResolvedVirtualModuleId(SB_VIRTUAL_FILES.VIRTUAL_ADDON_SETUP_FILE); } return undefined; }, async load(id, config) { - if (id === `${virtualStoriesFile}`) { + if (id === getResolvedVirtualModuleId(SB_VIRTUAL_FILES.VIRTUAL_STORIES_FILE)) { return generateImportFnScriptCode(options); } - if (id === `${virtualAddonSetupFile}`) { + if (id === getResolvedVirtualModuleId(SB_VIRTUAL_FILES.VIRTUAL_ADDON_SETUP_FILE)) { return generateAddonSetupCode(); } - if (id === `${virtualFileId}`) { + if (id === getResolvedVirtualModuleId(SB_VIRTUAL_FILES.VIRTUAL_APP_FILE)) { return generateModernIframeScriptCode(options, projectRoot); } diff --git a/code/builders/builder-vite/src/plugins/external-globals-plugin.test.ts b/code/builders/builder-vite/src/plugins/external-globals-plugin.test.ts index 9555a4dd6b86..67b62690da43 100644 --- a/code/builders/builder-vite/src/plugins/external-globals-plugin.test.ts +++ b/code/builders/builder-vite/src/plugins/external-globals-plugin.test.ts @@ -12,6 +12,22 @@ const cases = [ input: `import { Rain, Jour as Day, Nuit as Night, Sun } from "${packageName}"`, output: `const { Rain, Jour: Day, Nuit: Night, Sun } = ${globals[packageName]}`, }, + { + globals, + packageName, + input: `import { + Rain, + Jour as Day, + Nuit as Night, + Sun + } from "${packageName}"`, + output: `const { + Rain, + Jour: Day, + Nuit: Night, + Sun + } = ${globals[packageName]}`, + }, { globals, packageName, diff --git a/code/builders/builder-vite/src/plugins/external-globals-plugin.ts b/code/builders/builder-vite/src/plugins/external-globals-plugin.ts index 1c7ada46b7be..a71f9f3b0fe8 100644 --- a/code/builders/builder-vite/src/plugins/external-globals-plugin.ts +++ b/code/builders/builder-vite/src/plugins/external-globals-plugin.ts @@ -91,10 +91,7 @@ export async function externalGlobalsPlugin(externals: Record) { return { code: src.toString(), - map: src.generateMap({ - source: id, - hires: true, - }), + map: null, }; }, } satisfies Plugin; diff --git a/code/builders/builder-vite/src/plugins/webpack-stats-plugin.ts b/code/builders/builder-vite/src/plugins/webpack-stats-plugin.ts index 3b8deebc8111..7e6f596e022f 100644 --- a/code/builders/builder-vite/src/plugins/webpack-stats-plugin.ts +++ b/code/builders/builder-vite/src/plugins/webpack-stats-plugin.ts @@ -6,6 +6,12 @@ import type { BuilderStats } from 'storybook/internal/types'; import slash from 'slash'; import type { Plugin } from 'vite'; +import { + SB_VIRTUAL_FILES, + getOriginalVirtualModuleId, + getResolvedVirtualModuleId, +} from '../virtual-file-names'; + /* * Reason, Module are copied from chromatic types * https://github.com/chromaui/chromatic-cli/blob/145a5e295dde21042e96396c7e004f250d842182/bin-src/types.ts#L265-L276 @@ -34,11 +40,18 @@ function stripQueryParams(filePath: string): string { /** We only care about user code, not node_modules, vite files, or (most) virtual files. */ function isUserCode(moduleName: string) { + if (!moduleName) { + return false; + } + + // keep Storybook's virtual files because they import the story files, so they are essential to the module graph + if (Object.values(SB_VIRTUAL_FILES).includes(getOriginalVirtualModuleId(moduleName))) { + return true; + } + return Boolean( - moduleName && - !moduleName.startsWith('vite/') && - !moduleName.startsWith('\x00') && - !moduleName.startsWith('\u0000') && + !moduleName.startsWith('vite/') && + !moduleName.startsWith('\0') && moduleName !== 'react/jsx-runtime' && !moduleName.match(/node_modules\//) ); @@ -53,6 +66,14 @@ export function pluginWebpackStats({ workingDir }: WebpackStatsPluginOptions): W if (filename.startsWith('/virtual:')) { return filename; } + // ! Maintain backwards compatibility with the old virtual file names + // ! to ensure that the stats file doesn't change between the versions + // ! Turbosnap is also only compatible with the old virtual file names + // ! the old virtual file names did not start with the obligatory \0 character + if (Object.values(SB_VIRTUAL_FILES).includes(getOriginalVirtualModuleId(filename))) { + return getOriginalVirtualModuleId(filename); + } + // Otherwise, we need them in the format `./path/to/file.js`. else { const relativePath = relative(workingDir, stripQueryParams(filename)); @@ -82,25 +103,27 @@ export function pluginWebpackStats({ workingDir }: WebpackStatsPluginOptions): W // We want this to run after the vite build plugins (https://vitejs.dev/guide/api-plugin.html#plugin-ordering) enforce: 'post', moduleParsed: function (mod) { - if (isUserCode(mod.id)) { - mod.importedIds - .concat(mod.dynamicallyImportedIds) - .filter((name) => isUserCode(name)) - .forEach((depIdUnsafe) => { - const depId = normalize(depIdUnsafe); - if (statsMap.has(depId)) { - const m = statsMap.get(depId); - if (m) { - m.reasons = (m.reasons ?? []) - .concat(createReasons([mod.id])) - .filter((r) => r.moduleName !== depId); - statsMap.set(depId, m); - } - } else { - statsMap.set(depId, createStatsMapModule(depId, [mod.id])); - } - }); + if (!isUserCode(mod.id)) { + return; } + mod.importedIds + .concat(mod.dynamicallyImportedIds) + .filter((name) => isUserCode(name)) + .forEach((depIdUnsafe) => { + const depId = normalize(depIdUnsafe); + if (!statsMap.has(depId)) { + statsMap.set(depId, createStatsMapModule(depId, [mod.id])); + return; + } + const m = statsMap.get(depId); + if (!m) { + return; + } + m.reasons = (m.reasons ?? []) + .concat(createReasons([mod.id])) + .filter((r) => r.moduleName !== depId); + statsMap.set(depId, m); + }); }, storybookGetStats() { diff --git a/code/builders/builder-vite/src/virtual-file-names.ts b/code/builders/builder-vite/src/virtual-file-names.ts index 0da0c5517dec..c38cb7322db6 100644 --- a/code/builders/builder-vite/src/virtual-file-names.ts +++ b/code/builders/builder-vite/src/virtual-file-names.ts @@ -1,4 +1,14 @@ -export const virtualFileId = '/virtual:/@storybook/builder-vite/vite-app.js'; -export const virtualStoriesFile = '/virtual:/@storybook/builder-vite/storybook-stories.js'; -export const virtualPreviewFile = '/virtual:/@storybook/builder-vite/preview-entry.js'; -export const virtualAddonSetupFile = '/virtual:/@storybook/builder-vite/setup-addons.js'; +export const SB_VIRTUAL_FILES = { + VIRTUAL_APP_FILE: '/virtual:/@storybook/builder-vite/vite-app.js', + VIRTUAL_STORIES_FILE: '/virtual:/@storybook/builder-vite/storybook-stories.js', + VIRTUAL_PREVIEW_FILE: '/virtual:/@storybook/builder-vite/preview-entry.js', + VIRTUAL_ADDON_SETUP_FILE: '/virtual:/@storybook/builder-vite/setup-addons.js', +}; + +export function getResolvedVirtualModuleId(virtualModuleId: string) { + return `\0${virtualModuleId}`; +} + +export function getOriginalVirtualModuleId(resolvedVirtualModuleId: string) { + return resolvedVirtualModuleId.slice(1); +} diff --git a/code/frameworks/vue3-vite/src/plugins/vue-component-meta.ts b/code/frameworks/vue3-vite/src/plugins/vue-component-meta.ts index bb06a958a771..036ab18945ae 100644 --- a/code/frameworks/vue3-vite/src/plugins/vue-component-meta.ts +++ b/code/frameworks/vue3-vite/src/plugins/vue-component-meta.ts @@ -26,7 +26,7 @@ export async function vueComponentMeta(tsconfigPath = 'tsconfig.json'): Promise< // exclude stories, virtual modules and storybook internals const exclude = - /\.stories\.(ts|tsx|js|jsx)$|^\/virtual:|^\/sb-preview\/|\.storybook\/.*\.(ts|js)$/; + /\.stories\.(ts|tsx|js|jsx)$|^\0\/virtual:|^\/virtual:|^\/sb-preview\/|\.storybook\/.*\.(ts|js)$/; const include = /\.(vue|ts|js|tsx|jsx)$/; const filter = createFilter(include, exclude);