Skip to content

Commit 5ab28cd

Browse files
authored
Simplify Server Action Webpack plugin (#71721)
This PR simplifies our `flight-client-entry-plugin` to not re-compute the server reference IDs because they're already provided in `buildInfo` via our SWC transform. It's also a small performance win depending on the number of Server Actions in the project. Cherry-picked from #71463.
1 parent ddc8807 commit 5ab28cd

File tree

4 files changed

+51
-58
lines changed

4 files changed

+51
-58
lines changed

packages/next/src/build/analysis/get-page-static-info.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ export function getRSCModuleInformation(
149149
return {
150150
type,
151151
actions,
152+
actionIds: parsedActionsMeta,
152153
clientRefs,
153154
clientEntryType,
154155
isClientRef,

packages/next/src/build/webpack/loaders/next-flight-action-entry-loader.ts

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,18 @@
1-
import { generateActionId } from './utils'
2-
31
export type NextFlightActionEntryLoaderOptions = {
42
actions: string
5-
encryptionKey: string
63
}
74

85
function nextFlightActionEntryLoader(this: any) {
9-
const { actions, encryptionKey }: NextFlightActionEntryLoaderOptions =
10-
this.getOptions()
6+
const { actions }: NextFlightActionEntryLoaderOptions = this.getOptions()
117

12-
const actionList = JSON.parse(actions) as [string, string[]][]
8+
const actionList = JSON.parse(actions) as [
9+
string,
10+
[id: string, name: string][],
11+
][]
1312
const individualActions = actionList
14-
.map(([path, names]) => {
15-
return names.map((name) => {
16-
const id = generateActionId(encryptionKey, path, name)
17-
return [id, path, name] as [string, string, string]
13+
.map(([path, actionsFromModule]) => {
14+
return actionsFromModule.map(([id, name]) => {
15+
return [id, path, name]
1816
})
1917
})
2018
.flat()

packages/next/src/build/webpack/loaders/utils.ts

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import type webpack from 'webpack'
2-
import { createHash } from 'crypto'
32
import { RSC_MODULE_TYPES } from '../../../shared/lib/constants'
43

54
const imageExtensions = ['jpg', 'jpeg', 'png', 'webp', 'avif', 'ico', 'svg']
@@ -47,25 +46,15 @@ export function isCSSMod(mod: {
4746
export function getActionsFromBuildInfo(mod: {
4847
resource: string
4948
buildInfo?: any
50-
}): undefined | string[] {
51-
return mod.buildInfo?.rsc?.actions
49+
}): undefined | Record<string, string> {
50+
return mod.buildInfo?.rsc?.actionIds
5251
}
5352

54-
export function generateActionId(
55-
hashSalt: string,
56-
filePath: string,
57-
exportName: string
58-
) {
59-
return createHash('sha1')
60-
.update(hashSalt + filePath + ':' + exportName)
61-
.digest('hex')
62-
}
63-
64-
export function encodeToBase64<T extends {}>(obj: T): string {
53+
export function encodeToBase64<T extends object>(obj: T): string {
6554
return Buffer.from(JSON.stringify(obj)).toString('base64')
6655
}
6756

68-
export function decodeFromBase64<T extends {}>(str: string): T {
57+
export function decodeFromBase64<T extends object>(str: string): T {
6958
return JSON.parse(Buffer.from(str, 'base64').toString('utf8'))
7059
}
7160

packages/next/src/build/webpack/plugins/flight-client-entry-plugin.ts

Lines changed: 38 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import {
2525
} from '../../../shared/lib/constants'
2626
import {
2727
getActionsFromBuildInfo,
28-
generateActionId,
2928
isClientComponentEntryModule,
3029
isCSSMod,
3130
regexCSS,
@@ -43,6 +42,8 @@ import { getModuleBuildInfo } from '../loaders/get-module-build-info'
4342
import { getAssumedSourceType } from '../loaders/next-flight-loader'
4443
import { isAppRouteRoute } from '../../../lib/is-app-route-route'
4544

45+
type ActionIdNamePair = [id: string, name: string]
46+
4647
interface Options {
4748
dev: boolean
4849
appDir: string
@@ -283,14 +284,17 @@ export class FlightClientEntryPlugin {
283284

284285
const addActionEntryList: Array<ReturnType<typeof this.injectActionEntry>> =
285286
[]
286-
const actionMapsPerEntry: Record<string, Map<string, string[]>> = {}
287-
const createdActions = new Set<string>()
287+
const actionMapsPerEntry: Record<
288+
string,
289+
Map<string, ActionIdNamePair[]>
290+
> = {}
291+
const createdActionIds = new Set<string>()
288292

289293
// For each SC server compilation entry, we need to create its corresponding
290294
// client component entry.
291295
forEachEntryModule(compilation, ({ name, entryModule }) => {
292296
const internalClientComponentEntryImports: ClientComponentImports = {}
293-
const actionEntryImports = new Map<string, string[]>()
297+
const actionEntryImports = new Map<string, ActionIdNamePair[]>()
294298
const clientEntriesToInject = []
295299
const mergedCSSimports: CssImports = {}
296300

@@ -310,8 +314,8 @@ export class FlightClientEntryPlugin {
310314
resolvedModule: connection.resolvedModule,
311315
})
312316

313-
actionImports.forEach(([dep, names]) =>
314-
actionEntryImports.set(dep, names)
317+
actionImports.forEach(([dep, actions]) =>
318+
actionEntryImports.set(dep, actions)
315319
)
316320

317321
const isAbsoluteRequest = path.isAbsolute(entryRequest)
@@ -429,7 +433,7 @@ export class FlightClientEntryPlugin {
429433
actions: actionEntryImports,
430434
entryName: name,
431435
bundlePath: name,
432-
createdActions,
436+
createdActionIds,
433437
})
434438
)
435439
}
@@ -458,7 +462,10 @@ export class FlightClientEntryPlugin {
458462
await Promise.all(addActionEntryList)
459463

460464
const addedClientActionEntryList: Promise<any>[] = []
461-
const actionMapsPerClientEntry: Record<string, Map<string, string[]>> = {}
465+
const actionMapsPerClientEntry: Record<
466+
string,
467+
Map<string, ActionIdNamePair[]>
468+
> = {}
462469

463470
// We need to create extra action entries that are created from the
464471
// client layer.
@@ -484,21 +491,21 @@ export class FlightClientEntryPlugin {
484491
}
485492
}
486493

487-
for (const [name, actionEntryImports] of Object.entries(
494+
for (const [entryName, actionEntryImports] of Object.entries(
488495
actionMapsPerClientEntry
489496
)) {
490497
// If an action method is already created in the server layer, we don't
491498
// need to create it again in the action layer.
492499
// This is to avoid duplicate action instances and make sure the module
493500
// state is shared.
494501
let remainingClientImportedActions = false
495-
const remainingActionEntryImports = new Map<string, string[]>()
496-
for (const [dep, actionNames] of actionEntryImports) {
502+
const remainingActionEntryImports = new Map<string, ActionIdNamePair[]>()
503+
for (const [dep, actions] of actionEntryImports) {
497504
const remainingActionNames = []
498-
for (const actionName of actionNames) {
499-
const id = name + '@' + dep + '@' + actionName
500-
if (!createdActions.has(id)) {
501-
remainingActionNames.push(actionName)
505+
for (const action of actions) {
506+
// `action` is a [id, name] pair.
507+
if (!createdActionIds.has(entryName + '@' + action[0])) {
508+
remainingActionNames.push(action)
502509
}
503510
}
504511
if (remainingActionNames.length > 0) {
@@ -513,10 +520,10 @@ export class FlightClientEntryPlugin {
513520
compiler,
514521
compilation,
515522
actions: remainingActionEntryImports,
516-
entryName: name,
517-
bundlePath: name,
523+
entryName,
524+
bundlePath: entryName,
518525
fromClient: true,
519-
createdActions,
526+
createdActionIds,
520527
})
521528
)
522529
}
@@ -533,7 +540,7 @@ export class FlightClientEntryPlugin {
533540
dependencies: ReturnType<typeof webpack.EntryPlugin.createDependency>[]
534541
}) {
535542
// action file path -> action names
536-
const collectedActions = new Map<string, string[]>()
543+
const collectedActions = new Map<string, ActionIdNamePair[]>()
537544

538545
// Keep track of checked modules to avoid infinite loops with recursive imports.
539546
const visitedModule = new Set<string>()
@@ -558,7 +565,7 @@ export class FlightClientEntryPlugin {
558565

559566
const actions = getActionsFromBuildInfo(mod)
560567
if (actions) {
561-
collectedActions.set(modResource, actions)
568+
collectedActions.set(modResource, Object.entries(actions))
562569
}
563570

564571
// Collect used exported actions transversely.
@@ -617,14 +624,14 @@ export class FlightClientEntryPlugin {
617624
}): {
618625
cssImports: CssImports
619626
clientComponentImports: ClientComponentImports
620-
actionImports: [string, string[]][]
627+
actionImports: [string, ActionIdNamePair[]][]
621628
} {
622629
// Keep track of checked modules to avoid infinite loops with recursive imports.
623630
const visitedOfClientComponentsTraverse = new Set()
624631

625632
// Info to collect.
626633
const clientComponentImports: ClientComponentImports = {}
627-
const actionImports: [string, string[]][] = []
634+
const actionImports: [string, ActionIdNamePair[]][] = []
628635
const CSSImports = new Set<string>()
629636

630637
const filterClientComponents = (
@@ -652,7 +659,7 @@ export class FlightClientEntryPlugin {
652659

653660
const actions = getActionsFromBuildInfo(mod)
654661
if (actions) {
655-
actionImports.push([modResource, actions])
662+
actionImports.push([modResource, Object.entries(actions)])
656663
}
657664

658665
if (isCSSMod(mod)) {
@@ -839,20 +846,20 @@ export class FlightClientEntryPlugin {
839846
entryName,
840847
bundlePath,
841848
fromClient,
842-
createdActions,
849+
createdActionIds,
843850
}: {
844851
compiler: webpack.Compiler
845852
compilation: webpack.Compilation
846-
actions: Map<string, string[]>
853+
actions: Map<string, ActionIdNamePair[]>
847854
entryName: string
848855
bundlePath: string
849-
createdActions: Set<string>
856+
createdActionIds: Set<string>
850857
fromClient?: boolean
851858
}) {
852859
const actionsArray = Array.from(actions.entries())
853-
for (const [dep, actionNames] of actions) {
854-
for (const actionName of actionNames) {
855-
createdActions.add(entryName + '@' + dep + '@' + actionName)
860+
for (const [, actionsFromModule] of actions) {
861+
for (const [id] of actionsFromModule) {
862+
createdActionIds.add(entryName + '@' + id)
856863
}
857864
}
858865

@@ -862,17 +869,15 @@ export class FlightClientEntryPlugin {
862869

863870
const actionLoader = `next-flight-action-entry-loader?${stringify({
864871
actions: JSON.stringify(actionsArray),
865-
encryptionKey: this.encryptionKey,
866872
__client_imported__: fromClient,
867873
})}!`
868874

869875
const currentCompilerServerActions = this.isEdgeServer
870876
? pluginState.edgeServerActions
871877
: pluginState.serverActions
872878

873-
for (const [actionFilePath, actionNames] of actionsArray) {
874-
for (const name of actionNames) {
875-
const id = generateActionId(this.encryptionKey, actionFilePath, name)
879+
for (const [, actionsFromModule] of actionsArray) {
880+
for (const [id] of actionsFromModule) {
876881
if (typeof currentCompilerServerActions[id] === 'undefined') {
877882
currentCompilerServerActions[id] = {
878883
workers: {},

0 commit comments

Comments
 (0)