Skip to content

Commit 276533d

Browse files
Eagerly bundle external ESM dependencies for pages (#42741)
One potential risk is ESM dependencies that can't be bundled will cause a build error. This also means that the `esmExternals` configuration will be affected. Closes #42249, closes #42588. Related: #1395. ## Bug - [ ] Related issues linked using `fixes #number` - [x] Integration tests added - [ ] Errors have a helpful link attached, see `contributing.md` ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have a helpful link attached, see `contributing.md` ## Documentation / Examples - [ ] Make sure the linting passes by running `pnpm build && pnpm lint` - [ ] The "examples guidelines" are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
1 parent 6ab52ed commit 276533d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+94
-7
lines changed

packages/next/build/webpack-config.ts

+10-5
Original file line numberDiff line numberDiff line change
@@ -1256,11 +1256,16 @@ export default async function getBaseWebpackConfig(
12561256
}
12571257
}
12581258

1259-
const shouldBeBundled = isResourceInPackages(
1260-
res,
1261-
config.experimental.transpilePackages,
1262-
resolvedExternalPackageDirs
1263-
)
1259+
// If a package is included in `transpilePackages`, we don't want to make it external.
1260+
// And also, if that resource is an ES module, we bundle it too because we can't
1261+
// rely on the require hook to alias `react` to our precompiled version.
1262+
const shouldBeBundled =
1263+
isResourceInPackages(
1264+
res,
1265+
config.experimental.transpilePackages,
1266+
resolvedExternalPackageDirs
1267+
) ||
1268+
(isEsm && config.experimental.appDir)
12641269

12651270
if (/node_modules[/\\].*\.[mc]?js$/.test(res)) {
12661271
if (layer === WEBPACK_LAYERS.server) {

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

+28-2
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ async function resolveStreamResponse(response: any, onData?: any) {
1818
return result
1919
}
2020

21-
describe('app dir - rsc external dependency', () => {
21+
describe('app dir - external dependency', () => {
2222
let next: NextInstance
2323

2424
if ((global as any).isNextDeploy) {
@@ -28,7 +28,7 @@ describe('app dir - rsc external dependency', () => {
2828

2929
beforeAll(async () => {
3030
next = await createNext({
31-
files: new FileRef(path.join(__dirname, './rsc-external')),
31+
files: new FileRef(path.join(__dirname, './app-external')),
3232
dependencies: {
3333
'@next/font': 'canary',
3434
react: 'latest',
@@ -166,4 +166,30 @@ describe('app dir - rsc external dependency', () => {
166166
)
167167
).toMatch(/^__myFont_.{6}, __myFont_Fallback_.{6}$/)
168168
})
169+
170+
describe('react in external esm packages', () => {
171+
it('should use the same react in client app', async () => {
172+
const html = await renderViaHTTP(next.url, '/esm/client')
173+
174+
const v1 = html.match(/App React Version: ([^<]+)</)[1]
175+
const v2 = html.match(/External React Version: ([^<]+)</)[1]
176+
expect(v1).toBe(v2)
177+
})
178+
179+
it('should use the same react in server app', async () => {
180+
const html = await renderViaHTTP(next.url, '/esm/server')
181+
182+
const v1 = html.match(/App React Version: ([^<]+)</)[1]
183+
const v2 = html.match(/External React Version: ([^<]+)</)[1]
184+
expect(v1).toBe(v2)
185+
})
186+
187+
it('should use the same react in pages', async () => {
188+
const html = await renderViaHTTP(next.url, '/test-pages-esm')
189+
190+
const v1 = html.match(/App React Version: ([^<]+)</)[1]
191+
const v2 = html.match(/External React Version: ([^<]+)</)[1]
192+
expect(v1).toBe(v2)
193+
})
194+
})
169195
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
'use client'
2+
import { version, useValue } from 'esm-with-react'
3+
4+
import React from 'react'
5+
6+
export default function Index() {
7+
const value = useValue()
8+
return (
9+
<div>
10+
<h2>{'App React Version: ' + React.version}</h2>
11+
<h2>{'External React Version: ' + version}</h2>
12+
<h2>{'Test: ' + value}</h2>
13+
</div>
14+
)
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { version } from 'esm-with-react'
2+
3+
import React from 'react'
4+
5+
export default function Index() {
6+
return (
7+
<div>
8+
<h2>{'App React Version: ' + React.version}</h2>
9+
<h2>{'External React Version: ' + version}</h2>
10+
</div>
11+
)
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import React from 'react'
2+
3+
const Context = React.createContext ? React.createContext('hello') : null
4+
5+
export const version = React.version
6+
export const useValue = () => React.useContext(Context)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"name": "esm-with-react",
3+
"type": "module",
4+
"exports": "./index.js",
5+
"dependencies": {
6+
"react": "^18",
7+
"react-dom": "^18"
8+
}
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { version, useValue } from 'esm-with-react'
2+
3+
import React from 'react'
4+
5+
export default function Index() {
6+
const value = useValue()
7+
return (
8+
<div>
9+
<h2>{'App React Version: ' + React.version}</h2>
10+
<h2>{'External React Version: ' + version}</h2>
11+
<h2>{'Test: ' + value}</h2>
12+
</div>
13+
)
14+
}

0 commit comments

Comments
 (0)