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'