Skip to content

Commit

Permalink
Bundle ssr client layer excepts react externals (#41606)
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
3 people committed Oct 22, 2022
1 parent 73499e4 commit b0f87fb
Show file tree
Hide file tree
Showing 85 changed files with 131,391 additions and 298 deletions.
6 changes: 3 additions & 3 deletions package.json
Expand Up @@ -39,7 +39,6 @@
"lint-staged": "lint-staged",
"next-with-deps": "./scripts/next-with-deps.sh",
"next": "node --trace-deprecation --enable-source-maps packages/next/dist/bin/next",
"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",
"next-no-sourcemaps": "node --trace-deprecation packages/next/dist/bin/next",
"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",
"debug": "node --inspect packages/next/dist/bin/next",
Expand Down Expand Up @@ -179,10 +178,10 @@
"random-seed": "0.3.0",
"react": "18.2.0",
"react-17": "npm:react@17.0.2",
"react-builtin": "npm:react@0.0.0-experimental-9cdf8a99e-20221018",
"react-dom": "18.2.0",
"react-dom-17": "npm:react-dom@17.0.2",
"react-exp": "npm:react@0.0.0-experimental-9cdf8a99e-20221018",
"react-dom-exp": "npm:react-dom@0.0.0-experimental-9cdf8a99e-20221018",
"react-dom-builtin": "npm:react-dom@0.0.0-experimental-9cdf8a99e-20221018",
"react-server-dom-webpack": "0.0.0-experimental-9cdf8a99e-20221018",
"react-ssr-prepass": "1.0.8",
"react-virtualized": "9.22.3",
Expand All @@ -198,6 +197,7 @@
"shell-quote": "1.7.3",
"styled-components": "5.3.3",
"styled-jsx-plugin-postcss": "3.0.2",
"swr": "2.0.0-rc.0",
"tailwindcss": "1.1.3",
"taskr": "1.1.0",
"tree-kill": "1.2.2",
Expand Down
2 changes: 1 addition & 1 deletion packages/next/build/entries.ts
Expand Up @@ -207,7 +207,7 @@ export function getEdgeServerEntry(opts: {
// The Edge bundle includes the server in its entrypoint, so it has to
// be in the SSR layer — we later convert the page request to the RSC layer
// via a webpack rule.
layer: undefined,
layer: WEBPACK_LAYERS.client,
}
}

Expand Down
5 changes: 0 additions & 5 deletions packages/next/build/index.ts
Expand Up @@ -120,7 +120,6 @@ import { RemotePattern } from '../shared/lib/image-config'
import { eventSwcPlugins } from '../telemetry/events/swc-plugins'
import { normalizeAppPath } from '../shared/lib/router/utils/app-paths'
import { AppBuildManifest } from './webpack/plugins/app-build-manifest-plugin'
import { verifyAppReactVersion } from '../lib/verifyAppReactVersion'

export type SsgRoute = {
initialRevalidateSeconds: number | false
Expand Down Expand Up @@ -307,10 +306,6 @@ export default async function build(
process.env.NEXT_PREBUNDLED_REACT = '1'
}
const { pagesDir, appDir } = findPagesDir(dir, isAppDirEnabled)

if (isAppDirEnabled) {
await verifyAppReactVersion({ dir })
}
const hasPublicDir = await fileExists(publicDir)

telemetry.record(
Expand Down
149 changes: 76 additions & 73 deletions packages/next/build/webpack-config.ts
Expand Up @@ -125,9 +125,11 @@ function isResourceInPackages(resource: string, packageNames?: string[]) {
)
}

const builtInReactImports = [
const bundledReactImports = [
'react',
'react/jsx-runtime',
'react/jsx-dev-runtime',
// 'react-dom',
'next/dist/compiled/react-server-dom-webpack/server.browser',
]

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

const reactDir = path.dirname(require.resolve('react/package.json'))
const reactDomDir = path.dirname(require.resolve('react-dom/package.json'))

const resolveConfig = {
// Disable .mjs for node_modules bundling
extensions: isNodeServer
Expand Down Expand Up @@ -878,25 +877,19 @@ export default async function getBaseWebpackConfig(

next: NEXT_PROJECT_ROOT,

// Disable until pre-bundling is landed
// ...(hasServerComponents
// ? {
// // For react and react-dom, alias them dynamically for server layer
// // and others in the loaders configuration
// 'react-dom/client$': 'next/dist/compiled/react-dom/client',
// 'react-dom/server$': 'next/dist/compiled/react-dom/server',
// 'react-dom/server.browser$':
// 'next/dist/compiled/react-dom/server.browser',
// 'react/jsx-dev-runtime$':
// 'next/dist/compiled/react/jsx-dev-runtime',
// 'react/jsx-runtime$': 'next/dist/compiled/react/jsx-runtime',
// }
// : undefined),
react: reactDir,
'react-dom$': reactDomDir,
'react-dom/server$': `${reactDomDir}/server`,
'react-dom/server.browser$': `${reactDomDir}/server.browser`,
'react-dom/client$': `${reactDomDir}/client`,
...(hasServerComponents
? {
// For react and react-dom, alias them dynamically for server layer
// and others in the loaders configuration
'react-dom/client$': 'next/dist/compiled/react-dom/client',
'react-dom/server$': 'next/dist/compiled/react-dom/server',
'react-dom/server.browser$':
'next/dist/compiled/react-dom/server.browser',
'react/jsx-dev-runtime$':
'next/dist/compiled/react/jsx-dev-runtime',
'react/jsx-runtime$': 'next/dist/compiled/react/jsx-runtime',
}
: undefined),

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

const optoutBundlingPackages = EXTERNAL_PACKAGES.concat(
const optOutBundlingPackages = EXTERNAL_PACKAGES.concat(
...(config.experimental.serverComponentsExternalPackages || [])
)

Expand Down Expand Up @@ -1062,7 +1055,7 @@ export default async function getBaseWebpackConfig(

// Special internal modules that must be bundled for Server Components.
if (layer === WEBPACK_LAYERS.server) {
if (builtInReactImports.includes(request)) {
if (bundledReactImports.includes(request)) {
return
}
}
Expand Down Expand Up @@ -1197,13 +1190,31 @@ export default async function getBaseWebpackConfig(
if (layer === WEBPACK_LAYERS.server) {
// All packages should be bundled for the server layer if they're not opted out.
// This option takes priority over the transpilePackages option.
if (isResourceInPackages(res, optoutBundlingPackages)) {
if (isResourceInPackages(res, optOutBundlingPackages)) {
return `${externalType} ${request}`
}

return
}

// Treat react packages as external for SSR layer,
// then let require-hook mapping them to internals.
if (layer === WEBPACK_LAYERS.client) {
if (
[
'react',
'react/jsx-runtime',
'react/jsx-dev-runtime',
'react-dom',
'scheduler',
].includes(request)
) {
return `commonjs next/dist/compiled/${request}`
} else {
return
}
}

if (shouldBeBundled) return

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

return true
},
resolve: process.env.__NEXT_REACT_CHANNEL
? {
conditionNames: ['react-server', 'node', 'require'],
alias: {
react: `react-${process.env.__NEXT_REACT_CHANNEL}`,
'react-dom': `react-dom-${process.env.__NEXT_REACT_CHANNEL}`,
},
}
: {
conditionNames: ['react-server', 'node', 'require'],
alias: {
// If missing the alias override here, the default alias will be used which aliases
// react to the direct file path, not the package name. In that case the condition
// will be ignored completely.
react: 'react',
'react-dom': 'react-dom',
},
},
resolve: {
conditionNames: ['react-server', 'node', 'require'],
alias: {
// If missing the alias override here, the default alias will be used which aliases
// react to the direct file path, not the package name. In that case the condition
// will be ignored completely.
react: 'next/dist/compiled/react',
'react-dom$': isClient
? 'next/dist/compiled/react-dom/index'
: 'next/dist/compiled/react-dom/server-rendering-stub',
'react-dom/client$': 'next/dist/compiled/react-dom/client',
},
},
},
]
: []),
Expand All @@ -1613,7 +1619,6 @@ export default async function getBaseWebpackConfig(
},
]
: []),

...(hasServerComponents && !isClient
? [
// RSC server compilation loaders
Expand All @@ -1636,7 +1641,12 @@ export default async function getBaseWebpackConfig(
? [
{
test: codeCondition.test,
include: [appDir],
issuerLayer(layer: string) {
return (
layer === WEBPACK_LAYERS.client ||
layer === WEBPACK_LAYERS.server
)
},
resolve: {
alias: {
// Alias `next/dynamic` to React.lazy implementation for RSC
Expand All @@ -1650,16 +1660,15 @@ export default async function getBaseWebpackConfig(
// Alias react-dom for ReactDOM.preload usage.
// Alias react for switching between default set and share subset.
oneOf: [
/*
{
// test: codeCondition.test,
issuerLayer: WEBPACK_LAYERS.server,
test: (req: string) => {
test(req: string) {
// If it's not a source code file, or has been opted out of
// bundling, don't resolve it.
if (
!codeCondition.test.test(req) ||
isResourceInPackages(req, optoutBundlingPackages)
isResourceInPackages(req, optOutBundlingPackages)
) {
return false
}
Expand All @@ -1669,34 +1678,28 @@ export default async function getBaseWebpackConfig(
resolve: {
// It needs `conditionNames` here to require the proper asset,
// when react is acting as dependency of compiled/react-dom.
conditionNames: ['react-server', 'node', 'require'],
alias: {
react: 'react',
'react-dom': 'react-dom',
// Disable before prebundling is landed
// react: 'next/dist/compiled/react/react.shared-subset',
// // Use server rendering stub for RSC
// // x-ref: https://github.com/facebook/react/pull/25436
// 'react-dom$':
// 'next/dist/compiled/react-dom/server-rendering-stub',
react: 'next/dist/compiled/react/react.shared-subset',
// Use server rendering stub for RSC
// x-ref: https://github.com/facebook/react/pull/25436
'react-dom$':
'next/dist/compiled/react-dom/server-rendering-stub',
},
},
},
{
test: codeCondition.test,
resolve: {
alias: {
react: 'next/dist/compiled/react',
'react-dom$': isClient
? 'next/dist/compiled/react-dom/index'
: 'next/dist/compiled/react-dom/server-rendering-stub',
'react-dom/client$':
'next/dist/compiled/react-dom/client',
},
},
},
*/
// Disable before prebundling is landed
// {
// test: codeCondition.test,
// resolve: {
// alias: {
// react: 'next/dist/compiled/react',
// 'react-dom$': isClient
// ? 'next/dist/compiled/react-dom/index'
// : 'next/dist/compiled/react-dom/server-rendering-stub',
// 'react-dom/client$':
// 'next/dist/compiled/react-dom/client',
// },
// },
// },
],
},
]
Expand Down

0 comments on commit b0f87fb

Please sign in to comment.