From ed38f03e4616df3cd370cbe3c5869c6857570988 Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Tue, 26 Sep 2023 18:03:51 +0200 Subject: [PATCH] Fix barrel optimizer conflicts with client entry module (#56020) The barrel optimization loader creates a virtual module to re-export from the original file, which causes the situation that now there are 2 modules with the same resource but only one of them is the actual code. When the code contains `"use client"`, the Flight plugin has to collect its `buildInfo` and generate the manifest and client entry module. However, we currently deduplicate module by resource in the traversal logic to avoid unnecessary loops. To make it work together with the virtual barrel module, we'll need to prefix the resource path to make it different from the original module. Closes #54967. Closes #55609. Closes #55566. --- packages/next/src/build/webpack-config.ts | 5 +++-- .../plugins/flight-client-entry-plugin.ts | 19 ++++++++++++++++++- packages/next/src/shared/lib/constants.ts | 1 + .../basic/barrel-optimization.test.ts | 5 +++++ .../barrel-optimization/app/client/page.js | 9 +++++++++ .../node_modules_bak/my-lib/client.js | 5 +++++ .../node_modules_bak/my-lib/index.js | 1 + 7 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 test/development/basic/barrel-optimization/app/client/page.js create mode 100644 test/development/basic/barrel-optimization/node_modules_bak/my-lib/client.js diff --git a/packages/next/src/build/webpack-config.ts b/packages/next/src/build/webpack-config.ts index 86240d87f9071..2e49b5b2692ec 100644 --- a/packages/next/src/build/webpack-config.ts +++ b/packages/next/src/build/webpack-config.ts @@ -37,6 +37,7 @@ import { SERVER_DIRECTORY, COMPILER_NAMES, CompilerNameValues, + BARREL_OPTIMIZATION_PREFIX, } from '../shared/lib/constants' import { execOnce } from '../shared/lib/utils' import { NextConfigComplete } from '../server/config-shared' @@ -1217,9 +1218,9 @@ export default async function getBaseWebpackConfig( return } - // __barrel_optimize__ is a special marker that tells Next.js to + // BARREL_OPTIMIZATION_PREFIX is a special marker that tells Next.js to // optimize the import by removing unused exports. This has to be compiled. - if (request.startsWith('__barrel_optimize__')) { + if (request.startsWith(BARREL_OPTIMIZATION_PREFIX)) { return } diff --git a/packages/next/src/build/webpack/plugins/flight-client-entry-plugin.ts b/packages/next/src/build/webpack/plugins/flight-client-entry-plugin.ts index 7927f2748752e..0c2e964c7439e 100644 --- a/packages/next/src/build/webpack/plugins/flight-client-entry-plugin.ts +++ b/packages/next/src/build/webpack/plugins/flight-client-entry-plugin.ts @@ -17,6 +17,7 @@ import { import { WEBPACK_LAYERS } from '../../../lib/constants' import { APP_CLIENT_INTERNALS, + BARREL_OPTIMIZATION_PREFIX, COMPILER_NAMES, EDGE_RUNTIME_WEBPACK, SERVER_REFERENCE_MANIFEST, @@ -510,9 +511,17 @@ export class FlightClientEntryPlugin { // We have to always use the resolved request here to make sure the // server and client are using the same module path (required by RSC), as // the server compiler and client compiler have different resolve configs. - const modRequest: string | undefined = + let modRequest: string | undefined = mod.resourceResolveData?.path + mod.resourceResolveData?.query + // For the barrel optimization, we need to use the match resource instead + // because there will be 2 modules for the same file (same resource path) + // but they're different modules and can't be deduped via `visitedModule`. + // The first module is a virtual re-export module created by the loader. + if (mod.matchResource?.startsWith(BARREL_OPTIMIZATION_PREFIX)) { + modRequest = mod.matchResource + ':' + modRequest + } + if (!modRequest || visitedModule.has(modRequest)) return visitedModule.add(modRequest) @@ -596,6 +605,14 @@ export class FlightClientEntryPlugin { modRequest = (mod as any)._identifier } + // For the barrel optimization, we need to use the match resource instead + // because there will be 2 modules for the same file (same resource path) + // but they're different modules and can't be deduped via `visitedModule`. + // The first module is a virtual re-export module created by the loader. + if (mod.matchResource?.startsWith(BARREL_OPTIMIZATION_PREFIX)) { + modRequest = mod.matchResource + ':' + modRequest + } + if (!modRequest || visited.has(modRequest)) return visited.add(modRequest) diff --git a/packages/next/src/shared/lib/constants.ts b/packages/next/src/shared/lib/constants.ts index 86a1c2059b585..eef3be8b70913 100644 --- a/packages/next/src/shared/lib/constants.ts +++ b/packages/next/src/shared/lib/constants.ts @@ -61,6 +61,7 @@ export const CLIENT_PUBLIC_FILES_PATH = 'public' export const CLIENT_STATIC_FILES_PATH = 'static' export const STRING_LITERAL_DROP_BUNDLE = '__NEXT_DROP_CLIENT_FILE__' export const NEXT_BUILTIN_DOCUMENT = '__NEXT_BUILTIN_DOCUMENT__' +export const BARREL_OPTIMIZATION_PREFIX = '__barrel_optimize__' // server/[entry]/page_client-reference-manifest.js export const CLIENT_REFERENCE_MANIFEST = 'client-reference-manifest' diff --git a/test/development/basic/barrel-optimization.test.ts b/test/development/basic/barrel-optimization.test.ts index 7673773f13705..fe18298a59dcf 100644 --- a/test/development/basic/barrel-optimization.test.ts +++ b/test/development/basic/barrel-optimization.test.ts @@ -126,4 +126,9 @@ describe('optimizePackageImports', () => { const html = await next.render('/visx') expect(html).toContain(' { + const html = await next.render('/client') + expect(html).toContain('this is a client component') + }) }) diff --git a/test/development/basic/barrel-optimization/app/client/page.js b/test/development/basic/barrel-optimization/app/client/page.js new file mode 100644 index 0000000000000..04479b2fbba37 --- /dev/null +++ b/test/development/basic/barrel-optimization/app/client/page.js @@ -0,0 +1,9 @@ +import { Client } from 'my-lib' + +export default function Page() { + return ( +

+ +

+ ) +} diff --git a/test/development/basic/barrel-optimization/node_modules_bak/my-lib/client.js b/test/development/basic/barrel-optimization/node_modules_bak/my-lib/client.js new file mode 100644 index 0000000000000..c0132a9655020 --- /dev/null +++ b/test/development/basic/barrel-optimization/node_modules_bak/my-lib/client.js @@ -0,0 +1,5 @@ +'use client' + +export function Client() { + return 'this is a client component' +} diff --git a/test/development/basic/barrel-optimization/node_modules_bak/my-lib/index.js b/test/development/basic/barrel-optimization/node_modules_bak/my-lib/index.js index 21c83d4780fc6..40ed72505157f 100644 --- a/test/development/basic/barrel-optimization/node_modules_bak/my-lib/index.js +++ b/test/development/basic/barrel-optimization/node_modules_bak/my-lib/index.js @@ -1 +1,2 @@ export * from './a' +export * from './client'