From 8ecd668842ffb24626c5b121f4eddbee2884da5d Mon Sep 17 00:00:00 2001 From: Kyle Gach Date: Thu, 17 Nov 2022 14:20:17 -0700 Subject: [PATCH 1/3] Fix next@13 `next/link` --- code/frameworks/nextjs/src/preset.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/code/frameworks/nextjs/src/preset.ts b/code/frameworks/nextjs/src/preset.ts index 2833d9c5752f..a40204593c60 100644 --- a/code/frameworks/nextjs/src/preset.ts +++ b/code/frameworks/nextjs/src/preset.ts @@ -1,5 +1,6 @@ // https://storybook.js.org/docs/react/addons/writing-presets import { dirname, join } from 'path'; +import semver from 'semver'; import type { Options, PresetProperty } from '@storybook/types'; import type { TransformOptions } from '@babel/core'; import { configureConfig } from './config/webpack'; @@ -9,7 +10,7 @@ import { configureRouting } from './routing/webpack'; import { configureStyledJsx } from './styledJsx/webpack'; import { configureStyledJsxTransforms } from './styledJsx/babel'; import { configureImages } from './images/webpack'; -import { configureRuntimeNextjsVersionResolution } from './utils'; +import { configureRuntimeNextjsVersionResolution, getNextjsVersion } from './utils'; import type { FrameworkOptions, StorybookConfig } from './types'; import { configureTypescript } from './config/babel'; @@ -18,6 +19,8 @@ export const addons: PresetProperty<'addons', StorybookConfig> = [ dirname(require.resolve(join('@storybook/builder-webpack5', 'package.json'))), ]; +const version = getNextjsVersion(); + const defaultFrameworkOptions: FrameworkOptions = {}; export const frameworkOptions = async ( @@ -68,6 +71,17 @@ export const config: StorybookConfig['previewAnnotations'] = (entry = []) => [ require.resolve('@storybook/nextjs/preview.js'), ]; +export const env = (envConfig: PresetProperty<'env', StorybookConfig>) => { + return { + ...envConfig, + // Can be removed if https://github.com/vercel/next.js/issues/42621 is resolved + ...(semver.gte(version, '13.0.0') && { + // TODO: This should also respect `newNextLinkBehavior`: https://github.com/vercel/next.js/blob/07d3da102dfef65be9c13fd4b754a12eda7eded1/packages/next/server/config-shared.ts#L88 + __NEXT_NEW_LINK_BEHAVIOR: 'true', + }), + }; +}; + // Not even sb init - automigrate - running dev // You're using a version of Nextjs prior to v10, which is unsupported by this framework. From 58090fe46a4a39fc859bc54efeba32eab0db4a55 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Fri, 18 Nov 2022 17:08:53 +0100 Subject: [PATCH 2/3] rework next/link flag for Next.js 12 and 13 --- code/frameworks/nextjs/src/config/webpack.ts | 78 +++++--------------- code/frameworks/nextjs/src/preset.ts | 16 +--- code/frameworks/nextjs/src/utils.ts | 55 +++++++++++++- 3 files changed, 72 insertions(+), 77 deletions(-) diff --git a/code/frameworks/nextjs/src/config/webpack.ts b/code/frameworks/nextjs/src/config/webpack.ts index 2bd4ba751691..1bc86473fe56 100644 --- a/code/frameworks/nextjs/src/config/webpack.ts +++ b/code/frameworks/nextjs/src/config/webpack.ts @@ -1,11 +1,9 @@ import type { Configuration as WebpackConfig } from 'webpack'; -import { PHASE_DEVELOPMENT_SERVER } from 'next/constants'; -import findUp from 'find-up'; -import { pathExists } from 'fs-extra'; +import semver from 'semver'; + import type { NextConfig } from 'next'; -import dedent from 'ts-dedent'; import { DefinePlugin } from 'webpack'; -import { addScopedAlias } from '../utils'; +import { addScopedAlias, getNextjsVersion, resolveNextConfig } from '../utils'; export const configureConfig = async ({ baseConfig, @@ -24,63 +22,21 @@ export const configureConfig = async ({ return nextConfig; }; -const findNextConfigFile = async (configDir: string) => { - const supportedExtensions = ['mjs', 'js']; - return supportedExtensions.reduce>( - async (acc, ext: string | undefined) => { - const resolved = await acc; - if (!resolved) { - acc = findUp(`next.config.${ext}`, { cwd: configDir }); - } - - return acc; - }, - Promise.resolve(undefined) - ); -}; - -const resolveNextConfig = async ({ - baseConfig, - nextConfigPath, - configDir, -}: { - baseConfig: WebpackConfig; - nextConfigPath?: string; - configDir: string; -}): Promise => { - const nextConfigFile = nextConfigPath || (await findNextConfigFile(configDir)); - - if (!nextConfigFile || (await pathExists(nextConfigFile)) === false) { - throw new Error( - dedent` - Could not find or resolve your Next config file. Please provide the next config file path as a framework option. +const version = getNextjsVersion(); - More info: https://github.com/storybookjs/storybook/blob/next/code/frameworks/nextjs/README.md#options - ` - ); +const setupRuntimeConfig = (baseConfig: WebpackConfig, nextConfig: NextConfig): void => { + const definePluginConfig: Record = { + // this mimics what nextjs does client side + // https://github.com/vercel/next.js/blob/57702cb2a9a9dba4b552e0007c16449cf36cfb44/packages/next/client/index.tsx#L101 + 'process.env.__NEXT_RUNTIME_CONFIG': JSON.stringify({ + serverRuntimeConfig: {}, + publicRuntimeConfig: nextConfig.publicRuntimeConfig, + }), + }; + + if (semver.gte(version, '13.0.0') || nextConfig.experimental?.newNextLinkBehavior) { + definePluginConfig['process.env.__NEXT_NEW_LINK_BEHAVIOR'] = true; } - const nextConfigExport = await import(nextConfigFile); - - const nextConfig = - typeof nextConfigExport === 'function' - ? nextConfigExport(PHASE_DEVELOPMENT_SERVER, { - defaultConfig: baseConfig, - }) - : nextConfigExport; - - return nextConfig; -}; - -const setupRuntimeConfig = (baseConfig: WebpackConfig, nextConfig: NextConfig): void => { - baseConfig.plugins?.push( - new DefinePlugin({ - // this mimics what nextjs does client side - // https://github.com/vercel/next.js/blob/57702cb2a9a9dba4b552e0007c16449cf36cfb44/packages/next/client/index.tsx#L101 - 'process.env.__NEXT_RUNTIME_CONFIG': JSON.stringify({ - serverRuntimeConfig: {}, - publicRuntimeConfig: nextConfig.publicRuntimeConfig, - }), - }) - ); + baseConfig.plugins?.push(new DefinePlugin(definePluginConfig)); }; diff --git a/code/frameworks/nextjs/src/preset.ts b/code/frameworks/nextjs/src/preset.ts index a40204593c60..2833d9c5752f 100644 --- a/code/frameworks/nextjs/src/preset.ts +++ b/code/frameworks/nextjs/src/preset.ts @@ -1,6 +1,5 @@ // https://storybook.js.org/docs/react/addons/writing-presets import { dirname, join } from 'path'; -import semver from 'semver'; import type { Options, PresetProperty } from '@storybook/types'; import type { TransformOptions } from '@babel/core'; import { configureConfig } from './config/webpack'; @@ -10,7 +9,7 @@ import { configureRouting } from './routing/webpack'; import { configureStyledJsx } from './styledJsx/webpack'; import { configureStyledJsxTransforms } from './styledJsx/babel'; import { configureImages } from './images/webpack'; -import { configureRuntimeNextjsVersionResolution, getNextjsVersion } from './utils'; +import { configureRuntimeNextjsVersionResolution } from './utils'; import type { FrameworkOptions, StorybookConfig } from './types'; import { configureTypescript } from './config/babel'; @@ -19,8 +18,6 @@ export const addons: PresetProperty<'addons', StorybookConfig> = [ dirname(require.resolve(join('@storybook/builder-webpack5', 'package.json'))), ]; -const version = getNextjsVersion(); - const defaultFrameworkOptions: FrameworkOptions = {}; export const frameworkOptions = async ( @@ -71,17 +68,6 @@ export const config: StorybookConfig['previewAnnotations'] = (entry = []) => [ require.resolve('@storybook/nextjs/preview.js'), ]; -export const env = (envConfig: PresetProperty<'env', StorybookConfig>) => { - return { - ...envConfig, - // Can be removed if https://github.com/vercel/next.js/issues/42621 is resolved - ...(semver.gte(version, '13.0.0') && { - // TODO: This should also respect `newNextLinkBehavior`: https://github.com/vercel/next.js/blob/07d3da102dfef65be9c13fd4b754a12eda7eded1/packages/next/server/config-shared.ts#L88 - __NEXT_NEW_LINK_BEHAVIOR: 'true', - }), - }; -}; - // Not even sb init - automigrate - running dev // You're using a version of Nextjs prior to v10, which is unsupported by this framework. diff --git a/code/frameworks/nextjs/src/utils.ts b/code/frameworks/nextjs/src/utils.ts index 289fe4acd724..38607adee0ef 100644 --- a/code/frameworks/nextjs/src/utils.ts +++ b/code/frameworks/nextjs/src/utils.ts @@ -1,6 +1,11 @@ import path from 'path'; -import type { Configuration as WebpackConfig } from 'webpack'; import { DefinePlugin } from 'webpack'; +import { PHASE_DEVELOPMENT_SERVER } from 'next/constants'; +import findUp from 'find-up'; +import { pathExists } from 'fs-extra'; +import dedent from 'ts-dedent'; +import type { Configuration as WebpackConfig } from 'webpack'; +import type { NextConfig } from 'next'; export const configureRuntimeNextjsVersionResolution = (baseConfig: WebpackConfig): void => { baseConfig.plugins?.push( @@ -12,6 +17,54 @@ export const configureRuntimeNextjsVersionResolution = (baseConfig: WebpackConfi export const getNextjsVersion = (): string => require(scopedResolve('next/package.json')).version; +const findNextConfigFile = async (configDir: string) => { + const supportedExtensions = ['mjs', 'js']; + return supportedExtensions.reduce>( + async (acc, ext: string | undefined) => { + const resolved = await acc; + if (!resolved) { + acc = findUp(`next.config.${ext}`, { cwd: configDir }); + } + + return acc; + }, + Promise.resolve(undefined) + ); +}; + +export const resolveNextConfig = async ({ + baseConfig = {}, + nextConfigPath, + configDir, +}: { + baseConfig?: WebpackConfig; + nextConfigPath?: string; + configDir: string; +}): Promise => { + const nextConfigFile = nextConfigPath || (await findNextConfigFile(configDir)); + + if (!nextConfigFile || (await pathExists(nextConfigFile)) === false) { + throw new Error( + dedent` + Could not find or resolve your Next config file. Please provide the next config file path as a framework option. + + More info: https://github.com/storybookjs/storybook/blob/next/code/frameworks/nextjs/README.md#options + ` + ); + } + + const nextConfigExport = await import(nextConfigFile); + + const nextConfig = + typeof nextConfigExport === 'function' + ? nextConfigExport(PHASE_DEVELOPMENT_SERVER, { + defaultConfig: baseConfig, + }) + : nextConfigExport; + + return nextConfig.default || nextConfig; +}; + // This is to help the addon in development // Without it, webpack resolves packages in its node_modules instead of the example's node_modules export const addScopedAlias = (baseConfig: WebpackConfig, name: string, alias?: string): void => { From 223f969017221a2c26d4adebfcb3b5085024c1dc Mon Sep 17 00:00:00 2001 From: Kyle Gach Date: Fri, 18 Nov 2022 13:12:03 -0700 Subject: [PATCH 3/3] Update `newNextLinkBehavior` logic to match Next's --- code/frameworks/nextjs/src/config/webpack.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/code/frameworks/nextjs/src/config/webpack.ts b/code/frameworks/nextjs/src/config/webpack.ts index 1bc86473fe56..799f95a73df0 100644 --- a/code/frameworks/nextjs/src/config/webpack.ts +++ b/code/frameworks/nextjs/src/config/webpack.ts @@ -34,7 +34,20 @@ const setupRuntimeConfig = (baseConfig: WebpackConfig, nextConfig: NextConfig): }), }; - if (semver.gte(version, '13.0.0') || nextConfig.experimental?.newNextLinkBehavior) { + /** + * In Next12.2, the `newNextLinkBehavior` option was introduced, defaulted to + * falsy in the Next app (`undefined` in the config itself), and `next/link` + * was engineered to opt *in* to it + * + * In Next13, the `newNextLinkBehavior` option now defaults to truthy (still + * `undefined` in the config), and `next/link` was engineered to opt *out* + * of it + */ + const newNextLinkBehavior = nextConfig.experimental?.newNextLinkBehavior; + if ( + (semver.gte(version, '13.0.0') && newNextLinkBehavior !== false) || + (semver.gte(version, '12.2.0') && newNextLinkBehavior) + ) { definePluginConfig['process.env.__NEXT_NEW_LINK_BEHAVIOR'] = true; }