Skip to content

Commit

Permalink
Prevent ESM client component modules from getting DCE'd in the RSC layer
Browse files Browse the repository at this point in the history
  • Loading branch information
unstubbable committed Oct 28, 2024
1 parent cc6e954 commit 6025413
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 69 deletions.
14 changes: 3 additions & 11 deletions packages/next/src/build/webpack-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@ import {
getBabelLoader,
getReactCompilerLoader,
} from './get-babel-loader-config'
import type { NextFlightLoaderOptions } from './webpack/loaders/next-flight-loader'
import {
NEXT_PROJECT_ROOT,
NEXT_PROJECT_ROOT_DIST_CLIENT,
Expand Down Expand Up @@ -519,13 +518,6 @@ export default async function getBaseWebpackConfig(
babel: useSWCLoader ? swcDefaultLoader : babelLoader!,
}

const nextFlightLoader = {
loader: 'next-flight-loader',
options: {
isEdgeServer,
} satisfies NextFlightLoaderOptions,
}

const appServerLayerLoaders = hasAppDir
? [
// When using Babel, we will have to add the SWC loader
Expand All @@ -539,7 +531,7 @@ export default async function getBaseWebpackConfig(
: []

const instrumentLayerLoaders = [
nextFlightLoader,
'next-flight-loader',
// When using Babel, we will have to add the SWC loader
// as an additional pass to handle RSC correctly.
// This will cause some performance overhead but
Expand All @@ -549,7 +541,7 @@ export default async function getBaseWebpackConfig(
].filter(Boolean)

const middlewareLayerLoaders = [
nextFlightLoader,
'next-flight-loader',
// When using Babel, we will have to use SWC to do the optimization
// for middleware to tree shake the unused default optimized imports like "next/server".
// This will cause some performance overhead but
Expand Down Expand Up @@ -1366,7 +1358,7 @@ export default async function getBaseWebpackConfig(
isEdgeServer,
}),
},
use: nextFlightLoader,
use: 'next-flight-loader',
},
]
: []),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ import type { webpack } from 'next/dist/compiled/webpack/webpack'
import { RSC_MOD_REF_PROXY_ALIAS } from '../../../../lib/constants'
import {
BARREL_OPTIMIZATION_PREFIX,
DEFAULT_RUNTIME_WEBPACK,
EDGE_RUNTIME_WEBPACK,
RSC_MODULE_TYPES,
} from '../../../../shared/lib/constants'
import { warnOnce } from '../../../../shared/lib/utils/warn-once'
Expand All @@ -14,11 +12,6 @@ import type {
javascript,
LoaderContext,
} from 'next/dist/compiled/webpack/webpack'
import picomatch from 'next/dist/compiled/picomatch'

export interface NextFlightLoaderOptions {
isEdgeServer: boolean
}

type SourceType = javascript.JavascriptParser['sourceType'] | 'commonjs'

Expand All @@ -27,10 +20,6 @@ const noopHeadPath = require.resolve('next/dist/client/components/noop-head')
const MODULE_PROXY_PATH =
'next/dist/build/webpack/loaders/next-flight-loader/module-proxy'

const isSharedRuntime = picomatch('**/next/dist/**/*.shared-runtime.js', {
dot: true, // required for .pnpm paths
})

