From 8e4e5c543a2f80c11bae034281a9a7df99b513e4 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Fri, 24 Nov 2023 13:54:09 +0100 Subject: [PATCH] Webpack5: Resolve circular dependency and fix HMR --- code/builders/builder-webpack5/package.json | 2 + code/builders/builder-webpack5/src/index.ts | 1 + .../src/preview/iframe-webpack.config.ts | 14 +++---- .../src/preview}/virtual-module-mapping.ts | 37 +++++++++++-------- .../templates/virtualModuleEntry.template.js | 0 .../templates/virtualModuleStory.template.js | 0 code/frameworks/nextjs/package.json | 1 - code/frameworks/nextjs/src/swc/loader.ts | 4 +- code/lib/core-webpack/package.json | 1 - code/lib/core-webpack/src/index.ts | 1 - code/yarn.lock | 4 +- 11 files changed, 34 insertions(+), 31 deletions(-) rename code/{lib/core-webpack/src => builders/builder-webpack5/src/preview}/virtual-module-mapping.ts (79%) rename code/{lib/core-webpack => builders/builder-webpack5}/templates/virtualModuleEntry.template.js (100%) rename code/{lib/core-webpack => builders/builder-webpack5}/templates/virtualModuleStory.template.js (100%) diff --git a/code/builders/builder-webpack5/package.json b/code/builders/builder-webpack5/package.json index 795b63001afa..50f5ef35d6aa 100644 --- a/code/builders/builder-webpack5/package.json +++ b/code/builders/builder-webpack5/package.json @@ -43,6 +43,8 @@ }, "./templates/virtualModuleModernEntry.js.handlebars": "./templates/virtualModuleModernEntry.js.handlebars", "./templates/preview.ejs": "./templates/preview.ejs", + "./templates/virtualModuleEntry.template.js": "./templates/virtualModuleEntry.template.js", + "./templates/virtualModuleStory.template.js": "./templates/virtualModuleStory.template.js", "./package.json": "./package.json" }, "main": "dist/index.js", diff --git a/code/builders/builder-webpack5/src/index.ts b/code/builders/builder-webpack5/src/index.ts index a5624c9816a5..53c4fba32df5 100644 --- a/code/builders/builder-webpack5/src/index.ts +++ b/code/builders/builder-webpack5/src/index.ts @@ -18,6 +18,7 @@ import { import prettyTime from 'pretty-hrtime'; export * from './types'; +export * from './preview/virtual-module-mapping'; export const printDuration = (startTime: [number, number]) => prettyTime(process.hrtime(startTime)) 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 ad8fe95f4b39..f1598a66e5da 100644 --- a/code/builders/builder-webpack5/src/preview/iframe-webpack.config.ts +++ b/code/builders/builder-webpack5/src/preview/iframe-webpack.config.ts @@ -17,11 +17,11 @@ import { normalizeStories, isPreservingSymlinks, } from '@storybook/core-common'; -import type { BuilderOptions } from '@storybook/core-webpack'; -import { getVirtualModuleMapping } from '@storybook/core-webpack'; +import { type BuilderOptions } from '@storybook/core-webpack'; import { dedent } from 'ts-dedent'; import type { TypescriptOptions } from '../types'; import { createBabelLoader, createSWCLoader } from './loaders'; +import { getVirtualModules } from './virtual-module-mapping'; const getAbsolutePath = (input: I): I => dirname(require.resolve(join(input, 'package.json'))) as any; @@ -135,18 +135,16 @@ export default async ( externals['@storybook/blocks'] = '__STORYBOOK_BLOCKS_EMPTY_MODULE__'; } - const virtualModuleMapping = await getVirtualModuleMapping(options); - - Object.keys(virtualModuleMapping).forEach((key) => { - entries.push(key); - }); + const { virtualModules: virtualModuleMapping, entries: dynamicEntries } = await getVirtualModules( + options + ); return { name: 'preview', mode: isProd ? 'production' : 'development', bail: isProd, devtool: options.build?.test?.disableSourcemaps ? false : 'cheap-module-source-map', - entry: entries, + entry: [...(entries ?? []), ...dynamicEntries], output: { path: resolve(process.cwd(), outputDir), filename: isProd ? '[name].[contenthash:8].iframe.bundle.js' : '[name].iframe.bundle.js', diff --git a/code/lib/core-webpack/src/virtual-module-mapping.ts b/code/builders/builder-webpack5/src/preview/virtual-module-mapping.ts similarity index 79% rename from code/lib/core-webpack/src/virtual-module-mapping.ts rename to code/builders/builder-webpack5/src/preview/virtual-module-mapping.ts index 09941ba859a3..c0a3fff0ee72 100644 --- a/code/lib/core-webpack/src/virtual-module-mapping.ts +++ b/code/builders/builder-webpack5/src/preview/virtual-module-mapping.ts @@ -1,5 +1,3 @@ -import type { Options, PreviewAnnotation } from '@storybook/types'; -import { isAbsolute, join, resolve } from 'path'; import { getBuilderOptions, getRendererName, @@ -9,17 +7,19 @@ import { normalizeStories, readTemplate, } from '@storybook/core-common'; +import type { Options, PreviewAnnotation } from '@storybook/types'; +import { isAbsolute, join, resolve } from 'path'; import slash from 'slash'; -import type { BuilderOptions } from './types'; -import { toImportFn } from './to-importFn'; -import { toRequireContextString } from './to-require-context'; +import { toImportFn, toRequireContextString } from '@storybook/core-webpack'; +import type { BuilderOptions } from '../types'; -export const getVirtualModuleMapping = async (options: Options) => { - const virtualModuleMapping: Record = {}; +export const getVirtualModules = async (options: Options) => { + const virtualModules: Record = {}; const builderOptions = await getBuilderOptions(options); const workingDir = process.cwd(); const isProd = options.configType === 'PRODUCTION'; const nonNormalizedStories = await options.presets.apply('stories', []); + const entries = []; const stories = normalizeStories(nonNormalizedStories, { configDir: options.configDir, @@ -53,9 +53,9 @@ export const getVirtualModuleMapping = async (options: Options) => { const storiesPath = resolve(join(workingDir, storiesFilename)); const needPipelinedImport = !!builderOptions.lazyCompilation && !isProd; - virtualModuleMapping[storiesPath] = toImportFn(stories, { needPipelinedImport }); + virtualModules[storiesPath] = toImportFn(stories, { needPipelinedImport }); const configEntryPath = resolve(join(workingDir, 'storybook-config-entry.js')); - virtualModuleMapping[configEntryPath] = handlebars( + virtualModules[configEntryPath] = handlebars( await readTemplate( require.resolve( '@storybook/builder-webpack5/templates/virtualModuleModernEntry.js.handlebars' @@ -67,14 +67,16 @@ export const getVirtualModuleMapping = async (options: Options) => { } // We need to double escape `\` for webpack. We may have some in windows paths ).replace(/\\/g, '\\\\'); + entries.push(configEntryPath); } else { const rendererName = await getRendererName(options); const rendererInitEntry = resolve(join(workingDir, 'storybook-init-renderer-entry.js')); - virtualModuleMapping[rendererInitEntry] = `import '${slash(rendererName)}';`; + virtualModules[rendererInitEntry] = `import '${slash(rendererName)}';`; + entries.push(rendererInitEntry); const entryTemplate = await readTemplate( - join(__dirname, '..', 'templates', 'virtualModuleEntry.template.js') + require.resolve('@storybook/builder-webpack5/templates/virtualModuleEntry.template.js') ); previewAnnotations.forEach((previewAnnotationFilename: string | undefined) => { @@ -87,25 +89,30 @@ export const getVirtualModuleMapping = async (options: Options) => { : `${previewAnnotationFilename}-generated-config-entry.js`; // NOTE: although this file is also from the `dist/cjs` directory, it is actually a ESM // file, see https://github.com/storybookjs/storybook/pull/16727#issuecomment-986485173 - virtualModuleMapping[entryFilename] = interpolate(entryTemplate, { + virtualModules[entryFilename] = interpolate(entryTemplate, { previewAnnotationFilename, }); + entries.push(entryFilename); }); if (stories.length > 0) { const storyTemplate = await readTemplate( - join(__dirname, '..', 'templates', 'virtualModuleStory.template.js') + require.resolve('@storybook/builder-webpack5/templates/virtualModuleStory.template.js') ); // NOTE: this file has a `.cjs` extension as it is a CJS file (from `dist/cjs`) and runs // in the user's webpack mode, which may be strict about the use of require/import. // See https://github.com/storybookjs/storybook/issues/14877 const storiesFilename = resolve(join(workingDir, `generated-stories-entry.cjs`)); - virtualModuleMapping[storiesFilename] = interpolate(storyTemplate, { + virtualModules[storiesFilename] = interpolate(storyTemplate, { rendererName, }) // Make sure we also replace quotes for this one .replace("'{{stories}}'", stories.map(toRequireContextString).join(',')); + entries.push(storiesFilename); } } - return virtualModuleMapping; + return { + virtualModules, + entries, + }; }; diff --git a/code/lib/core-webpack/templates/virtualModuleEntry.template.js b/code/builders/builder-webpack5/templates/virtualModuleEntry.template.js similarity index 100% rename from code/lib/core-webpack/templates/virtualModuleEntry.template.js rename to code/builders/builder-webpack5/templates/virtualModuleEntry.template.js diff --git a/code/lib/core-webpack/templates/virtualModuleStory.template.js b/code/builders/builder-webpack5/templates/virtualModuleStory.template.js similarity index 100% rename from code/lib/core-webpack/templates/virtualModuleStory.template.js rename to code/builders/builder-webpack5/templates/virtualModuleStory.template.js diff --git a/code/frameworks/nextjs/package.json b/code/frameworks/nextjs/package.json index 9c7dccf742ad..791f1bd92126 100644 --- a/code/frameworks/nextjs/package.json +++ b/code/frameworks/nextjs/package.json @@ -92,7 +92,6 @@ "@storybook/builder-webpack5": "workspace:*", "@storybook/core-common": "workspace:*", "@storybook/core-events": "workspace:*", - "@storybook/core-webpack": "workspace:*", "@storybook/node-logger": "workspace:*", "@storybook/preset-react-webpack": "workspace:*", "@storybook/preview-api": "workspace:*", diff --git a/code/frameworks/nextjs/src/swc/loader.ts b/code/frameworks/nextjs/src/swc/loader.ts index 08bb292efae4..099914e1be85 100644 --- a/code/frameworks/nextjs/src/swc/loader.ts +++ b/code/frameworks/nextjs/src/swc/loader.ts @@ -1,5 +1,5 @@ import { getProjectRoot } from '@storybook/core-common'; -import { getVirtualModuleMapping } from '@storybook/core-webpack'; +import { getVirtualModules } from '@storybook/builder-webpack5'; import type { Options, Preset } from '@storybook/types'; import type { NextConfig } from 'next'; import path from 'path'; @@ -29,7 +29,7 @@ export const configureSWCLoader = async ( const dir = getProjectRoot(); - const virtualModules = await getVirtualModuleMapping(options); + const { virtualModules } = await getVirtualModules(options); baseConfig.module.rules = [ // TODO: Remove filtering in Storybook 8.0 diff --git a/code/lib/core-webpack/package.json b/code/lib/core-webpack/package.json index 4d1b8b3e83fc..bdd546d988a0 100644 --- a/code/lib/core-webpack/package.json +++ b/code/lib/core-webpack/package.json @@ -51,7 +51,6 @@ "ts-dedent": "^2.0.0" }, "devDependencies": { - "slash": "^5.1.0", "typescript": "~4.9.3", "webpack": "5" }, diff --git a/code/lib/core-webpack/src/index.ts b/code/lib/core-webpack/src/index.ts index 562860cbe1a6..370187367538 100644 --- a/code/lib/core-webpack/src/index.ts +++ b/code/lib/core-webpack/src/index.ts @@ -4,4 +4,3 @@ export * from './check-webpack-version'; export * from './merge-webpack-config'; export * from './to-importFn'; export * from './to-require-context'; -export * from './virtual-module-mapping'; diff --git a/code/yarn.lock b/code/yarn.lock index 88ea95a5e552..be5721edec43 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -6490,7 +6490,6 @@ __metadata: "@storybook/node-logger": "workspace:*" "@storybook/types": "workspace:*" "@types/node": "npm:^18.0.0" - slash: "npm:^5.1.0" ts-dedent: "npm:^2.0.0" typescript: "npm:~4.9.3" webpack: "npm:5" @@ -6858,7 +6857,6 @@ __metadata: "@storybook/builder-webpack5": "workspace:*" "@storybook/core-common": "workspace:*" "@storybook/core-events": "workspace:*" - "@storybook/core-webpack": "workspace:*" "@storybook/node-logger": "workspace:*" "@storybook/preset-react-webpack": "workspace:*" "@storybook/preview-api": "workspace:*" @@ -27444,7 +27442,7 @@ __metadata: languageName: node linkType: hard -"slash@npm:^5.0.0, slash@npm:^5.1.0": +"slash@npm:^5.0.0": version: 5.1.0 resolution: "slash@npm:5.1.0" checksum: eb48b815caf0bdc390d0519d41b9e0556a14380f6799c72ba35caf03544d501d18befdeeef074bc9c052acf69654bc9e0d79d7f1de0866284137a40805299eb3