diff --git a/packages/next/src/build/webpack-config.ts b/packages/next/src/build/webpack-config.ts index b6c97a0974301..237e36acec02c 100644 --- a/packages/next/src/build/webpack-config.ts +++ b/packages/next/src/build/webpack-config.ts @@ -47,7 +47,8 @@ import { WellKnownErrorsPlugin } from './webpack/plugins/wellknown-errors-plugin import { regexLikeCss } from './webpack/config/blocks/css' import { CopyFilePlugin } from './webpack/plugins/copy-file-plugin' import { ClientReferenceManifestPlugin } from './webpack/plugins/flight-manifest-plugin' -import { FlightClientEntryPlugin } from './webpack/plugins/flight-client-entry-plugin' +import { FlightClientEntryPlugin as NextFlightClientEntryPlugin } from './webpack/plugins/flight-client-entry-plugin' +import { RspackFlightClientEntryPlugin } from './webpack/plugins/rspack-flight-client-entry-plugin' import { NextTypesPlugin } from './webpack/plugins/next-types-plugin' import type { Feature, @@ -341,6 +342,10 @@ export default async function getBaseWebpackConfig( const isRspack = Boolean(process.env.NEXT_RSPACK) +const FlightClientEntryPlugin = isRspack && process.env.BUILTIN_FLIGHT_CLIENT_ENTRY_PLUGIN + ? RspackFlightClientEntryPlugin + : NextFlightClientEntryPlugin; + // If the current compilation is aimed at server-side code instead of client-side code. const isNodeOrEdgeCompilation = isNodeServer || isEdgeServer diff --git a/packages/next/src/build/webpack/loaders/next-flight-client-module-loader.ts b/packages/next/src/build/webpack/loaders/next-flight-client-module-loader.ts index cd0538776b90d..51cd619dcbd83 100644 --- a/packages/next/src/build/webpack/loaders/next-flight-client-module-loader.ts +++ b/packages/next/src/build/webpack/loaders/next-flight-client-module-loader.ts @@ -30,6 +30,11 @@ const flightClientModuleLoader: webpack.LoaderDefinitionFunction = .join('\n') } + if (process.env.BUILTIN_FLIGHT_CLIENT_ENTRY_PLUGIN) { + const rscModuleInformationJson = JSON.stringify(buildInfo.rsc); + source += `\n/* __rspack_internal_rsc_module_information_do_not_use__ ${rscModuleInformationJson} */`; + } + return this.callback(null, source, sourceMap) } diff --git a/packages/next/src/build/webpack/loaders/next-flight-loader/index.ts b/packages/next/src/build/webpack/loaders/next-flight-loader/index.ts index e5d5b420b1c18..246282bddd3e5 100644 --- a/packages/next/src/build/webpack/loaders/next-flight-loader/index.ts +++ b/packages/next/src/build/webpack/loaders/next-flight-loader/index.ts @@ -134,6 +134,11 @@ ${JSON.stringify(ref)}, } } + if (process.env.BUILTIN_FLIGHT_CLIENT_ENTRY_PLUGIN) { + const rscModuleInformationJson = JSON.stringify(buildInfo.rsc); + esmSource += `\n/* __rspack_internal_rsc_module_information_do_not_use__ ${rscModuleInformationJson} */`; + } + return this.callback(null, esmSource, sourceMap) } else if (assumedSourceType === 'commonjs') { let cjsSource = `\ @@ -141,6 +146,10 @@ const { createProxy } = require("${MODULE_PROXY_PATH}") module.exports = createProxy(${stringifiedResourceKey}) ` + if (process.env.BUILTIN_FLIGHT_CLIENT_ENTRY_PLUGIN) { + const rscModuleInformationJson = JSON.stringify(buildInfo.rsc); + cjsSource += `\n/* __rspack_internal_rsc_module_information_do_not_use__ ${rscModuleInformationJson} */`; + } return this.callback(null, cjsSource, sourceMap) } @@ -154,9 +163,15 @@ module.exports = createProxy(${stringifiedResourceKey}) } } - const replacedSource = source.replace( + let replacedSource = source.replace( RSC_MOD_REF_PROXY_ALIAS, MODULE_PROXY_PATH ) + + if (process.env.BUILTIN_FLIGHT_CLIENT_ENTRY_PLUGIN) { + const rscModuleInformationJson = JSON.stringify(buildInfo.rsc); + replacedSource += `\n/* __rspack_internal_rsc_module_information_do_not_use__ ${rscModuleInformationJson} */`; + } + this.callback(null, replacedSource, sourceMap) } diff --git a/packages/next/src/build/webpack/plugins/rspack-flight-client-entry-plugin.ts b/packages/next/src/build/webpack/plugins/rspack-flight-client-entry-plugin.ts new file mode 100644 index 0000000000000..c45bb0bd218d7 --- /dev/null +++ b/packages/next/src/build/webpack/plugins/rspack-flight-client-entry-plugin.ts @@ -0,0 +1,155 @@ +import type { Compiler } from '@rspack/core' +import { + getInvalidator, + getEntries, + EntryTypes, + getEntryKey, +} from '../../../server/dev/on-demand-entry-handler' +import { + COMPILER_NAMES, +} from '../../../shared/lib/constants' + +import { getProxiedPluginState } from '../../build-context' +import { PAGE_TYPES } from '../../../lib/page-types' + +import { FlightClientEntryPlugin } from '@rspack/core' + +type Actions = { + [actionId: string]: { + workers: { + [name: string]: { moduleId: string | number; async: boolean } + } + // Record which layer the action is in (rsc or sc_action), in the specific entry. + layer: { + [name: string]: string + } + } +} + +export type ActionManifest = { + // Assign a unique encryption key during production build. + encryptionKey: string + node: Actions + edge: Actions +} + +export interface ModuleInfo { + moduleId: string | number + async: boolean + } + +const pluginState = getProxiedPluginState({ + // A map to track "action" -> "list of bundles". + serverActions: {} as ActionManifest['node'], + edgeServerActions: {} as ActionManifest['edge'], + + serverActionModules: {} as { + [workerName: string]: { server?: ModuleInfo; client?: ModuleInfo } + }, + + edgeServerActionModules: {} as { + [workerName: string]: { server?: ModuleInfo; client?: ModuleInfo } + }, + + ssrModules: {} as { [ssrModuleId: string]: ModuleInfo }, + edgeSsrModules: {} as { [ssrModuleId: string]: ModuleInfo }, + + rscModules: {} as { [rscModuleId: string]: ModuleInfo }, + edgeRscModules: {} as { [rscModuleId: string]: ModuleInfo }, + + injectedClientEntries: {} as Record, +}) + +interface Options { + dev: boolean + appDir: string + isEdgeServer: boolean + encryptionKey: string +} + +export class RspackFlightClientEntryPlugin { + plugin: any + compiler?: Compiler + + constructor(options: Options) { + this.plugin = new FlightClientEntryPlugin({ + ...options, + builtinAppLoader: !!process.env.BUILTIN_SWC_LOADER, + shouldInvalidateCb: ({ + bundlePath, + entryName, + absolutePagePath, + clientBrowserLoader, + }) => { + console.log( + "shouldInvalidateCb", + bundlePath, + entryName, + absolutePagePath, + clientBrowserLoader + ); + let shouldInvalidate = false + const compiler = this.compiler! + + const entries = getEntries(compiler.outputPath) + const pageKey = getEntryKey( + COMPILER_NAMES.client, + PAGE_TYPES.APP, + bundlePath + ) + + if (!entries[pageKey]) { + entries[pageKey] = { + type: EntryTypes.CHILD_ENTRY, + parentEntries: new Set([entryName]), + absoluteEntryFilePath: absolutePagePath, + bundlePath, + request: clientBrowserLoader, + dispose: false, + lastActiveTime: Date.now(), + } + shouldInvalidate = true + } else { + const entryData = entries[pageKey] + // New version of the client loader + if (entryData.request !== clientBrowserLoader) { + entryData.request = clientBrowserLoader + shouldInvalidate = true + } + if (entryData.type === EntryTypes.CHILD_ENTRY) { + entryData.parentEntries.add(entryName) + } + entryData.dispose = false + entryData.lastActiveTime = Date.now() + } + + return shouldInvalidate + }, + invalidateCb: () => { + const compiler = this.compiler! + + // Invalidate in development to trigger recompilation + const invalidator = getInvalidator(compiler.outputPath) + // Check if any of the entry injections need an invalidation + if (invalidator) { + invalidator.invalidate([COMPILER_NAMES.client]) + } + }, + stateCb: (state) => { + Object.assign(pluginState.serverActions, state.serverActions); + Object.assign(pluginState.serverActionModules, state.serverActionModules); + Object.assign(pluginState.edgeServerActionModules, state.edgeServerActionModules); + Object.assign(pluginState.ssrModules, state.ssrModules); + Object.assign(pluginState.edgeSsrModules, state.edgeSsrModules); + Object.assign(pluginState.rscModules, state.rscModules); + Object.assign(pluginState.edgeRscModules, state.edgeRscModules); + Object.assign(pluginState.injectedClientEntries, state.injectedClientEntries); + }, + }) + } + + apply(compiler: Compiler) { + this.compiler = compiler; + this.plugin.apply(compiler) + } +}