export function getAssumedSourceType(
mod: webpack.Module,
sourceType: SourceType
Expand Down Expand Up @@ -66,7 +55,7 @@ export function getAssumedSourceType(
}

export default function transformSource(
this: LoaderContext<NextFlightLoaderOptions>,
this: LoaderContext<undefined>,
source: string,
sourceMap: any
) {
Expand All @@ -75,8 +64,6 @@ export default function transformSource(
throw new Error('Expected source to have been transformed to a string.')
}

const options = this.getOptions()
const { isEdgeServer } = options
const module = this._module!

// Assign the RSC meta information to buildInfo.
Expand Down Expand Up @@ -124,18 +111,6 @@ export default function transformSource(
return
}

if (!isSharedRuntime(resourceKey)) {
// Prevent module concatenation, and prevent export names from being
// mangled, in production builds, so that exports of client reference
// modules can be resolved by React using the metadata from the client
// manifest.
this._compilation!.moduleGraph.getExportsInfo(
module
).setUsedInUnknownWay(
isEdgeServer ? EDGE_RUNTIME_WEBPACK : DEFAULT_RUNTIME_WEBPACK
)
}

let esmSource = `\
import { registerClientReference } from "react-server-dom-webpack/server.edge";
`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ export class FlightClientEntryPlugin {
> = []
const createdSSRDependenciesForEntry: Record<
string,
ReturnType<typeof this.injectClientEntryAndSSRModules>[2][]
ReturnType<typeof this.injectClientEntryAndSSRModules>[3][]
> = {}

const addActionEntryList: Array<ReturnType<typeof this.injectActionEntry>> =
Expand Down Expand Up @@ -400,7 +400,7 @@ export class FlightClientEntryPlugin {
createdSSRDependenciesForEntry[clientEntryToInject.entryName] = []
}
createdSSRDependenciesForEntry[clientEntryToInject.entryName].push(
injected[2]
injected[3]
)

addClientEntryAndSSRModulesList.push(injected)
Expand Down Expand Up @@ -457,12 +457,14 @@ export class FlightClientEntryPlugin {
invalidator.invalidate([COMPILER_NAMES.client])
}

// Client compiler is invalidated before awaiting the compilation of the SSR client component entries
// so that the client compiler is running in parallel to the server compiler.
// Client compiler is invalidated before awaiting the compilation of the SSR
// and RSC client component entries so that the client compiler is running
// in parallel to the server compiler.
await Promise.all(
addClientEntryAndSSRModulesList.map(
(addClientEntryAndSSRModules) => addClientEntryAndSSRModules[1]
)
addClientEntryAndSSRModulesList.flatMap((addClientEntryAndSSRModules) => [
addClientEntryAndSSRModules[1],
addClientEntryAndSSRModules[2],
])
)

// Wait for action entries to be added.
Expand Down Expand Up @@ -744,7 +746,8 @@ export class FlightClientEntryPlugin {
absolutePagePath?: string
}): [
shouldInvalidate: boolean,
addEntryPromise: Promise<void>,
addSSREntryPromise: Promise<void>,
addRSCEntryPromise: Promise<void>,
ssrDep: ReturnType<typeof webpack.EntryPlugin.createDependency>,
] {
let shouldInvalidate = false
Expand Down Expand Up @@ -773,7 +776,7 @@ export class FlightClientEntryPlugin {
server: false,
})}!`

const clientSSRLoader = `next-flight-client-entry-loader?${stringify({
const clientServerLoader = `next-flight-client-entry-loader?${stringify({
modules: modules.map((x) => JSON.stringify(x)),
server: true,
})}!`
Expand Down Expand Up @@ -816,33 +819,30 @@ export class FlightClientEntryPlugin {
pluginState.injectedClientEntries[bundlePath] = clientBrowserLoader
}

// Inject the entry to the server compiler (__ssr__).
const clientComponentEntryDep = webpack.EntryPlugin.createDependency(
clientSSRLoader,
{
name: bundlePath,
}
const clientComponentSSREntryDep = webpack.EntryPlugin.createDependency(
clientServerLoader,
{ name: bundlePath }
)

const clientComponentRSCEntryDep = webpack.EntryPlugin.createDependency(
clientServerLoader,
{ name: bundlePath }
)

return [
shouldInvalidate,
// Add the dependency to the server compiler.
// This promise is awaited later using `Promise.all` in order to parallelize adding the entries.
// It ensures we can parallelize the SSR and Client compiler entries.
this.addEntry(
compilation,
// Reuse compilation context.
compiler.context,
clientComponentEntryDep,
{
// By using the same entry name
name: entryName,
// Layer should be client for the SSR modules
// This ensures the client components are bundled on client layer
layer: WEBPACK_LAYERS.serverSideRendering,
}
),
clientComponentEntryDep,
// Add the entries to the SSR and RSC server compilers. The promises are
// awaited later using `Promise.all` in order to parallelize adding the
// entries.
this.addEntry(compilation, compiler.context, clientComponentSSREntryDep, {
name: entryName,
layer: WEBPACK_LAYERS.serverSideRendering,
}),
this.addEntry(compilation, compiler.context, clientComponentRSCEntryDep, {
name: entryName,
layer: WEBPACK_LAYERS.reactServerComponents,
}),
clientComponentSSREntryDep,
]
}

Expand Down

0 comments on commit 6025413

Please sign in to comment.