Skip to content

Commit ed38f03

Browse files
authored
Fix barrel optimizer conflicts with client entry module (vercel#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 vercel#54967. Closes vercel#55609. Closes vercel#55566.
1 parent 4e44598 commit ed38f03

File tree

7 files changed

+42
-3
lines changed

7 files changed

+42
-3
lines changed

packages/next/src/build/webpack-config.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import {
3737
SERVER_DIRECTORY,
3838
COMPILER_NAMES,
3939
CompilerNameValues,
40+
BARREL_OPTIMIZATION_PREFIX,
4041
} from '../shared/lib/constants'
4142
import { execOnce } from '../shared/lib/utils'
4243
import { NextConfigComplete } from '../server/config-shared'
@@ -1217,9 +1218,9 @@ export default async function getBaseWebpackConfig(
12171218
return
12181219
}
12191220

1220-
// __barrel_optimize__ is a special marker that tells Next.js to
1221+
// BARREL_OPTIMIZATION_PREFIX is a special marker that tells Next.js to
12211222
// optimize the import by removing unused exports. This has to be compiled.
1222-
if (request.startsWith('__barrel_optimize__')) {
1223+
if (request.startsWith(BARREL_OPTIMIZATION_PREFIX)) {
12231224
return
12241225
}
12251226

packages/next/src/build/webpack/plugins/flight-client-entry-plugin.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
import { WEBPACK_LAYERS } from '../../../lib/constants'
1818
import {
1919
APP_CLIENT_INTERNALS,
20+
BARREL_OPTIMIZATION_PREFIX,
2021
COMPILER_NAMES,
2122
EDGE_RUNTIME_WEBPACK,
2223
SERVER_REFERENCE_MANIFEST,
@@ -510,9 +511,17 @@ export class FlightClientEntryPlugin {
510511
// We have to always use the resolved request here to make sure the
511512
// server and client are using the same module path (required by RSC), as
512513
// the server compiler and client compiler have different resolve configs.
513-
const modRequest: string | undefined =
514+
let modRequest: string | undefined =
514515
mod.resourceResolveData?.path + mod.resourceResolveData?.query
515516

517+
// For the barrel optimization, we need to use the match resource instead
518+
// because there will be 2 modules for the same file (same resource path)
519+
// but they're different modules and can't be deduped via `visitedModule`.
520+
// The first module is a virtual re-export module created by the loader.
521+
if (mod.matchResource?.startsWith(BARREL_OPTIMIZATION_PREFIX)) {
522+
modRequest = mod.matchResource + ':' + modRequest
523+
}
524+
516525
if (!modRequest || visitedModule.has(modRequest)) return
517526
visitedModule.add(modRequest)
518527

@@ -596,6 +605,14 @@ export class FlightClientEntryPlugin {
596605
modRequest = (mod as any)._identifier
597606
}
598607

608+
// For the barrel optimization, we need to use the match resource instead
609+
// because there will be 2 modules for the same file (same resource path)
610+
// but they're different modules and can't be deduped via `visitedModule`.
611+
// The first module is a virtual re-export module created by the loader.
612+
if (mod.matchResource?.startsWith(BARREL_OPTIMIZATION_PREFIX)) {
613+
modRequest = mod.matchResource + ':' + modRequest
614+
}
615+
599616
if (!modRequest || visited.has(modRequest)) return
600617
visited.add(modRequest)
601618

packages/next/src/shared/lib/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ export const CLIENT_PUBLIC_FILES_PATH = 'public'
6161
export const CLIENT_STATIC_FILES_PATH = 'static'
6262
export const STRING_LITERAL_DROP_BUNDLE = '__NEXT_DROP_CLIENT_FILE__'
6363
export const NEXT_BUILTIN_DOCUMENT = '__NEXT_BUILTIN_DOCUMENT__'
64+
export const BARREL_OPTIMIZATION_PREFIX = '__barrel_optimize__'
6465

6566
// server/[entry]/page_client-reference-manifest.js
6667
export const CLIENT_REFERENCE_MANIFEST = 'client-reference-manifest'

test/development/basic/barrel-optimization.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,4 +126,9 @@ describe('optimizePackageImports', () => {
126126
const html = await next.render('/visx')
127127
expect(html).toContain('<linearGradient')
128128
})
129+
130+
it('should not break "use client" directive in optimized packages', async () => {
131+
const html = await next.render('/client')
132+
expect(html).toContain('this is a client component')
133+
})
129134
})
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { Client } from 'my-lib'
2+
3+
export default function Page() {
4+
return (
5+
<h1>
6+
<Client />
7+
</h1>
8+
)
9+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
'use client'
2+
3+
export function Client() {
4+
return 'this is a client component'
5+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export * from './a'
2+
export * from './client'

0 commit comments

Comments
 (0)