@reckona/mreact-router 0.0.97 → 0.0.98

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/README.md +4 -0
  2. package/dist/actions.d.ts +7 -0
  3. package/dist/actions.d.ts.map +1 -1
  4. package/dist/actions.js +26 -2
  5. package/dist/actions.js.map +1 -1
  6. package/dist/adapters/aws-lambda.d.ts.map +1 -1
  7. package/dist/adapters/aws-lambda.js +10 -7
  8. package/dist/adapters/aws-lambda.js.map +1 -1
  9. package/dist/build.d.ts +38 -1
  10. package/dist/build.d.ts.map +1 -1
  11. package/dist/build.js +1087 -304
  12. package/dist/build.js.map +1 -1
  13. package/dist/bundle-pipeline.d.ts +7 -0
  14. package/dist/bundle-pipeline.d.ts.map +1 -1
  15. package/dist/bundle-pipeline.js +121 -87
  16. package/dist/bundle-pipeline.js.map +1 -1
  17. package/dist/cache.d.ts.map +1 -1
  18. package/dist/cache.js +10 -8
  19. package/dist/cache.js.map +1 -1
  20. package/dist/cli-options.d.ts +1 -0
  21. package/dist/cli-options.d.ts.map +1 -1
  22. package/dist/cli-options.js +16 -0
  23. package/dist/cli-options.js.map +1 -1
  24. package/dist/cli.js +1 -0
  25. package/dist/cli.js.map +1 -1
  26. package/dist/client.d.ts +6 -1
  27. package/dist/client.d.ts.map +1 -1
  28. package/dist/client.js +113 -8
  29. package/dist/client.js.map +1 -1
  30. package/dist/csrf.d.ts.map +1 -1
  31. package/dist/csrf.js +7 -3
  32. package/dist/csrf.js.map +1 -1
  33. package/dist/http.d.ts.map +1 -1
  34. package/dist/http.js +18 -1
  35. package/dist/http.js.map +1 -1
  36. package/dist/import-policy.d.ts.map +1 -1
  37. package/dist/import-policy.js +7 -3
  38. package/dist/import-policy.js.map +1 -1
  39. package/dist/index.d.ts +4 -3
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js.map +1 -1
  42. package/dist/link.d.ts +5 -3
  43. package/dist/link.d.ts.map +1 -1
  44. package/dist/link.js +92 -3
  45. package/dist/link.js.map +1 -1
  46. package/dist/logger.d.ts +7 -1
  47. package/dist/logger.d.ts.map +1 -1
  48. package/dist/logger.js.map +1 -1
  49. package/dist/module-runner.d.ts +2 -0
  50. package/dist/module-runner.d.ts.map +1 -1
  51. package/dist/module-runner.js +1 -0
  52. package/dist/module-runner.js.map +1 -1
  53. package/dist/native-route-matcher.d.ts +2 -1
  54. package/dist/native-route-matcher.d.ts.map +1 -1
  55. package/dist/native-route-matcher.js +13 -2
  56. package/dist/native-route-matcher.js.map +1 -1
  57. package/dist/render.d.ts.map +1 -1
  58. package/dist/render.js +117 -2
  59. package/dist/render.js.map +1 -1
  60. package/dist/route-source.d.ts +4 -4
  61. package/dist/route-source.d.ts.map +1 -1
  62. package/dist/route-source.js +8 -8
  63. package/dist/route-source.js.map +1 -1
  64. package/dist/route-styles.d.ts +6 -0
  65. package/dist/route-styles.d.ts.map +1 -1
  66. package/dist/route-styles.js +10 -1
  67. package/dist/route-styles.js.map +1 -1
  68. package/dist/serve.d.ts +5 -0
  69. package/dist/serve.d.ts.map +1 -1
  70. package/dist/serve.js +7 -4
  71. package/dist/serve.js.map +1 -1
  72. package/package.json +11 -11
  73. package/src/actions.ts +42 -2
  74. package/src/adapters/aws-lambda.ts +33 -16
  75. package/src/build.ts +1578 -397
  76. package/src/bundle-pipeline.ts +136 -88
  77. package/src/cache.ts +13 -8
  78. package/src/cli-options.ts +20 -0
  79. package/src/cli.ts +1 -0
  80. package/src/client.ts +145 -14
  81. package/src/csrf.ts +8 -3
  82. package/src/http.ts +21 -1
  83. package/src/import-policy.ts +8 -3
  84. package/src/index.ts +5 -0
  85. package/src/link.ts +135 -8
  86. package/src/logger.ts +9 -1
  87. package/src/module-runner.ts +4 -0
  88. package/src/native-route-matcher.ts +13 -2
  89. package/src/render.ts +177 -2
  90. package/src/route-source.ts +8 -8
  91. package/src/route-styles.ts +17 -1
  92. package/src/serve.ts +11 -6
package/dist/build.js CHANGED
@@ -1,12 +1,14 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { copyFile, cp, mkdir, readdir, readFile, rm, stat, writeFile } from "node:fs/promises";
3
3
  import { builtinModules } from "node:module";
4
+ import { availableParallelism } from "node:os";
4
5
  import { dirname, join, relative, resolve, sep } from "node:path";
6
+ import { fileURLToPath } from "node:url";
5
7
  import { collectStaticImportReferences, collectTopLevelValueExportNames, formatDiagnostic, hasModuleDirective, transform, } from "@reckona/mreact-compiler";
6
8
  import { transformCompilerModuleContext } from "@reckona/mreact-compiler/internal";
7
9
  import { compilerModuleContextForSource, collectClientRouteReferences, createClientRouteInferenceCache, detectClientNavigationHint, formatClientRouteInferenceDiagnostic, inferClientRouteModule, resolveNavigationRuntime, } from "./client-route-inference.js";
8
10
  import { buildClientRouteBatchOutput, buildNavigationRuntimeBundle, clientScriptForPath, routeIdForPath, } from "./navigation-runtime.js";
9
- import { bundleAppRouterSourceModule, importAppRouterSourceModule } from "./module-runner.js";
11
+ import { bundleAppRouterSourceModule, fileImportMetaUrlPlugin, importAppRouterSourceModule, } from "./module-runner.js";
10
12
  import { scanAppRoutes } from "./routes.js";
11
13
  import { appFileConventionForRootFilename, } from "./file-conventions.js";
12
14
  import { resolveAppRouterProjectOptions, resolveBuildTargets, } from "./config.js";
@@ -14,110 +16,250 @@ import { routeCachePolicyFromSource } from "./cache.js";
14
16
  import { bundleMiddlewareModuleCode, renderAppRequest } from "./render.js";
15
17
  import { createAppRouterImportPolicyPlugin } from "./import-policy.js";
16
18
  import { hasGenerateStaticParamsExport, hasLoaderExport, hasPrerenderExport, isStreamRouteSource, routeClosureMayUseAwaitBoundary, stripRouteBuildExports, stripRouteClientOnlyExports, stripRouteClientSource, stripRouteLoaderOnlyExports, stripRouteMetadataOnlyExports, stripRouteRequestOnlyExports, } from "./route-source.js";
17
- import { bundleRouterModule, } from "./bundle-pipeline.js";
18
- import { collectRouteCssFiles } from "./route-styles.js";
19
+ import { bundleRouterModule, bundleRouterModules, } from "./bundle-pipeline.js";
20
+ import { collectRouteCssFilesFromSources } from "./route-styles.js";
19
21
  import { existingRouteShellCandidates } from "./route-shells.js";
20
22
  import { sourceModuleCandidates } from "./source-modules.js";
21
23
  import { collectBuildInferredServerActions } from "./server-action-inference.js";
24
+ import { vitePluginsCacheKey } from "./vite-plugin-cache-key.js";
22
25
  import { workspacePackageFile } from "./workspace-packages.js";
23
26
  const nativeEscapeTransform = {
24
27
  batchImportName: "escapeHtmlBatch",
25
28
  batchImportSource: "@reckona/mreact-router/native-escape",
26
29
  };
