Skip to content

Commit

Permalink
Merge pull request #19834 from storybookjs/fix-next-13-link
Browse files Browse the repository at this point in the history
NextJS: Fix v13 `next/link`
  • Loading branch information
shilman committed Nov 21, 2022
2 parents 9807537 + fbd3da7 commit d890789
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 63 deletions.
92 changes: 30 additions & 62 deletions code/frameworks/nextjs/src/config/webpack.ts
@@ -1,12 +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 { pathToFileURL } from 'node:url';
import { addScopedAlias } from '../utils';
import { addScopedAlias, getNextjsVersion, resolveNextConfig } from '../utils';

export const configureConfig = async ({
baseConfig,
Expand All @@ -25,63 +22,34 @@ export const configureConfig = async ({
return nextConfig;
};

const findNextConfigFile = async (configDir: string) => {
const supportedExtensions = ['mjs', 'js'];
return supportedExtensions.reduce<Promise<undefined | string>>(
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<NextConfig> => {
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<string, any> = {
// 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,
}),
};

/**
* 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;
}

const nextConfigExport = await import(pathToFileURL(nextConfigFile).href);

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));
};
56 changes: 55 additions & 1 deletion code/frameworks/nextjs/src/utils.ts
@@ -1,6 +1,12 @@
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';
import { pathToFileURL } from 'node:url';

export const configureRuntimeNextjsVersionResolution = (baseConfig: WebpackConfig): void => {
baseConfig.plugins?.push(
Expand All @@ -12,6 +18,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<Promise<undefined | string>>(
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<NextConfig> => {
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(pathToFileURL(nextConfigFile).href);

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 => {
Expand Down

0 comments on commit d890789

Please sign in to comment.