Skip to content

Commit

Permalink
Prebundle react for appDir (vercel#41337)
Browse files Browse the repository at this point in the history
Inline a react and react-dom for app dir, when `appDir` flag is enabled
opt into the built-in version for all.

For server layer react, use the react share subset for server
components.
For all server side of react-dom usage, use the server-rendering-stub.

Co-authored-by: Shu Ding <g@shud.in>
Co-authored-by: JJ Kasper <jj@jjsweb.site>
  • Loading branch information
3 people authored and Kikobeats committed Oct 24, 2022
1 parent 9ec68ff commit d414973
Show file tree
Hide file tree
Showing 85 changed files with 134,145 additions and 265 deletions.
4 changes: 0 additions & 4 deletions package.json
Expand Up @@ -40,11 +40,9 @@
"next-with-deps": "./scripts/next-with-deps.sh",
"next": "node --trace-deprecation --enable-source-maps packages/next/dist/bin/next",
"next-react-17": "__NEXT_REACT_CHANNEL=17 node --trace-deprecation --enable-source-maps -r ./test/lib/react-channel-require-hook.js 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",
"debug-react-exp": "__NEXT_REACT_CHANNEL=exp node --inspect --trace-deprecation --enable-source-maps -r ./test/lib/react-channel-require-hook.js packages/next/dist/bin/next",
"postinstall": "git config feature.manyFiles true && node scripts/install-native.mjs",
"version": "pnpm install && git add pnpm-lock.yaml",
"prepare": "husky install"
Expand Down Expand Up @@ -183,8 +181,6 @@
"react-17": "npm:react@17.0.2",
"react-dom": "18.2.0",
"react-dom-17": "npm:react-dom@17.0.2",
"react-dom-exp": "npm:react-dom@0.0.0-experimental-a8c16a004-20221012",
"react-exp": "npm:react@0.0.0-experimental-a8c16a004-20221012",
"react-ssr-prepass": "1.0.8",
"react-virtualized": "9.22.3",
"relay-compiler": "13.0.2",
Expand Down
3 changes: 3 additions & 0 deletions packages/next/build/index.ts
Expand Up @@ -300,6 +300,9 @@ export default async function build(

const publicDir = path.join(dir, 'public')
const isAppDirEnabled = !!config.experimental.appDir
if (isAppDirEnabled) {
process.env.HAS_APP_DIR = '1'
}
const { pagesDir, appDir } = findPagesDir(dir, isAppDirEnabled)

const hasPublicDir = await fileExists(publicDir)
Expand Down
12 changes: 9 additions & 3 deletions packages/next/build/utils.ts
@@ -1,7 +1,6 @@
import type { NextConfigComplete } from '../server/config-shared'

import '../server/node-polyfill-fetch'
import loadRequireHook from '../build/webpack/require-hook'
import chalk from 'next/dist/compiled/chalk'
import getGzipSize from 'next/dist/compiled/gzip-size'
import textTable from 'next/dist/compiled/text-table'
Expand Down Expand Up @@ -49,6 +48,15 @@ import { denormalizePagePath } from '../shared/lib/page-path/denormalize-page-pa
import { normalizePagePath } from '../shared/lib/page-path/normalize-page-path'
import { AppBuildManifest } from './webpack/plugins/app-build-manifest-plugin'
import { getRuntimeContext } from '../server/web/sandbox'
import {
loadRequireHook,
overrideBuiltInReactPackages,
} from './webpack/require-hook'

loadRequireHook()
if (process.env.HAS_APP_DIR) {
overrideBuiltInReactPackages()
}

export type ROUTER_TYPE = 'pages' | 'app'

Expand All @@ -69,8 +77,6 @@ const fsStat = (file: string) => {
return (fileStats[file] = fileSize(file))
}

loadRequireHook()

export function unique<T>(main: ReadonlyArray<T>, sub: ReadonlyArray<T>): T[] {
return [...new Set([...main, ...sub])]
}
Expand Down
181 changes: 112 additions & 69 deletions packages/next/build/webpack-config.ts
Expand Up @@ -2,7 +2,7 @@ import ReactRefreshWebpackPlugin from 'next/dist/compiled/@next/react-refresh-ut
import chalk from 'next/dist/compiled/chalk'
import crypto from 'crypto'
import { webpack } from 'next/dist/compiled/webpack/webpack'
import path, { dirname, join as pathJoin, relative as relativePath } from 'path'
import path, { join as pathJoin, relative as relativePath } from 'path'
import { escapeStringRegexp } from '../shared/lib/escape-regexp'
import {
DOT_NEXT_ALIAS,
Expand Down Expand Up @@ -89,9 +89,6 @@ const nodePathList = (process.env.NODE_PATH || '')
.split(process.platform === 'win32' ? ';' : ':')
.filter((p) => !!p)

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

const watchOptions = Object.freeze({
aggregateTimeout: 5,
ignored: ['**/.git/**', '**/.next/**'],
Expand Down Expand Up @@ -125,6 +122,12 @@ function isResourceInPackages(resource: string, packageNames?: string[]) {
)
}

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

export function getDefineEnv({
dev,
config,
Expand Down Expand Up @@ -573,11 +576,6 @@ export default async function getBaseWebpackConfig(
'`experimental.runtime` requires React 18 to be installed.'
)
}
if (hasAppDir) {
throw new Error(
'`experimental.appDir` requires React 18 to be installed.'
)
}
}
}

Expand Down Expand Up @@ -869,11 +867,19 @@ export default async function getBaseWebpackConfig(

next: NEXT_PROJECT_ROOT,

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 @@ -1035,12 +1041,7 @@ export default async function getBaseWebpackConfig(

// Special internal modules that must be bundled for Server Components.
if (layer === WEBPACK_LAYERS.server) {
if (
request === 'react' ||
request === 'react/jsx-runtime' ||
request ===
'next/dist/compiled/react-server-dom-webpack/writer.browser.server'
) {
if (builtInReactImports.includes(request)) {
return
}
}
Expand Down Expand Up @@ -1514,47 +1515,6 @@ export default async function getBaseWebpackConfig(
},
module: {
rules: [
...(hasAppDir && !isClient && !isEdgeServer
? [
{
issuerLayer: WEBPACK_LAYERS.server,
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,
config.experimental.serverComponentsExternalPackages
)
) {
return false
}

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',
},
},
},
]
: []),

// TODO: FIXME: do NOT webpack 5 support with this
// x-ref: https://github.com/webpack/webpack/issues/11467
...(!config.experimental.fullySpecified
Expand All @@ -1578,30 +1538,47 @@ export default async function getBaseWebpackConfig(
},
]
: []),
// Alias `next/dynamic` to React.lazy implementation for RSC
...(hasServerComponents
...(hasAppDir && !isClient
? [
{
test: codeCondition.test,
include: [appDir],
issuerLayer: WEBPACK_LAYERS.server,
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,
config.experimental.serverComponentsExternalPackages
)
) {
return false
}

return true
},
resolve: {
conditionNames: ['react-server', 'node', 'require'],
alias: {
[require.resolve('next/dynamic')]:
'next/dist/client/components/dynamic',
// 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',
},
},
},
]
: []),
...(hasServerComponents && (isNodeServer || isEdgeServer)
...(hasServerComponents && !isClient
? [
// RSC server compilation loaders
{
test: codeCondition.test,
include: [
dir,
// To let the internal client components passing through flight loader
/next[\\/]dist/,
NEXT_PROJECT_ROOT_DIST,
],
issuerLayer: WEBPACK_LAYERS.server,
use: {
Expand All @@ -1610,6 +1587,72 @@ export default async function getBaseWebpackConfig(
},
]
: []),
// Alias `next/dynamic` to React.lazy implementation for RSC
...(hasServerComponents
? [
{
test: codeCondition.test,
include: [appDir],
resolve: {
alias: {
// Alias `next/dynamic` to React.lazy implementation for RSC
[require.resolve('next/dynamic')]: require.resolve(
'next/dist/client/components/dynamic'
),
},
},
},
{
// 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) => {
// 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,
config.experimental.serverComponentsExternalPackages
)
) {
return false
}

return true
},
resolve: {
// It needs `conditionNames` here to require the proper asset,
// when react is acting as dependency of compiled/react-dom.
alias: {
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',
},
},
},
],
},
]
: []),
{
test: /\.(js|cjs|mjs)$/,
issuerLayer: WEBPACK_LAYERS.api,
Expand Down

0 comments on commit d414973

Please sign in to comment.