rsnext/packages/next/webpack.config.js
Josh Story 65b0bb24af
Separate RSC and SSR jsx-runtime modules (#56438)
There should be no shared react packages in our server runtime. rsc should always be separate from ssr.

This update reconfigures the runtiem to eliminate shared react modules. the jsx runtime will now be separate for RSC and SSR. this is necessary because the implementations for the jsx runtime rely on React and they need to see the right versions.

Additionally I fixed an alias so that the shared subset react is used when using react-server condition.

I also fixed a bug in 2 tests related to class/className.

Note: this PR blocks upgrading React canary due to internal changes in how React state is managed in when using the `react-server` condition
2023-10-04 20:29:10 +00:00

264 lines
8.7 KiB
JavaScript

const webpack = require('webpack')
const path = require('path')
const TerserPlugin = require('terser-webpack-plugin')
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')
const pagesExternals = [
'react',
'react/package.json',
'react/jsx-runtime',
'react/jsx-dev-runtime',
'react-dom',
'react-dom/package.json',
'react-dom/client',
'react-dom/server',
'react-dom/server.browser',
'react-dom/server.edge',
'react-server-dom-webpack/client',
'react-server-dom-webpack/client.edge',
'react-server-dom-webpack/server.edge',
'react-server-dom-webpack/server.node',
]
function makeAppAliases(reactChannel = '') {
return {
react$: `next/dist/compiled/react${reactChannel}`,
'react/shared-subset$': `next/dist/compiled/react${reactChannel}/react.shared-subset`,
'react-dom/server-rendering-stub$': `next/dist/compiled/react-dom${reactChannel}/server-rendering-stub`,
'react-dom$': `next/dist/compiled/react-dom${reactChannel}/server-rendering-stub`,
'react/jsx-runtime$': `next/dist/compiled/react${reactChannel}/jsx-runtime`,
'react/jsx-dev-runtime$': `next/dist/compiled/react${reactChannel}/jsx-dev-runtime`,
'react-dom/client$': `next/dist/compiled/react-dom${reactChannel}/client`,
'react-dom/server$': `next/dist/compiled/react-dom${reactChannel}/server`,
'react-dom/server.edge$': `next/dist/compiled/react-dom${reactChannel}/server.edge`,
'react-dom/server.browser$': `next/dist/compiled/react-dom${reactChannel}/server.browser`,
'react-server-dom-turbopack/client$': `next/dist/compiled/react-server-dom-turbopack${reactChannel}/client`,
'react-server-dom-turbopack/client.edge$': `next/dist/compiled/react-server-dom-turbopack${reactChannel}/client.edge`,
'react-server-dom-turbopack/server.edge$': `next/dist/compiled/react-server-dom-turbopack${reactChannel}/server.edge`,
'react-server-dom-turbopack/server.node$': `next/dist/compiled/react-server-dom-turbopack${reactChannel}/server.node`,
'react-server-dom-webpack/client$': `next/dist/compiled/react-server-dom-webpack${reactChannel}/client`,
'react-server-dom-webpack/client.edge$': `next/dist/compiled/react-server-dom-webpack${reactChannel}/client.edge`,
'react-server-dom-webpack/server.edge$': `next/dist/compiled/react-server-dom-webpack${reactChannel}/server.edge`,
'react-server-dom-webpack/server.node$': `next/dist/compiled/react-server-dom-webpack${reactChannel}/server.node`,
// optimisations to ignore the legacy build of react-dom/server
'./cjs/react-dom-server-legacy.browser.production.min.js': `next/dist/build/noop-react-dom-server-legacy`,
'./cjs/react-dom-server-legacy.browser.development.js': `next/dist/build/noop-react-dom-server-legacy`,
}
}
const appAliases = makeAppAliases()
const appExperimentalAliases = makeAppAliases('-experimental')
const sharedExternals = [
'styled-jsx',
'styled-jsx/style',
'@opentelemetry/api',
'next/dist/compiled/@next/react-dev-overlay/dist/middleware',
'next/dist/compiled/@ampproject/toolbox-optimizer',
'next/dist/compiled/edge-runtime',
'next/dist/compiled/@edge-runtime/ponyfill',
'next/dist/compiled/undici',
'next/dist/compiled/raw-body',
'next/dist/server/capsize-font-metrics.json',
'critters',
'next/dist/compiled/node-html-parser',
'next/dist/compiled/compression',
'next/dist/compiled/jsonwebtoken',
'next/dist/compiled/@opentelemetry/api',
'next/dist/compiled/@mswjs/interceptors/ClientRequest',
'next/dist/compiled/ws',
]
const externalsMap = {
'./web/sandbox': 'next/dist/server/web/sandbox',
}
const externalsRegexMap = {
'(.*)trace/tracer$': 'next/dist/server/lib/trace/tracer',
}
const bundleTypes = {
app: {
'app-page': path.join(
__dirname,
'dist/esm/server/future/route-modules/app-page/module.js'
),
'app-route': path.join(
__dirname,
'dist/esm/server/future/route-modules/app-route/module.js'
),
},
pages: {
pages: path.join(
__dirname,
'dist/esm/server/future/route-modules/pages/module.js'
),
'pages-api': path.join(
__dirname,
'dist/esm/server/future/route-modules/pages-api/module.js'
),
},
server: {
server: path.join(__dirname, 'dist/esm/server/next-server.js'),
},
}
module.exports = ({ dev, turbo, bundleType, experimental }) => {
const externalHandler = ({ context, request, getResolve }, callback) => {
;(async () => {
if (request.endsWith('.external')) {
const resolve = getResolve()
const resolved = await resolve(context, request)
const relative = path.relative(
path.join(__dirname, '..'),
resolved.replace('esm' + path.sep, '')
)
callback(null, `commonjs ${relative}`)
} else {
const regexMatch = Object.keys(externalsRegexMap).find((regex) =>
new RegExp(regex).test(request)
)
if (regexMatch) {
return callback(null, 'commonjs ' + externalsRegexMap[regexMatch])
}
callback()
}
})()
}
/** @type {webpack.Configuration} */
return {
entry: bundleTypes[bundleType],
target: 'node',
mode: 'production',
output: {
path: path.join(__dirname, 'dist/compiled/next-server'),
filename: `[name]${turbo ? '-turbo' : ''}${
experimental ? '-experimental' : ''
}.runtime.${dev ? 'dev' : 'prod'}.js`,
libraryTarget: 'commonjs2',
},
devtool: 'source-map',
optimization: {
moduleIds: 'named',
minimize: true,
concatenateModules: true,
minimizer: [
new TerserPlugin({
minify: TerserPlugin.swcMinify,
terserOptions: {
compress: {
dead_code: true,
// Zero means no limit.
passes: 0,
},
format: {
preamble: '',
},
},
}),
],
},
plugins: [
new webpack.DefinePlugin({
'typeof window': JSON.stringify('undefined'),
'process.env.NEXT_MINIMAL': JSON.stringify('true'),
'this.serverOptions.experimentalTestProxy': JSON.stringify(false),
'this.minimalMode': JSON.stringify(true),
'this.renderOpts.dev': JSON.stringify(dev),
'process.env.NODE_ENV': JSON.stringify(
dev ? 'development' : 'production'
),
'process.env.NEXT_RUNTIME': JSON.stringify('nodejs'),
...(!dev ? { 'process.env.TURBOPACK': JSON.stringify(turbo) } : {}),
}),
!!process.env.ANALYZE &&
new BundleAnalyzerPlugin({
analyzerPort: calculateUniquePort(
dev,
turbo,
experimental,
bundleType
),
openAnalyzer: false,
}),
].filter(Boolean),
stats: {
optimizationBailout: true,
},
resolve: {
alias:
bundleType === 'app'
? experimental
? appExperimentalAliases
: appAliases
: {},
},
module: {
rules: [
{
include: /[\\/]react-server.node/,
layer: 'react-server',
},
{
include: /vendored[\\/]rsc[\\/]entrypoints/,
resolve: {
conditionNames: ['react-server', '...'],
alias: {
react$: `next/dist/compiled/react${
experimental ? '-experimental' : ''
}/react.shared-subset`,
'next/dist/compiled/react$': `next/dist/compiled/react${
experimental ? '-experimental' : ''
}/react.shared-subset`,
},
},
layer: 'react-server',
},
{
issuerLayer: 'react-server',
resolve: {
conditionNames: ['react-server', '...'],
alias: {
react$: `next/dist/compiled/react${
experimental ? '-experimental' : ''
}/react.shared-subset`,
'next/dist/compiled/react$': `next/dist/compiled/react${
experimental ? '-experimental' : ''
}/react.shared-subset`,
},
},
},
],
},
externals: [
...sharedExternals,
...(bundleType === 'pages' ? pagesExternals : []),
externalsMap,
externalHandler,
],
experiments: {
layers: true,
},
}
}
function calculateUniquePort(dev, turbo, experimental, bundleType) {
const devOffset = dev ? 1000 : 0
const turboOffset = turbo ? 200 : 0
const experimentalOffset = experimental ? 40 : 0
let bundleTypeOffset
switch (bundleType) {
case 'app':
bundleTypeOffset = 1
break
case 'pages':
bundleTypeOffset = 2
break
default:
bundleTypeOffset = 3
}
return 8888 + devOffset + turboOffset + experimentalOffset + bundleTypeOffset
}