30
+ const defaultBuildConcurrency = Math.max(1, Math.min(2, availableParallelism()));
31
+ const serverArtifactFilesystemConcurrency = 2;
27
32
  export async function buildApp(options) {
33
+ const timingSink = options.onBuildPhaseTiming;
28
34
  const project = resolveAppRouterProjectOptions(options);
29
35
  const buildTargets = resolveBuildTargets(options.targets ?? project.buildTargets);
30
36
  const shouldBuildCloudflare = buildTargets.includes("cloudflare");
31
37
  const shouldBuildAwsLambda = buildTargets.includes("aws-lambda");
32
- const routes = await scanAppRoutes({ appDir: project.routesDir });
38
+ const routes = timingSink === undefined
39
+ ? await scanAppRoutes({ appDir: project.routesDir })
40
+ : await timeBuildPhase(timingSink, "scan", () => scanAppRoutes({ appDir: project.routesDir }));
33
41
  const vitePlugins = options.viteConfig?.plugins;
34
- const files = await collectBuildFiles(project.projectRoot, project.allowedSourceDirs, project.routesDir);
42
+ const files = timingSink === undefined
43
+ ? await collectBuildFiles(project.projectRoot, project.allowedSourceDirs, project.routesDir)
44
+ : await timeBuildPhase(timingSink, "collectFiles", () => collectBuildFiles(project.projectRoot, project.allowedSourceDirs, project.routesDir));
45
+ const serverClientRouteInferenceCache = createClientRouteInferenceCache();
46
+ const serverTransformCache = new Map();
35
47
  const serverDir = join(options.outDir, "server");
36
48
  const clientDir = join(options.outDir, "client");
37
49
  const cloudflareDir = join(options.outDir, "cloudflare");
38
- await validateProductionRoutes({ files, projectRoot: project.projectRoot, routes });
39
- await rm(options.outDir, { force: true, recursive: true });
40
- await mkdir(serverDir, { recursive: true });
41
- await mkdir(clientDir, { recursive: true });
42
- if (shouldBuildCloudflare) {
43
- await mkdir(cloudflareDir, { recursive: true });
44
- }
45
- await mkdir(join(clientDir, ".vite"), { recursive: true });
46
- await mkdir(join(clientDir, "assets", "routes"), { recursive: true });
47
- await copyPublicAssets(project.publicDir, join(clientDir, "public"));
48
- const appConventionPublicAssets = await copyAppFileConventionAssets(project.routesDir, clientDir);
49
- await copyPublicAssets(project.publicDir, clientDir);
50
- const publicAssets = [
51
- ...new Set([...(await collectPublicAssetPaths(project.publicDir)), ...appConventionPublicAssets]),
52
- ].sort();
53
- const serverActionManifest = await collectBuildServerActionManifest({
54
- files,
55
- projectRoot: project.projectRoot,
56
- routes,
57
- routesDir: project.routesDir,
58
- });
50
+ const sourceAnalysis = timingSink === undefined
51
+ ? await analyzeBuildRouteSources({
52
+ clientRouteInferenceCache: serverClientRouteInferenceCache,
53
+ files,
54
+ project,
55
+ projectRoot: project.projectRoot,
56
+ routes,
57
+ vitePlugins,
58
+ })
59
+ : await timeBuildPhase(timingSink, "analyzeSources", () => analyzeBuildRouteSources({
60
+ clientRouteInferenceCache: serverClientRouteInferenceCache,
61
+ files,
62
+ project,
63
+ projectRoot: project.projectRoot,
64
+ routes,
65
+ vitePlugins,
66
+ }));
67
+ if (timingSink === undefined) {
68
+ await validateProductionRoutes({
69
+ clientRouteInferenceCache: serverClientRouteInferenceCache,
70
+ files,
71
+ project,
72
+ projectRoot: project.projectRoot,
73
+ routes,
74
+ sourceAnalysis,
75
+ serverTransformCache,
76
+ vitePlugins,
77
+ });
78
+ }
79
+ else {
80
+ await timeBuildPhase(timingSink, "validate", () => validateProductionRoutes({
81
+ clientRouteInferenceCache: serverClientRouteInferenceCache,
82
+ files,
83
+ project,
84
+ projectRoot: project.projectRoot,
85
+ routes,
86
+ sourceAnalysis,
87
+ serverTransformCache,
88
+ vitePlugins,
89
+ }));
90
+ }
91
+ if (timingSink === undefined) {
92
+ await rm(options.outDir, { force: true, recursive: true });
93
+ await Promise.all([
94
+ mkdir(serverDir, { recursive: true }),
95
+ mkdir(clientDir, { recursive: true }),
96
+ ...(shouldBuildCloudflare ? [mkdir(cloudflareDir, { recursive: true })] : []),
97
+ mkdir(join(clientDir, ".vite"), { recursive: true }),
98
+ mkdir(join(clientDir, "assets", "routes"), { recursive: true }),
99
+ ]);
100
+ }
101
+ else {
102
+ await timeBuildPhase(timingSink, "prepareOutput", async () => {
103
+ await rm(options.outDir, { force: true, recursive: true });
104
+ await Promise.all([
105
+ mkdir(serverDir, { recursive: true }),
106
+ mkdir(clientDir, { recursive: true }),
107
+ ...(shouldBuildCloudflare ? [mkdir(cloudflareDir, { recursive: true })] : []),
108
+ mkdir(join(clientDir, ".vite"), { recursive: true }),
109
+ mkdir(join(clientDir, "assets", "routes"), { recursive: true }),
110
+ ]);
111
+ });
112
+ }
113
+ const publicAssets = timingSink === undefined
114
+ ? await buildPublicAssetManifest(project, clientDir)
115
+ : await timeBuildPhase(timingSink, "publicAssets", () => buildPublicAssetManifest(project, clientDir));
59
116
  const clientRouteInferenceCache = createClientRouteInferenceCache();
60
- const serverModules = await buildServerModuleArtifacts({
61
- clientRouteInferenceCache,
62
- files,
63
- prebundleServerComponents: buildTargets.includes("node") || shouldBuildAwsLambda,
64
- project,
65
- projectRoot: project.projectRoot,
66
- routes,
67
- vitePlugins,
68
- });
69
- const generatedImportPolicy = await buildGeneratedImportPolicy({
70
- files,
71
- projectRoot: project.projectRoot,
72
- routes,
73
- routesDir: project.routesDir,
74
- });
75
- const serverModuleArtifacts = await writeServerModuleArtifactFiles(serverDir, serverModules);
117
+ const [serverActionManifest, serverModules, generatedImportPolicy] = await Promise.all([
118
+ timingSink === undefined
119
+ ? collectBuildServerActionManifest({
120
+ files,
121
+ projectRoot: project.projectRoot,
122
+ routes,
123
+ routesDir: project.routesDir,
124
+ })
125
+ : timeBuildPhase(timingSink, "serverActionManifest", () => collectBuildServerActionManifest({
126
+ files,
127
+ projectRoot: project.projectRoot,
128
+ routes,
129
+ routesDir: project.routesDir,
130
+ })),
131
+ timingSink === undefined
132
+ ? buildServerModuleArtifacts({
133
+ bundleCache: new Map(),
134
+ clientRouteInferenceCache: serverClientRouteInferenceCache,
135
+ files,
136
+ prebundleServerComponents: buildTargets.includes("node") || shouldBuildAwsLambda,
137
+ project,
138
+ projectRoot: project.projectRoot,
139
+ routes,
140
+ sourceAnalysis,
141
+ serverTransformCache,
142
+ vitePlugins,
143
+ })
144
+ : timeBuildPhase(timingSink, "serverModules", () => buildServerModuleArtifacts({
145
+ bundleCache: new Map(),
146
+ clientRouteInferenceCache: serverClientRouteInferenceCache,
147
+ files,
148
+ prebundleServerComponents: buildTargets.includes("node") || shouldBuildAwsLambda,
149
+ project,
150
+ projectRoot: project.projectRoot,
151
+ routes,
152
+ sourceAnalysis,
153
+ serverTransformCache,
154
+ vitePlugins,
155
+ })),
156
+ timingSink === undefined
157
+ ? buildGeneratedImportPolicy({
158
+ files,
159
+ projectRoot: project.projectRoot,
160
+ routes,
161
+ routesDir: project.routesDir,
162
+ })
163
+ : timeBuildPhase(timingSink, "importPolicy", () => buildGeneratedImportPolicy({
164
+ files,
165
+ projectRoot: project.projectRoot,
166
+ routes,
167
+ routesDir: project.routesDir,
168
+ })),
169
+ ]);
76
170
  const serverRoutes = routes.map((route) => ({
77
171
  ...route,
78
172
  file: relative(project.projectRoot, route.file),
79
173
  }));
80
- const clientRoutes = await writeClientRouteBundles({
81
- appDir: project.routesDir,
82
- assetBaseUrl: project.assetBaseUrl,
83
- clientDir,
84
- clientConsolePureFunctions: project.clientConsolePureFunctions,
85
- clientRouteInferenceCache,
86
- projectRoot: project.projectRoot,
87
- routes,
88
- sourceMapDir: join(options.outDir, "source-maps", "client"),
89
- sourceMaps: project.clientSourceMaps,
90
- vitePlugins,
91
- });
174
+ const [serverModuleArtifacts, clientRoutes] = await Promise.all([
175
+ timingSink === undefined
176
+ ? writeServerModuleArtifactFiles(serverDir, serverModules, generatedImportPolicy.runtimePackages)
177
+ : timeBuildPhase(timingSink, "serverModuleArtifacts", () => writeServerModuleArtifactFiles(serverDir, serverModules, generatedImportPolicy.runtimePackages)),
178
+ timingSink === undefined
179
+ ? writeClientRouteBundles({
180
+ appDir: project.routesDir,
181
+ assetBaseUrl: project.assetBaseUrl,
182
+ clientDir,
183
+ clientConsolePureFunctions: project.clientConsolePureFunctions,
184
+ clientRouteInferenceCache,
185
+ projectRoot: project.projectRoot,
186
+ routes,
187
+ sourceAnalysis,
188
+ sourceMapDir: join(options.outDir, "source-maps", "client"),
189
+ sourceMaps: project.clientSourceMaps,
190
+ vitePlugins,
191
+ })
192
+ : timeBuildPhase(timingSink, "clientBundles", () => writeClientRouteBundles({
193
+ appDir: project.routesDir,
194
+ assetBaseUrl: project.assetBaseUrl,
195
+ clientDir,
196
+ clientConsolePureFunctions: project.clientConsolePureFunctions,
197
+ clientRouteInferenceCache,
198
+ projectRoot: project.projectRoot,
199
+ routes,
200
+ sourceAnalysis,
201
+ sourceMapDir: join(options.outDir, "source-maps", "client"),
202
+ sourceMaps: project.clientSourceMaps,
203
+ vitePlugins,
204
+ })),
205
+ ]);
92
206
  const navigationRuntimeScript = clientRoutes.some((route) => route.navigation === true && !route.client)
93
- ? await writeNavigationRuntimeBundle(clientDir, project.clientConsolePureFunctions)
207
+ ? timingSink === undefined
208
+ ? await writeNavigationRuntimeBundle(clientDir, project.clientConsolePureFunctions)
209
+ : await timeBuildPhase(timingSink, "navigationRuntime", () => writeNavigationRuntimeBundle(clientDir, project.clientConsolePureFunctions))
94
210
  : undefined;
95
211
  const clientManifestRoutes = navigationRuntimeScript === undefined
96
212
  ? clientRoutes
97
213
  : clientRoutes.map((route) => route.navigation === true && !route.client
98
214
  ? { ...route, navigationScript: navigationRuntimeScript }
99
215
  : route);
100
- const prerenderedRoutes = await prerenderStaticRoutes({
101
- appDir: project.routesDir,
102
- assetBaseUrl: project.assetBaseUrl,
103
- clientRoutes: clientManifestRoutes,
104
- project,
105
- routes,
106
- serverModules,
107
- vitePlugins,
108
- });
109
- let cloudflareRouteModules;
110
- if (shouldBuildCloudflare) {
111
- cloudflareRouteModules = await writeCloudflareRouteModules({
112
- cloudflareDir,
113
- files,
114
- prerenderedRoutes,
115
- projectRoot: project.projectRoot,
116
- routesDir: project.routesDir,
216
+ const prerenderedRoutes = timingSink === undefined
217
+ ? await prerenderStaticRoutes({
218
+ appDir: project.routesDir,
219
+ assetBaseUrl: project.assetBaseUrl,
220
+ clientRoutes: clientManifestRoutes,
221
+ project,
117
222
  routes,
118
223
  serverModules,
224
+ sourceAnalysis,
119
225
  vitePlugins,
120
- });
226
+ })
227
+ : await timeBuildPhase(timingSink, "prerender", () => prerenderStaticRoutes({
228
+ appDir: project.routesDir,
229
+ assetBaseUrl: project.assetBaseUrl,
230
+ clientRoutes: clientManifestRoutes,
231
+ project,
232
+ routes,
233
+ serverModules,
234
+ sourceAnalysis,
235
+ vitePlugins,
236
+ }));
237
+ let cloudflareRouteModules;
238
+ if (shouldBuildCloudflare) {
239
+ cloudflareRouteModules =
240
+ timingSink === undefined
241
+ ? await writeCloudflareRouteModules({
242
+ cloudflareDir,
243
+ files,
244
+ prerenderedRoutes,
245
+ projectRoot: project.projectRoot,
246
+ routesDir: project.routesDir,
247
+ routes,
248
+ serverModules,
249
+ sourceAnalysis,
250
+ vitePlugins,
251
+ })
252
+ : await timeBuildPhase(timingSink, "cloudflare", () => writeCloudflareRouteModules({
253
+ cloudflareDir,
254
+ files,
255
+ prerenderedRoutes,
256
+ projectRoot: project.projectRoot,
257
+ routesDir: project.routesDir,
258
+ routes,
259
+ serverModules,
260
+ sourceAnalysis,
261
+ vitePlugins,
262
+ }));
121
263
  }
122
264
  const routeServerActionReferences = Object.fromEntries([...serverActionManifest.routeReferences.entries()].sort(([left], [right]) => left.localeCompare(right)));
123
265
  const serverManifest = {
@@ -151,82 +293,263 @@ export async function buildApp(options) {
151
293
  ...(publicAssets.length === 0 ? {} : { publicAssets }),
152
294
  routes: clientManifestRoutes,
153
295
  };
154
- await writeFile(join(serverDir, "manifest.json"), JSON.stringify(serverManifest, null, 2));
155
- await writeFile(join(serverDir, "import-policy.json"), JSON.stringify(generatedImportPolicy, null, 2));
156
- await writeFile(join(clientDir, "manifest.json"), JSON.stringify(clientManifest, null, 2));
157
- await writeFile(join(clientDir, ".vite", "manifest.json"), JSON.stringify(viteManifestFromClientRoutes(clientManifestRoutes), null, 2));
158
- if (shouldBuildAwsLambda) {
159
- await writeAwsLambdaHandlerArtifact(options.outDir);
160
- }
161
- if (shouldBuildCloudflare) {
162
- await writeCloudflareWorkerArtifact({
163
- cloudflareDir,
164
- clientManifest,
165
- modulesFile: cloudflareRouteModules?.registryFile ?? "route-modules.mjs",
166
- serverManifest,
296
+ const writeManifestFiles = async () => {
297
+ await Promise.all([
298
+ writeFile(join(serverDir, "manifest.json"), JSON.stringify(serverManifest, null, 2)),
299
+ writeFile(join(serverDir, "import-policy.json"), JSON.stringify(generatedImportPolicy, null, 2)),
300
+ writeFile(join(clientDir, "manifest.json"), JSON.stringify(clientManifest, null, 2)),
301
+ writeFile(join(clientDir, ".vite", "manifest.json"), JSON.stringify(viteManifestFromClientRoutes(clientManifestRoutes), null, 2)),
302
+ ]);
303
+ };
304
+ if (timingSink === undefined) {
305
+ await writeManifestFiles();
306
+ }
307
+ else {
308
+ await timeBuildPhase(timingSink, "writeManifests", writeManifestFiles);
309
+ }
310
+ if (timingSink === undefined) {
311
+ await Promise.all([
312
+ ...(shouldBuildAwsLambda ? [writeAwsLambdaHandlerArtifact(options.outDir)] : []),
313
+ ...(shouldBuildCloudflare
314
+ ? [
315
+ writeCloudflareWorkerArtifact({
316
+ cloudflareDir,
317
+ clientManifest,
318
+ modulesFile: cloudflareRouteModules?.registryFile ?? "route-modules.mjs",
319
+ serverManifest,
320
+ }),
321
+ ]
322
+ : []),
323
+ ]);
324
+ }
325
+ else {
326
+ await timeBuildPhase(timingSink, "adapterArtifacts", async () => {
327
+ await Promise.all([
328
+ ...(shouldBuildAwsLambda ? [writeAwsLambdaHandlerArtifact(options.outDir)] : []),
329
+ ...(shouldBuildCloudflare
330
+ ? [
331
+ writeCloudflareWorkerArtifact({
332
+ cloudflareDir,
333
+ clientManifest,
334
+ modulesFile: cloudflareRouteModules?.registryFile ?? "route-modules.mjs",
335
+ serverManifest,
336
+ }),
337
+ ]
338
+ : []),
339
+ ]);
167
340
  });
168
341
  }
169
342
  return { routes };
170
343
  }
171
- async function writeServerModuleArtifactFiles(serverDir, serverModules) {
344
+ async function timeBuildPhase(sink, phase, run) {
345
+ const startedAt = performance.now();
346
+ try {
347
+ return await run();
348
+ }
349
+ finally {
350
+ sink({
351
+ ms: roundBuildPhaseMs(performance.now() - startedAt),
352
+ phase,
353
+ });
354
+ }
355
+ }
356
+ function roundBuildPhaseMs(value) {
357
+ return Math.round(value * 100) / 100;
358
+ }
359
+ async function mapWithBuildConcurrency(items, map, concurrency = defaultBuildConcurrency) {
360
+ if (items.length === 0) {
361
+ return [];
362
+ }
363
+ const workerCount = Math.max(1, Math.min(concurrency, items.length));
364
+ const results = [];
365
+ results.length = items.length;
366
+ let nextIndex = 0;
367
+ async function runWorker() {
368
+ while (nextIndex < items.length) {
369
+ const index = nextIndex;
370
+ nextIndex += 1;
371
+ results[index] = await map(items[index], index);
372
+ }
373
+ }
374
+ await Promise.all(Array.from({ length: workerCount }, async () => {
375
+ await runWorker();
376
+ }));
377
+ return results;
378
+ }
379
+ export async function __mapWithBuildConcurrencyForTests(items, map, concurrency) {
380
+ return await mapWithBuildConcurrency(items, map, concurrency);
381
+ }
382
+ async function analyzeBuildRouteSources(options) {
383
+ const byFile = new Map();
384
+ for (const [file, source] of Object.entries(options.files)) {
385
+ byFile.set(file, analyzeBuildSource(source, join(options.projectRoot, file)));
386
+ }
387
+ const routeAnalyses = await mapWithBuildConcurrency(options.routes.filter((route) => route.kind === "page"), async (route) => {
388
+ const file = relative(options.projectRoot, route.file).split(sep).join("/");
389
+ const source = options.files[file];
390
+ if (source === undefined) {
391
+ return undefined;
392
+ }
393
+ const routeCode = stripRouteBuildExports(source, route.file);
394
+ const clientInference = await inferClientRouteModule({
395
+ appDir: options.project.routesDir,
396
+ cache: options.clientRouteInferenceCache,
397
+ code: stripRouteClientSource({ code: source, filename: route.file }),
398
+ filename: route.file,
399
+ routePath: route.path,
400
+ vitePlugins: options.vitePlugins,
401
+ });
402
+ for (const diagnostic of clientInference.diagnostics) {
403
+ console.warn(formatClientRouteInferenceDiagnostic(diagnostic));
404
+ }
405
+ return [
406
+ file,
407
+ {
408
+ ...analyzeBuildSource(source, route.file),
409
+ clientBoundaryImports: clientInference.clientBoundaryImports,
410
+ clientRoute: clientInference.client,
411
+ file,
412
+ route,
413
+ routeCode,
414
+ streamRoute: shouldBuildRouteAsStream({
415
+ filename: file,
416
+ files: options.files,
417
+ projectRoot: options.projectRoot,
418
+ source,
419
+ }),
420
+ },
421
+ ];
422
+ });
423
+ const byRouteFile = new Map();
424
+ for (const entry of routeAnalyses) {
425
+ if (entry !== undefined) {
426
+ byRouteFile.set(entry[0], entry[1]);
427
+ }
428
+ }
429
+ return { byFile, byRouteFile };
430
+ }
431
+ function analyzeBuildSource(source, filename) {
432
+ const cachePolicy = routeCachePolicyFromSource(source);
433
+ const sourceHash = hashText(source);
434
+ if (!isSourceModuleFile(filename)) {
435
+ return {
436
+ authIncludesClaims: false,
437
+ ...(cachePolicy === undefined ? {} : { cachePolicy }),
438
+ hasGenerateStaticParams: false,
439
+ hasLoader: false,
440
+ hasMetadata: false,
441
+ hasPrerender: false,
442
+ source,
443
+ sourceHash,
444
+ usesRuntimeCacheControl: usesRuntimeCacheControl(source),
445
+ };
446
+ }
447
+ return {
448
+ authIncludesClaims: authIncludesClaims(source),
449
+ ...(cachePolicy === undefined ? {} : { cachePolicy }),
450
+ hasGenerateStaticParams: hasGenerateStaticParamsExport(source, filename),
451
+ hasLoader: hasLoaderExport(source, filename),
452
+ hasMetadata: hasMetadataExport(source),
453
+ hasPrerender: hasPrerenderExport(source, filename),
454
+ source,
455
+ sourceHash,
456
+ usesRuntimeCacheControl: usesRuntimeCacheControl(source),
457
+ };
458
+ }
459
+ function buildSourceAnalysisForFile(sourceAnalysis, projectRoot, file) {
460
+ return sourceAnalysis.byFile.get(relative(projectRoot, file).split(sep).join("/"));
461
+ }
462
+ async function buildPublicAssetManifest(project, clientDir) {
463
+ const [publicAssetPaths, appConventionPublicAssets] = await Promise.all([
464
+ collectPublicAssetPaths(project.publicDir),
465
+ copyAppFileConventionAssets(project.routesDir, clientDir),
466
+ copyPublicAssets(project.publicDir, join(clientDir, "public")),
467
+ copyPublicAssets(project.publicDir, clientDir),
468
+ ]);
469
+ return [...new Set([...publicAssetPaths, ...appConventionPublicAssets])].sort();
470
+ }
471
+ async function writeServerModuleArtifactFiles(serverDir, serverModules, portableRuntimePackages = []) {
172
472
  const files = {};
173
473
  const renderFiles = {};
174
474
  const requestFiles = {};
175
475
  const modulesDir = join(serverDir, "server-modules");
176
- for (const [file, artifact] of Object.entries(serverModules)) {
177
- const externalized = await externalizeServerModuleArtifactCode(serverDir, artifact);
476
+ const artifactEntries = Object.entries(serverModules);
477
+ await Promise.all([
478
+ mkdir(modulesDir, { recursive: true }),
479
+ mkdir(join(modulesDir, "code"), { recursive: true }),
480
+ mkdir(join(modulesDir, "request"), { recursive: true }),
481
+ mkdir(join(modulesDir, "render"), { recursive: true }),
482
+ ]);
483
+ const writtenArtifacts = await mapWithBuildConcurrency(artifactEntries, async ([file, artifact]) => {
484
+ const externalized = await externalizeServerModuleArtifactCode(serverDir, artifact, portableRuntimePackages);
178
485
  const requestArtifact = requestServerModuleArtifact(externalized);
179
486
  const renderArtifact = renderServerModuleArtifact(externalized);
180
- await mkdir(modulesDir, { recursive: true });
181
487
  if (Object.keys(requestArtifact).length > 0 && Object.keys(renderArtifact).length > 0) {
182
488
  const requestJson = JSON.stringify(requestArtifact);
183
489
  const requestArtifactFile = `server-modules/request/${hashText(`${file}\0request\0${requestJson}`).slice(0, 16)}.json`;
184
- await mkdir(join(modulesDir, "request"), { recursive: true });
185
490
  await writeFile(join(serverDir, requestArtifactFile), requestJson);
186
- requestFiles[file] = requestArtifactFile;
187
491
  const renderJson = JSON.stringify(renderArtifact);
188
492
  const renderArtifactFile = `server-modules/render/${hashText(`${file}\0render\0${renderJson}`).slice(0, 16)}.json`;
189
- await mkdir(join(modulesDir, "render"), { recursive: true });
190
493
  await writeFile(join(serverDir, renderArtifactFile), renderJson);
191
- renderFiles[file] = renderArtifactFile;
192
- continue;
494
+ return { file, renderArtifactFile, requestArtifactFile };
193
495
  }
194
496
  const json = JSON.stringify(externalized);
195
497
  const artifactFile = `server-modules/${hashText(`${file}\0${json}`).slice(0, 16)}.json`;
196
498
  await writeFile(join(serverDir, artifactFile), json);
197
- files[file] = artifactFile;
499
+ return { artifactFile, file };
500
+ }, serverArtifactFilesystemConcurrency);
501
+ for (const artifact of writtenArtifacts) {
502
+ if ("requestArtifactFile" in artifact) {
503
+ requestFiles[artifact.file] = artifact.requestArtifactFile;
504
+ renderFiles[artifact.file] = artifact.renderArtifactFile;
505
+ continue;
506
+ }
507
+ files[artifact.file] = artifact.artifactFile;
198
508
  }
199
509
  return { files, renderFiles, requestFiles };
200
510
  }
201
- async function externalizeServerModuleArtifactCode(serverDir, artifact) {
511
+ export async function __writeServerModuleArtifactFilesForTests(serverDir, serverModules) {
512
+ return await writeServerModuleArtifactFiles(serverDir, serverModules);
513
+ }
514
+ async function externalizeServerModuleArtifactCode(serverDir, artifact, portableRuntimePackages) {
202
515
  return {
203
516
  ...(artifact.analysis === undefined ? {} : { analysis: artifact.analysis }),
204
517
  ...(artifact.loader === undefined
205
518
  ? {}
206
- : { loader: await externalizeServerModuleOutputCode(serverDir, artifact.loader, "code") }),
519
+ : {
520
+ loader: await externalizeServerModuleOutputCode(serverDir, artifact.loader, "code", portableRuntimePackages),
521
+ }),
207
522
  ...(artifact.routeMetadata === undefined
208
523
  ? {}
209
524
  : {
210
- routeMetadata: await externalizeServerModuleOutputCode(serverDir, artifact.routeMetadata, "code"),
525
+ routeMetadata: await externalizeServerModuleOutputCode(serverDir, artifact.routeMetadata, "code", portableRuntimePackages),
211
526
  }),
212
527
  ...(artifact.request === undefined
213
528
  ? {}
214
- : { request: await externalizeServerModuleOutputCode(serverDir, artifact.request, "code") }),
529
+ : {
530
+ request: await externalizeServerModuleOutputCode(serverDir, artifact.request, "code", portableRuntimePackages),
531
+ }),
215
532
  ...(artifact.stream === undefined
216
533
  ? {}
217
- : { stream: await externalizeServerModuleOutputCode(serverDir, artifact.stream, "bundle") }),
534
+ : {
535
+ stream: await externalizeServerModuleOutputCode(serverDir, artifact.stream, "bundle", portableRuntimePackages),
536
+ }),
218
537
  ...(artifact.string === undefined
219
538
  ? {}
220
- : { string: await externalizeServerModuleOutputCode(serverDir, artifact.string, "bundle") }),
539
+ : {
540
+ string: await externalizeServerModuleOutputCode(serverDir, artifact.string, "bundle", portableRuntimePackages),
541
+ }),
221
542
  };
222
543
  }
223
- async function externalizeServerModuleOutputCode(serverDir, output, kind) {
224
- const moduleCode = kind === "bundle" ? output.bundleCode : output.code;
544
+ async function externalizeServerModuleOutputCode(serverDir, output, kind, portableRuntimePackages) {
545
+ const sourceCode = kind === "bundle" ? output.bundleCode : output.code;
546
+ const moduleCode = sourceCode === undefined
547
+ ? undefined
548
+ : rewritePortableRuntimePackageImports(sourceCode, portableRuntimePackages);
225
549
  if (moduleCode === undefined || moduleCode.length === 0) {
226
550
  return output;
227
551
  }
228
552
  const moduleFile = `server-modules/code/${hashText(moduleCode).slice(0, 16)}.mjs`;
229
- await mkdir(join(serverDir, "server-modules", "code"), { recursive: true });
230
553
  await writeFile(join(serverDir, moduleFile), moduleCode);
231
554
  return {
232
555
  code: kind === "code" ? "" : output.code,
@@ -235,6 +558,27 @@ async function externalizeServerModuleOutputCode(serverDir, output, kind) {
235
558
  sourceHash: output.sourceHash,
236
559
  };
237
560
  }
561
+ function rewritePortableRuntimePackageImports(code, runtimePackages) {
562
+ if (runtimePackages.length === 0 || !code.includes("file://")) {
563
+ return code;
564
+ }
565
+ return code.replace(/\b(from\s+|import\s*)["'](?<specifier>file:\/\/[^"']+)["']/gu, (match, prefix, specifier) => {
566
+ const packageName = specifier === undefined ? undefined : runtimePackageForFileUrl(specifier, runtimePackages);
567
+ return packageName === undefined ? match : `${prefix}"${packageName}"`;
568
+ });
569
+ }
570
+ function runtimePackageForFileUrl(specifier, runtimePackages) {
571
+ let filePath;
572
+ try {
573
+ filePath = fileURLToPath(specifier).split(sep).join("/");
574
+ }
575
+ catch {
576
+ return undefined;
577
+ }
578
+ return [...runtimePackages]
579
+ .sort((left, right) => right.length - left.length)
580
+ .find((packageName) => filePath.includes(`/node_modules/${packageName}/`));
581
+ }
238
582
  function requestServerModuleArtifact(artifact) {
239
583
  return {
240
584
  ...(artifact.analysis === undefined ? {} : { analysis: artifact.analysis }),
@@ -604,15 +948,14 @@ async function prerenderStaticRoutes(options) {
604
948
  join(options.project.projectRoot, file),
605
949
  artifact,
606
950
  ]));
607
- for (const route of options.routes) {
608
- if (route.kind !== "page") {
609
- continue;
610
- }
611
- const source = await readFile(route.file, "utf8");
612
- if (!hasPrerenderExport(source)) {
613
- continue;
951
+ const prerenderedEntries = await mapWithBuildConcurrency(options.routes.filter((route) => route.kind === "page"), async (route) => {
952
+ const routeFile = relative(options.project.projectRoot, route.file).split(sep).join("/");
953
+ const analysis = options.sourceAnalysis.byRouteFile.get(routeFile);
954
+ if (analysis === undefined || !analysis.hasPrerender) {
955
+ return [];
614
956
  }
615
- for (const pathname of await prerenderPathsForRoute(route, source, options.vitePlugins)) {
957
+ const entries = [];
958
+ for (const pathname of await prerenderPathsForRoute(route, analysis, options.vitePlugins)) {
616
959
  const response = await renderAppRequest({
617
960
  appDir: options.appDir,
618
961
  assetBaseUrl: options.assetBaseUrl,
@@ -626,24 +969,33 @@ async function prerenderStaticRoutes(options) {
626
969
  response.headers.forEach((value, key) => {
627
970
  headers[key] = value;
628
971
  });
629
- prerendered[pathname] = {
630
- headers,
631
- html: await response.text(),
632
- status: response.status,
633
- };
972
+ entries.push([
973
+ pathname,
974
+ {
975
+ headers,
976
+ html: await response.text(),
977
+ status: response.status,
978
+ },
979
+ ]);
980
+ }
981
+ return entries;
982
+ });
983
+ for (const entries of prerenderedEntries) {
984
+ for (const [pathname, route] of entries) {
985
+ prerendered[pathname] = route;
634
986
  }
635
987
  }
636
988
  return prerendered;
637
989
  }
638
- async function prerenderPathsForRoute(route, source, vitePlugins) {
990
+ async function prerenderPathsForRoute(route, analysis, vitePlugins) {
639
991
  if (route.segments.every((segment) => segment.kind === "static")) {
640
992
  return [route.path];
641
993
  }
642
- if (!hasGenerateStaticParamsExport(source)) {
994
+ if (!analysis.hasGenerateStaticParams) {
643
995
  return [];
644
996
  }
645
997
  const module = await importAppRouterSourceModule({
646
- code: source,
998
+ code: analysis.source,
647
999
  label: `generate-static-params:${route.file}`,
648
1000
  resolveDir: dirname(route.file),
649
1001
  sourcefile: route.file,
@@ -683,119 +1035,171 @@ async function buildServerModuleArtifacts(options) {
683
1035
  projectRoot: options.project.projectRoot,
684
1036
  };
685
1037
  const artifacts = {};
686
- for (const [file, source] of Object.entries(options.files)) {
1038
+ for (const file of Object.keys(options.files)) {
687
1039
  const absoluteFile = join(options.projectRoot, file);
688
1040
  const route = routeByFile.get(file);
1041
+ const analysis = options.sourceAnalysis.byFile.get(file);
689
1042
  if (isMiddlewareFile(options.project.routesDir, absoluteFile)) {
690
1043
  requestArtifactFiles.add(file);
691
1044
  }
692
1045
  if (route?.kind === "server" || route?.kind === "metadata") {
693
1046
  requestArtifactFiles.add(file);
694
1047
  }
695
- if (route?.kind === "page" && hasLoaderExport(source)) {
1048
+ if (route?.kind === "page" && analysis?.hasLoader === true) {
696
1049
  loaderArtifactFiles.add(file);
697
1050
  }
698
- if (isServerComponentFile(file) && hasMetadataExport(source)) {
1051
+ if (isServerComponentFile(file) && analysis?.hasMetadata === true) {
699
1052
  metadataArtifactFiles.add(file);
700
1053
  }
701
1054
  }
1055
+ const requestBatchEntries = [];
702
1056
  for (const [file, source] of Object.entries(options.files)) {
703
1057
  const absoluteFile = join(options.projectRoot, file);
704
1058
  const route = routeByFile.get(file);
1059
+ if (loaderArtifactFiles.has(file)) {
1060
+ requestBatchEntries.push({
1061
+ code: stripRouteLoaderOnlyExports(source, absoluteFile),
1062
+ filename: absoluteFile,
1063
+ key: routeRequestArtifactBatchKey(file, "loader"),
1064
+ label: "Loader",
1065
+ });
1066
+ }
1067
+ if (metadataArtifactFiles.has(file)) {
1068
+ requestBatchEntries.push({
1069
+ code: stripRouteMetadataOnlyExports(source, absoluteFile),
1070
+ filename: absoluteFile,
1071
+ key: routeRequestArtifactBatchKey(file, "metadata"),
1072
+ label: "Metadata",
1073
+ });
1074
+ }
1075
+ if (requestArtifactFiles.has(file) &&
1076
+ !isMiddlewareFile(options.project.routesDir, absoluteFile) &&
1077
+ route?.kind !== "server" &&
1078
+ route?.kind !== "metadata") {
1079
+ requestBatchEntries.push({
1080
+ code: stripRouteRequestOnlyExports(source, absoluteFile),
1081
+ filename: absoluteFile,
1082
+ key: routeRequestArtifactBatchKey(file, "request"),
1083
+ label: "Loader",
1084
+ });
1085
+ }
1086
+ }
1087
+ const requestBatchOutputs = requestBatchEntries.length >= 3
1088
+ ? await bundleRouteRequestModuleBatchCode({
1089
+ appDir: options.project.routesDir,
1090
+ bundleCache: options.bundleCache,
1091
+ cacheDir: options.cacheDir,
1092
+ entries: requestBatchEntries,
1093
+ importPolicy: requestModuleImportPolicy,
1094
+ vitePlugins: options.vitePlugins,
1095
+ })
1096
+ : new Map();
1097
+ const artifactEntries = await mapWithBuildConcurrency(Object.entries(options.files), async ([file, source]) => {
1098
+ const absoluteFile = join(options.projectRoot, file);
1099
+ const route = routeByFile.get(file);
1100
+ const routeAnalysis = options.sourceAnalysis.byRouteFile.get(file);
705
1101
  const artifact = {};
706
1102
  if (requestArtifactFiles.has(file) ||
707
1103
  loaderArtifactFiles.has(file) ||
708
1104
  metadataArtifactFiles.has(file)) {
709
1105
  if (loaderArtifactFiles.has(file)) {
710
- const code = await bundleRouteLoaderModuleCode({
711
- appDir: options.project.routesDir,
712
- code: stripRouteLoaderOnlyExports(source, absoluteFile),
713
- filename: absoluteFile,
714
- importPolicy: requestModuleImportPolicy,
715
- vitePlugins: options.vitePlugins,
716
- });
1106
+ const code = requestBatchOutputs.get(routeRequestArtifactBatchKey(file, "loader")) ??
1107
+ await bundleRouteLoaderModuleCode({
1108
+ appDir: options.project.routesDir,
1109
+ bundleCache: options.bundleCache,
1110
+ cacheDir: options.cacheDir,
1111
+ code: stripRouteLoaderOnlyExports(source, absoluteFile),
1112
+ filename: absoluteFile,
1113
+ importPolicy: requestModuleImportPolicy,
1114
+ vitePlugins: options.vitePlugins,
1115
+ });
717
1116
  artifact.loader = {
718
1117
  code,
719
1118
  sourceHash: hashText(source),
720
1119
  };
721
1120
  }
722
1121
  if (metadataArtifactFiles.has(file)) {
723
- const code = await bundleRouteMetadataModuleCode({
724
- appDir: options.project.routesDir,
725
- code: stripRouteMetadataOnlyExports(source, absoluteFile),
726
- filename: absoluteFile,
727
- importPolicy: requestModuleImportPolicy,
728
- vitePlugins: options.vitePlugins,
729
- });
1122
+ const code = requestBatchOutputs.get(routeRequestArtifactBatchKey(file, "metadata")) ??
1123
+ await bundleRouteRequestModuleCode({
1124
+ appDir: options.project.routesDir,
1125
+ bundleCache: options.bundleCache,
1126
+ cacheDir: options.cacheDir,
1127
+ code: stripRouteMetadataOnlyExports(source, absoluteFile),
1128
+ filename: absoluteFile,
1129
+ importPolicy: requestModuleImportPolicy,
1130
+ label: "Metadata",
1131
+ vitePlugins: options.vitePlugins,
1132
+ });
730
1133
  artifact.routeMetadata = {
731
1134
  code,
732
1135
  sourceHash: hashText(source),
733
1136
  };
734
1137
  }
735
1138
  if (requestArtifactFiles.has(file)) {
1139
+ const batchedRequestCode = requestBatchOutputs.get(routeRequestArtifactBatchKey(file, "request"));
736
1140
  artifact.request = {
737
- code: await buildRequestModuleArtifactCode({
738
- appDir: options.project.routesDir,
739
- filename: absoluteFile,
740
- importPolicy: requestModuleImportPolicy,
741
- routeKind: route?.kind,
742
- source,
743
- vitePlugins: options.vitePlugins,
744
- }),
1141
+ code: batchedRequestCode ??
1142
+ await buildRequestModuleArtifactCode({
1143
+ appDir: options.project.routesDir,
1144
+ bundleCache: options.bundleCache,
1145
+ cacheDir: options.cacheDir,
1146
+ filename: absoluteFile,
1147
+ importPolicy: requestModuleImportPolicy,
1148
+ routeKind: route?.kind,
1149
+ source,
1150
+ vitePlugins: options.vitePlugins,
1151
+ }),
745
1152
  sourceHash: hashText(source),
746
1153
  };
747
1154
  }
748
1155
  }
749
1156
  if (!isServerComponentFile(file)) {
750
- if (Object.keys(artifact).length > 0) {
751
- artifacts[file] = artifact;
752
- }
753
- continue;
1157
+ return Object.keys(artifact).length > 0 ? [file, artifact] : undefined;
754
1158
  }
755
- const closureUsesAwait = shouldBuildRouteAsStream({
756
- filename: file,
757
- files: options.files,
758
- projectRoot: options.projectRoot,
759
- source,
760
- });
1159
+ const closureUsesAwait = routeAnalysis?.streamRoute ??
1160
+ shouldBuildRouteAsStream({
1161
+ filename: file,
1162
+ files: options.files,
1163
+ projectRoot: options.projectRoot,
1164
+ source,
1165
+ });
761
1166
  const streamRoute = route !== undefined && closureUsesAwait;
762
1167
  const serverOutputs = streamRoute || (route === undefined && closureUsesAwait)
763
1168
  ? ["stream", "string"]
764
1169
  : ["string"];
765
- const code = route === undefined ? source : stripRouteBuildExports(source, absoluteFile);
766
- const clientInference = await inferClientRouteModule({
767
- ...(route === undefined ? {} : { appDir: options.project.routesDir }),
768
- cache: options.clientRouteInferenceCache,
769
- code: route === undefined
770
- ? stripRouteClientOnlyExports(source, absoluteFile)
771
- : stripRouteClientSource({ code: source, filename: route.file }),
772
- filename: join(options.projectRoot, file),
773
- ...(route === undefined ? {} : { routePath: route.path }),
774
- vitePlugins: options.vitePlugins,
775
- });
776
- const clientBoundaryImports = clientInference.clientBoundaryImports;
777
- for (const diagnostic of clientInference.diagnostics) {
1170
+ const code = routeAnalysis?.routeCode ??
1171
+ (route === undefined ? source : stripRouteBuildExports(source, absoluteFile));
1172
+ const clientInference = routeAnalysis === undefined
1173
+ ? await inferClientRouteModule({
1174
+ ...(route === undefined ? {} : { appDir: options.project.routesDir }),
1175
+ cache: options.clientRouteInferenceCache,
1176
+ code: route === undefined
1177
+ ? stripRouteClientOnlyExports(source, absoluteFile)
1178
+ : stripRouteClientSource({ code: source, filename: route.file }),
1179
+ filename: join(options.projectRoot, file),
1180
+ ...(route === undefined ? {} : { routePath: route.path }),
1181
+ vitePlugins: options.vitePlugins,
1182
+ })
1183
+ : undefined;
1184
+ const clientBoundaryImports = routeAnalysis?.clientBoundaryImports ??
1185
+ clientInference?.clientBoundaryImports ??
1186
+ [];
1187
+ for (const diagnostic of clientInference?.diagnostics ?? []) {
778
1188
  console.warn(formatClientRouteInferenceDiagnostic(diagnostic));
779
1189
  }
780
- if (route?.kind === "page") {
1190
+ if (routeAnalysis !== undefined) {
781
1191
  artifact.analysis = builtRouteSourceAnalysisSummary({
782
- clientBoundaryImports,
783
- clientRoute: clientInference.client,
784
- route,
785
- routeCode: code,
786
- source,
787
- streamRoute,
1192
+ analysis: routeAnalysis,
788
1193
  });
789
1194
  }
790
- for (const serverOutput of serverOutputs) {
791
- const output = transform({
1195
+ const serverOutputArtifacts = await mapWithBuildConcurrency(serverOutputs, async (serverOutput) => {
1196
+ const output = await transformServerRouteSource({
1197
+ cache: options.serverTransformCache,
792
1198
  code,
793
1199
  clientBoundaryImports,
794
- dev: false,
795
1200
  filename: join(options.projectRoot, file),
796
- serverEscape: nativeEscapeTransform,
1201
+ moduleContextCache: options.clientRouteInferenceCache,
797
1202
  serverOutput,
798
- target: "server",
799
1203
  });
800
1204
  const fatalDiagnostics = output.diagnostics.filter((diagnostic) => diagnostic.code !== "MR_UNSUPPORTED_SERVER_EVENT_HANDLER");
801
1205
  if (fatalDiagnostics.length > 0) {
@@ -803,28 +1207,52 @@ async function buildServerModuleArtifacts(options) {
803
1207
  streamRoute &&
804
1208
  route?.kind === "page" &&
805
1209
  fatalDiagnostics.every((diagnostic) => diagnostic.code === "MR_UNSUPPORTED_AWAIT_INNER_COMPONENT")) {
806
- continue;
1210
+ return undefined;
807
1211
  }
808
1212
  throw new Error(fatalDiagnostics.map((diagnostic) => formatDiagnostic(file, diagnostic)).join("\n"));
809
1213
  }
810
- artifact[serverOutput] = {
811
- ...(options.prebundleServerComponents
812
- ? {
813
- bundleCode: await buildServerComponentBundleArtifactCode({
814
- clientRouteInferenceCache: options.clientRouteInferenceCache,
815
- code: output.code,
816
- filename: absoluteFile,
817
- serverOutput,
818
- vitePlugins: options.vitePlugins,
819
- }),
820
- }
821
- : {}),
822
- code: output.code,
823
- metadata: output.metadata,
824
- sourceHash: hashText(code),
825
- };
1214
+ if (serverOutput === "string" &&
1215
+ streamRoute &&
1216
+ route?.kind === "page" &&
1217
+ source.includes("<Await") &&
1218
+ /\bLink\b/.test(source)) {
1219
+ // Native Link renders pre-rendered children as HTML. Keep direct
1220
+ // Await renderers on the stream artifact so the string artifact
1221
+ // cannot drop deferred Link content before the boundary resolves.
1222
+ return undefined;
1223
+ }
1224
+ return [
1225
+ serverOutput,
1226
+ {
1227
+ ...(options.prebundleServerComponents
1228
+ ? {
1229
+ bundleCode: await buildServerComponentBundleArtifactCode({
1230
+ clientRouteInferenceCache: options.clientRouteInferenceCache,
1231
+ code: output.code,
1232
+ filename: absoluteFile,
1233
+ root: options.projectRoot,
1234
+ serverOutput,
1235
+ vitePlugins: options.vitePlugins,
1236
+ }),
1237
+ }
1238
+ : {}),
1239
+ code: output.code,
1240
+ metadata: output.metadata,
1241
+ sourceHash: hashText(code),
1242
+ },
1243
+ ];
1244
+ });
1245
+ for (const entry of serverOutputArtifacts) {
1246
+ if (entry !== undefined) {
1247
+ artifact[entry[0]] = entry[1];
1248
+ }
1249
+ }
1250
+ return [file, artifact];
1251
+ });
1252
+ for (const entry of artifactEntries) {
1253
+ if (entry !== undefined) {
1254
+ artifacts[entry[0]] = entry[1];
826
1255
  }
827
- artifacts[file] = artifact;
828
1256
  }
829
1257
  return artifacts;
830
1258
  }
@@ -833,6 +1261,7 @@ async function buildServerComponentBundleArtifactCode(options) {
833
1261
  code: options.code,
834
1262
  label: `server-component:${options.filename}`,
835
1263
  resolveDir: dirname(options.filename),
1264
+ root: options.root,
836
1265
  serverSourceTransform: {
837
1266
  clientRouteInferenceCache: options.clientRouteInferenceCache,
838
1267
  dev: false,
@@ -843,23 +1272,54 @@ async function buildServerComponentBundleArtifactCode(options) {
843
1272
  vitePlugins: options.vitePlugins,
844
1273
  });
845
1274
  }
1275
+ async function transformServerRouteSource(options) {
1276
+ const cacheKey = stableCacheKey({
1277
+ clientBoundaryImports: options.clientBoundaryImports,
1278
+ codeHash: hashText(options.code),
1279
+ filename: resolve(options.filename),
1280
+ serverOutput: options.serverOutput,
1281
+ target: "server",
1282
+ });
1283
+ const cached = options.cache.get(cacheKey);
1284
+ if (cached !== undefined) {
1285
+ return await cached;
1286
+ }
1287
+ const transformed = Promise.resolve().then(async () => {
1288
+ const moduleContext = await compilerModuleContextForSource({
1289
+ cache: options.moduleContextCache,
1290
+ code: options.code,
1291
+ filename: options.filename,
1292
+ });
1293
+ return transformCompilerModuleContext({
1294
+ code: options.code,
1295
+ clientBoundaryImports: options.clientBoundaryImports,
1296
+ dev: false,
1297
+ filename: options.filename,
1298
+ moduleContext,
1299
+ serverEscape: nativeEscapeTransform,
1300
+ serverOutput: options.serverOutput,
1301
+ target: "server",
1302
+ });
1303
+ });
1304
+ options.cache.set(cacheKey, transformed);
1305
+ return await transformed;
1306
+ }
846
1307
  function builtRouteSourceAnalysisSummary(options) {
847
- const cachePolicy = routeCachePolicyFromSource(options.source);
848
1308
  return {
849
- authIncludesClaims: authIncludesClaims(options.source),
850
- ...(cachePolicy === undefined ? {} : { cachePolicy }),
851
- clientBoundaryImports: options.clientBoundaryImports,
852
- clientRoute: options.clientRoute,
853
- hasLoader: hasLoaderExport(options.source),
854
- routeCode: options.routeCode,
855
- routePath: options.route.path,
856
- sourceHash: hashText(options.source),
857
- streamRoute: options.streamRoute,
858
- usesRuntimeCacheControl: usesRuntimeCacheControl(options.source),
1309
+ authIncludesClaims: options.analysis.authIncludesClaims,
1310
+ ...(options.analysis.cachePolicy === undefined ? {} : { cachePolicy: options.analysis.cachePolicy }),
1311
+ clientBoundaryImports: options.analysis.clientBoundaryImports,
1312
+ clientRoute: options.analysis.clientRoute,
1313
+ hasLoader: options.analysis.hasLoader,
1314
+ routeCode: options.analysis.routeCode,
1315
+ routePath: options.analysis.route.path,
1316
+ sourceHash: options.analysis.sourceHash,
1317
+ streamRoute: options.analysis.streamRoute,
1318
+ usesRuntimeCacheControl: options.analysis.usesRuntimeCacheControl,
859
1319
  };
860
1320
  }
861
1321
  function shouldBuildRouteAsStream(options) {
862
- return (isStreamRouteSource(options.source) ||
1322
+ return (isStreamRouteSource(options.source, sourceFilenameForBuildAnalysis(options.projectRoot, options.filename)) ||
863
1323
  routeClosureMayUseAwaitBoundary({
864
1324
  filename: options.filename,
865
1325
  files: options.files,
@@ -867,6 +1327,9 @@ function shouldBuildRouteAsStream(options) {
867
1327
  source: options.source,
868
1328
  }));
869
1329
  }
1330
+ function sourceFilenameForBuildAnalysis(projectRoot, filename) {
1331
+ return filename.startsWith("/") ? filename : join(projectRoot, filename);
1332
+ }
870
1333
  function resolveBuildLocalSourceImport(files, importer, specifier) {
871
1334
  if (!specifier.startsWith(".")) {
872
1335
  return undefined;
@@ -882,6 +1345,9 @@ function resolveBuildLocalSourceImport(files, importer, specifier) {
882
1345
  function usesRuntimeCacheControl(code) {
883
1346
  return /\bcacheControl\s*\(/.test(code);
884
1347
  }
1348
+ function routeRequestArtifactBatchKey(file, kind) {
1349
+ return `${kind}:${file}`;
1350
+ }
885
1351
  function authIncludesClaims(code) {
886
1352
  return /\bexport\s+const\s+auth\s*=\s*["']include-claims["']\s*;?/.test(code);
887
1353
  }
@@ -899,13 +1365,17 @@ async function buildRequestModuleArtifactCode(options) {
899
1365
  return await bundleAppRouterSourceModule({
900
1366
  code: options.source,
901
1367
  label: `server-route:${options.filename}`,
1368
+ plugins: [fileImportMetaUrlPlugin()],
902
1369
  resolveDir: dirname(options.filename),
1370
+ root: options.importPolicy.projectRoot,
903
1371
  sourcefile: options.filename,
904
1372
  vitePlugins: options.vitePlugins,
905
1373
  });
906
1374
  }
907
1375
  return await bundleRouteLoaderModuleCode({
908
1376
  appDir: options.appDir,
1377
+ bundleCache: options.bundleCache,
1378
+ cacheDir: options.cacheDir,
909
1379
  code: stripRouteRequestOnlyExports(options.source, options.filename),
910
1380
  filename: options.filename,
911
1381
  importPolicy: options.importPolicy,
@@ -918,19 +1388,103 @@ async function bundleRouteLoaderModuleCode(options) {
918
1388
  label: "Loader",
919
1389
  });
920
1390
  }
921
- async function bundleRouteMetadataModuleCode(options) {
922
- return await bundleRouteRequestModuleCode({
923
- ...options,
924
- label: "Metadata",
1391
+ async function bundleRouteRequestModuleBatchCode(options) {
1392
+ if (options.entries.length === 0) {
1393
+ return new Map();
1394
+ }
1395
+ if (options.entries.length === 1) {
1396
+ const entry = options.entries[0];
1397
+ if (entry === undefined) {
1398
+ return new Map();
1399
+ }
1400
+ return new Map([
1401
+ [
1402
+ entry.key,
1403
+ await bundleRouteRequestModuleCode({
1404
+ appDir: options.appDir,
1405
+ bundleCache: options.bundleCache,
1406
+ cacheDir: options.cacheDir,
1407
+ code: entry.code,
1408
+ filename: entry.filename,
1409
+ importPolicy: options.importPolicy,
1410
+ label: entry.label,
1411
+ vitePlugins: options.vitePlugins,
1412
+ }),
1413
+ ],
1414
+ ]);
1415
+ }
1416
+ const namesByKey = new Map(options.entries.map((entry, index) => [
1417
+ entry.key,
1418
+ `request-${index}-${hashText(entry.key).slice(0, 8)}`,
1419
+ ]));
1420
+ const output = await bundleRouterModules({
1421
+ cacheDir: options.cacheDir,
1422
+ chunkFileNames: "request/chunks/[name].[hash].js",
1423
+ entries: options.entries.map((entry) => ({
1424
+ code: entry.code,
1425
+ filename: entry.filename,
1426
+ name: namesByKey.get(entry.key) ?? hashText(entry.key).slice(0, 8),
1427
+ })),
1428
+ entryFileNames: "request/[name].js",
1429
+ platform: "node",
1430
+ plugins: [
1431
+ fileImportMetaUrlPlugin(),
1432
+ createAppRouterImportPolicyPlugin({
1433
+ appDir: options.appDir,
1434
+ importPolicy: options.importPolicy,
1435
+ label: "Request artifact",
1436
+ }),
1437
+ ],
1438
+ root: options.importPolicy?.projectRoot ?? dirname(options.entries[0]?.filename ?? options.appDir),
1439
+ vitePlugins: options.vitePlugins,
925
1440
  });
1441
+ if (output.chunks.some((chunk) => !chunk.isEntry)) {
1442
+ const fallbackEntries = await mapWithBuildConcurrency(options.entries, async (entry) => [
1443
+ entry.key,
1444
+ await bundleRouteRequestModuleCode({
1445
+ appDir: options.appDir,
1446
+ bundleCache: options.bundleCache,
1447
+ cacheDir: options.cacheDir,
1448
+ code: entry.code,
1449
+ filename: entry.filename,
1450
+ importPolicy: options.importPolicy,
1451
+ label: entry.label,
1452
+ vitePlugins: options.vitePlugins,
1453
+ }),
1454
+ ]);
1455
+ return new Map(fallbackEntries);
1456
+ }
1457
+ const chunksByName = new Map(output.chunks.map((chunk) => [chunk.name, chunk]));
1458
+ return new Map(options.entries.map((entry) => {
1459
+ const name = namesByKey.get(entry.key);
1460
+ const chunk = name === undefined ? undefined : chunksByName.get(name);
1461
+ if (chunk === undefined) {
1462
+ throw new Error(`Failed to compile request artifact for ${entry.filename}.`);
1463
+ }
1464
+ return [entry.key, chunk.code];
1465
+ }));
1466
+ }
1467
+ export async function __bundleRouteRequestModuleBatchForTests(options) {
1468
+ return Object.fromEntries(await bundleRouteRequestModuleBatchCode({
1469
+ appDir: options.appDir,
1470
+ cacheDir: options.cacheDir,
1471
+ entries: options.entries,
1472
+ importPolicy: options.importPolicy,
1473
+ vitePlugins: options.vitePlugins,
1474
+ }));
926
1475
  }
927
1476
  async function bundleRouteRequestModuleCode(options) {
928
1477
  const output = await bundleRouterModule({
1478
+ cache: options.bundleCache,
1479
+ cacheDir: options.cacheDir,
1480
+ cacheKey: routeRequestBundleCacheKey(options),
929
1481
  code: options.code,
930
1482
  filename: options.filename,
931
1483
  platform: "node",
1484
+ root: options.importPolicy?.projectRoot,
932
1485
  vitePlugins: options.vitePlugins,
933
1486
  plugins: [
1487
+ fileImportMetaUrlPlugin(),
934
1488
  createAppRouterImportPolicyPlugin({
935
1489
  appDir: options.appDir,
936
1490
  importPolicy: options.importPolicy,
@@ -944,6 +1498,31 @@ async function bundleRouteRequestModuleCode(options) {
944
1498
  }
945
1499
  return code;
946
1500
  }
1501
+ function routeRequestBundleCacheKey(options) {
1502
+ return stableCacheKey({
1503
+ appDir: resolve(options.appDir),
1504
+ codeHash: hashText(options.code),
1505
+ filename: resolve(options.filename),
1506
+ importPolicy: options.importPolicy === undefined
1507
+ ? undefined
1508
+ : {
1509
+ allowedPackages: [...(options.importPolicy.allowedPackages ?? [])].sort(),
1510
+ allowedSourceDirs: (options.importPolicy.allowedSourceDirs ?? [])
1511
+ .map((dir) => resolve(dir))
1512
+ .sort(),
1513
+ projectRoot: options.importPolicy.projectRoot === undefined
1514
+ ? undefined
1515
+ : resolve(options.importPolicy.projectRoot),
1516
+ },
1517
+ label: options.label,
1518
+ platform: "node",
1519
+ target: "es2022",
1520
+ vitePlugins: vitePluginsCacheKey(options.vitePlugins),
1521
+ });
1522
+ }
1523
+ function stableCacheKey(value) {
1524
+ return JSON.stringify(value);
1525
+ }
947
1526
  function isMiddlewareFile(appDir, file) {
948
1527
  return file === join(appDir, "middleware.ts") || file === join(appDir, "middleware.mreact.ts");
949
1528
  }
@@ -968,23 +1547,50 @@ async function readDeclaredProjectPackages(projectRoot) {
968
1547
  }
969
1548
  async function writeCloudflareRouteModules(options) {
970
1549
  const routesDir = join(options.cloudflareDir, "routes");
971
- const requiredRoutes = options.routes.filter((route) => cloudflareRouteRequiresGeneratedModule(route, options.prerenderedRoutes));
972
- const registryEntries = [];
973
- await mkdir(routesDir, { recursive: true });
974
- for (const route of requiredRoutes) {
1550
+ const requiredRoutes = await Promise.all(options.routes
1551
+ .filter((route) => cloudflareRouteRequiresGeneratedModule(route, options.prerenderedRoutes))
1552
+ .flatMap((route) => {
975
1553
  const routeFile = relative(options.projectRoot, route.file).replaceAll(sep, "/");
976
- const source = await readFile(route.file, "utf8");
977
- const routeId = routeIdForPath(route.path);
1554
+ const analysis = options.sourceAnalysis.byFile.get(routeFile);
1555
+ return analysis === undefined
1556
+ ? []
1557
+ : [{
1558
+ route,
1559
+ routeFile,
1560
+ routeId: routeIdForPath(route.path),
1561
+ }];
1562
+ }));
1563
+ await mkdir(routesDir, { recursive: true });
1564
+ const serverRouteModules = await buildCloudflareServerRouteModuleBatch({
1565
+ cacheDir: options.cacheDir,
1566
+ routes: requiredRoutes
1567
+ .filter(({ route }) => route.kind === "server" || route.kind === "metadata")
1568
+ .map(({ route, routeId }) => ({ filename: route.file, routeId })),
1569
+ root: options.projectRoot,
1570
+ vitePlugins: options.vitePlugins,
1571
+ });
1572
+ const loaderRouteModules = await buildCloudflareRouteLoaderModuleBatch({
1573
+ cacheDir: options.cacheDir,
1574
+ routes: requiredRoutes
1575
+ .filter(({ route, routeFile }) => route.kind === "page" && options.sourceAnalysis.byRouteFile.get(routeFile)?.hasLoader === true)
1576
+ .map(({ route, routeId }) => ({ filename: route.file, routeId })),
1577
+ root: options.projectRoot,
1578
+ vitePlugins: options.vitePlugins,
1579
+ });
1580
+ await Promise.all([
1581
+ writeCloudflareBatchedRouteModuleChunks(options.cloudflareDir, serverRouteModules),
1582
+ writeCloudflareBatchedRouteModuleChunks(options.cloudflareDir, loaderRouteModules),
1583
+ ]);
1584
+ const registryEntries = await mapWithBuildConcurrency(requiredRoutes, async ({ route, routeFile, routeId }) => {
978
1585
  const routeModuleFile = `routes/${routeId}.mjs`;
979
1586
  let routeModuleExports;
980
1587
  if (route.kind === "server" || route.kind === "metadata") {
981
1588
  try {
982
- const routeOutput = await buildCloudflareServerRouteModule({
983
- filename: route.file,
984
- vitePlugins: options.vitePlugins,
985
- });
986
- const serverRouteFile = `routes/${routeId}.${hashText(routeOutput).slice(0, 8)}.server.mjs`;
987
- await writeFile(join(options.cloudflareDir, serverRouteFile), routeOutput);
1589
+ const serverRouteModule = serverRouteModules.entries.get(routeId);
1590
+ if (serverRouteModule === undefined) {
1591
+ throw new Error(`Missing bundled Cloudflare ${route.kind} route module.`);
1592
+ }
1593
+ const serverRouteFile = serverRouteModule.fileName;
988
1594
  const serverRouteImport = `./${serverRouteFile.split("/").pop() ?? serverRouteFile}`;
989
1595
  routeModuleExports = [
990
1596
  `export * from ${JSON.stringify(serverRouteImport)};`,
@@ -997,32 +1603,30 @@ async function writeCloudflareRouteModules(options) {
997
1603
  throw new Error(`Failed to build Cloudflare ${route.kind} route module for ${routeFile}: ${errorMessage(error)}`);
998
1604
  }
999
1605
  await writeFile(join(options.cloudflareDir, routeModuleFile), `${routeModuleExports.join("\n")}\n`);
1000
- registryEntries.push(`${JSON.stringify(routeFile)}: () => import(${JSON.stringify(`./${routeModuleFile}`)})`);
1001
- continue;
1606
+ return `${JSON.stringify(routeFile)}: () => import(${JSON.stringify(`./${routeModuleFile}`)})`;
1002
1607
  }
1003
1608
  const serverOutput = options.serverModules[routeFile]?.analysis?.streamRoute === true ||
1004
- shouldBuildRouteAsStream({
1005
- filename: routeFile,
1006
- files: options.files,
1007
- projectRoot: options.projectRoot,
1008
- source,
1009
- })
1609
+ options.sourceAnalysis.byRouteFile.get(routeFile)?.streamRoute === true
1010
1610
  ? "stream"
1011
1611
  : "string";
1012
1612
  try {
1013
1613
  const componentOutput = serverOutput === "stream"
1014
1614
  ? await buildCloudflareStreamRouteComponentModule({
1615
+ cacheDir: options.cacheDir,
1015
1616
  filename: route.file,
1016
1617
  projectRoot: options.projectRoot,
1017
1618
  routesDir: options.routesDir,
1018
1619
  serverModules: options.serverModules,
1620
+ sourceAnalysis: options.sourceAnalysis,
1019
1621
  vitePlugins: options.vitePlugins,
1020
1622
  })
1021
1623
  : await buildCloudflareStringRouteComponentModule({
1624
+ cacheDir: options.cacheDir,
1022
1625
  filename: route.file,
1023
1626
  projectRoot: options.projectRoot,
1024
1627
  routesDir: options.routesDir,
1025
1628
  serverModules: options.serverModules,
1629
+ sourceAnalysis: options.sourceAnalysis,
1026
1630
  vitePlugins: options.vitePlugins,
1027
1631
  });
1028
1632
  const componentFile = `routes/${routeId}.${hashText(componentOutput).slice(0, 8)}.component.mjs`;
@@ -1035,15 +1639,13 @@ async function writeCloudflareRouteModules(options) {
1035
1639
  catch (error) {
1036
1640
  throw new Error(`Failed to build Cloudflare route module for ${routeFile}: ${errorMessage(error)}`);
1037
1641
  }
1038
- if (hasLoaderExport(source)) {
1642
+ if (options.sourceAnalysis.byRouteFile.get(routeFile)?.hasLoader === true) {
1039
1643
  try {
1040
- const loaderOutput = await buildCloudflareRouteLoaderModule({
1041
- filename: route.file,
1042
- projectRoot: options.projectRoot,
1043
- vitePlugins: options.vitePlugins,
1044
- });
1045
- const loaderFile = `routes/${routeId}.${hashText(loaderOutput).slice(0, 8)}.loader.mjs`;
1046
- await writeFile(join(options.cloudflareDir, loaderFile), loaderOutput);
1644
+ const loaderModule = loaderRouteModules.entries.get(routeId);
1645
+ if (loaderModule === undefined) {
1646
+ throw new Error("Missing bundled Cloudflare loader module.");
1647
+ }
1648
+ const loaderFile = loaderModule.fileName;
1047
1649
  const loaderImport = `./${loaderFile.split("/").pop() ?? loaderFile}`;
1048
1650
  routeModuleExports.push(`export { loader } from ${JSON.stringify(loaderImport)};`);
1049
1651
  }
@@ -1052,8 +1654,8 @@ async function writeCloudflareRouteModules(options) {
1052
1654
  }
1053
1655
  }
1054
1656
  await writeFile(join(options.cloudflareDir, routeModuleFile), `${routeModuleExports.join("\n")}\n`);
1055
- registryEntries.push(`${JSON.stringify(routeFile)}: () => import(${JSON.stringify(`./${routeModuleFile}`)})`);
1056
- }
1657
+ return `${JSON.stringify(routeFile)}: () => import(${JSON.stringify(`./${routeModuleFile}`)})`;
1658
+ });
1057
1659
  const registrySource = [
1058
1660
  `export const routeModules = {`,
1059
1661
  ...registryEntries.map((entry) => ` ${entry},`),
@@ -1066,7 +1668,10 @@ async function writeCloudflareRouteModules(options) {
1066
1668
  }
1067
1669
  async function buildCloudflareServerComponentModule(options) {
1068
1670
  const metadataModule = await buildCloudflareRouteMetadataExportModule({
1671
+ cacheDir: options.cacheDir,
1069
1672
  filename: options.filename,
1673
+ hasMetadata: buildSourceAnalysisForFile(options.sourceAnalysis, options.projectRoot, options.filename)?.hasMetadata,
1674
+ root: options.projectRoot,
1070
1675
  vitePlugins: options.vitePlugins,
1071
1676
  });
1072
1677
  const entry = `import * as routeModule from ${JSON.stringify(options.filename)};
@@ -1081,6 +1686,7 @@ export const slots = routeModule.slots;`;
1081
1686
  return bundleCloudflareVirtualModule({
1082
1687
  entry,
1083
1688
  filename: `${options.filename}.mreact-cloudflare-component.js`,
1689
+ cacheDir: options.cacheDir,
1084
1690
  modules: metadataModule === undefined
1085
1691
  ? new Map()
1086
1692
  : new Map([["mreact:metadata", metadataModule]]),
@@ -1094,6 +1700,7 @@ export const slots = routeModule.slots;`;
1094
1700
  cloudflareWorkspaceRuntimePlugin(),
1095
1701
  ],
1096
1702
  resolveDir: dirname(options.filename),
1703
+ root: options.projectRoot,
1097
1704
  vitePlugins: options.vitePlugins,
1098
1705
  });
1099
1706
  }
@@ -1101,25 +1708,31 @@ async function buildCloudflareStringRouteComponentModule(options) {
1101
1708
  const shellFiles = await cloudflareShellFilesForPage(options.routesDir, options.filename);
1102
1709
  if (shellFiles.length === 0) {
1103
1710
  return buildCloudflareServerComponentModule({
1711
+ cacheDir: options.cacheDir,
1104
1712
  filename: options.filename,
1105
1713
  projectRoot: options.projectRoot,
1106
1714
  serverModules: options.serverModules,
1107
1715
  serverOutput: "string",
1716
+ sourceAnalysis: options.sourceAnalysis,
1108
1717
  vitePlugins: options.vitePlugins,
1109
1718
  });
1110
1719
  }
1111
1720
  const pageModule = await buildCloudflareComponentExportModule({
1721
+ cacheDir: options.cacheDir,
1112
1722
  filename: options.filename,
1113
1723
  projectRoot: options.projectRoot,
1114
1724
  serverModules: options.serverModules,
1115
1725
  serverOutput: "string",
1726
+ sourceAnalysis: options.sourceAnalysis,
1116
1727
  vitePlugins: options.vitePlugins,
1117
1728
  });
1118
1729
  const shellModules = await Promise.all(shellFiles.map((shell) => buildCloudflareComponentExportModule({
1730
+ cacheDir: options.cacheDir,
1119
1731
  filename: shell.file,
1120
1732
  projectRoot: options.projectRoot,
1121
1733
  serverModules: options.serverModules,
1122
1734
  serverOutput: "string",
1735
+ sourceAnalysis: options.sourceAnalysis,
1123
1736
  vitePlugins: options.vitePlugins,
1124
1737
  })));
1125
1738
  const shellImports = shellFiles.map((_, index) => `import * as shell${index} from "mreact:shell-${index}";`);
@@ -1175,6 +1788,7 @@ async function renderRouteSlots(slots, props) {
1175
1788
  ${cloudflareShellRuntimeSource()}`;
1176
1789
  return bundleCloudflareVirtualModule({
1177
1790
  entry,
1791
+ cacheDir: options.cacheDir,
1178
1792
  filename: `${options.filename}.mreact-cloudflare-string-route.js`,
1179
1793
  modules: new Map([
1180
1794
  ["mreact:page", pageModule],
@@ -1182,23 +1796,28 @@ ${cloudflareShellRuntimeSource()}`;
1182
1796
  ]),
1183
1797
  plugins: [cloudflareWorkspaceRuntimePlugin()],
1184
1798
  resolveDir: dirname(options.filename),
1799
+ root: options.projectRoot,
1185
1800
  vitePlugins: options.vitePlugins,
1186
1801
  });
1187
1802
  }
1188
1803
  async function buildCloudflareStreamRouteComponentModule(options) {
1189
1804
  const pageModule = await buildCloudflareComponentExportModule({
1805
+ cacheDir: options.cacheDir,
1190
1806
  filename: options.filename,
1191
1807
  projectRoot: options.projectRoot,
1192
1808
  serverModules: options.serverModules,
1193
1809
  serverOutput: "stream",
1810
+ sourceAnalysis: options.sourceAnalysis,
1194
1811
  vitePlugins: options.vitePlugins,
1195
1812
  });
1196
1813
  const shellFiles = await cloudflareShellFilesForPage(options.routesDir, options.filename);
1197
1814
  const shellModules = await Promise.all(shellFiles.map((shell) => buildCloudflareComponentExportModule({
1815
+ cacheDir: options.cacheDir,
1198
1816
  filename: shell.file,
1199
1817
  projectRoot: options.projectRoot,
1200
1818
  serverModules: options.serverModules,
1201
1819
  serverOutput: "string",
1820
+ sourceAnalysis: options.sourceAnalysis,
1202
1821
  vitePlugins: options.vitePlugins,
1203
1822
  })));
1204
1823
  const shellImports = shellFiles.map((_, index) => `import * as shell${index} from "mreact:shell-${index}";`);
@@ -1270,6 +1889,7 @@ async function renderRouteSlots(slots, props) {
1270
1889
  ${cloudflareShellRuntimeSource()}`;
1271
1890
  return bundleCloudflareVirtualModule({
1272
1891
  entry,
1892
+ cacheDir: options.cacheDir,
1273
1893
  filename: `${options.filename}.mreact-cloudflare-stream-route.js`,
1274
1894
  modules: new Map([
1275
1895
  ["mreact:page", pageModule],
@@ -1277,6 +1897,7 @@ ${cloudflareShellRuntimeSource()}`;
1277
1897
  ]),
1278
1898
  plugins: [cloudflareWorkspaceRuntimePlugin()],
1279
1899
  resolveDir: dirname(options.filename),
1900
+ root: options.projectRoot,
1280
1901
  vitePlugins: options.vitePlugins,
1281
1902
  });
1282
1903
  }
@@ -1529,7 +2150,10 @@ function escapeHtml(value) {
1529
2150
  }
1530
2151
  async function buildCloudflareComponentExportModule(options) {
1531
2152
  const metadataModule = await buildCloudflareRouteMetadataExportModule({
2153
+ cacheDir: options.cacheDir,
1532
2154
  filename: options.filename,
2155
+ hasMetadata: buildSourceAnalysisForFile(options.sourceAnalysis, options.projectRoot, options.filename)?.hasMetadata,
2156
+ root: options.projectRoot,
1533
2157
  vitePlugins: options.vitePlugins,
1534
2158
  });
1535
2159
  const entry = `import * as routeModule from ${JSON.stringify(options.filename)};
@@ -1543,6 +2167,7 @@ export const metadata = metadataModule.metadata;
1543
2167
  export const slots = routeModule.slots;`;
1544
2168
  return bundleCloudflareVirtualModule({
1545
2169
  entry,
2170
+ cacheDir: options.cacheDir,
1546
2171
  filename: `${options.filename}.mreact-cloudflare-${options.serverOutput}-component.js`,
1547
2172
  modules: metadataModule === undefined
1548
2173
  ? new Map()
@@ -1557,22 +2182,89 @@ export const slots = routeModule.slots;`;
1557
2182
  cloudflareWorkspaceRuntimePlugin(),
1558
2183
  ],
1559
2184
  resolveDir: dirname(options.filename),
2185
+ root: options.projectRoot,
1560
2186
  vitePlugins: options.vitePlugins,
1561
2187
  });
1562
2188
  }
1563
- async function buildCloudflareRouteLoaderModule(options) {
1564
- const entry = `export { loader } from ${JSON.stringify(options.filename)};`;
1565
- return bundleCloudflareModule({
1566
- entry,
1567
- filename: `${options.filename}.mreact-cloudflare-loader.js`,
2189
+ async function writeCloudflareBatchedRouteModuleChunks(cloudflareDir, modules) {
2190
+ if (modules.chunks.some((chunk) => chunk.fileName.startsWith("routes/chunks/"))) {
2191
+ await mkdir(join(cloudflareDir, "routes", "chunks"), { recursive: true });
2192
+ }
2193
+ await Promise.all(modules.chunks.map((chunk) => writeFile(join(cloudflareDir, chunk.fileName), chunk.code)));
2194
+ }
2195
+ async function buildCloudflareRouteLoaderModuleBatch(options) {
2196
+ return await bundleCloudflareModuleBatch({
2197
+ cacheDir: options.cacheDir,
2198
+ entries: options.routes.map((route) => ({
2199
+ code: cloudflareRouteLoaderModuleEntry(route.filename),
2200
+ filename: `${route.filename}.mreact-cloudflare-loader.js`,
2201
+ name: `${route.routeId}.loader`,
2202
+ routeId: route.routeId,
2203
+ })),
2204
+ root: options.root,
2205
+ vitePlugins: options.vitePlugins,
2206
+ });
2207
+ }
2208
+ export async function __buildCloudflareRouteLoaderModuleBatchForTests(options) {
2209
+ const output = await buildCloudflareRouteLoaderModuleBatch({
2210
+ cacheDir: options.cacheDir,
2211
+ root: options.projectRoot,
2212
+ routes: options.routes,
2213
+ vitePlugins: options.vitePlugins,
2214
+ });
2215
+ return Object.fromEntries(options.routes.map((route) => [route.routeId, output.entries.get(route.routeId)?.code ?? ""]));
2216
+ }
2217
+ async function buildCloudflareServerRouteModuleBatch(options) {
2218
+ return await bundleCloudflareModuleBatch({
2219
+ cacheDir: options.cacheDir,
2220
+ entries: options.routes.map((route) => ({
2221
+ code: cloudflareServerRouteModuleEntry(route.filename),
2222
+ filename: `${route.filename}.mreact-cloudflare-server-route.js`,
2223
+ name: `${route.routeId}.server`,
2224
+ routeId: route.routeId,
2225
+ })),
2226
+ root: options.root,
2227
+ vitePlugins: options.vitePlugins,
2228
+ });
2229
+ }
2230
+ async function bundleCloudflareModuleBatch(options) {
2231
+ if (options.entries.length === 0) {
2232
+ return { chunks: [], entries: new Map() };
2233
+ }
2234
+ const output = await bundleRouterModules({
2235
+ cacheDir: options.cacheDir,
2236
+ chunkFileNames: "routes/chunks/[name].[hash].mjs",
2237
+ entries: options.entries,
2238
+ entryFileNames: "routes/[name].[hash].mjs",
2239
+ minify: true,
2240
+ platform: "node",
1568
2241
  plugins: [cloudflareWorkspaceRuntimePlugin()],
1569
- resolveDir: dirname(options.filename),
2242
+ root: options.root,
2243
+ target: "es2022",
1570
2244
  vitePlugins: options.vitePlugins,
1571
2245
  });
2246
+ const entriesByName = new Map(options.entries.map((entry) => [entry.name, entry]));
2247
+ const entries = new Map();
2248
+ for (const chunk of output.chunks) {
2249
+ if (!chunk.isEntry) {
2250
+ continue;
2251
+ }
2252
+ const entry = entriesByName.get(chunk.name);
2253
+ if (entry === undefined) {
2254
+ continue;
2255
+ }
2256
+ entries.set(entry.routeId, {
2257
+ code: chunk.code,
2258
+ fileName: chunk.fileName,
2259
+ });
2260
+ }
2261
+ return { chunks: output.chunks, entries };
2262
+ }
2263
+ function cloudflareRouteLoaderModuleEntry(filename) {
2264
+ return `export { loader } from ${JSON.stringify(filename)};`;
1572
2265
  }
1573
2266
  async function buildCloudflareRouteMetadataExportModule(options) {
1574
- const source = await readFile(options.filename, "utf8");
1575
- if (!hasMetadataExport(source)) {
2267
+ if (options.hasMetadata !== true) {
1576
2268
  return undefined;
1577
2269
  }
1578
2270
  const entry = `import * as routeMetadataModule from ${JSON.stringify(options.filename)};
@@ -1580,14 +2272,16 @@ export const generateMetadata = routeMetadataModule.generateMetadata;
1580
2272
  export const metadata = routeMetadataModule.metadata;`;
1581
2273
  return bundleCloudflareModule({
1582
2274
  entry,
2275
+ cacheDir: options.cacheDir,
1583
2276
  filename: `${options.filename}.mreact-cloudflare-metadata.js`,
1584
2277
  plugins: [cloudflareWorkspaceRuntimePlugin()],
1585
2278
  resolveDir: dirname(options.filename),
2279
+ root: options.root,
1586
2280
  vitePlugins: options.vitePlugins,
1587
2281
  });
1588
2282
  }
1589
- async function buildCloudflareServerRouteModule(options) {
1590
- const entry = `import * as routeModule from ${JSON.stringify(options.filename)};
2283
+ function cloudflareServerRouteModuleEntry(filename) {
2284
+ return `import * as routeModule from ${JSON.stringify(filename)};
1591
2285
 
1592
2286
  export const GET = routeModule.GET;
1593
2287
  export const HEAD = routeModule.HEAD;
@@ -1599,22 +2293,17 @@ export const OPTIONS = routeModule.OPTIONS;
1599
2293
  export const ALL = routeModule.ALL;
1600
2294
  const defaultHandler = routeModule.default;
1601
2295
  export default defaultHandler;`;
1602
- return bundleCloudflareModule({
1603
- entry,
1604
- filename: `${options.filename}.mreact-cloudflare-server-route.js`,
1605
- plugins: [cloudflareWorkspaceRuntimePlugin()],
1606
- resolveDir: dirname(options.filename),
1607
- vitePlugins: options.vitePlugins,
1608
- });
1609
2296
  }
1610
2297
  async function bundleCloudflareModule(options) {
1611
2298
  const output = await bundleRouterModule({
2299
+ cacheDir: options.cacheDir,
1612
2300
  code: options.entry,
1613
2301
  filename: options.filename,
1614
2302
  minify: true,
1615
2303
  platform: "node",
1616
2304
  preserveExports: true,
1617
2305
  plugins: options.plugins,
2306
+ root: options.root,
1618
2307
  target: "es2022",
1619
2308
  vitePlugins: options.vitePlugins,
1620
2309
  });
@@ -1626,6 +2315,7 @@ async function bundleCloudflareModule(options) {
1626
2315
  }
1627
2316
  async function bundleCloudflareVirtualModule(options) {
1628
2317
  return bundleCloudflareModule({
2318
+ cacheDir: options.cacheDir,
1629
2319
  entry: options.entry,
1630
2320
  filename: options.filename,
1631
2321
  plugins: [
@@ -1652,6 +2342,7 @@ async function bundleCloudflareVirtualModule(options) {
1652
2342
  ...options.plugins,
1653
2343
  ],
1654
2344
  resolveDir: options.resolveDir,
2345
+ root: options.root,
1655
2346
  vitePlugins: options.vitePlugins,
1656
2347
  });
1657
2348
  }
@@ -1874,27 +2565,36 @@ function viteManifestFromClientRoutes(routes) {
1874
2565
  return manifest;
1875
2566
  }
1876
2567
  async function writeClientRouteBundles(options) {
2568
+ const pageRoutes = options.routes.filter((route) => route.kind === "page");
2569
+ const routeCssAssets = await writeRouteCssAssetBatches({
2570
+ appDir: options.appDir,
2571
+ cacheDir: options.cacheDir,
2572
+ clientDir: options.clientDir,
2573
+ pageRoutes,
2574
+ projectRoot: options.projectRoot,
2575
+ sourceAnalysis: options.sourceAnalysis,
2576
+ vitePlugins: options.vitePlugins,
2577
+ });
1877
2578
  const entries = await Promise.all(options.routes.map(async (route) => {
1878
2579
  if (route.kind !== "page") {
1879
2580
  return {
1880
2581
  manifest: { path: route.path, kind: route.kind, client: false },
1881
2582
  };
1882
2583
  }
1883
- const css = await writeRouteCssAssets({
1884
- appDir: options.appDir,
1885
- clientDir: options.clientDir,
1886
- pageFile: route.file,
1887
- projectRoot: options.projectRoot,
1888
- routeId: routeIdForPath(route.path),
1889
- vitePlugins: options.vitePlugins,
1890
- });
1891
- const source = await readFile(route.file, "utf8");
2584
+ const css = routeCssAssets.get(route.file) ?? [];
2585
+ const source = buildSourceAnalysisForFile(options.sourceAnalysis, options.projectRoot, route.file)?.source;
2586
+ if (source === undefined) {
2587
+ return {
2588
+ manifest: { path: route.path, kind: route.kind, client: false },
2589
+ };
2590
+ }
1892
2591
  const clientSource = stripRouteClientSource({ code: source, filename: route.file });
1893
2592
  const references = await collectClientRouteReferences({
1894
2593
  appDir: options.appDir,
1895
2594
  cache: options.clientRouteInferenceCache,
1896
2595
  code: clientSource,
1897
2596
  filename: route.file,
2597
+ routePath: route.path,
1898
2598
  vitePlugins: options.vitePlugins,
1899
2599
  });
1900
2600
  const navigation = await resolveNavigationRuntime({
@@ -1928,6 +2628,7 @@ async function writeClientRouteBundles(options) {
1928
2628
  clientReferenceImports: references.clientReferenceImports,
1929
2629
  clientReferenceManifest: references.clientReferenceManifest,
1930
2630
  clientNavigation: detectClientNavigationHint(source),
2631
+ cacheDir: options.cacheDir,
1931
2632
  dropConsoleFunctions: options.clientConsolePureFunctions,
1932
2633
  filename: route.file,
1933
2634
  minify: true,
@@ -1945,6 +2646,7 @@ async function writeClientRouteBundles(options) {
1945
2646
  try {
1946
2647
  output = await buildClientRouteBatchOutput({
1947
2648
  assetBaseUrl: options.assetBaseUrl,
2649
+ cacheDir: options.cacheDir,
1948
2650
  dropConsoleFunctions: options.clientConsolePureFunctions,
1949
2651
  minify: true,
1950
2652
  projectRoot: options.projectRoot,
@@ -2015,12 +2717,51 @@ async function writeClientRouteBundles(options) {
2015
2717
  };
2016
2718
  });
2017
2719
  }
2018
- async function writeRouteCssAssets(options) {
2019
- const cssFiles = await collectRouteCssFiles({
2020
- appDir: options.appDir,
2021
- pageFile: options.pageFile,
2022
- projectRoot: options.projectRoot,
2720
+ async function writeRouteCssAssetBatches(options) {
2721
+ const cssInputs = await mapWithBuildConcurrency(options.pageRoutes, async (route) => {
2722
+ const cssFiles = await collectRouteCssFilesFromSources({
2723
+ appDir: options.appDir,
2724
+ pageFile: route.file,
2725
+ projectRoot: options.projectRoot,
2726
+ readSource: (file) => buildSourceAnalysisForFile(options.sourceAnalysis, options.projectRoot, file)?.source,
2727
+ });
2728
+ return { cssFiles, route };
2023
2729
  });
2730
+ const groups = new Map();
2731
+ for (const { cssFiles, route } of cssInputs) {
2732
+ if (cssFiles.length === 0) {
2733
+ continue;
2734
+ }
2735
+ const key = cssFiles.join("\0");
2736
+ const group = groups.get(key) ?? { cssFiles, routeFiles: [], routeIds: [] };
2737
+ group.routeFiles.push(route.file);
2738
+ group.routeIds.push(routeIdForPath(route.path));
2739
+ groups.set(key, group);
2740
+ }
2741
+ const writtenGroups = await mapWithBuildConcurrency([...groups.entries()], async ([key, group]) => [
2742
+ key,
2743
+ await writeRouteCssAssetsForFiles({
2744
+ cacheDir: options.cacheDir,
2745
+ clientDir: options.clientDir,
2746
+ cssFiles: group.cssFiles,
2747
+ pageFile: group.routeFiles[0] ?? options.appDir,
2748
+ projectRoot: options.projectRoot,
2749
+ routeIds: group.routeIds,
2750
+ vitePlugins: options.vitePlugins,
2751
+ }),
2752
+ ]);
2753
+ const cssByGroup = new Map(writtenGroups);
2754
+ const cssByRoute = new Map();
2755
+ for (const [key, group] of groups) {
2756
+ const css = cssByGroup.get(key) ?? [];
2757
+ for (const routeFile of group.routeFiles) {
2758
+ cssByRoute.set(routeFile, css);
2759
+ }
2760
+ }
2761
+ return cssByRoute;
2762
+ }
2763
+ async function writeRouteCssAssetsForFiles(options) {
2764
+ const cssFiles = [...options.cssFiles];
2024
2765
  if (cssFiles.length === 0) {
2025
2766
  return [];
2026
2767
  }
@@ -2029,20 +2770,25 @@ async function writeRouteCssAssets(options) {
2029
2770
  "export default undefined;",
2030
2771
  ].join("\n");
2031
2772
  const output = await bundleRouterModule({
2773
+ cacheDir: options.cacheDir,
2032
2774
  code,
2033
2775
  filename: options.pageFile,
2034
2776
  minify: true,
2035
2777
  platform: "browser",
2778
+ root: options.projectRoot,
2036
2779
  vitePlugins: options.vitePlugins,
2037
2780
  });
2038
2781
  const cssAssets = (output.assets ?? []).filter((asset) => asset.fileName.endsWith(".css"));
2039
2782
  const written = [];
2040
- for (const asset of cssAssets) {
2783
+ const routeStem = options.routeIds.length === 1
2784
+ ? (options.routeIds[0] ?? "index")
2785
+ : `shared.${hashText(cssFiles.join("\0")).slice(0, 8)}`;
2786
+ for (const [index, asset] of cssAssets.entries()) {
2041
2787
  const source = typeof asset.source === "string"
2042
2788
  ? asset.source
2043
2789
  : Buffer.from(asset.source).toString("utf8");
2044
2790
  const hash = createHash("sha256").update(source).digest("hex").slice(0, 8);
2045
- const cssFile = `assets/routes/${options.routeId}.${hash}.css`;
2791
+ const cssFile = `assets/routes/${routeStem}${cssAssets.length === 1 ? "" : `.${index}`}.${hash}.css`;
2046
2792
  await mkdir(dirname(join(options.clientDir, cssFile)), { recursive: true });
2047
2793
  await writeFile(join(options.clientDir, cssFile), source);
2048
2794
  written.push(cssFile);
@@ -2113,7 +2859,16 @@ export async function packageAwsLambdaArtifact(options) {
2113
2859
  fromDir: dirname(options.fromDir),
2114
2860
  outDir: options.outDir,
2115
2861
  });
2116
- await writeFile(join(options.outDir, "mreact-handler.mjs"), awsLambdaHandlerSource(".mreact"));
2862
+ if (options.handlerEntry === undefined) {
2863
+ await writeFile(join(options.outDir, "mreact-handler.mjs"), awsLambdaHandlerSource(".mreact"));
2864
+ }
2865
+ else {
2866
+ await writeAwsLambdaCustomHandlerArtifact({
2867
+ entry: options.handlerEntry,
2868
+ projectRoot: dirname(options.fromDir),
2869
+ outDir: options.outDir,
2870
+ });
2871
+ }
2117
2872
  if (options.skipRuntimeDependencyCheck !== true) {
2118
2873
  await assertAwsLambdaRuntimeDependencies(options.outDir);
2119
2874
  }
@@ -2128,6 +2883,32 @@ export async function packageAwsLambdaArtifact(options) {
2128
2883
  await writeFile(join(options.outDir, "mreact-lambda-artifact.json"), JSON.stringify(manifest, null, 2));
2129
2884
  return manifest;
2130
2885
  }
2886
+ async function writeAwsLambdaCustomHandlerArtifact(options) {
2887
+ const entry = resolve(options.entry);
2888
+ const output = await bundleRouterModule({
2889
+ code: await readFile(entry, "utf8"),
2890
+ filename: entry,
2891
+ outfile: "mreact-handler.mjs",
2892
+ platform: "node",
2893
+ plugins: [externalizePackageImportsPlugin()],
2894
+ preserveExports: true,
2895
+ root: options.projectRoot,
2896
+ target: "node24",
2897
+ });
2898
+ await writeFile(join(options.outDir, "mreact-handler.mjs"), output.code);
2899
+ }
2900
+ function externalizePackageImportsPlugin() {
2901
+ return {
2902
+ name: "mreact-router-externalize-package-imports",
2903
+ setup(buildApi) {
2904
+ buildApi.onResolve({ filter: /^(?:node:|[A-Za-z@])/ }, (args) => isBarePackageImport(args.path) ? { external: true, path: args.path } : undefined);
2905
+ },
2906
+ };
2907
+ }
2908
+ function isBarePackageImport(specifier) {
2909
+ return (specifier.startsWith("node:") ||
2910
+ (!specifier.startsWith(".") && !specifier.startsWith("/") && !specifier.includes("\\")));
2911
+ }
2131
2912
  async function assertAwsLambdaRuntimeDependencies(outDir) {
2132
2913
  try {
2133
2914
  const info = await stat(join(outDir, "node_modules", "@reckona", "mreact-router", "package.json"));
@@ -2269,22 +3050,18 @@ async function validateProductionRoutes(options) {
2269
3050
  if (route.kind !== "page") {
2270
3051
  continue;
2271
3052
  }
2272
- const source = await readFile(route.file, "utf8");
2273
3053
  const filename = relative(options.projectRoot, route.file);
2274
- const output = transform({
2275
- code: stripRouteBuildExports(source, route.file),
2276
- dev: false,
3054
+ const analysis = options.sourceAnalysis.byRouteFile.get(filename.split(sep).join("/"));
3055
+ if (analysis === undefined) {
3056
+ continue;
3057
+ }
3058
+ const output = await transformServerRouteSource({
3059
+ cache: options.serverTransformCache,
3060
+ code: analysis.routeCode,
3061
+ clientBoundaryImports: analysis.clientBoundaryImports,
2277
3062
  filename: route.file,
2278
- serverEscape: nativeEscapeTransform,
2279
- serverOutput: shouldBuildRouteAsStream({
2280
- filename,
2281
- files: options.files,
2282
- projectRoot: options.projectRoot,
2283
- source,
2284
- })
2285
- ? "stream"
2286
- : "string",
2287
- target: "server",
3063
+ moduleContextCache: options.clientRouteInferenceCache,
3064
+ serverOutput: analysis.streamRoute ? "stream" : "string",
2288
3065
  });
2289
3066
  const fatalDiagnostics = output.diagnostics.filter((diagnostic) => diagnostic.code !== "MR_UNSUPPORTED_SERVER_EVENT_HANDLER");
2290
3067
  if (fatalDiagnostics.length > 0) {
@@ -2295,15 +3072,22 @@ async function validateProductionRoutes(options) {
2295
3072
  async function collectBuildFiles(projectRoot, allowedSourceDirs, appDir) {
2296
3073
  const files = {};
2297
3074
  for (const directory of allowedSourceDirs) {
2298
- for (const file of await collectFiles(directory)) {
3075
+ const sourceFiles = (await collectFiles(directory)).flatMap((file) => {
2299
3076
  if (isAppFileConventionAsset(file, appDir)) {
2300
- continue;
3077
+ return [];
2301
3078
  }
2302
3079
  const relativeFile = relative(projectRoot, file);
2303
3080
  if (relativeFile === "" || relativeFile.startsWith("..") || relativeFile.startsWith(sep)) {
2304
- continue;
3081
+ return [];
2305
3082
  }
2306
- files[relativeFile] = await readFile(file, "utf8");
3083
+ return [{ file, relativeFile }];
3084
+ });
3085
+ const fileContents = await mapWithBuildConcurrency(sourceFiles, async ({ file, relativeFile }) => [
3086
+ relativeFile,
3087
+ await readFile(file, "utf8"),
3088
+ ]);
3089
+ for (const [relativeFile, source] of fileContents) {
3090
+ files[relativeFile] = source;
2307
3091
  }
2308
3092
  }
2309
3093
  return files;
@@ -2317,18 +3101,17 @@ function isAppFileConventionAsset(file, appDir) {
2317
3101
  }
2318
3102
  async function collectFiles(directory) {
2319
3103
  const entries = await readdir(directory, { withFileTypes: true });
2320
- const files = [];
2321
- for (const entry of entries) {
3104
+ const nestedFiles = await mapWithBuildConcurrency(entries, async (entry) => {
2322
3105
  const path = join(directory, entry.name);
2323
3106
  if (entry.isDirectory()) {
2324
- files.push(...(await collectFiles(path)));
2325
- continue;
3107
+ return await collectFiles(path);
2326
3108
  }
2327
3109
  if (entry.isFile()) {
2328
- files.push(path);
3110
+ return [path];
2329
3111
  }
2330
- }
2331
- return files;
3112
+ return [];
3113
+ });
3114
+ return nestedFiles.flat();
2332
3115
  }
2333
3116
  function hashText(text) {
2334
3117
  return createHash("sha256").update(text).digest("hex").slice(0, 16);