diff --git a/packages/next/src/build/analysis/get-page-static-info.ts b/packages/next/src/build/analysis/get-page-static-info.ts index 33fb5284e9b1e..ff1e705774d8d 100644 --- a/packages/next/src/build/analysis/get-page-static-info.ts +++ b/packages/next/src/build/analysis/get-page-static-info.ts @@ -149,6 +149,7 @@ export function getRSCModuleInformation( return { type, actions, + actionIds: parsedActionsMeta, clientRefs, clientEntryType, isClientRef, diff --git a/packages/next/src/build/webpack/loaders/next-flight-action-entry-loader.ts b/packages/next/src/build/webpack/loaders/next-flight-action-entry-loader.ts index 43715a887bd80..d3ac21b920752 100644 --- a/packages/next/src/build/webpack/loaders/next-flight-action-entry-loader.ts +++ b/packages/next/src/build/webpack/loaders/next-flight-action-entry-loader.ts @@ -1,20 +1,18 @@ -import { generateActionId } from './utils' - export type NextFlightActionEntryLoaderOptions = { actions: string - encryptionKey: string } function nextFlightActionEntryLoader(this: any) { - const { actions, encryptionKey }: NextFlightActionEntryLoaderOptions = - this.getOptions() + const { actions }: NextFlightActionEntryLoaderOptions = this.getOptions() - const actionList = JSON.parse(actions) as [string, string[]][] + const actionList = JSON.parse(actions) as [ + string, + [id: string, name: string][], + ][] const individualActions = actionList - .map(([path, names]) => { - return names.map((name) => { - const id = generateActionId(encryptionKey, path, name) - return [id, path, name] as [string, string, string] + .map(([path, actionsFromModule]) => { + return actionsFromModule.map(([id, name]) => { + return [id, path, name] }) }) .flat() diff --git a/packages/next/src/build/webpack/loaders/utils.ts b/packages/next/src/build/webpack/loaders/utils.ts index b98db333b0505..4a8536ec86845 100644 --- a/packages/next/src/build/webpack/loaders/utils.ts +++ b/packages/next/src/build/webpack/loaders/utils.ts @@ -1,5 +1,4 @@ import type webpack from 'webpack' -import { createHash } from 'crypto' import { RSC_MODULE_TYPES } from '../../../shared/lib/constants' const imageExtensions = ['jpg', 'jpeg', 'png', 'webp', 'avif', 'ico', 'svg'] @@ -47,25 +46,15 @@ export function isCSSMod(mod: { export function getActionsFromBuildInfo(mod: { resource: string buildInfo?: any -}): undefined | string[] { - return mod.buildInfo?.rsc?.actions +}): undefined | Record { + return mod.buildInfo?.rsc?.actionIds } -export function generateActionId( - hashSalt: string, - filePath: string, - exportName: string -) { - return createHash('sha1') - .update(hashSalt + filePath + ':' + exportName) - .digest('hex') -} - -export function encodeToBase64(obj: T): string { +export function encodeToBase64(obj: T): string { return Buffer.from(JSON.stringify(obj)).toString('base64') } -export function decodeFromBase64(str: string): T { +export function decodeFromBase64(str: string): T { return JSON.parse(Buffer.from(str, 'base64').toString('utf8')) } diff --git a/packages/next/src/build/webpack/plugins/flight-client-entry-plugin.ts b/packages/next/src/build/webpack/plugins/flight-client-entry-plugin.ts index 90031aca88eaa..8348438b989f1 100644 --- a/packages/next/src/build/webpack/plugins/flight-client-entry-plugin.ts +++ b/packages/next/src/build/webpack/plugins/flight-client-entry-plugin.ts @@ -25,7 +25,6 @@ import { } from '../../../shared/lib/constants' import { getActionsFromBuildInfo, - generateActionId, isClientComponentEntryModule, isCSSMod, regexCSS, @@ -43,6 +42,8 @@ import { getModuleBuildInfo } from '../loaders/get-module-build-info' import { getAssumedSourceType } from '../loaders/next-flight-loader' import { isAppRouteRoute } from '../../../lib/is-app-route-route' +type ActionIdNamePair = [id: string, name: string] + interface Options { dev: boolean appDir: string @@ -283,14 +284,17 @@ export class FlightClientEntryPlugin { const addActionEntryList: Array> = [] - const actionMapsPerEntry: Record> = {} - const createdActions = new Set() + const actionMapsPerEntry: Record< + string, + Map + > = {} + const createdActionIds = new Set() // For each SC server compilation entry, we need to create its corresponding // client component entry. forEachEntryModule(compilation, ({ name, entryModule }) => { const internalClientComponentEntryImports: ClientComponentImports = {} - const actionEntryImports = new Map() + const actionEntryImports = new Map() const clientEntriesToInject = [] const mergedCSSimports: CssImports = {} @@ -310,8 +314,8 @@ export class FlightClientEntryPlugin { resolvedModule: connection.resolvedModule, }) - actionImports.forEach(([dep, names]) => - actionEntryImports.set(dep, names) + actionImports.forEach(([dep, actions]) => + actionEntryImports.set(dep, actions) ) const isAbsoluteRequest = path.isAbsolute(entryRequest) @@ -429,7 +433,7 @@ export class FlightClientEntryPlugin { actions: actionEntryImports, entryName: name, bundlePath: name, - createdActions, + createdActionIds, }) ) } @@ -458,7 +462,10 @@ export class FlightClientEntryPlugin { await Promise.all(addActionEntryList) const addedClientActionEntryList: Promise[] = [] - const actionMapsPerClientEntry: Record> = {} + const actionMapsPerClientEntry: Record< + string, + Map + > = {} // We need to create extra action entries that are created from the // client layer. @@ -484,7 +491,7 @@ export class FlightClientEntryPlugin { } } - for (const [name, actionEntryImports] of Object.entries( + for (const [entryName, actionEntryImports] of Object.entries( actionMapsPerClientEntry )) { // If an action method is already created in the server layer, we don't @@ -492,13 +499,13 @@ export class FlightClientEntryPlugin { // This is to avoid duplicate action instances and make sure the module // state is shared. let remainingClientImportedActions = false - const remainingActionEntryImports = new Map() - for (const [dep, actionNames] of actionEntryImports) { + const remainingActionEntryImports = new Map() + for (const [dep, actions] of actionEntryImports) { const remainingActionNames = [] - for (const actionName of actionNames) { - const id = name + '@' + dep + '@' + actionName - if (!createdActions.has(id)) { - remainingActionNames.push(actionName) + for (const action of actions) { + // `action` is a [id, name] pair. + if (!createdActionIds.has(entryName + '@' + action[0])) { + remainingActionNames.push(action) } } if (remainingActionNames.length > 0) { @@ -513,10 +520,10 @@ export class FlightClientEntryPlugin { compiler, compilation, actions: remainingActionEntryImports, - entryName: name, - bundlePath: name, + entryName, + bundlePath: entryName, fromClient: true, - createdActions, + createdActionIds, }) ) } @@ -533,7 +540,7 @@ export class FlightClientEntryPlugin { dependencies: ReturnType[] }) { // action file path -> action names - const collectedActions = new Map() + const collectedActions = new Map() // Keep track of checked modules to avoid infinite loops with recursive imports. const visitedModule = new Set() @@ -558,7 +565,7 @@ export class FlightClientEntryPlugin { const actions = getActionsFromBuildInfo(mod) if (actions) { - collectedActions.set(modResource, actions) + collectedActions.set(modResource, Object.entries(actions)) } // Collect used exported actions transversely. @@ -617,14 +624,14 @@ export class FlightClientEntryPlugin { }): { cssImports: CssImports clientComponentImports: ClientComponentImports - actionImports: [string, string[]][] + actionImports: [string, ActionIdNamePair[]][] } { // Keep track of checked modules to avoid infinite loops with recursive imports. const visitedOfClientComponentsTraverse = new Set() // Info to collect. const clientComponentImports: ClientComponentImports = {} - const actionImports: [string, string[]][] = [] + const actionImports: [string, ActionIdNamePair[]][] = [] const CSSImports = new Set() const filterClientComponents = ( @@ -652,7 +659,7 @@ export class FlightClientEntryPlugin { const actions = getActionsFromBuildInfo(mod) if (actions) { - actionImports.push([modResource, actions]) + actionImports.push([modResource, Object.entries(actions)]) } if (isCSSMod(mod)) { @@ -839,20 +846,20 @@ export class FlightClientEntryPlugin { entryName, bundlePath, fromClient, - createdActions, + createdActionIds, }: { compiler: webpack.Compiler compilation: webpack.Compilation - actions: Map + actions: Map entryName: string bundlePath: string - createdActions: Set + createdActionIds: Set fromClient?: boolean }) { const actionsArray = Array.from(actions.entries()) - for (const [dep, actionNames] of actions) { - for (const actionName of actionNames) { - createdActions.add(entryName + '@' + dep + '@' + actionName) + for (const [, actionsFromModule] of actions) { + for (const [id] of actionsFromModule) { + createdActionIds.add(entryName + '@' + id) } } @@ -862,7 +869,6 @@ export class FlightClientEntryPlugin { const actionLoader = `next-flight-action-entry-loader?${stringify({ actions: JSON.stringify(actionsArray), - encryptionKey: this.encryptionKey, __client_imported__: fromClient, })}!` @@ -870,9 +876,8 @@ export class FlightClientEntryPlugin { ? pluginState.edgeServerActions : pluginState.serverActions - for (const [actionFilePath, actionNames] of actionsArray) { - for (const name of actionNames) { - const id = generateActionId(this.encryptionKey, actionFilePath, name) + for (const [, actionsFromModule] of actionsArray) { + for (const [id] of actionsFromModule) { if (typeof currentCompilerServerActions[id] === 'undefined') { currentCompilerServerActions[id] = { workers: {},