Skip to content

Commit 8108de4

Browse files
committed
Revert "Revert "Prefer module over main on main fields for app router server compiler" (#56766)"
This reverts commit 476af24.
1 parent 489a528 commit 8108de4

File tree

14 files changed

+99
-47
lines changed

14 files changed

+99
-47
lines changed

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

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ export async function resolveExternal(
4444
context: string,
4545
request: string,
4646
isEsmRequested: boolean,
47-
hasAppDir: boolean,
4847
getResolve: (
4948
options: any
5049
) => (
@@ -66,11 +65,7 @@ export async function resolveExternal(
6665

6766
let preferEsmOptions =
6867
esmExternals && isEsmRequested ? [true, false] : [false]
69-
// Disable esm resolving for app/ and pages/ so for esm package using under pages/
70-
// won't load react through esm loader
71-
if (hasAppDir) {
72-
preferEsmOptions = [false]
73-
}
68+
7469
for (const preferEsm of preferEsmOptions) {
7570
const resolve = getResolve(
7671
preferEsm ? esmResolveOptions : nodeResolveOptions
@@ -135,12 +130,10 @@ export function makeExternalHandler({
135130
config,
136131
optOutBundlingPackageRegex,
137132
dir,
138-
hasAppDir,
139133
}: {
140134
config: NextConfigComplete
141135
optOutBundlingPackageRegex: RegExp
142136
dir: string
143-
hasAppDir: boolean
144137
}) {
145138
let resolvedExternalPackageDirs: Map<string, string>
146139
const looseEsmExternals = config.experimental?.esmExternals === 'loose'
@@ -293,7 +286,6 @@ export function makeExternalHandler({
293286
context,
294287
request,
295288
isEsmRequested,
296-
hasAppDir,
297289
getResolve,
298290
isLocal ? resolveNextExternal : undefined
299291
)
@@ -353,7 +345,6 @@ export function makeExternalHandler({
353345
config.experimental.esmExternals,
354346
context,
355347
pkg + '/package.json',
356-
hasAppDir,
357348
isEsmRequested,
358349
getResolve,
359350
isLocal ? resolveNextExternal : undefined
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import {
2+
COMPILER_NAMES,
3+
type CompilerNameValues,
4+
} from '../../shared/lib/constants'
5+
6+
// exports.<conditionName>
7+
export const edgeConditionNames = [
8+
'edge-light',
9+
'worker',
10+
// inherits the default conditions
11+
'...',
12+
]
13+
14+
const mainFieldsPerCompiler: Record<
15+
CompilerNameValues | 'app-router-server',
16+
string[]
17+
> = {
18+
// For default case, prefer CJS over ESM on server side. e.g. pages dir SSR
19+
[COMPILER_NAMES.server]: ['main', 'module'],
20+
[COMPILER_NAMES.client]: ['browser', 'module', 'main'],
21+
[COMPILER_NAMES.edgeServer]: edgeConditionNames,
22+
// For app router since everything is bundled, prefer ESM over CJS
23+
'app-router-server': ['module', 'main'],
24+
}
25+
26+
export function getMainField(
27+
pageType: 'app' | 'pages',
28+
compilerType: CompilerNameValues
29+
) {
30+
if (compilerType === COMPILER_NAMES.edgeServer) {
31+
return edgeConditionNames
32+
} else if (compilerType === COMPILER_NAMES.client) {
33+
return mainFieldsPerCompiler[COMPILER_NAMES.client]
34+
}
35+
36+
// Prefer module fields over main fields for isomorphic packages on server layer
37+
return pageType === 'app'
38+
? mainFieldsPerCompiler['app-router-server']
39+
: mainFieldsPerCompiler[COMPILER_NAMES.server]
40+
}

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

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ import { needsExperimentalReact } from '../lib/needs-experimental-react'
7474
import { getDefineEnvPlugin } from './webpack/plugins/define-env-plugin'
7575
import type { SWCLoaderOptions } from './webpack/loaders/next-swc-loader'
7676
import { isResourceInPackages, makeExternalHandler } from './handle-externals'
77+
import {
78+
getMainField,
79+
edgeConditionNames,
80+
} from './webpack-config-rules/resolve'
7781

7882
type ExcludesFalse = <T>(x: T | false) => x is T
7983
type ClientEntries = {
@@ -104,21 +108,6 @@ const babelIncludeRegexes: RegExp[] = [
104108
const asyncStoragesRegex =
105109
/next[\\/]dist[\\/](esm[\\/])?client[\\/]components[\\/](static-generation-async-storage|action-async-storage|request-async-storage)/
106110

107-
// exports.<conditionName>
108-
const edgeConditionNames = [
109-
'edge-light',
110-
'worker',
111-
// inherits the default conditions
112-
'...',
113-
]
114-
115-
// packageJson.<mainField>
116-
const mainFieldsPerCompiler: Record<CompilerNameValues, string[]> = {
117-
[COMPILER_NAMES.server]: ['main', 'module'],
118-
[COMPILER_NAMES.client]: ['browser', 'module', 'main'],
119-
[COMPILER_NAMES.edgeServer]: edgeConditionNames,
120-
}
121-
122111
// Support for NODE_PATH
123112
const nodePathList = (process.env.NODE_PATH || '')
124113
.split(process.platform === 'win32' ? ';' : ':')
@@ -931,7 +920,8 @@ export default async function getBaseWebpackConfig(
931920
},
932921
}
933922
: undefined),
934-
mainFields: mainFieldsPerCompiler[compilerType],
923+
// default main fields use pages dir ones, and customize app router ones in loaders.
924+
mainFields: getMainField('pages', compilerType),
935925
...(isEdgeServer && {
936926
conditionNames: edgeConditionNames,
937927
}),
@@ -1039,7 +1029,6 @@ export default async function getBaseWebpackConfig(
10391029
config,
10401030
optOutBundlingPackageRegex,
10411031
dir,
1042-
hasAppDir,
10431032
})
10441033

10451034
const shouldIncludeExternalDirs =
@@ -1610,6 +1599,7 @@ export default async function getBaseWebpackConfig(
16101599
],
16111600
},
16121601
resolve: {
1602+
mainFields: getMainField('app', compilerType),
16131603
conditionNames: reactServerCondition,
16141604
// If missing the alias override here, the default alias will be used which aliases
16151605
// react to the direct file path, not the package name. In that case the condition
@@ -1754,6 +1744,9 @@ export default async function getBaseWebpackConfig(
17541744
],
17551745
exclude: [codeCondition.exclude],
17561746
use: swcLoaderForClientLayer,
1747+
resolve: {
1748+
mainFields: getMainField('app', compilerType),
1749+
},
17571750
},
17581751
]
17591752
: []),

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -743,7 +743,6 @@ export class TraceEntryPointsPlugin implements webpack.WebpackPluginInstance {
743743
context,
744744
request,
745745
isEsmRequested,
746-
!!this.appDirEnabled,
747746
(options) => (_: string, resRequest: string) => {
748747
return getResolve(options)(parent, resRequest, job)
749748
},

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

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,15 @@ createNextDescribe(
3939
buildCommand: 'yarn build',
4040
skipDeployment: true,
4141
},
42-
({ next, isNextDev }) => {
42+
({ next }) => {
4343
it('should be able to opt-out 3rd party packages being bundled in server components', async () => {
4444
await next.fetch('/react-server/optout').then(async (response) => {
4545
const result = await resolveStreamResponse(response)
4646
expect(result).toContain('Server: index.default')
4747
expect(result).toContain('Server subpath: subpath.default')
4848
expect(result).toContain('Client: index.default')
4949
expect(result).toContain('Client subpath: subpath.default')
50+
expect(result).toContain('opt-out-react-version: 18.2.0')
5051
})
5152
})
5253

@@ -91,24 +92,23 @@ createNextDescribe(
9192
})
9293

9394
it('should resolve 3rd party package exports based on the react-server condition', async () => {
94-
await next
95-
.fetch('/react-server/3rd-party-package')
96-
.then(async (response) => {
97-
const result = await resolveStreamResponse(response)
98-
99-
// Package should be resolved based on the react-server condition,
100-
// as well as package's internal & external dependencies.
101-
expect(result).toContain(
102-
'Server: index.react-server:react.subset:dep.server'
103-
)
104-
expect(result).toContain(
105-
'Client: index.default:react.full:dep.default'
106-
)
107-
108-
// Subpath exports should be resolved based on the condition too.
109-
expect(result).toContain('Server subpath: subpath.react-server')
110-
expect(result).toContain('Client subpath: subpath.default')
111-
})
95+
const $ = await next.render$('/react-server/3rd-party-package')
96+
97+
const result = $('body').text()
98+
99+
// Package should be resolved based on the react-server condition,
100+
// as well as package's internal & external dependencies.
101+
expect(result).toContain(
102+
'Server: index.react-server:react.subset:dep.server'
103+
)
104+
expect(result).toContain('Client: index.default:react.full:dep.default')
105+
106+
// Subpath exports should be resolved based on the condition too.
107+
expect(result).toContain('Server subpath: subpath.react-server')
108+
expect(result).toContain('Client subpath: subpath.default')
109+
110+
// Prefer `module` field for isomorphic packages.
111+
expect($('#main-field').text()).toContain('server-module-field:module')
112112
})
113113

114114
it('should correctly collect global css imports and mark them as side effects', async () => {

test/e2e/app-dir/app-external/app/react-server/3rd-party-package/page.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import v from 'conditional-exports'
22
import v1 from 'conditional-exports/subpath'
3+
import { name as serverFieldName } from 'server-module-field'
34

45
import Client from './client'
56

@@ -11,6 +12,8 @@ export default function Page() {
1112
{`Server subpath: ${v1}`}
1213
<br />
1314
<Client />
15+
<br />
16+
<div id="main-field">{`Server module field: ${serverFieldName}`}</div>
1417
</div>
1518
)
1619
}

test/e2e/app-dir/app-external/app/react-server/optout/page.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import v from 'conditional-exports-optout'
22
import v1 from 'conditional-exports-optout/subpath'
3+
import { getReactVersion } from 'conditional-exports-optout/react'
34

45
import Client from './client'
56

@@ -11,6 +12,9 @@ export default function Page() {
1112
{`Server subpath: ${v1}`}
1213
<br />
1314
<Client />
15+
<p id="optout-react-version">
16+
{`opt-out-react-version: ${getReactVersion()}`}
17+
</p>
1418
</div>
1519
)
1620
}

test/e2e/app-dir/app-external/node_modules_bak/conditional-exports-optout/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
"react-server": "./subpath.server.js",
1111
"default": "./subpath.js"
1212
},
13+
"./react": {
14+
"import": "./react.js"
15+
},
1316
"./package.json": "./package.json"
1417
}
1518
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import React from 'react'
2+
3+
export function getReactVersion() {
4+
return React.version
5+
}

test/e2e/app-dir/app-external/node_modules_bak/conditional-exports/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
"react-server": "./subpath.server.js",
1717
"default": "./subpath.js"
1818
},
19+
"./react": {
20+
"import": "./react.js"
21+
},
1922
"./package.json": "./package.json"
2023
}
2124
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import React from 'react'
2+
3+
export function getReactVersion() {
4+
return React.version
5+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
exports.name = 'server-module-field:main'
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const name = 'server-module-field:module'
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"main": "./index.cjs",
3+
"module": "./index.esm.js"
4+
}

0 commit comments

Comments
 (0)