@@ -3,9 +3,19 @@ import type { ClientReferenceManifest } from '../../build/webpack/plugins/flight
33import type { DeepReadonly } from '../../shared/lib/deep-readonly'
44import { InvariantError } from '../../shared/lib/invariant-error'
55import { 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'
78import { 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+
165274export 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