Skip to content

Commit 2874bc0

Browse files
authored
Fix server output bundling packages module resolving (#59369)
1 parent 42ec6c8 commit 2874bc0

File tree

10 files changed

+80
-4
lines changed

10 files changed

+80
-4
lines changed

packages/next/src/build/handle-externals.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export async function resolveExternal(
4747
context: string,
4848
request: string,
4949
isEsmRequested: boolean,
50+
optOutBundlingPackages: string[],
5051
getResolve: (
5152
options: any
5253
) => (
@@ -66,8 +67,15 @@ export async function resolveExternal(
6667
let res: string | null = null
6768
let isEsm: boolean = false
6869

69-
let preferEsmOptions =
70-
esmExternals && isEsmRequested ? [true, false] : [false]
70+
const preferEsmOptions =
71+
esmExternals &&
72+
isEsmRequested &&
73+
// For package that marked as externals that should be not bundled,
74+
// we don't resolve them as ESM since it could be resolved as async module,
75+
// such as `import(external package)` in the bundle, valued as a `Promise`.
76+
!optOutBundlingPackages.some((optOut) => request.startsWith(optOut))
77+
? [true, false]
78+
: [false]
7179

7280
for (const preferEsm of preferEsmOptions) {
7381
const resolve = getResolve(
@@ -131,10 +139,12 @@ export async function resolveExternal(
131139

132140
export function makeExternalHandler({
133141
config,
142+
optOutBundlingPackages,
134143
optOutBundlingPackageRegex,
135144
dir,
136145
}: {
137146
config: NextConfigComplete
147+
optOutBundlingPackages: string[]
138148
optOutBundlingPackageRegex: RegExp
139149
dir: string
140150
}) {
@@ -289,6 +299,7 @@ export function makeExternalHandler({
289299
context,
290300
request,
291301
isEsmRequested,
302+
optOutBundlingPackages,
292303
getResolve,
293304
isLocal ? resolveNextExternal : undefined
294305
)
@@ -349,6 +360,7 @@ export function makeExternalHandler({
349360
context,
350361
pkg + '/package.json',
351362
isEsmRequested,
363+
optOutBundlingPackages,
352364
getResolve,
353365
isLocal ? resolveNextExternal : undefined
354366
)

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -727,9 +727,11 @@ export default async function getBaseWebpackConfig(
727727

728728
const crossOrigin = config.crossOrigin
729729

730+
// For original request, such as `package name`
730731
const optOutBundlingPackages = EXTERNAL_PACKAGES.concat(
731732
...(config.experimental.serverComponentsExternalPackages || [])
732733
)
734+
// For resolved request, such as `absolute path/package name/foo/bar.js`
733735
const optOutBundlingPackageRegex = new RegExp(
734736
`[/\\\\]node_modules[/\\\\](${optOutBundlingPackages
735737
.map((p) => p.replace(/\//g, '[/\\\\]'))
@@ -738,6 +740,7 @@ export default async function getBaseWebpackConfig(
738740

739741
const handleExternals = makeExternalHandler({
740742
config,
743+
optOutBundlingPackages,
741744
optOutBundlingPackageRegex,
742745
dir,
743746
})
@@ -1662,6 +1665,7 @@ export default async function getBaseWebpackConfig(
16621665
outputFileTracingRoot: config.experimental.outputFileTracingRoot,
16631666
appDirEnabled: hasAppDir,
16641667
turbotrace: config.experimental.turbotrace,
1668+
optOutBundlingPackages,
16651669
traceIgnores: config.experimental.outputFileTracingIgnores || [],
16661670
}
16671671
),

packages/next/src/build/webpack/plugins/next-trace-entrypoints-plugin.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ export class TraceEntryPointsPlugin implements webpack.WebpackPluginInstance {
133133
private rootDir: string
134134
private appDir: string | undefined
135135
private pagesDir: string | undefined
136+
private optOutBundlingPackages: string[]
136137
private appDirEnabled?: boolean
137138
private tracingRoot: string
138139
private entryTraces: Map<string, Set<string>>
@@ -144,6 +145,7 @@ export class TraceEntryPointsPlugin implements webpack.WebpackPluginInstance {
144145
rootDir,
145146
appDir,
146147
pagesDir,
148+
optOutBundlingPackages,
147149
appDirEnabled,
148150
traceIgnores,
149151
esmExternals,
@@ -153,6 +155,7 @@ export class TraceEntryPointsPlugin implements webpack.WebpackPluginInstance {
153155
rootDir: string
154156
appDir: string | undefined
155157
pagesDir: string | undefined
158+
optOutBundlingPackages: string[]
156159
appDirEnabled?: boolean
157160
traceIgnores?: string[]
158161
outputFileTracingRoot?: string
@@ -168,6 +171,7 @@ export class TraceEntryPointsPlugin implements webpack.WebpackPluginInstance {
168171
this.traceIgnores = traceIgnores || []
169172
this.tracingRoot = outputFileTracingRoot || rootDir
170173
this.turbotrace = turbotrace
174+
this.optOutBundlingPackages = optOutBundlingPackages
171175
}
172176

173177
// Here we output all traced assets and webpack chunks to a
@@ -743,6 +747,7 @@ export class TraceEntryPointsPlugin implements webpack.WebpackPluginInstance {
743747
context,
744748
request,
745749
isEsmRequested,
750+
this.optOutBundlingPackages,
746751
(options) => (_: string, resRequest: string) => {
747752
return getResolve(options)(parent, resRequest, job)
748753
},

test/e2e/app-dir/app-external/app-external.test.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { createNextDescribe } from 'e2e-utils'
2-
import { shouldRunTurboDevTest } from '../../../lib/next-test-utils'
2+
import { check, shouldRunTurboDevTest } from 'next-test-utils'
33

44
async function resolveStreamResponse(response: any, onData?: any) {
55
let result = ''
@@ -250,5 +250,22 @@ createNextDescribe(
250250
const html = await next.render('/async-storage')
251251
expect(html).toContain('success')
252252
})
253+
254+
it('should not prefer to resolve esm over cjs for bundling optout packages', async () => {
255+
const browser = await next.browser('/optout/action')
256+
expect(await browser.elementByCss('#dual-pkg-outout p').text()).toBe('')
257+
258+
browser.elementByCss('#dual-pkg-outout button').click()
259+
await check(async () => {
260+
const text = await browser.elementByCss('#dual-pkg-outout p').text()
261+
if (process.env.TURBOPACK) {
262+
// The prefer esm won't effect turbopack resolving
263+
expect(text).toBe('dual-pkg-optout:mjs')
264+
} else {
265+
expect(text).toBe('dual-pkg-optout:cjs')
266+
}
267+
return 'success'
268+
}, /success/)
269+
})
253270
}
254271
)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
'use server'
2+
3+
import { value as dualPkgOptoutValue } from 'dual-pkg-optout'
4+
5+
export async function getDualOptoutValue() {
6+
return dualPkgOptoutValue
7+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
'use client'
2+
3+
import { useState } from 'react'
4+
import { getDualOptoutValue } from './actions'
5+
6+
export default function Page() {
7+
const [optoutDisplayValue, setOptoutDisplayValue] = useState('')
8+
return (
9+
<div id="dual-pkg-outout">
10+
<p>{optoutDisplayValue}</p>
11+
<button
12+
onClick={async () => {
13+
setOptoutDisplayValue(await getDualOptoutValue())
14+
}}
15+
>
16+
dual-pkg-optout
17+
</button>
18+
</div>
19+
)
20+
}

test/e2e/app-dir/app-external/next.config.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ module.exports = {
22
reactStrictMode: true,
33
transpilePackages: ['untranspiled-module', 'css', 'font'],
44
experimental: {
5-
serverComponentsExternalPackages: ['conditional-exports-optout'],
5+
serverComponentsExternalPackages: [
6+
'conditional-exports-optout',
7+
'dual-pkg-optout',
8+
],
69
},
710
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
exports.value = 'dual-pkg-optout:cjs'
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const value = 'dual-pkg-optout:mjs'
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"exports": {
3+
"import": "./index.mjs",
4+
"require": "./index.cjs"
5+
}
6+
}

0 commit comments

Comments
 (0)