From 6d0bd20ffa6ab628247bf419fa5fd408bc658ea4 Mon Sep 17 00:00:00 2001 From: Neeraj Rajpurohit <31539812+neeraj3029@users.noreply.github.com> Date: Mon, 7 Feb 2022 04:08:42 +0530 Subject: [PATCH] Fixes #31240: Adding a recursive addPackagePath function in webpack-config (#31264) Fixes: https://github.com/vercel/next.js/issues/31240 Closes: https://github.com/vercel/next.js/pull/32324 Adding a try-catch block to handle situations when packages are found at relative path in getPackagePath function. This is likely to occur when using `preact` instead of `react-dom`, as `scheduler` package will not be found wrt `react-dom` ## Bug - [x] Related issues linked using `fixes #31240` Co-authored-by: JJ Kasper <22380829+ijjk@users.noreply.github.com> --- packages/next/build/webpack-config.ts | 64 ++++++++++++++++----------- 1 file changed, 38 insertions(+), 26 deletions(-) diff --git a/packages/next/build/webpack-config.ts b/packages/next/build/webpack-config.ts index 190d531b745ad6a..6a16a2cf65f3bcf 100644 --- a/packages/next/build/webpack-config.ts +++ b/packages/next/build/webpack-config.ts @@ -685,35 +685,47 @@ export default async function getBaseWebpackConfig( ) } - const getPackagePath = (name: string, relativeToPath: string) => { - const packageJsonPath = require.resolve(`${name}/package.json`, { - paths: [relativeToPath], - }) - // Include a trailing slash so that a `.startsWith(packagePath)` check avoids false positives - // when one package name starts with the full name of a different package. - // For example: - // "node_modules/react-slider".startsWith("node_modules/react") // true - // "node_modules/react-slider".startsWith("node_modules/react/") // false - return path.join(packageJsonPath, '../') - } - // Packages which will be split into the 'framework' chunk. // Only top-level packages are included, e.g. nested copies like // 'node_modules/meow/node_modules/object-assign' are not included. - const topLevelFrameworkPaths = [ - getPackagePath('react', dir), - getPackagePath('react-dom', dir), - getPackagePath('scheduler', require.resolve('react-dom', { paths: [dir] })), - getPackagePath('object-assign', require.resolve('react', { paths: [dir] })), - getPackagePath( - 'object-assign', - require.resolve('react-dom', { paths: [dir] }) - ), - getPackagePath( - 'use-subscription', - require.resolve('next', { paths: [dir] }) - ), - ] + const topLevelFrameworkPaths: string[] = [] + const visitedFrameworkPackages = new Set() + + // Adds package-paths of dependencies recursively + const addPackagePath = (packageName: string, relativeToPath: string) => { + try { + if (visitedFrameworkPackages.has(packageName)) { + return + } + visitedFrameworkPackages.add(packageName) + + const packageJsonPath = require.resolve(`${packageName}/package.json`, { + paths: [relativeToPath], + }) + + // Include a trailing slash so that a `.startsWith(packagePath)` check avoids false positives + // when one package name starts with the full name of a different package. + // For example: + // "node_modules/react-slider".startsWith("node_modules/react") // true + // "node_modules/react-slider".startsWith("node_modules/react/") // false + const directory = path.join(packageJsonPath, '../') + + // Returning from the function in case the directory has already been added and traversed + if (topLevelFrameworkPaths.includes(directory)) return + topLevelFrameworkPaths.push(directory) + + const dependencies = require(packageJsonPath).dependencies || {} + for (const name of Object.keys(dependencies)) { + addPackagePath(name, directory) + } + } catch (_) { + // don't error on failing to resolve framework packages + } + } + + for (const packageName of ['react', 'react-dom']) { + addPackagePath(packageName, dir) + } // Select appropriate SplitChunksPlugin config for this build const splitChunksConfig: webpack.Options.SplitChunksOptions | false = dev