Skip to content

Commit bf49f62

Browse files
authored
Fix Server Actions defined in both layers in one entry (#49248)
This fixes an existing bug where there're Server Actions defined in both the "server" and "client" layers (client imported Actions). They have the same worker name as they exist in one route entry, but they're built into different modules and compiled differently (server and client layers). Because of that, each route entry can have 2 "action module entries". This PR adds the logic to differentiate that via a "layer" field so they don't conflict.
1 parent 7fa4946 commit bf49f62

File tree

2 files changed

+98
-27
lines changed

2 files changed

+98
-27
lines changed

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

Lines changed: 89 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ export type ActionManifest = {
4848
workers: {
4949
[name: string]: string | number
5050
}
51+
// Record which layer the action is in (sc_server or sc_action), in the specific entry.
52+
layer: {
53+
[name: string]: string
54+
}
5155
}
5256
}
5357
}
@@ -56,8 +60,21 @@ const pluginState = getProxiedPluginState({
5660
// A map to track "action" -> "list of bundles".
5761
serverActions: {} as ActionManifest['node'],
5862
edgeServerActions: {} as ActionManifest['edge'],
59-
actionModServerId: {} as Record<string, string | number>,
60-
actionModEdgeServerId: {} as Record<string, string | number>,
63+
64+
actionModServerId: {} as Record<
65+
string,
66+
{
67+
server?: string | number
68+
client?: string | number
69+
}
70+
>,
71+
actionModEdgeServerId: {} as Record<
72+
string,
73+
{
74+
server?: string | number
75+
client?: string | number
76+
}
77+
>,
6178

6279
// Manifest of CSS entry files for server/edge server.
6380
serverCSSManifest: {} as ClientCSSReferenceManifest,
@@ -170,6 +187,7 @@ export class ClientReferenceEntryPlugin {
170187

171188
const addActionEntryList: Array<ReturnType<typeof this.injectActionEntry>> =
172189
[]
190+
const actionMapsPerEntry: Record<string, Map<string, string[]>> = {}
173191

174192
// For each SC server compilation entry, we need to create its corresponding
175193
// client component entry.
@@ -247,19 +265,31 @@ export class ClientReferenceEntryPlugin {
247265
)
248266
)
249267
} else {
250-
addActionEntryList.push(
251-
this.injectActionEntry({
252-
compiler,
253-
compilation,
254-
actions: actionEntryImports,
255-
entryName: name,
256-
bundlePath: name,
257-
})
258-
)
268+
if (!actionMapsPerEntry[name]) {
269+
actionMapsPerEntry[name] = new Map()
270+
}
271+
actionMapsPerEntry[name] = new Map([
272+
...actionMapsPerEntry[name],
273+
...actionEntryImports,
274+
])
259275
}
260276
}
261277
})
262278

