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 3cea0d84fc7a..ad8fe95f4b39 100644 --- a/code/builders/builder-webpack5/src/preview/iframe-webpack.config.ts +++ b/code/builders/builder-webpack5/src/preview/iframe-webpack.config.ts @@ -1,4 +1,4 @@ -import { dirname, isAbsolute, join, resolve } from 'path'; +import { dirname, join, resolve } from 'path'; import { DefinePlugin, HotModuleReplacementPlugin, ProgressPlugin, ProvidePlugin } from 'webpack'; import type { Configuration } from 'webpack'; import HtmlWebpackPlugin from 'html-webpack-plugin'; @@ -7,25 +7,20 @@ import CaseSensitivePathsPlugin from 'case-sensitive-paths-webpack-plugin'; import TerserWebpackPlugin from 'terser-webpack-plugin'; import VirtualModulePlugin from 'webpack-virtual-modules'; import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin'; -import slash from 'slash'; import type { TransformOptions as EsbuildOptions } from 'esbuild'; import type { JsMinifyOptions as SwcOptions } from '@swc/core'; -import type { Options, CoreConfig, DocsOptions, PreviewAnnotation } from '@storybook/types'; +import type { Options, CoreConfig, DocsOptions } from '@storybook/types'; import { globalsNameReferenceMap } from '@storybook/preview/globals'; import { getBuilderOptions, - getRendererName, stringifyProcessEnvs, - handlebars, - interpolate, normalizeStories, - readTemplate, - loadPreviewOrConfigFile, isPreservingSymlinks, } from '@storybook/core-common'; -import { toRequireContextString, toImportFn } from '@storybook/core-webpack'; +import type { BuilderOptions } from '@storybook/core-webpack'; +import { getVirtualModuleMapping } from '@storybook/core-webpack'; import { dedent } from 'ts-dedent'; -import type { BuilderOptions, TypescriptOptions } from '../types'; +import type { TypescriptOptions } from '../types'; import { createBabelLoader, createSWCLoader } from './loaders'; const getAbsolutePath = (input: I): I => @@ -114,92 +109,6 @@ export default async ( const builderOptions = await getBuilderOptions(options); - const previewAnnotations = [ - ...(await presets.apply('previewAnnotations', [], options)).map( - (entry) => { - // If entry is an object, use the absolute import specifier. - // This is to maintain back-compat with community addons that bundle other addons - // and package managers that "hide" sub dependencies (e.g. pnpm / yarn pnp) - // The vite builder uses the bare import specifier. - if (typeof entry === 'object') { - return entry.absolute; - } - - // TODO: Remove as soon as we drop support for disabled StoryStoreV7 - if (isAbsolute(entry)) { - return entry; - } - - return slash(entry); - } - ), - loadPreviewOrConfigFile(options), - ].filter(Boolean); - - const virtualModuleMapping: Record = {}; - if (features?.storyStoreV7) { - const storiesFilename = 'storybook-stories.js'; - const storiesPath = resolve(join(workingDir, storiesFilename)); - - const needPipelinedImport = !!builderOptions.lazyCompilation && !isProd; - virtualModuleMapping[storiesPath] = toImportFn(stories, { needPipelinedImport }); - const configEntryPath = resolve(join(workingDir, 'storybook-config-entry.js')); - virtualModuleMapping[configEntryPath] = handlebars( - await readTemplate( - require.resolve( - '@storybook/builder-webpack5/templates/virtualModuleModernEntry.js.handlebars' - ) - ), - { - storiesFilename, - previewAnnotations, - } - // 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)}';`; - entries.push(rendererInitEntry); - - const entryTemplate = await readTemplate( - join(__dirname, '..', '..', 'templates', 'virtualModuleEntry.template.js') - ); - - previewAnnotations.forEach((previewAnnotationFilename: string | undefined) => { - if (!previewAnnotationFilename) return; - - // Ensure that relative paths end up mapped to a filename in the cwd, so a later import - // of the `previewAnnotationFilename` in the template works. - const entryFilename = previewAnnotationFilename.startsWith('.') - ? `${previewAnnotationFilename.replace(/(\w)(\/|\\)/g, '$1-')}-generated-config-entry.js` - : `${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, { - previewAnnotationFilename, - }); - entries.push(entryFilename); - }); - if (stories.length > 0) { - const storyTemplate = await readTemplate( - join(__dirname, '..', '..', '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, { - rendererName, - }) - // Make sure we also replace quotes for this one - .replace("'{{stories}}'", stories.map(toRequireContextString).join(',')); - entries.push(storiesFilename); - } - } - const shouldCheckTs = typescriptOptions.check && !typescriptOptions.skipBabel && !typescriptOptions.skipCompiler; const tsCheckOptions = typescriptOptions.checkOptions || {}; @@ -226,6 +135,12 @@ export default async ( externals['@storybook/blocks'] = '__STORYBOOK_BLOCKS_EMPTY_MODULE__'; } + const virtualModuleMapping = await getVirtualModuleMapping(options); + + Object.keys(virtualModuleMapping).forEach((key) => { + entries.push(key); + }); + return { name: 'preview', mode: isProd ? 'production' : 'development', diff --git a/code/builders/builder-webpack5/src/preview/loaders.ts b/code/builders/builder-webpack5/src/preview/loaders.ts index 26943cd90e06..f10bd93a5ebc 100644 --- a/code/builders/builder-webpack5/src/preview/loaders.ts +++ b/code/builders/builder-webpack5/src/preview/loaders.ts @@ -10,6 +10,7 @@ export const createBabelLoader = ( typescriptOptions: TypescriptOptions, excludes: string[] = [] ) => { + logger.info(dedent`Using Babel compiler`); return { test: typescriptOptions.skipBabel ? /\.(mjs|jsx?)$/ : /\.(mjs|tsx?|jsx?)$/, use: [ @@ -24,9 +25,7 @@ export const createBabelLoader = ( }; export const createSWCLoader = async (excludes: string[] = [], options: Options) => { - logger.warn(dedent` - The SWC loader is an experimental feature and may change or even be removed at any time. - `); + logger.info(dedent`Using SWC compiler`); const swc = await options.presets.apply('swc', {}, options); const typescriptOptions = await options.presets.apply<{ skipCompiler?: boolean }>( @@ -49,12 +48,8 @@ export const createSWCLoader = async (excludes: string[] = [], options: Options) }; return { test: typescriptOptions.skipCompiler ? /\.(mjs|cjs|jsx?)$/ : /\.(mjs|cjs|tsx?|jsx?)$/, - use: [ - { - loader: require.resolve('swc-loader'), - options: config, - }, - ], + loader: require.resolve('swc-loader'), + options: config, include: [getProjectRoot()], exclude: [/node_modules/, ...excludes], }; diff --git a/code/e2e-tests/addon-controls.spec.ts b/code/e2e-tests/addon-controls.spec.ts index df376782aae8..2c388d7ef598 100644 --- a/code/e2e-tests/addon-controls.spec.ts +++ b/code/e2e-tests/addon-controls.spec.ts @@ -31,18 +31,22 @@ test.describe('addon-controls', () => { ); const toggle = sbPage.panelContent().locator('input[name=primary]'); await toggle.click(); - await expect(sbPage.previewRoot().locator('button')).toHaveCSS( - 'background-color', - 'rgba(0, 0, 0, 0)' - ); + await expect(async () => { + await expect(sbPage.previewRoot().locator('button')).toHaveCSS( + 'background-color', + 'rgba(0, 0, 0, 0)' + ); + }).toPass(); // Color picker: Background color const color = sbPage.panelContent().locator('input[placeholder="Choose color..."]'); await color.fill('red'); - await expect(sbPage.previewRoot().locator('button')).toHaveCSS( - 'background-color', - 'rgb(255, 0, 0)' - ); + await expect(async () => { + await expect(sbPage.previewRoot().locator('button')).toHaveCSS( + 'background-color', + 'rgb(255, 0, 0)' + ); + }).toPass(); // TODO: enable this once the controls for size are aligned in all CLI templates. // Radio buttons: Size diff --git a/code/frameworks/nextjs/README.md b/code/frameworks/nextjs/README.md index 03cc1cbebc62..2a834fb7ee4a 100644 --- a/code/frameworks/nextjs/README.md +++ b/code/frameworks/nextjs/README.md @@ -122,7 +122,12 @@ export default { framework: { // name: '@storybook/react-webpack5', // Remove this name: '@storybook/nextjs', // Add this - options: {}, + options: { + builder: { + // Set useSWC to true if you want to try out the experimental SWC compiler in Next.js >= 14.0.0 + useSWC: true, + }, + }, }, }; ``` diff --git a/code/frameworks/nextjs/package.json b/code/frameworks/nextjs/package.json index fab731daf27d..ba32244e8c5d 100644 --- a/code/frameworks/nextjs/package.json +++ b/code/frameworks/nextjs/package.json @@ -36,6 +36,11 @@ "types": "./dist/preset.d.ts", "require": "./dist/preset.js" }, + "./font/webpack/loader/storybook-nextjs-font-loader": { + "types": "./dist/font/webpack/loader/storybook-nextjs-font-loader.d.ts", + "require": "./dist/font/webpack/loader/storybook-nextjs-font-loader.js", + "import": "./dist/font/webpack/loader/storybook-nextjs-font-loader.mjs" + }, "./dist/preview.mjs": "./dist/preview.mjs", "./next-image-loader-stub.js": { "types": "./dist/next-image-loader-stub.d.ts", @@ -83,10 +88,12 @@ "@babel/preset-react": "^7.22.15", "@babel/preset-typescript": "^7.23.2", "@babel/runtime": "^7.23.2", + "@pmmmwh/react-refresh-webpack-plugin": "^0.5.11", "@storybook/addon-actions": "workspace:*", "@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:*", @@ -117,7 +124,7 @@ "@types/babel__plugin-transform-runtime": "^7", "@types/babel__preset-env": "^7", "@types/loader-utils": "^2.0.5", - "next": "^14.0.0", + "next": "^14.0.2", "typescript": "^4.9.3", "webpack": "^5.65.0" }, @@ -156,7 +163,8 @@ "./src/images/next-future-image.tsx", "./src/images/next-legacy-image.tsx", "./src/images/next-image.tsx", - "./src/font/webpack/loader/storybook-nextjs-font-loader.ts" + "./src/font/webpack/loader/storybook-nextjs-font-loader.ts", + "./src/swc/next-swc-loader-patch.ts" ], "externals": [ "sb-original/next/image", diff --git a/code/frameworks/nextjs/src/css/webpack.ts b/code/frameworks/nextjs/src/css/webpack.ts index 8f5ed1bfcae3..75718527e7dd 100644 --- a/code/frameworks/nextjs/src/css/webpack.ts +++ b/code/frameworks/nextjs/src/css/webpack.ts @@ -34,6 +34,9 @@ export const configureCss = (baseConfig: WebpackConfig, nextConfig: NextConfig): }, require.resolve('postcss-loader'), ], + // We transform the "target.css" files from next.js into Javascript + // for Next.js to support fonts, so it should be ignored by the css-loader. + exclude: /next\/.*\/target.css$/, }; } }); diff --git a/code/frameworks/nextjs/src/font/webpack/configureNextFont.ts b/code/frameworks/nextjs/src/font/webpack/configureNextFont.ts index 47723fed4034..b1e3851611d6 100644 --- a/code/frameworks/nextjs/src/font/webpack/configureNextFont.ts +++ b/code/frameworks/nextjs/src/font/webpack/configureNextFont.ts @@ -1,14 +1,22 @@ import type { Configuration } from 'webpack'; -export function configureNextFont(baseConfig: Configuration) { - baseConfig.plugins = [...(baseConfig.plugins || [])]; - baseConfig.resolveLoader = { - ...baseConfig.resolveLoader, - alias: { - ...baseConfig.resolveLoader?.alias, - 'storybook-nextjs-font-loader': require.resolve( - './font/webpack/loader/storybook-nextjs-font-loader' - ), - }, - }; +export function configureNextFont(baseConfig: Configuration, isSWC?: boolean) { + const fontLoaderPath = require.resolve( + '@storybook/nextjs/font/webpack/loader/storybook-nextjs-font-loader' + ); + + if (isSWC) { + baseConfig.module?.rules?.push({ + test: /next\/.*\/target.css$/, + loader: fontLoaderPath, + }); + } else { + baseConfig.resolveLoader = { + ...baseConfig.resolveLoader, + alias: { + ...baseConfig.resolveLoader?.alias, + 'storybook-nextjs-font-loader': fontLoaderPath, + }, + }; + } } diff --git a/code/frameworks/nextjs/src/font/webpack/loader/local/get-font-face-declarations.ts b/code/frameworks/nextjs/src/font/webpack/loader/local/get-font-face-declarations.ts index 006c7f126f5b..d7d26ae55a37 100644 --- a/code/frameworks/nextjs/src/font/webpack/loader/local/get-font-face-declarations.ts +++ b/code/frameworks/nextjs/src/font/webpack/loader/local/get-font-face-declarations.ts @@ -1,6 +1,7 @@ // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error import loaderUtils from 'next/dist/compiled/loader-utils3'; +import { getProjectRoot } from '@storybook/core-common'; import path from 'path'; import type { LoaderOptions } from '../types'; @@ -11,7 +12,9 @@ export async function getFontFaceDeclarations(options: LoaderOptions, rootContex const localFontSrc = options.props.src as LocalFontSrc; // Parent folder relative to the root context - const parentFolder = path.dirname(options.filename).replace(rootContext, ''); + const parentFolder = path + .dirname(path.join(getProjectRoot(), options.filename)) + .replace(rootContext, ''); const { validateData } = require('../utils/local-font-utils'); const { weight, style, variable } = validateData('', options.props); diff --git a/code/frameworks/nextjs/src/font/webpack/loader/storybook-nextjs-font-loader.ts b/code/frameworks/nextjs/src/font/webpack/loader/storybook-nextjs-font-loader.ts index 85076ecd7201..8b7c08894f72 100644 --- a/code/frameworks/nextjs/src/font/webpack/loader/storybook-nextjs-font-loader.ts +++ b/code/frameworks/nextjs/src/font/webpack/loader/storybook-nextjs-font-loader.ts @@ -14,18 +14,34 @@ type FontFaceDeclaration = { }; export default async function storybookNextjsFontLoader(this: any) { - const options = this.getOptions() as LoaderOptions; + const loaderOptions = this.getOptions() as LoaderOptions; + let options; + + if (Object.keys(loaderOptions).length > 0) { + // handles Babel mode + options = loaderOptions; + } else { + // handles SWC mode + const importQuery = JSON.parse(this.resourceQuery.slice(1)); + + options = { + filename: importQuery.path, + fontFamily: importQuery.import, + props: importQuery.arguments[0], + source: this.context.replace(this.rootContext, ''), + }; + } // get execution context const rootCtx = this.rootContext; let fontFaceDeclaration: FontFaceDeclaration | undefined; - if (options.source === 'next/font/google' || options.source === '@next/font/google') { + if (options.source.endsWith('next/font/google') || options.source.endsWith('@next/font/google')) { fontFaceDeclaration = await getGoogleFontFaceDeclarations(options); } - if (options.source === 'next/font/local' || options.source === '@next/font/local') { + if (options.source.endsWith('next/font/local') || options.source.endsWith('@next/font/local')) { fontFaceDeclaration = await getLocalFontFaceDeclarations(options, rootCtx); } diff --git a/code/frameworks/nextjs/src/preset.ts b/code/frameworks/nextjs/src/preset.ts index 7b0e150f735a..e4fdd7936fb9 100644 --- a/code/frameworks/nextjs/src/preset.ts +++ b/code/frameworks/nextjs/src/preset.ts @@ -17,6 +17,7 @@ import { configureNextFont } from './font/webpack/configureNextFont'; import nextBabelPreset from './babel/preset'; import { configureNodePolyfills } from './nodePolyfills/webpack'; import { configureAliasing } from './dependency-map'; +import { configureSWCLoader } from './swc/loader'; export const addons: PresetProperty<'addons', StorybookConfig> = [ dirname(require.resolve(join('@storybook/preset-react-webpack', 'package.json'))), @@ -61,7 +62,9 @@ export const core: PresetProperty<'core', StorybookConfig> = async (config, opti name: dirname( require.resolve(join('@storybook/builder-webpack5', 'package.json')) ) as '@storybook/builder-webpack5', - options: typeof framework === 'string' ? {} : framework.options.builder || {}, + options: { + ...(typeof framework === 'string' ? {} : framework.options.builder || {}), + }, }, renderer: dirname(require.resolve(join('@storybook/react', 'package.json'))), }; @@ -135,7 +138,7 @@ export const webpackFinal: StorybookConfig['webpackFinal'] = async (baseConfig, const frameworkOptions = await options.presets.apply<{ options: FrameworkOptions }>( 'frameworkOptions' ); - const { options: { nextConfigPath } = {} } = frameworkOptions; + const { options: { nextConfigPath, builder } = {} } = frameworkOptions; const nextConfig = await configureConfig({ baseConfig, nextConfigPath, @@ -143,7 +146,7 @@ export const webpackFinal: StorybookConfig['webpackFinal'] = async (baseConfig, }); configureAliasing(baseConfig); - configureNextFont(baseConfig); + configureNextFont(baseConfig, builder?.useSWC); configureNextImport(baseConfig); configureRuntimeNextjsVersionResolution(baseConfig); configureImports({ baseConfig, configDir: options.configDir }); @@ -152,5 +155,10 @@ export const webpackFinal: StorybookConfig['webpackFinal'] = async (baseConfig, configureStyledJsx(baseConfig); configureNodePolyfills(baseConfig); + // TODO: In Storybook 8.0, we have to check whether the babel-compiler addon is used. Otherwise, swc should be used. + if (builder?.useSWC) { + await configureSWCLoader(baseConfig, options, nextConfig); + } + return baseConfig; }; diff --git a/code/frameworks/nextjs/src/swc/loader.ts b/code/frameworks/nextjs/src/swc/loader.ts new file mode 100644 index 000000000000..201bdc52e0d8 --- /dev/null +++ b/code/frameworks/nextjs/src/swc/loader.ts @@ -0,0 +1,68 @@ +import { getProjectRoot } from '@storybook/core-common'; +import { getVirtualModuleMapping } from '@storybook/core-webpack'; +import type { Options } from '@storybook/types'; +import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin'; +import type { NextConfig } from 'next'; +import path from 'path'; +import type { RuleSetRule } from 'webpack'; +import semver from 'semver'; +import { NextjsSWCNotSupportedError } from 'lib/core-events/src/errors/server-errors'; +import { getNextjsVersion } from '../utils'; + +export const configureSWCLoader = async ( + baseConfig: any, + options: Options, + nextConfig: NextConfig +) => { + const isDevelopment = options.configType !== 'PRODUCTION'; + const version = getNextjsVersion(); + + if (semver.lt(version, '14.0.0')) { + throw new NextjsSWCNotSupportedError(); + } + + const dir = getProjectRoot(); + + baseConfig.plugins = [ + ...baseConfig.plugins, + new ReactRefreshWebpackPlugin({ + overlay: { + sockIntegration: 'whm', + }, + }), + ]; + + const virtualModules = await getVirtualModuleMapping(options); + + baseConfig.module.rules = [ + // TODO: Remove filtering in Storybook 8.0 + ...baseConfig.module.rules.filter((r: RuleSetRule) => { + return !r.loader?.includes('swc-loader'); + }), + { + test: /\.(m?(j|t)sx?)$/, + include: [getProjectRoot()], + exclude: [/(node_modules)/, ...Object.keys(virtualModules)], + enforce: 'post', + use: { + // we use our own patch because we need to remove tracing from the original code + // which is not possible otherwise + loader: require.resolve('./swc/next-swc-loader-patch.js'), + options: { + isServer: false, + rootDir: dir, + pagesDir: `${dir}/pages`, + appDir: `${dir}/apps`, + hasReactRefresh: isDevelopment, + nextConfig, + supportedBrowsers: require('next/dist/build/utils').getSupportedBrowsers( + dir, + isDevelopment + ), + swcCacheDir: path.join(dir, nextConfig?.distDir ?? '.next', 'cache', 'swc'), + bundleTarget: 'default', + }, + }, + }, + ]; +}; diff --git a/code/frameworks/nextjs/src/swc/next-swc-loader-patch.ts b/code/frameworks/nextjs/src/swc/next-swc-loader-patch.ts new file mode 100644 index 000000000000..114daaddc293 --- /dev/null +++ b/code/frameworks/nextjs/src/swc/next-swc-loader-patch.ts @@ -0,0 +1,193 @@ +// THIS IS A PATCH over the original code from Next 14.0.0 +// we use our own patch because we need to remove tracing from the original code +// which is not possible otherwise + +/* eslint-disable no-restricted-syntax */ +/* +Copyright (c) 2017 The swc Project Developers +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +import type { NextConfig } from 'next'; +import { isWasm, transform } from 'next/dist/build/swc'; +import { getLoaderSWCOptions } from 'next/dist/build/swc/options'; +import path, { isAbsolute } from 'path'; + +export interface SWCLoaderOptions { + rootDir: string; + isServer: boolean; + pagesDir?: string; + appDir?: string; + hasReactRefresh: boolean; + optimizeServerReact?: boolean; + nextConfig: NextConfig; + jsConfig: any; + supportedBrowsers: string[] | undefined; + swcCacheDir: string; + serverComponents?: boolean; + isReactServerLayer?: boolean; +} + +const mockCurrentTraceSpan = { + traceChild: (name: string) => mockCurrentTraceSpan, + traceAsyncFn: async (fn: any) => fn(), +}; + +async function loaderTransform(this: any, parentTrace: any, source?: string, inputSourceMap?: any) { + // Make the loader async + const filename = this.resourcePath; + + const loaderOptions: SWCLoaderOptions = this.getOptions() || {}; + + const { + isServer, + rootDir, + pagesDir, + appDir, + hasReactRefresh, + nextConfig, + jsConfig, + supportedBrowsers, + swcCacheDir, + serverComponents, + isReactServerLayer, + } = loaderOptions; + const isPageFile = filename.startsWith(pagesDir); + const relativeFilePathFromRoot = path.relative(rootDir, filename); + + const swcOptions = getLoaderSWCOptions({ + pagesDir, + appDir, + filename, + isServer, + isPageFile, + development: this.mode === 'development', + hasReactRefresh, + modularizeImports: nextConfig?.modularizeImports, + optimizePackageImports: nextConfig?.experimental?.optimizePackageImports, + swcPlugins: nextConfig?.experimental?.swcPlugins, + compilerOptions: nextConfig?.compiler, + optimizeServerReact: nextConfig?.experimental?.optimizeServerReact, + jsConfig, + supportedBrowsers, + swcCacheDir, + relativeFilePathFromRoot, + serverComponents, + isReactServerLayer, + }); + + const programmaticOptions = { + ...swcOptions, + filename, + inputSourceMap: inputSourceMap ? JSON.stringify(inputSourceMap) : undefined, + + // Set the default sourcemap behavior based on Webpack's mapping flag, + sourceMaps: this.sourceMap, + inlineSourcesContent: this.sourceMap, + + // Ensure that Webpack will get a full absolute path in the sourcemap + // so that it can properly map the module back to its internal cached + // modules. + sourceFileName: filename, + }; + + if (!programmaticOptions.inputSourceMap) { + delete programmaticOptions.inputSourceMap; + } + + // auto detect development mode + if ( + this.mode && + programmaticOptions.jsc && + programmaticOptions.jsc.transform && + programmaticOptions.jsc.transform.react && + !Object.prototype.hasOwnProperty.call(programmaticOptions.jsc.transform.react, 'development') + ) { + programmaticOptions.jsc.transform.react.development = this.mode === 'development'; + } + + const swcSpan = parentTrace.traceChild('next-swc-transform'); + return swcSpan.traceAsyncFn(() => + transform(source as any, programmaticOptions).then((output) => { + if (output.eliminatedPackages && this.eliminatedPackages) { + for (const pkg of JSON.parse(output.eliminatedPackages)) { + this.eliminatedPackages.add(pkg); + } + } + return [output.code, output.map ? JSON.parse(output.map) : undefined]; + }) + ); +} + +const EXCLUDED_PATHS = /[\\/](cache[\\/][^\\/]+\.zip[\\/]node_modules|__virtual__)[\\/]/g; + +export function pitch(this: any) { + const callback = this.async(); + (async () => { + if ( + // TODO: investigate swc file reading in PnP mode? + !process.versions.pnp && + !EXCLUDED_PATHS.test(this.resourcePath) && + this.loaders.length - 1 === this.loaderIndex && + isAbsolute(this.resourcePath) && + !(await isWasm()) + ) { + const loaderSpan = mockCurrentTraceSpan.traceChild('next-swc-loader'); + this.addDependency(this.resourcePath); + return loaderSpan.traceAsyncFn(() => loaderTransform.call(this, loaderSpan)); + } + + return null; + })().then((r) => { + if (r) return callback(null, ...r); + callback(); + return null; + }, callback); +} + +function sanitizeSourceMap(rawSourceMap: any): any { + const { sourcesContent, ...sourceMap } = rawSourceMap ?? {}; + + // JSON parse/stringify trick required for swc to accept the SourceMap + return JSON.parse(JSON.stringify(sourceMap)); +} + +export default function swcLoader(this: any, inputSource: string, inputSourceMap: any) { + const loaderSpan = mockCurrentTraceSpan.traceChild('next-swc-loader'); + const callback = this.async(); + loaderSpan + .traceAsyncFn(() => + loaderTransform.call(this, loaderSpan, inputSource, sanitizeSourceMap(inputSourceMap)) + ) + .then( + ([transformedSource, outputSourceMap]: any) => { + callback(null, transformedSource, outputSourceMap || inputSourceMap); + }, + (err: Error) => { + callback(err); + } + ); +} + +// accept Buffers instead of strings +export const raw = true; diff --git a/code/lib/cli/src/generators/ANGULAR/index.ts b/code/lib/cli/src/generators/ANGULAR/index.ts index 3c4d9a1f0ca9..c8c7f3f288b3 100644 --- a/code/lib/cli/src/generators/ANGULAR/index.ts +++ b/code/lib/cli/src/generators/ANGULAR/index.ts @@ -1,5 +1,4 @@ import { join } from 'path'; -import semver from 'semver'; import { baseGenerator } from '../baseGenerator'; import type { Generator } from '../types'; import { CoreBuilder } from '../../project_types'; @@ -13,10 +12,6 @@ const generator: Generator<{ projectName: string }> = async ( options, commandOptions ) => { - const angularVersion = await packageManager.getPackageVersion('@angular/core'); - const isWebpack5 = angularVersion && semver.gte(angularVersion, '12.0.0'); - const updatedOptions = isWebpack5 ? { ...options, builder: CoreBuilder.Webpack5 } : options; - const angularJSON = new AngularJSON(); if ( @@ -62,7 +57,8 @@ const generator: Generator<{ projectName: string }> = async ( packageManager, npmOptions, { - ...updatedOptions, + ...options, + builder: CoreBuilder.Webpack5, ...(useCompodoc && { frameworkPreviewParts: { prefix: compoDocPreviewPrefix, diff --git a/code/lib/cli/src/generators/HTML/index.ts b/code/lib/cli/src/generators/HTML/index.ts index 781d580d2067..c4e0af830fbd 100755 --- a/code/lib/cli/src/generators/HTML/index.ts +++ b/code/lib/cli/src/generators/HTML/index.ts @@ -1,8 +1,11 @@ +import { CoreBuilder } from '../../project_types'; import { baseGenerator } from '../baseGenerator'; import type { Generator } from '../types'; const generator: Generator = async (packageManager, npmOptions, options) => { - await baseGenerator(packageManager, npmOptions, options, 'html'); + await baseGenerator(packageManager, npmOptions, options, 'html', { + useSWC: ({ builder }) => builder === CoreBuilder.Webpack5, + }); }; export default generator; diff --git a/code/lib/cli/src/generators/PREACT/index.ts b/code/lib/cli/src/generators/PREACT/index.ts index e5bf286467a6..6bbab88e16a3 100644 --- a/code/lib/cli/src/generators/PREACT/index.ts +++ b/code/lib/cli/src/generators/PREACT/index.ts @@ -1,8 +1,11 @@ +import { CoreBuilder } from '../../project_types'; import { baseGenerator } from '../baseGenerator'; import type { Generator } from '../types'; const generator: Generator = async (packageManager, npmOptions, options) => { - await baseGenerator(packageManager, npmOptions, options, 'preact'); + await baseGenerator(packageManager, npmOptions, options, 'preact', { + useSWC: ({ builder }) => builder === CoreBuilder.Webpack5, + }); }; export default generator; diff --git a/code/lib/cli/src/generators/REACT/index.ts b/code/lib/cli/src/generators/REACT/index.ts index 208c62bf25d4..046860356c56 100644 --- a/code/lib/cli/src/generators/REACT/index.ts +++ b/code/lib/cli/src/generators/REACT/index.ts @@ -1,5 +1,5 @@ import { detectLanguage } from '../../detect'; -import { SupportedLanguage } from '../../project_types'; +import { CoreBuilder, SupportedLanguage } from '../../project_types'; import { baseGenerator } from '../baseGenerator'; import type { Generator } from '../types'; @@ -10,6 +10,7 @@ const generator: Generator = async (packageManager, npmOptions, options) => { await baseGenerator(packageManager, npmOptions, options, 'react', { extraPackages, + useSWC: ({ builder }) => builder === CoreBuilder.Webpack5, extraAddons: ['@storybook/addon-onboarding'], }); }; diff --git a/code/lib/cli/src/generators/REACT_SCRIPTS/index.ts b/code/lib/cli/src/generators/REACT_SCRIPTS/index.ts index 866b2210664a..2177a9d5090e 100644 --- a/code/lib/cli/src/generators/REACT_SCRIPTS/index.ts +++ b/code/lib/cli/src/generators/REACT_SCRIPTS/index.ts @@ -59,6 +59,7 @@ const generator: Generator = async (packageManager, npmOptions, options) => { { ...options, builder: CoreBuilder.Webpack5 }, 'react', { + useSWC: () => true, extraAddons, extraPackages, staticDir: fs.existsSync(path.resolve('./public')) ? 'public' : undefined, diff --git a/code/lib/cli/src/generators/SERVER/index.ts b/code/lib/cli/src/generators/SERVER/index.ts index c99b69e4902d..7ced80f7441c 100755 --- a/code/lib/cli/src/generators/SERVER/index.ts +++ b/code/lib/cli/src/generators/SERVER/index.ts @@ -9,6 +9,7 @@ const generator: Generator = async (packageManager, npmOptions, options) => { { ...options, builder: CoreBuilder.Webpack5 }, 'server', { + useSWC: () => true, extensions: ['json', 'yaml', 'yml'], } ); diff --git a/code/lib/cli/src/generators/SFC_VUE/index.ts b/code/lib/cli/src/generators/SFC_VUE/index.ts index e2849a352d90..a3e2f15ea604 100644 --- a/code/lib/cli/src/generators/SFC_VUE/index.ts +++ b/code/lib/cli/src/generators/SFC_VUE/index.ts @@ -1,8 +1,11 @@ +import { CoreBuilder } from '../../project_types'; import { baseGenerator } from '../baseGenerator'; import type { Generator } from '../types'; const generator: Generator = async (packageManager, npmOptions, options) => { - await baseGenerator(packageManager, npmOptions, options, 'vue'); + await baseGenerator(packageManager, npmOptions, options, 'vue', { + useSWC: ({ builder }) => builder === CoreBuilder.Webpack5, + }); }; export default generator; diff --git a/code/lib/cli/src/generators/VUE/index.ts b/code/lib/cli/src/generators/VUE/index.ts index c1869a539695..6b771973a283 100644 --- a/code/lib/cli/src/generators/VUE/index.ts +++ b/code/lib/cli/src/generators/VUE/index.ts @@ -7,6 +7,7 @@ const generator: Generator = async (packageManager, npmOptions, options) => { extraPackages: async ({ builder }) => { return builder === CoreBuilder.Webpack5 ? ['vue-loader@^15.7.0'] : []; }, + useSWC: ({ builder }) => builder === CoreBuilder.Webpack5, }); }; diff --git a/code/lib/cli/src/generators/VUE3/index.ts b/code/lib/cli/src/generators/VUE3/index.ts index 63dbddede7b5..ec20a5b946a1 100644 --- a/code/lib/cli/src/generators/VUE3/index.ts +++ b/code/lib/cli/src/generators/VUE3/index.ts @@ -9,6 +9,7 @@ const generator: Generator = async (packageManager, npmOptions, options) => { ? ['vue-loader@^17.0.0', '@vue/compiler-sfc@^3.2.0'] : []; }, + useSWC: ({ builder }) => builder === CoreBuilder.Webpack5, }); }; diff --git a/code/lib/cli/src/generators/WEB-COMPONENTS/index.ts b/code/lib/cli/src/generators/WEB-COMPONENTS/index.ts index 667395096743..bf5ceee43c15 100755 --- a/code/lib/cli/src/generators/WEB-COMPONENTS/index.ts +++ b/code/lib/cli/src/generators/WEB-COMPONENTS/index.ts @@ -1,9 +1,11 @@ +import { CoreBuilder } from '../../project_types'; import { baseGenerator } from '../baseGenerator'; import type { Generator } from '../types'; const generator: Generator = async (packageManager, npmOptions, options) => { return baseGenerator(packageManager, npmOptions, options, 'web-components', { extraPackages: ['lit'], + useSWC: ({ builder }) => builder === CoreBuilder.Webpack5, }); }; diff --git a/code/lib/cli/src/generators/WEBPACK_REACT/index.ts b/code/lib/cli/src/generators/WEBPACK_REACT/index.ts index 867bd82b80c2..94e8babee466 100644 --- a/code/lib/cli/src/generators/WEBPACK_REACT/index.ts +++ b/code/lib/cli/src/generators/WEBPACK_REACT/index.ts @@ -1,9 +1,11 @@ +import { CoreBuilder } from '../../project_types'; import { baseGenerator } from '../baseGenerator'; import type { Generator } from '../types'; const generator: Generator = async (packageManager, npmOptions, options) => { await baseGenerator(packageManager, npmOptions, options, 'react', { extraAddons: ['@storybook/addon-onboarding'], + useSWC: ({ builder }) => builder === CoreBuilder.Webpack5, }); }; diff --git a/code/lib/cli/src/generators/baseGenerator.ts b/code/lib/cli/src/generators/baseGenerator.ts index a7bdb1764668..6083fae9e873 100644 --- a/code/lib/cli/src/generators/baseGenerator.ts +++ b/code/lib/cli/src/generators/baseGenerator.ts @@ -29,6 +29,7 @@ const defaultOptions: FrameworkOptions = { addMainFile: true, addComponents: true, skipBabel: false, + useSWC: () => false, extraMain: undefined, framework: undefined, extensions: undefined, @@ -194,6 +195,21 @@ export async function baseGenerator( builder = await detectBuilder(packageManager, projectType); } + const { + packages: frameworkPackages, + type, + rendererId, + framework: frameworkInclude, + builder: builderInclude, + } = getFrameworkDetails( + renderer, + builder, + pnp, + language, + framework, + shouldApplyRequireWrapperOnPackageNames + ); + const { extraAddons: extraAddonPackages, extraPackages, @@ -201,30 +217,26 @@ export async function baseGenerator( addScripts, addMainFile, addComponents, - skipBabel, extraMain, extensions, storybookConfigFolder, componentsDestinationPath, + useSWC, } = { ...defaultOptions, ...options, }; - const { - packages: frameworkPackages, - type, - rendererId, - framework: frameworkInclude, - builder: builderInclude, - } = getFrameworkDetails( - renderer, - builder, - pnp, - language, - framework, - shouldApplyRequireWrapperOnPackageNames - ); + let { skipBabel } = { + ...defaultOptions, + ...options, + }; + + const swc = useSWC({ builder }); + + if (swc) { + skipBabel = true; + } const extraAddonsToInstall = typeof extraAddonPackages === 'function' @@ -401,7 +413,18 @@ export async function baseGenerator( : []; await configureMain({ - framework: { name: frameworkInclude, options: options.framework || {} }, + framework: { + name: frameworkInclude, + options: swc + ? { + ...(options.framework ?? {}), + builder: { + ...(options.framework?.builder ?? {}), + useSWC: true, + }, + } + : options.framework || {}, + }, prefixes, storybookConfigFolder, docs: { autodocs: 'tag' }, diff --git a/code/lib/cli/src/generators/types.ts b/code/lib/cli/src/generators/types.ts index 1711505e0bdc..2f97a34df126 100644 --- a/code/lib/cli/src/generators/types.ts +++ b/code/lib/cli/src/generators/types.ts @@ -24,6 +24,7 @@ export interface FrameworkOptions { addMainFile?: boolean; addComponents?: boolean; skipBabel?: boolean; + useSWC?: ({ builder }: { builder: Builder }) => boolean; extraMain?: any; extensions?: string[]; framework?: Record; diff --git a/code/lib/cli/src/initiate.ts b/code/lib/cli/src/initiate.ts index e2966a0c1798..3afd703a1e08 100644 --- a/code/lib/cli/src/initiate.ts +++ b/code/lib/cli/src/initiate.ts @@ -57,7 +57,7 @@ const installStorybook = async ( linkable: !!options.linkable, pnp: pnp || options.usePnp, yes: options.yes, - projectType: options.type, + projectType, }; const runGenerator: () => Promise = async () => { diff --git a/code/lib/codemod/package.json b/code/lib/codemod/package.json index 9f8c8af0dbf9..ac98a974c4b3 100644 --- a/code/lib/codemod/package.json +++ b/code/lib/codemod/package.json @@ -68,7 +68,7 @@ "recast": "^0.23.1" }, "devDependencies": { - "@types/jscodeshift": "^0.11.6", + "@types/jscodeshift": "^0.11.10", "ansi-regex": "^5.0.1", "jest": "^29.7.0", "jest-specific-snapshot": "^8.0.0", diff --git a/code/lib/core-events/src/errors/server-errors.ts b/code/lib/core-events/src/errors/server-errors.ts index 951a482e9570..6ca430ae1966 100644 --- a/code/lib/core-events/src/errors/server-errors.ts +++ b/code/lib/core-events/src/errors/server-errors.ts @@ -368,3 +368,20 @@ export class GoogleFontsLoadingError extends StorybookError { `; } } + +export class NextjsSWCNotSupportedError extends StorybookError { + readonly category = Category.FRAMEWORK_NEXTJS; + + readonly code = 3; + + public readonly documentation = + 'https://github.com/storybookjs/storybook/blob/next/code/frameworks/nextjs/README.md#manual-migration'; + + template() { + return dedent` + You have activated the SWC mode for Next.js, but you are not using Next.js 14.0.0 or higher. + SWC is only supported in Next.js 14.0.0 and higher. Please go to your .storybook/main. file + and remove the { framework: { options: { builder: { useSWC: true } } } } option or upgrade to Next.js v14 or later. + `; + } +} diff --git a/code/lib/core-webpack/package.json b/code/lib/core-webpack/package.json index 0b9a988df5d4..fd69a427abbd 100644 --- a/code/lib/core-webpack/package.json +++ b/code/lib/core-webpack/package.json @@ -51,6 +51,7 @@ "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 370187367538..562860cbe1a6 100644 --- a/code/lib/core-webpack/src/index.ts +++ b/code/lib/core-webpack/src/index.ts @@ -4,3 +4,4 @@ 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/lib/core-webpack/src/types.ts b/code/lib/core-webpack/src/types.ts index 012b95f41cf6..1028c08a0a4d 100644 --- a/code/lib/core-webpack/src/types.ts +++ b/code/lib/core-webpack/src/types.ts @@ -22,6 +22,12 @@ export interface WebpackConfiguration { devtool?: false | string; } +export type BuilderOptions = { + fsCache?: boolean; + useSWC?: boolean; + lazyCompilation?: boolean; +}; + export type StorybookConfig = StorybookConfigBase & { /** * Modify or return a custom Webpack config after the Storybook's default configuration diff --git a/code/lib/core-webpack/src/virtual-module-mapping.ts b/code/lib/core-webpack/src/virtual-module-mapping.ts new file mode 100644 index 000000000000..09941ba859a3 --- /dev/null +++ b/code/lib/core-webpack/src/virtual-module-mapping.ts @@ -0,0 +1,111 @@ +import type { Options, PreviewAnnotation } from '@storybook/types'; +import { isAbsolute, join, resolve } from 'path'; +import { + getBuilderOptions, + getRendererName, + handlebars, + interpolate, + loadPreviewOrConfigFile, + normalizeStories, + readTemplate, +} from '@storybook/core-common'; +import slash from 'slash'; +import type { BuilderOptions } from './types'; +import { toImportFn } from './to-importFn'; +import { toRequireContextString } from './to-require-context'; + +export const getVirtualModuleMapping = async (options: Options) => { + const virtualModuleMapping: Record = {}; + const builderOptions = await getBuilderOptions(options); + const workingDir = process.cwd(); + const isProd = options.configType === 'PRODUCTION'; + const nonNormalizedStories = await options.presets.apply('stories', []); + + const stories = normalizeStories(nonNormalizedStories, { + configDir: options.configDir, + workingDir, + }); + + const previewAnnotations = [ + ...(await options.presets.apply('previewAnnotations', [], options)).map( + (entry) => { + // If entry is an object, use the absolute import specifier. + // This is to maintain back-compat with community addons that bundle other addons + // and package managers that "hide" sub dependencies (e.g. pnpm / yarn pnp) + // The vite builder uses the bare import specifier. + if (typeof entry === 'object') { + return entry.absolute; + } + + // TODO: Remove as soon as we drop support for disabled StoryStoreV7 + if (isAbsolute(entry)) { + return entry; + } + + return slash(entry); + } + ), + loadPreviewOrConfigFile(options), + ].filter(Boolean); + + if (options.features?.storyStoreV7) { + const storiesFilename = 'storybook-stories.js'; + const storiesPath = resolve(join(workingDir, storiesFilename)); + + const needPipelinedImport = !!builderOptions.lazyCompilation && !isProd; + virtualModuleMapping[storiesPath] = toImportFn(stories, { needPipelinedImport }); + const configEntryPath = resolve(join(workingDir, 'storybook-config-entry.js')); + virtualModuleMapping[configEntryPath] = handlebars( + await readTemplate( + require.resolve( + '@storybook/builder-webpack5/templates/virtualModuleModernEntry.js.handlebars' + ) + ), + { + storiesFilename, + previewAnnotations, + } + // We need to double escape `\` for webpack. We may have some in windows paths + ).replace(/\\/g, '\\\\'); + } else { + const rendererName = await getRendererName(options); + + const rendererInitEntry = resolve(join(workingDir, 'storybook-init-renderer-entry.js')); + virtualModuleMapping[rendererInitEntry] = `import '${slash(rendererName)}';`; + + const entryTemplate = await readTemplate( + join(__dirname, '..', 'templates', 'virtualModuleEntry.template.js') + ); + + previewAnnotations.forEach((previewAnnotationFilename: string | undefined) => { + if (!previewAnnotationFilename) return; + + // Ensure that relative paths end up mapped to a filename in the cwd, so a later import + // of the `previewAnnotationFilename` in the template works. + const entryFilename = previewAnnotationFilename.startsWith('.') + ? `${previewAnnotationFilename.replace(/(\w)(\/|\\)/g, '$1-')}-generated-config-entry.js` + : `${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, { + previewAnnotationFilename, + }); + }); + if (stories.length > 0) { + const storyTemplate = await readTemplate( + join(__dirname, '..', '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, { + rendererName, + }) + // Make sure we also replace quotes for this one + .replace("'{{stories}}'", stories.map(toRequireContextString).join(',')); + } + } + + return virtualModuleMapping; +}; diff --git a/code/builders/builder-webpack5/templates/virtualModuleEntry.template.js b/code/lib/core-webpack/templates/virtualModuleEntry.template.js similarity index 100% rename from code/builders/builder-webpack5/templates/virtualModuleEntry.template.js rename to code/lib/core-webpack/templates/virtualModuleEntry.template.js diff --git a/code/builders/builder-webpack5/templates/virtualModuleStory.template.js b/code/lib/core-webpack/templates/virtualModuleStory.template.js similarity index 100% rename from code/builders/builder-webpack5/templates/virtualModuleStory.template.js rename to code/lib/core-webpack/templates/virtualModuleStory.template.js diff --git a/code/yarn.lock b/code/yarn.lock index 90e5ab5564a5..df84273184ce 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -4031,72 +4031,72 @@ __metadata: languageName: node linkType: hard -"@next/env@npm:14.0.0": - version: 14.0.0 - resolution: "@next/env@npm:14.0.0" - checksum: c43e81dbd162a29a4b380342e416209d69d731e8ced7688d09668ec8196f543e358ed65adad81a26e943c63a293d7a018552f8389b6b1ac95cd0f63f4ef257c0 +"@next/env@npm:14.0.2": + version: 14.0.2 + resolution: "@next/env@npm:14.0.2" + checksum: 9fad703ce13b7b7fecf898d3c239f8976f2ec7f3c7c461c06da70898a0221775c48e1a2e2c76740216c4093c2db9bd7adaacd196586cd4283e09eb89de4c1db6 languageName: node linkType: hard -"@next/swc-darwin-arm64@npm:14.0.0": - version: 14.0.0 - resolution: "@next/swc-darwin-arm64@npm:14.0.0" +"@next/swc-darwin-arm64@npm:14.0.2": + version: 14.0.2 + resolution: "@next/swc-darwin-arm64@npm:14.0.2" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@next/swc-darwin-x64@npm:14.0.0": - version: 14.0.0 - resolution: "@next/swc-darwin-x64@npm:14.0.0" +"@next/swc-darwin-x64@npm:14.0.2": + version: 14.0.2 + resolution: "@next/swc-darwin-x64@npm:14.0.2" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@next/swc-linux-arm64-gnu@npm:14.0.0": - version: 14.0.0 - resolution: "@next/swc-linux-arm64-gnu@npm:14.0.0" +"@next/swc-linux-arm64-gnu@npm:14.0.2": + version: 14.0.2 + resolution: "@next/swc-linux-arm64-gnu@npm:14.0.2" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@next/swc-linux-arm64-musl@npm:14.0.0": - version: 14.0.0 - resolution: "@next/swc-linux-arm64-musl@npm:14.0.0" +"@next/swc-linux-arm64-musl@npm:14.0.2": + version: 14.0.2 + resolution: "@next/swc-linux-arm64-musl@npm:14.0.2" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@next/swc-linux-x64-gnu@npm:14.0.0": - version: 14.0.0 - resolution: "@next/swc-linux-x64-gnu@npm:14.0.0" +"@next/swc-linux-x64-gnu@npm:14.0.2": + version: 14.0.2 + resolution: "@next/swc-linux-x64-gnu@npm:14.0.2" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@next/swc-linux-x64-musl@npm:14.0.0": - version: 14.0.0 - resolution: "@next/swc-linux-x64-musl@npm:14.0.0" +"@next/swc-linux-x64-musl@npm:14.0.2": + version: 14.0.2 + resolution: "@next/swc-linux-x64-musl@npm:14.0.2" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@next/swc-win32-arm64-msvc@npm:14.0.0": - version: 14.0.0 - resolution: "@next/swc-win32-arm64-msvc@npm:14.0.0" +"@next/swc-win32-arm64-msvc@npm:14.0.2": + version: 14.0.2 + resolution: "@next/swc-win32-arm64-msvc@npm:14.0.2" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@next/swc-win32-ia32-msvc@npm:14.0.0": - version: 14.0.0 - resolution: "@next/swc-win32-ia32-msvc@npm:14.0.0" +"@next/swc-win32-ia32-msvc@npm:14.0.2": + version: 14.0.2 + resolution: "@next/swc-win32-ia32-msvc@npm:14.0.2" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@next/swc-win32-x64-msvc@npm:14.0.0": - version: 14.0.0 - resolution: "@next/swc-win32-x64-msvc@npm:14.0.0" +"@next/swc-win32-x64-msvc@npm:14.0.2": + version: 14.0.2 + resolution: "@next/swc-win32-x64-msvc@npm:14.0.2" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -6311,7 +6311,7 @@ __metadata: "@storybook/node-logger": "workspace:*" "@storybook/types": "workspace:*" "@types/cross-spawn": "npm:^6.0.2" - "@types/jscodeshift": "npm:^0.11.6" + "@types/jscodeshift": "npm:^0.11.10" ansi-regex: "npm:^5.0.1" cross-spawn: "npm:^7.0.3" globby: "npm:^11.0.2" @@ -6490,6 +6490,7 @@ __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" @@ -6853,10 +6854,12 @@ __metadata: "@babel/preset-typescript": "npm:^7.23.2" "@babel/runtime": "npm:^7.23.2" "@babel/types": "npm:^7.23.0" + "@pmmmwh/react-refresh-webpack-plugin": "npm:^0.5.11" "@storybook/addon-actions": "workspace:*" "@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:*" @@ -6871,7 +6874,7 @@ __metadata: fs-extra: "npm:^11.1.0" image-size: "npm:^1.0.0" loader-utils: "npm:^3.2.1" - next: "npm:^14.0.0" + next: "npm:^14.0.2" node-polyfill-webpack-plugin: "npm:^2.0.1" pnp-webpack-plugin: "npm:^1.7.0" postcss: "npm:^8.4.21" @@ -8936,13 +8939,13 @@ __metadata: languageName: node linkType: hard -"@types/jscodeshift@npm:^0.11.6": - version: 0.11.7 - resolution: "@types/jscodeshift@npm:0.11.7" +"@types/jscodeshift@npm:^0.11.10": + version: 0.11.10 + resolution: "@types/jscodeshift@npm:0.11.10" dependencies: ast-types: "npm:^0.14.1" recast: "npm:^0.20.3" - checksum: a2c26f8e64950296bae6176c52e832e1f5c5eb3672adad3c1cdc63e23b8bd3de47890ac8eaae7eb0788feea7628ce540513ff5189379f79e882ddcfa1c855cfc + checksum: 1d477ea1addd62a5949f028ef16bac3226341d65052e4f51d61e51789c6c7aa17e953dac34eb6d1e5a2b761fc4c7920df875e20e85cdf4122fc08836e7da547a languageName: node linkType: hard @@ -22737,20 +22740,20 @@ __metadata: languageName: node linkType: hard -"next@npm:^14.0.0": - version: 14.0.0 - resolution: "next@npm:14.0.0" +"next@npm:^14.0.2": + version: 14.0.2 + resolution: "next@npm:14.0.2" dependencies: - "@next/env": "npm:14.0.0" - "@next/swc-darwin-arm64": "npm:14.0.0" - "@next/swc-darwin-x64": "npm:14.0.0" - "@next/swc-linux-arm64-gnu": "npm:14.0.0" - "@next/swc-linux-arm64-musl": "npm:14.0.0" - "@next/swc-linux-x64-gnu": "npm:14.0.0" - "@next/swc-linux-x64-musl": "npm:14.0.0" - "@next/swc-win32-arm64-msvc": "npm:14.0.0" - "@next/swc-win32-ia32-msvc": "npm:14.0.0" - "@next/swc-win32-x64-msvc": "npm:14.0.0" + "@next/env": "npm:14.0.2" + "@next/swc-darwin-arm64": "npm:14.0.2" + "@next/swc-darwin-x64": "npm:14.0.2" + "@next/swc-linux-arm64-gnu": "npm:14.0.2" + "@next/swc-linux-arm64-musl": "npm:14.0.2" + "@next/swc-linux-x64-gnu": "npm:14.0.2" + "@next/swc-linux-x64-musl": "npm:14.0.2" + "@next/swc-win32-arm64-msvc": "npm:14.0.2" + "@next/swc-win32-ia32-msvc": "npm:14.0.2" + "@next/swc-win32-x64-msvc": "npm:14.0.2" "@swc/helpers": "npm:0.5.2" busboy: "npm:1.6.0" caniuse-lite: "npm:^1.0.30001406" @@ -22788,7 +22791,7 @@ __metadata: optional: true bin: next: dist/bin/next - checksum: cfb18a72d6e1d875efb1bb3806f9a06551f482c5cb87231e77e179a71d26f3d43700290988ad27e739302bfa7ff8ac8081aafd5456c39a2819fdd315617e5acf + checksum: 65ae7a09f1643bc3deafdbdae9ce0c02326346c4a60a7c739f8f6b154b2226b8fcc5efb984cdcb4ef100116910d4c1013089135800d30c7a50cf98c9d22e5a26 languageName: node linkType: hard @@ -27467,7 +27470,7 @@ __metadata: languageName: node linkType: hard -"slash@npm:^5.0.0": +"slash@npm:^5.0.0, slash@npm:^5.1.0": version: 5.1.0 resolution: "slash@npm:5.1.0" checksum: eb48b815caf0bdc390d0519d41b9e0556a14380f6799c72ba35caf03544d501d18befdeeef074bc9c052acf69654bc9e0d79d7f1de0866284137a40805299eb3