From 338bf9d8f09693c7556933087e1401d03bd61ee6 Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Fri, 30 Sep 2022 20:32:32 +0200 Subject: [PATCH] Use deterministic module IDs for server (#41066) Currently, we use the `isClient ? 'deterministic' : 'named'` condition for module IDs. We did that because in the context of server compiler, the server graph (RSC) can directly know the module ID of the referenced module in the client graph (SSR). The client module's ID _is_ the module reference module's resource path. However, that makes the server bundle and the manifest file larger because these module IDs cannot be minified. In this PR we are changing it to `deterministic`, with another mapping for server SSR. ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have a helpful link attached, see `contributing.md` ## 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` - [ ] 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` ## Documentation / Examples - [ ] Make sure the linting passes by running `pnpm lint` - [ ] The "examples guidelines" are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md) --- packages/next/build/webpack-config.ts | 7 +-- .../plugins/flight-client-entry-plugin.ts | 61 ++++++++++++++++++- .../webpack/plugins/flight-manifest-plugin.ts | 25 +++++--- 3 files changed, 78 insertions(+), 15 deletions(-) diff --git a/packages/next/build/webpack-config.ts b/packages/next/build/webpack-config.ts index b83571b7e7707..4e3d4ea27f58b 100644 --- a/packages/next/build/webpack-config.ts +++ b/packages/next/build/webpack-config.ts @@ -1278,12 +1278,7 @@ export default async function getBaseWebpackConfig( emitOnErrors: !dev, checkWasmTypes: false, nodeEnv: false, - ...(hasServerComponents - ? { - // We have to use the names here instead of hashes to ensure the consistency between compilers. - moduleIds: isClient ? 'deterministic' : 'named', - } - : {}), + ...(hasServerComponents ? { moduleIds: 'deterministic' } : {}), splitChunks: ((): | Required['optimization']['splitChunks'] | false => { diff --git a/packages/next/build/webpack/plugins/flight-client-entry-plugin.ts b/packages/next/build/webpack/plugins/flight-client-entry-plugin.ts index 1e16b917be012..538f92ff87de3 100644 --- a/packages/next/build/webpack/plugins/flight-client-entry-plugin.ts +++ b/packages/next/build/webpack/plugins/flight-client-entry-plugin.ts @@ -1,5 +1,6 @@ import { stringify } from 'querystring' import path from 'path' +import { relative } from 'path' import { webpack, sources } from 'next/dist/compiled/webpack/webpack' import { getInvalidator, @@ -11,7 +12,7 @@ import type { ClientComponentImports, NextFlightClientEntryLoaderOptions, } from '../loaders/next-flight-client-entry-loader' -import { APP_DIR_ALIAS } from '../../../lib/constants' +import { APP_DIR_ALIAS, WEBPACK_LAYERS } from '../../../lib/constants' import { COMPILER_NAMES, EDGE_RUNTIME_WEBPACK, @@ -31,6 +32,9 @@ const PLUGIN_NAME = 'ClientEntryPlugin' export const injectedClientEntries = new Map() +export const serverModuleIds = new Map() +export const edgeServerModuleIds = new Map() + // TODO-APP: ensure .scss / .sass also works. const regexCSS = /\.css$/ @@ -77,6 +81,60 @@ export class FlightClientEntryPlugin { } } }) + + const recordModule = (id: number | string, mod: any) => { + const modResource = mod.resourceResolveData?.path || mod.resource + + if ( + mod.resourceResolveData?.context?.issuerLayer === + WEBPACK_LAYERS.server + ) { + return + } + + if (typeof id !== 'undefined' && modResource) { + // Note that this isn't that reliable as webpack is still possible to assign + // additional queries to make sure there's no conflict even using the `named` + // module ID strategy. + let ssrNamedModuleId = relative(compiler.context, modResource) + if (!ssrNamedModuleId.startsWith('.')) { + // TODO use getModuleId instead + ssrNamedModuleId = `./${ssrNamedModuleId.replace(/\\/g, '/')}` + } + + if (this.isEdgeServer) { + edgeServerModuleIds.set( + ssrNamedModuleId.replace(/\/next\/dist\/esm\//, '/next/dist/'), + id + ) + } else { + serverModuleIds.set(ssrNamedModuleId, id) + } + } + } + + compilation.chunkGroups.forEach((chunkGroup) => { + chunkGroup.chunks.forEach((chunk: webpack.Chunk) => { + const chunkModules = compilation.chunkGraph.getChunkModulesIterable( + chunk + ) as Iterable + + for (const mod of chunkModules) { + const modId = compilation.chunkGraph.getModuleId(mod) + + recordModule(modId, mod) + + // If this is a concatenation, register each child to the parent ID. + // TODO: remove any + const anyModule = mod as any + if (anyModule.modules) { + anyModule.modules.forEach((concatenatedMod: any) => { + recordModule(modId, concatenatedMod) + }) + } + } + }) + }) }) } @@ -323,6 +381,7 @@ export class FlightClientEntryPlugin { // Check if request is for css file. if ((!inClientComponentBoundary && isClientComponent) || isCSS) { clientComponentImports.push(modRequest) + return } diff --git a/packages/next/build/webpack/plugins/flight-manifest-plugin.ts b/packages/next/build/webpack/plugins/flight-manifest-plugin.ts index fb09198806891..c3432035bca5d 100644 --- a/packages/next/build/webpack/plugins/flight-manifest-plugin.ts +++ b/packages/next/build/webpack/plugins/flight-manifest-plugin.ts @@ -10,6 +10,11 @@ import { FLIGHT_MANIFEST } from '../../../shared/lib/constants' import { relative } from 'path' import { isClientComponentModule } from '../loaders/utils' +import { + edgeServerModuleIds, + serverModuleIds, +} from './flight-client-entry-plugin' + // This is the module that will be used to anchor all client references to. // I.e. it will have all the client files as async deps from this point on. // We use the Flight client implementation because you can't get to these @@ -304,16 +309,20 @@ export class FlightManifestPlugin { } } - moduleIdMapping[id] = moduleIdMapping[id] || {} - moduleIdMapping[id][name] = { - ...moduleExports[name], - id: ssrNamedModuleId, + if (serverModuleIds.has(ssrNamedModuleId)) { + moduleIdMapping[id] = moduleIdMapping[id] || {} + moduleIdMapping[id][name] = { + ...moduleExports[name], + id: serverModuleIds.get(ssrNamedModuleId)!, + } } - edgeModuleIdMapping[id] = edgeModuleIdMapping[id] || {} - edgeModuleIdMapping[id][name] = { - ...moduleExports[name], - id: ssrNamedModuleId.replace(/\/next\/dist\//, '/next/dist/esm/'), + if (edgeServerModuleIds.has(ssrNamedModuleId)) { + edgeModuleIdMapping[id] = edgeModuleIdMapping[id] || {} + edgeModuleIdMapping[id][name] = { + ...moduleExports[name], + id: edgeServerModuleIds.get(ssrNamedModuleId)!, + } } })