279+
for (const [name, actionEntryImports] of Object.entries(
280+
actionMapsPerEntry
281+
)) {
282+
addActionEntryList.push(
283+
this.injectActionEntry({
284+
compiler,
285+
compilation,
286+
actions: actionEntryImports,
287+
entryName: name,
288+
bundlePath: name,
289+
})
290+
)
291+
}
292+
263293
// To collect all CSS imports and action imports for a specific entry
264294
// including the ones that are in the client graph, we need to store a
265295
// map for client boundary dependencies.
@@ -293,6 +323,7 @@ export class ClientReferenceEntryPlugin {
293323
// client layer.
294324
compilation.hooks.finishModules.tapPromise(PLUGIN_NAME, () => {
295325
const addedClientActionEntryList: Promise<any>[] = []
326+
const actionMapsPerClientEntry: Record<string, Map<string, string[]>> = {}
296327

297328
forEachEntryModule(compilation, ({ name, entryModule }) => {
298329
const actionEntryImports = new Map<string, string[]>()
@@ -324,18 +355,31 @@ export class ClientReferenceEntryPlugin {
324355
}
325356

326357
if (actionEntryImports.size > 0) {
327-
addedClientActionEntryList.push(
328-
this.injectActionEntry({
329-
compiler,
330-
compilation,
331-
actions: actionEntryImports,
332-
entryName: name,
333-
bundlePath: name,
334-
fromClient: true,
335-
})
336-
)
358+
if (!actionMapsPerClientEntry[name]) {
359+
actionMapsPerClientEntry[name] = new Map()
360+
}
361+
actionMapsPerClientEntry[name] = new Map([
362+
...actionMapsPerClientEntry[name],
363+
...actionEntryImports,
364+
])
337365
}
338366
})
367+
368+
for (const [name, actionEntryImports] of Object.entries(
369+
actionMapsPerClientEntry
370+
)) {
371+
addedClientActionEntryList.push(
372+
this.injectActionEntry({
373+
compiler,
374+
compilation,
375+
actions: actionEntryImports,
376+
entryName: name,
377+
bundlePath: name,
378+
fromClient: true,
379+
})
380+
)
381+
}
382+
339383
return Promise.all(addedClientActionEntryList)
340384
})
341385

@@ -719,6 +763,7 @@ export class ClientReferenceEntryPlugin {
719763
const actionsArray = Array.from(actions.entries())
720764
const actionLoader = `next-flight-action-entry-loader?${stringify({
721765
actions: JSON.stringify(actionsArray),
766+
__client_imported__: fromClient,
722767
})}!`
723768

724769
const currentCompilerServerActions = this.isEdgeServer
@@ -730,9 +775,13 @@ export class ClientReferenceEntryPlugin {
730775
if (typeof currentCompilerServerActions[id] === 'undefined') {
731776
currentCompilerServerActions[id] = {
732777
workers: {},
778+
layer: {},
733779
}
734780
}
735781
currentCompilerServerActions[id].workers[bundlePath] = ''
782+
currentCompilerServerActions[id].layer[bundlePath] = fromClient
783+
? WEBPACK_LAYERS.action
784+
: WEBPACK_LAYERS.server
736785
}
737786
}
738787

@@ -793,19 +842,28 @@ export class ClientReferenceEntryPlugin {
793842
mod.request &&
794843
/next-flight-action-entry-loader/.test(mod.request)
795844
) {
796-
if (this.isEdgeServer) {
797-
pluginState.actionModEdgeServerId[chunkGroup.name] = modId
798-
} else {
799-
pluginState.actionModServerId[chunkGroup.name] = modId
845+
const fromClient = /&__client_imported__=true/.test(mod.request)
846+
847+
const mapping = this.isEdgeServer
848+
? pluginState.actionModEdgeServerId
849+
: pluginState.actionModServerId
850+
851+
if (!mapping[chunkGroup.name]) {
852+
mapping[chunkGroup.name] = {}
800853
}
854+
mapping[chunkGroup.name][fromClient ? 'client' : 'server'] = modId
801855
}
802856
})
803857

804858
const serverActions: ActionManifest['node'] = {}
805859
for (let id in pluginState.serverActions) {
806860
const action = pluginState.serverActions[id]
807861
for (let name in action.workers) {
808-
action.workers[name] = pluginState.actionModServerId[name]
862+
const modId =
863+
pluginState.actionModServerId[name][
864+
action.layer[name] === WEBPACK_LAYERS.action ? 'client' : 'server'
865+
]
866+
action.workers[name] = modId!
809867
}
810868
serverActions[id] = action
811869
}
@@ -814,7 +872,11 @@ export class ClientReferenceEntryPlugin {
814872
for (let id in pluginState.edgeServerActions) {
815873
const action = pluginState.edgeServerActions[id]
816874
for (let name in action.workers) {
817-
action.workers[name] = pluginState.actionModEdgeServerId[name]
875+
const modId =
876+
pluginState.actionModEdgeServerId[name][
877+
action.layer[name] === WEBPACK_LAYERS.action ? 'client' : 'server'
878+
]
879+
action.workers[name] = modId!
818880
}
819881
edgeServerActions[id] = action
820882
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
async function noopAction() {
2+
'use server'
3+
}
4+
5+
console.log(noopAction())
6+
7+
export default function Layout({ children }) {
8+
return children
9+
}

0 commit comments

Comments
 (0)