Skip to content

Commit 1b3c51a

Browse files
authored
Create server module map proxy only once (#86750)
Small follow-up to #86591. We don't need to recreate the server module map proxy every time we set the manifests singleton. Instead, we can just update the underlying server actions manifest, and the server module map can read the current manifest from the singleton.
1 parent 1a1957c commit 1b3c51a

File tree

3 files changed

+115
-122
lines changed

3 files changed

+115
-122
lines changed

packages/next/src/server/app-render/action-handler.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,10 @@ import { warn } from '../../build/output/log'
4949
import { RequestCookies, ResponseCookies } from '../web/spec-extension/cookies'
5050
import { HeadersAdapter } from '../web/spec-extension/adapters/headers'
5151
import { fromNodeOutgoingHttpHeaders } from '../web/utils'
52-
import { selectWorkerForForwarding, type ServerModuleMap } from './action-utils'
52+
import {
53+
selectWorkerForForwarding,
54+
type ServerModuleMap,
55+
} from './manifests-singleton'
5356
import { isNodeNextRequest, isWebNextRequest } from '../base-http/helpers'
5457
import { RedirectStatusCode } from '../../client/components/redirect-status-code'
5558
import { synchronizeMutableCookies } from '../async-storage/request-store'

packages/next/src/server/app-render/action-utils.ts

Lines changed: 0 additions & 111 deletions
This file was deleted.

packages/next/src/server/app-render/manifests-singleton.ts

Lines changed: 111 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,19 @@ import type { ClientReferenceManifest } from '../../build/webpack/plugins/flight
33
import type { DeepReadonly } from '../../shared/lib/deep-readonly'
44
import { InvariantError } from '../../shared/lib/invariant-error'
55
import { normalizeAppPath } from '../../shared/lib/router/utils/app-paths'
6-
import { createServerModuleMap, type ServerModuleMap } from './action-utils'
6+
import { pathHasPrefix } from '../../shared/lib/router/utils/path-has-prefix'
7+
import { removePathPrefix } from '../../shared/lib/router/utils/remove-path-prefix'
78
import { workAsyncStorage } from './work-async-storage.external'
89

10+
export interface ServerModuleMap {
11+
readonly [name: string]: {
12+
readonly id: string | number
13+
readonly name: string
14+
readonly chunks: Readonly<Array<string>> // currently not used
15+
readonly async?: boolean
16+
}
17+
}
18+
919
// This is a global singleton that is, among other things, also used to
1020
// encode/decode bound args of server function closures. This can't be using a
1121
// AsyncLocalStorage as it might happen at the module level.
@@ -162,6 +172,105 @@ function createProxiedClientReferenceManifest(
162172
) as DeepReadonly<ClientReferenceManifest>
163173
}
164174

175+
/**
176+
* This function creates a Flight-acceptable server module map proxy from our
177+
* Server Reference Manifest similar to our client module map. This is because
178+
* our manifest contains a lot of internal Next.js data that are relevant to the
179+
* runtime, workers, etc. that React doesn't need to know.
180+
*/
181+
function createServerModuleMap(): ServerModuleMap {
182+
return new Proxy(
183+
{},
184+
{
185+
get: (_, id: string) => {
186+
const workers =
187+
getServerActionsManifest()[
188+
process.env.NEXT_RUNTIME === 'edge' ? 'edge' : 'node'
189+
]?.[id]?.workers
190+
191+
if (!workers) {
192+
return undefined
193+
}
194+
195+
const workStore = workAsyncStorage.getStore()
196+
197+
let workerEntry:
198+
| { moduleId: string | number; async: boolean }
199+
| undefined
200+
201+
if (workStore) {
202+
workerEntry = workers[normalizeWorkerPageName(workStore.page)]
203+
} else {
204+
// If there's no work store defined, we can assume that a server
205+
// module map is needed during module evaluation, e.g. to create a
206+
// server action using a higher-order function. Therefore it should be
207+
// safe to return any entry from the manifest that matches the action
208+
// ID. They all refer to the same module ID, which must also exist in
209+
// the current page bundle. TODO: This is currently not guaranteed in
210+
// Turbopack, and needs to be fixed.
211+
workerEntry = Object.values(workers).at(0)
212+
}
213+
214+
if (!workerEntry) {
215+
return undefined
216+
}
217+
218+
const { moduleId, async } = workerEntry
219+
220+
return { id: moduleId, name: id, chunks: [], async }
221+
},
222+
}
223+
)
224+
}
225+
226+
/**
227+
* The flight entry loader keys actions by bundlePath. bundlePath corresponds
228+
* with the relative path (including 'app') to the page entrypoint.
229+
*/
230+
function normalizeWorkerPageName(pageName: string) {
231+
if (pathHasPrefix(pageName, 'app')) {
232+
return pageName
233+
}
234+
235+
return 'app' + pageName
236+
}
237+
238+
/**
239+
* Converts a bundlePath (relative path to the entrypoint) to a routable page
240+
* name.
241+
*/
242+
function denormalizeWorkerPageName(bundlePath: string) {
243+
return normalizeAppPath(removePathPrefix(bundlePath, 'app'))
244+
}
245+
246+
/**
247+
* Checks if the requested action has a worker for the current page.
248+
* If not, it returns the first worker that has a handler for the action.
249+
*/
250+
export function selectWorkerForForwarding(
251+
actionId: string,
252+
pageName: string
253+
): string | undefined {
254+
const serverActionsManifest = getServerActionsManifest()
255+
const workers =
256+
serverActionsManifest[
257+
process.env.NEXT_RUNTIME === 'edge' ? 'edge' : 'node'
258+
][actionId]?.workers
259+
260+
// There are no workers to handle this action, nothing to forward to.
261+
if (!workers) {
262+
return
263+
}
264+
265+
// If there is an entry for the current page, we don't need to forward.
266+
if (workers[normalizeWorkerPageName(pageName)]) {
267+
return
268+
}
269+
270+
// Otherwise, grab the first worker that has a handler for this action id.
271+
return denormalizeWorkerPageName(Object.keys(workers)[0])
272+
}
273+
165274
export function setManifestsSingleton({
166275
page,
167276
clientReferenceManifest,
@@ -180,10 +289,6 @@ export function setManifestsSingleton({
180289
)
181290

182291
existingSingleton.serverActionsManifest = serverActionsManifest
183-
184-
existingSingleton.serverModuleMap = createServerModuleMap({
185-
serverActionsManifest,
186-
})
187292
} else {
188293
const clientReferenceManifestsPerRoute = new Map<
189294
string,
@@ -194,15 +299,11 @@ export function setManifestsSingleton({
194299
clientReferenceManifestsPerRoute
195300
)
196301

197-
const serverModuleMap = createServerModuleMap({
198-
serverActionsManifest,
199-
})
200-
201302
globalThisWithManifests[MANIFESTS_SINGLETON] = {
202303
clientReferenceManifestsPerRoute,
203304
proxiedClientReferenceManifest,
204305
serverActionsManifest,
205-
serverModuleMap,
306+
serverModuleMap: createServerModuleMap(),
206307
}
207308
}
208309
}

0 commit comments

Comments
 (0)