Skip to content

Commit b0f87fb

Browse files
huozhishudingijjk
authored
Bundle ssr client layer excepts react externals (vercel#41606)
Bundle the ssr client layer for RSC, this solves the problem when there's an esm package is using on client components, but esm imports the installed react instead of the built-in react version since esm imports is not intercepted by require hook. After bundling the ssr client layer and treating react as externals, now react compiles as cjs externals and could be intercepted by require hook, other code are bundled together which can also get optimized. ## 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` - [x] 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` Co-authored-by: Shu Ding <g@shud.in> Co-authored-by: JJ Kasper <jj@jjsweb.site>
1 parent 73499e4 commit b0f87fb

File tree

85 files changed

+131391
-298
lines changed

Some content is hidden

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

85 files changed

+131391
-298
lines changed

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@
3939
"lint-staged": "lint-staged",
4040
"next-with-deps": "./scripts/next-with-deps.sh",
4141
"next": "node --trace-deprecation --enable-source-maps packages/next/dist/bin/next",
42-
"next-react-exp": "__NEXT_REACT_CHANNEL=exp node --trace-deprecation --enable-source-maps -r ./test/lib/react-channel-require-hook.js packages/next/dist/bin/next",
4342
"next-no-sourcemaps": "node --trace-deprecation packages/next/dist/bin/next",
4443
"clean-trace-jaeger": "rm -rf test/integration/basic/.next && TRACE_TARGET=JAEGER node --trace-deprecation --enable-source-maps packages/next/dist/bin/next build test/integration/basic",
4544
"debug": "node --inspect packages/next/dist/bin/next",
@@ -179,10 +178,10 @@
179178
"random-seed": "0.3.0",
180179
"react": "18.2.0",
181180
"react-17": "npm:react@17.0.2",
181+
"react-builtin": "npm:react@0.0.0-experimental-9cdf8a99e-20221018",
182182
"react-dom": "18.2.0",
183183
"react-dom-17": "npm:react-dom@17.0.2",
184-
"react-exp": "npm:react@0.0.0-experimental-9cdf8a99e-20221018",
185-
"react-dom-exp": "npm:react-dom@0.0.0-experimental-9cdf8a99e-20221018",
184+
"react-dom-builtin": "npm:react-dom@0.0.0-experimental-9cdf8a99e-20221018",
186185
"react-server-dom-webpack": "0.0.0-experimental-9cdf8a99e-20221018",
187186
"react-ssr-prepass": "1.0.8",
188187
"react-virtualized": "9.22.3",
@@ -198,6 +197,7 @@
198197
"shell-quote": "1.7.3",
199198
"styled-components": "5.3.3",
200199
"styled-jsx-plugin-postcss": "3.0.2",
200+
"swr": "2.0.0-rc.0",
201201
"tailwindcss": "1.1.3",
202202
"taskr": "1.1.0",
203203
"tree-kill": "1.2.2",

packages/next/build/entries.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ export function getEdgeServerEntry(opts: {
207207
// The Edge bundle includes the server in its entrypoint, so it has to
208208
// be in the SSR layer — we later convert the page request to the RSC layer
209209
// via a webpack rule.
210-
layer: undefined,
210+
layer: WEBPACK_LAYERS.client,
211211
}
212212
}
213213

packages/next/build/index.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,6 @@ import { RemotePattern } from '../shared/lib/image-config'
120120
import { eventSwcPlugins } from '../telemetry/events/swc-plugins'
121121
import { normalizeAppPath } from '../shared/lib/router/utils/app-paths'
122122
import { AppBuildManifest } from './webpack/plugins/app-build-manifest-plugin'
123-
import { verifyAppReactVersion } from '../lib/verifyAppReactVersion'
124123

125124
export type SsgRoute = {
126125
initialRevalidateSeconds: number | false
@@ -307,10 +306,6 @@ export default async function build(
307306
process.env.NEXT_PREBUNDLED_REACT = '1'
308307
}
309308
const { pagesDir, appDir } = findPagesDir(dir, isAppDirEnabled)
310-
311-
if (isAppDirEnabled) {
312-
await verifyAppReactVersion({ dir })
313-
}
314309
const hasPublicDir = await fileExists(publicDir)
315310

316311
telemetry.record(

packages/next/build/webpack-config.ts

Lines changed: 76 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -125,9 +125,11 @@ function isResourceInPackages(resource: string, packageNames?: string[]) {
125125
)
126126
}
127127

128-
const builtInReactImports = [
128+
const bundledReactImports = [
129129
'react',
130130
'react/jsx-runtime',
131+
'react/jsx-dev-runtime',
132+
// 'react-dom',
131133
'next/dist/compiled/react-server-dom-webpack/server.browser',
132134
]
133135

@@ -837,9 +839,6 @@ export default async function getBaseWebpackConfig(
837839
[COMPILER_NAMES.edgeServer]: ['browser', 'module', 'main'],
838840
}
839841

840-
const reactDir = path.dirname(require.resolve('react/package.json'))
841-
const reactDomDir = path.dirname(require.resolve('react-dom/package.json'))
842-
843842
const resolveConfig = {
844843
// Disable .mjs for node_modules bundling
845844
extensions: isNodeServer
@@ -878,25 +877,19 @@ export default async function getBaseWebpackConfig(
878877

879878
next: NEXT_PROJECT_ROOT,
880879

881-
// Disable until pre-bundling is landed
882-
// ...(hasServerComponents
883-
// ? {
884-
// // For react and react-dom, alias them dynamically for server layer
885-
// // and others in the loaders configuration
886-
// 'react-dom/client$': 'next/dist/compiled/react-dom/client',
887-
// 'react-dom/server$': 'next/dist/compiled/react-dom/server',
888-
// 'react-dom/server.browser$':
889-
// 'next/dist/compiled/react-dom/server.browser',
890-
// 'react/jsx-dev-runtime$':
891-
// 'next/dist/compiled/react/jsx-dev-runtime',
892-
// 'react/jsx-runtime$': 'next/dist/compiled/react/jsx-runtime',
893-
// }
894-
// : undefined),
895-
react: reactDir,
896-
'react-dom$': reactDomDir,
897-
'react-dom/server$': `${reactDomDir}/server`,
898-
'react-dom/server.browser$': `${reactDomDir}/server.browser`,
899-
'react-dom/client$': `${reactDomDir}/client`,
880+
...(hasServerComponents
881+
? {
882+
// For react and react-dom, alias them dynamically for server layer
883+
// and others in the loaders configuration
884+
'react-dom/client$': 'next/dist/compiled/react-dom/client',
885+
'react-dom/server$': 'next/dist/compiled/react-dom/server',
886+
'react-dom/server.browser$':
887+
'next/dist/compiled/react-dom/server.browser',
888+
'react/jsx-dev-runtime$':
889+
'next/dist/compiled/react/jsx-dev-runtime',
890+
'react/jsx-runtime$': 'next/dist/compiled/react/jsx-runtime',
891+
}
892+
: undefined),
900893

901894
'styled-jsx/style$': require.resolve(`styled-jsx/style`),
902895
'styled-jsx$': require.resolve(`styled-jsx`),
@@ -1027,7 +1020,7 @@ export default async function getBaseWebpackConfig(
10271020
const crossOrigin = config.crossOrigin
10281021
const looseEsmExternals = config.experimental?.esmExternals === 'loose'
10291022

1030-
const optoutBundlingPackages = EXTERNAL_PACKAGES.concat(
1023+
const optOutBundlingPackages = EXTERNAL_PACKAGES.concat(
10311024
...(config.experimental.serverComponentsExternalPackages || [])
10321025
)
10331026

@@ -1062,7 +1055,7 @@ export default async function getBaseWebpackConfig(
10621055

10631056
// Special internal modules that must be bundled for Server Components.
10641057
if (layer === WEBPACK_LAYERS.server) {
1065-
if (builtInReactImports.includes(request)) {
1058+
if (bundledReactImports.includes(request)) {
10661059
return
10671060
}
10681061
}
@@ -1197,13 +1190,31 @@ export default async function getBaseWebpackConfig(
11971190
if (layer === WEBPACK_LAYERS.server) {
11981191
// All packages should be bundled for the server layer if they're not opted out.
11991192
// This option takes priority over the transpilePackages option.
1200-
if (isResourceInPackages(res, optoutBundlingPackages)) {
1193+
if (isResourceInPackages(res, optOutBundlingPackages)) {
12011194
return `${externalType} ${request}`
12021195
}
12031196

12041197
return
12051198
}
12061199

1200+
// Treat react packages as external for SSR layer,
1201+
// then let require-hook mapping them to internals.
1202+
if (layer === WEBPACK_LAYERS.client) {
1203+
if (
1204+
[
1205+
'react',
1206+
'react/jsx-runtime',
1207+
'react/jsx-dev-runtime',
1208+
'react-dom',
1209+
'scheduler',
1210+
].includes(request)
1211+
) {
1212+
return `commonjs next/dist/compiled/${request}`
1213+
} else {
1214+
return
1215+
}
1216+
}
1217+
12071218
if (shouldBeBundled) return
12081219

12091220
// Anything else that is standard JavaScript within `node_modules`
@@ -1569,24 +1580,19 @@ export default async function getBaseWebpackConfig(
15691580

15701581
return true
15711582
},
1572-
resolve: process.env.__NEXT_REACT_CHANNEL
1573-
? {
1574-
conditionNames: ['react-server', 'node', 'require'],
1575-
alias: {
1576-
react: `react-${process.env.__NEXT_REACT_CHANNEL}`,
1577-
'react-dom': `react-dom-${process.env.__NEXT_REACT_CHANNEL}`,
1578-
},
1579-
}
1580-
: {
1581-
conditionNames: ['react-server', 'node', 'require'],
1582-
alias: {
1583-
// If missing the alias override here, the default alias will be used which aliases
1584-
// react to the direct file path, not the package name. In that case the condition
1585-
// will be ignored completely.
1586-
react: 'react',
1587-
'react-dom': 'react-dom',
1588-
},
1589-
},
1583+
resolve: {
1584+
conditionNames: ['react-server', 'node', 'require'],
1585+
alias: {
1586+
// If missing the alias override here, the default alias will be used which aliases
1587+
// react to the direct file path, not the package name. In that case the condition
1588+
// will be ignored completely.
1589+
react: 'next/dist/compiled/react',
1590+
'react-dom$': isClient
1591+
? 'next/dist/compiled/react-dom/index'
1592+
: 'next/dist/compiled/react-dom/server-rendering-stub',
1593+
'react-dom/client$': 'next/dist/compiled/react-dom/client',
1594+
},
1595+
},
15901596
},
15911597
]
15921598
: []),
@@ -1613,7 +1619,6 @@ export default async function getBaseWebpackConfig(
16131619
},
16141620
]
16151621
: []),
1616-
16171622
...(hasServerComponents && !isClient
16181623
? [
16191624
// RSC server compilation loaders
@@ -1636,7 +1641,12 @@ export default async function getBaseWebpackConfig(
16361641
? [
16371642
{
16381643
test: codeCondition.test,
1639-
include: [appDir],
1644+
issuerLayer(layer: string) {
1645+
return (
1646+
layer === WEBPACK_LAYERS.client ||
1647+
layer === WEBPACK_LAYERS.server
1648+
)
1649+
},
16401650
resolve: {
16411651
alias: {
16421652
// Alias `next/dynamic` to React.lazy implementation for RSC
@@ -1650,16 +1660,15 @@ export default async function getBaseWebpackConfig(
16501660
// Alias react-dom for ReactDOM.preload usage.
16511661
// Alias react for switching between default set and share subset.
16521662
oneOf: [
1653-
/*
16541663
{
16551664
// test: codeCondition.test,
16561665
issuerLayer: WEBPACK_LAYERS.server,
1657-
test: (req: string) => {
1666+
test(req: string) {
16581667
// If it's not a source code file, or has been opted out of
16591668
// bundling, don't resolve it.
16601669
if (
16611670
!codeCondition.test.test(req) ||
1662-
isResourceInPackages(req, optoutBundlingPackages)
1671+
isResourceInPackages(req, optOutBundlingPackages)
16631672
) {
16641673
return false
16651674
}
@@ -1669,34 +1678,28 @@ export default async function getBaseWebpackConfig(
16691678
resolve: {
16701679
// It needs `conditionNames` here to require the proper asset,
16711680
// when react is acting as dependency of compiled/react-dom.
1672-
conditionNames: ['react-server', 'node', 'require'],
16731681
alias: {
1674-
react: 'react',
1675-
'react-dom': 'react-dom',
1676-
// Disable before prebundling is landed
1677-
// react: 'next/dist/compiled/react/react.shared-subset',
1678-
// // Use server rendering stub for RSC
1679-
// // x-ref: https://github.com/facebook/react/pull/25436
1680-
// 'react-dom$':
1681-
// 'next/dist/compiled/react-dom/server-rendering-stub',
1682+
react: 'next/dist/compiled/react/react.shared-subset',
1683+
// Use server rendering stub for RSC
1684+
// x-ref: https://github.com/facebook/react/pull/25436
1685+
'react-dom$':
1686+
'next/dist/compiled/react-dom/server-rendering-stub',
1687+
},
1688+
},
1689+
},
1690+
{
1691+
test: codeCondition.test,
1692+
resolve: {
1693+
alias: {
1694+
react: 'next/dist/compiled/react',
1695+
'react-dom$': isClient
1696+
? 'next/dist/compiled/react-dom/index'
1697+
: 'next/dist/compiled/react-dom/server-rendering-stub',
1698+
'react-dom/client$':
1699+
'next/dist/compiled/react-dom/client',
16821700
},
16831701
},
16841702
},
1685-
*/
1686-
// Disable before prebundling is landed
1687-
// {
1688-
// test: codeCondition.test,
1689-
// resolve: {
1690-
// alias: {
1691-
// react: 'next/dist/compiled/react',
1692-
// 'react-dom$': isClient
1693-
// ? 'next/dist/compiled/react-dom/index'
1694-
// : 'next/dist/compiled/react-dom/server-rendering-stub',
1695-
// 'react-dom/client$':
1696-
// 'next/dist/compiled/react-dom/client',
1697-
// },
1698-
// },
1699-
// },
17001703
],
17011704
},
17021705
]

0 commit comments

Comments
 (0)