diff --git a/packages/next/build/webpack-config.ts b/packages/next/build/webpack-config.ts index ec7d218a1e148..2c6a84fef6866 100644 --- a/packages/next/build/webpack-config.ts +++ b/packages/next/build/webpack-config.ts @@ -909,9 +909,8 @@ export default async function getBaseWebpackConfig( }, } - const rscCodeCondition = { + const serverComponentCodeCondition = { test: serverComponentsRegex, - // only apply to the pages as the begin process of rsc loaders include: [dir, /next[\\/]dist[\\/]pages/], } @@ -1210,7 +1209,7 @@ export default async function getBaseWebpackConfig( ? [ // RSC server compilation loaders { - ...rscCodeCondition, + ...serverComponentCodeCondition, use: { loader: 'next-flight-server-loader', }, @@ -1219,7 +1218,7 @@ export default async function getBaseWebpackConfig( : [ // RSC client compilation loaders { - ...rscCodeCondition, + ...serverComponentCodeCondition, use: { loader: 'next-flight-server-loader', options: { @@ -1589,8 +1588,12 @@ export default async function getBaseWebpackConfig( }, }), hasServerComponents && - isClient && - new FlightManifestPlugin({ dev, pageExtensions: rawPageExtensions }), + !isClient && + new FlightManifestPlugin({ + dev, + pageExtensions: rawPageExtensions, + isEdgeServer, + }), !dev && isClient && new TelemetryPlugin( diff --git a/packages/next/build/webpack/loaders/next-flight-server-loader.ts b/packages/next/build/webpack/loaders/next-flight-server-loader.ts index 2dd7cde2fb12e..83c4ac670f883 100644 --- a/packages/next/build/webpack/loaders/next-flight-server-loader.ts +++ b/packages/next/build/webpack/loaders/next-flight-server-loader.ts @@ -3,8 +3,8 @@ import { builtinModules } from 'module' import { parse } from '../../swc' import { buildExports, - createClientComponentFilter, - createServerComponentFilter, + clientComponentRegex, + serverComponentRegex, isNextBuiltinClientComponent, } from './utils' @@ -25,15 +25,11 @@ async function parseModuleInfo({ resourcePath, source, isClientCompilation, - isServerComponent, - isClientComponent, resolver, }: { resourcePath: string source: string isClientCompilation: boolean - isServerComponent: (name: string) => boolean - isClientComponent: (name: string) => boolean resolver: (req: string) => Promise }): Promise<{ source: string @@ -71,10 +67,10 @@ async function parseModuleInfo({ } function addClientImport(path: string) { - if (isServerComponent(path) || hasFlightLoader(path, 'server')) { + if (serverComponentRegex.test(path) || hasFlightLoader(path, 'server')) { // If it's a server component, we recursively import its dependencies. imports.push(path) - } else if (isClientComponent(path)) { + } else if (clientComponentRegex.test(path)) { // Client component. imports.push(path) } else { @@ -102,7 +98,7 @@ async function parseModuleInfo({ if (!isClientCompilation) { // Server compilation for .server.js. - if (isServerComponent(importSource)) { + if (serverComponentRegex.test(importSource)) { continue } @@ -111,7 +107,7 @@ async function parseModuleInfo({ node.source.span.start - beginPos ) - if (isClientComponent(importSource)) { + if (clientComponentRegex.test(importSource)) { transformedSource += importDeclarations transformedSource += JSON.stringify( `next-flight-client-loader!${importSource}` @@ -210,12 +206,10 @@ export default async function transformSource( throw new Error('Expected source to have been transformed to a string.') } - const isServerComponent = createServerComponentFilter() - const isClientComponent = createClientComponentFilter() const hasAppliedFlightServerLoader = this.loaders.some((loader: any) => { return hasFlightLoader(loader.path, 'server') }) - const isServerExt = isServerComponent(resourcePath) + const isServerExt = serverComponentRegex.test(resourcePath) if (!isClientCompilation) { // We only apply the loader to server components, or shared components that @@ -235,8 +229,6 @@ export default async function transformSource( resourcePath, source, isClientCompilation, - isServerComponent, - isClientComponent, resolver, }) diff --git a/packages/next/build/webpack/loaders/utils.ts b/packages/next/build/webpack/loaders/utils.ts index ba2c478b8af9a..d6e113f1f93cd 100644 --- a/packages/next/build/webpack/loaders/utils.ts +++ b/packages/next/build/webpack/loaders/utils.ts @@ -24,25 +24,18 @@ export function buildExports(moduleExports: any, isESM: boolean) { return ret } -export const createClientComponentFilter = () => { - // Special cases for Next.js APIs that are considered as client components: - // - .client.[ext] - // - next built-in client components - // - .[imageExt] - const regex = new RegExp( - '(' + - `\\.client(\\.(${defaultJsFileExtensions.join('|')}))?|` + - `next/(${nextClientComponents.join('|')})(\\.js)?|` + - `\\.(${imageExtensions.join('|')})` + - ')$' - ) - - return (importSource: string) => regex.test(importSource) -} +// Special cases for Next.js APIs that are considered as client components: +// - .client.[ext] +// - next built-in client components +// - .[imageExt] +export const clientComponentRegex = new RegExp( + '(' + + `\\.client(\\.(${defaultJsFileExtensions.join('|')}))?|` + + `next/(${nextClientComponents.join('|')})(\\.js)?|` + + `\\.(${imageExtensions.join('|')})` + + ')$' +) -export const createServerComponentFilter = () => { - const regex = new RegExp( - `\\.server(\\.(${defaultJsFileExtensions.join('|')}))?$` - ) - return (importSource: string) => regex.test(importSource) -} +export const serverComponentRegex = new RegExp( + `\\.server(\\.(${defaultJsFileExtensions.join('|')}))?$` +) diff --git a/packages/next/build/webpack/plugins/flight-manifest-plugin.ts b/packages/next/build/webpack/plugins/flight-manifest-plugin.ts index 73c82d8b7d322..1dea744726659 100644 --- a/packages/next/build/webpack/plugins/flight-manifest-plugin.ts +++ b/packages/next/build/webpack/plugins/flight-manifest-plugin.ts @@ -7,7 +7,7 @@ import { webpack, sources } from 'next/dist/compiled/webpack/webpack' import { MIDDLEWARE_FLIGHT_MANIFEST } from '../../../shared/lib/constants' -import { createClientComponentFilter } from '../loaders/utils' +import { clientComponentRegex } from '../loaders/utils' // 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. @@ -19,20 +19,25 @@ import { createClientComponentFilter } from '../loaders/utils' type Options = { dev: boolean pageExtensions: string[] + isEdgeServer: boolean } const PLUGIN_NAME = 'FlightManifestPlugin' -const isClientComponent = createClientComponentFilter() +let edgeFlightManifest = {} +let nodeFlightManifest = {} + export class FlightManifestPlugin { dev: boolean = false pageExtensions: string[] + isEdgeServer: boolean constructor(options: Options) { if (typeof options.dev === 'boolean') { this.dev = options.dev } this.pageExtensions = options.pageExtensions + this.isEdgeServer = options.isEdgeServer } apply(compiler: any) { @@ -72,9 +77,14 @@ export class FlightManifestPlugin { // TODO: Hook into deps instead of the target module. // That way we know by the type of dep whether to include. // It also resolves conflicts when the same module is in multiple chunks. - if (!resource || !isClientComponent(resource)) { + if ( + !resource || + !clientComponentRegex.test(resource) || + !clientComponentRegex.test(id) + ) { return } + const moduleExports: any = manifest[resource] || {} const exportsInfo = compilation.moduleGraph.getExportsInfo(mod) @@ -107,10 +117,13 @@ export class FlightManifestPlugin { for (const mod of chunkModules) { let modId = compilation.chunkGraph.getModuleId(mod) - // remove resource query on production - if (typeof modId === 'string') { - modId = modId.split('?')[0] - } + if (typeof modId !== 'string') continue + + // Remove resource queries. + modId = modId.split('?')[0] + // Remove the loader prefix. + modId = modId.split('next-flight-client-loader.js!')[1] || modId + recordModule(modId, chunk, mod) // If this is a concatenation, register each child to the parent ID. if (mod.modules) { @@ -124,8 +137,19 @@ export class FlightManifestPlugin { // With switchable runtime, we need to emit the manifest files for both // runtimes. - const file = `server/${MIDDLEWARE_FLIGHT_MANIFEST}` - const json = JSON.stringify(manifest) + if (this.isEdgeServer) { + edgeFlightManifest = manifest + } else { + nodeFlightManifest = manifest + } + const mergedManifest = { + ...nodeFlightManifest, + ...edgeFlightManifest, + } + const file = + (!this.dev && !this.isEdgeServer ? '../' : '') + + MIDDLEWARE_FLIGHT_MANIFEST + const json = JSON.stringify(mergedManifest) assets[file + '.js'] = new sources.RawSource('self.__RSC_MANIFEST=' + json) assets[file + '.json'] = new sources.RawSource(json)