@reckona/mreact-router 0.0.96 → 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.
- package/README.md +4 -0
- package/dist/actions.d.ts +7 -0
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +26 -2
- package/dist/actions.js.map +1 -1
- package/dist/adapters/aws-lambda.d.ts.map +1 -1
- package/dist/adapters/aws-lambda.js +10 -7
- package/dist/adapters/aws-lambda.js.map +1 -1
- package/dist/adapters/static.d.ts.map +1 -1
- package/dist/adapters/static.js +13 -0
- package/dist/adapters/static.js.map +1 -1
- package/dist/build.d.ts +38 -1
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +1093 -307
- package/dist/build.js.map +1 -1
- package/dist/bundle-pipeline.d.ts +7 -0
- package/dist/bundle-pipeline.d.ts.map +1 -1
- package/dist/bundle-pipeline.js +121 -69
- package/dist/bundle-pipeline.js.map +1 -1
- package/dist/cache.d.ts.map +1 -1
- package/dist/cache.js +10 -8
- package/dist/cache.js.map +1 -1
- package/dist/cli-options.d.ts +1 -0
- package/dist/cli-options.d.ts.map +1 -1
- package/dist/cli-options.js +16 -0
- package/dist/cli-options.js.map +1 -1
- package/dist/cli.js +1 -0
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts +6 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +120 -9
- package/dist/client.js.map +1 -1
- package/dist/csrf.d.ts.map +1 -1
- package/dist/csrf.js +7 -3
- package/dist/csrf.js.map +1 -1
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +18 -1
- package/dist/http.js.map +1 -1
- package/dist/import-policy.d.ts.map +1 -1
- package/dist/import-policy.js +7 -3
- package/dist/import-policy.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/link.d.ts +5 -3
- package/dist/link.d.ts.map +1 -1
- package/dist/link.js +92 -3
- package/dist/link.js.map +1 -1
- package/dist/logger.d.ts +7 -1
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js.map +1 -1
- package/dist/module-runner.d.ts +2 -0
- package/dist/module-runner.d.ts.map +1 -1
- package/dist/module-runner.js +1 -0
- package/dist/module-runner.js.map +1 -1
- package/dist/native-route-matcher.d.ts +2 -1
- package/dist/native-route-matcher.d.ts.map +1 -1
- package/dist/native-route-matcher.js +13 -2
- package/dist/native-route-matcher.js.map +1 -1
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +121 -6
- package/dist/render.js.map +1 -1
- package/dist/route-source.d.ts +11 -11
- package/dist/route-source.d.ts.map +1 -1
- package/dist/route-source.js +32 -25
- package/dist/route-source.js.map +1 -1
- package/dist/route-styles.d.ts +6 -0
- package/dist/route-styles.d.ts.map +1 -1
- package/dist/route-styles.js +10 -1
- package/dist/route-styles.js.map +1 -1
- package/dist/serve.d.ts +5 -0
- package/dist/serve.d.ts.map +1 -1
- package/dist/serve.js +7 -4
- package/dist/serve.js.map +1 -1
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +32 -5
- package/dist/vite.js.map +1 -1
- package/package.json +11 -11
- package/src/actions.ts +42 -2
- package/src/adapters/aws-lambda.ts +33 -16
- package/src/adapters/static.ts +25 -0
- package/src/build.ts +1605 -421
- package/src/bundle-pipeline.ts +136 -70
- package/src/cache.ts +13 -8
- package/src/cli-options.ts +20 -0
- package/src/cli.ts +1 -0
- package/src/client.ts +152 -15
- package/src/csrf.ts +8 -3
- package/src/http.ts +21 -1
- package/src/import-policy.ts +8 -3
- package/src/index.ts +5 -0
- package/src/link.ts +135 -8
- package/src/logger.ts +9 -1
- package/src/module-runner.ts +4 -0
- package/src/native-route-matcher.ts +13 -2
- package/src/render.ts +181 -6
- package/src/route-source.ts +33 -20
- package/src/route-styles.ts +17 -1
- package/src/serve.ts +11 -6
- package/src/vite.ts +41 -5
package/src/build.ts
CHANGED
|
@@ -2,7 +2,9 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import type { Dirent } from "node:fs";
|
|
3
3
|
import { copyFile, cp, mkdir, readdir, readFile, rm, stat, writeFile } from "node:fs/promises";
|
|
4
4
|
import { builtinModules } from "node:module";
|
|
5
|
+
import { availableParallelism } from "node:os";
|
|
5
6
|
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
6
8
|
import {
|
|
7
9
|
collectStaticImportReferences,
|
|
8
10
|
collectTopLevelValueExportNames,
|
|
@@ -30,7 +32,11 @@ import {
|
|
|
30
32
|
routeIdForPath,
|
|
31
33
|
type BuildClientRouteOutputOptions,
|
|
32
34
|
} from "./navigation-runtime.js";
|
|
33
|
-
import {
|
|
35
|
+
import {
|
|
36
|
+
bundleAppRouterSourceModule,
|
|
37
|
+
fileImportMetaUrlPlugin,
|
|
38
|
+
importAppRouterSourceModule,
|
|
39
|
+
} from "./module-runner.js";
|
|
34
40
|
import { scanAppRoutes } from "./routes.js";
|
|
35
41
|
import type { AppRoute } from "./routes.js";
|
|
36
42
|
import {
|
|
@@ -64,12 +70,16 @@ import {
|
|
|
64
70
|
} from "./route-source.js";
|
|
65
71
|
import {
|
|
66
72
|
bundleRouterModule,
|
|
73
|
+
bundleRouterModules,
|
|
67
74
|
type RouterCompatPlugin,
|
|
75
|
+
type RouterBundleChunkOutput,
|
|
76
|
+
type RouterBundleOutput,
|
|
68
77
|
} from "./bundle-pipeline.js";
|
|
69
|
-
import {
|
|
78
|
+
import { collectRouteCssFilesFromSources } from "./route-styles.js";
|
|
70
79
|
import { existingRouteShellCandidates } from "./route-shells.js";
|
|
71
80
|
import { sourceModuleCandidates } from "./source-modules.js";
|
|
72
81
|
import { collectBuildInferredServerActions } from "./server-action-inference.js";
|
|
82
|
+
import { vitePluginsCacheKey } from "./vite-plugin-cache-key.js";
|
|
73
83
|
import { workspacePackageFile } from "./workspace-packages.js";
|
|
74
84
|
import type { PluginOption, UserConfig } from "vite";
|
|
75
85
|
|
|
@@ -78,12 +88,42 @@ const nativeEscapeTransform = {
|
|
|
78
88
|
batchImportSource: "@reckona/mreact-router/native-escape",
|
|
79
89
|
} as const;
|
|
80
90
|
|
|
91
|
+
const defaultBuildConcurrency = Math.max(1, Math.min(2, availableParallelism()));
|
|
92
|
+
const serverArtifactFilesystemConcurrency = 2;
|
|
93
|
+
|
|
94
|
+
type ServerTransformOutput = ReturnType<typeof transform>;
|
|
95
|
+
type ServerTransformCache = Map<string, Promise<ServerTransformOutput>>;
|
|
96
|
+
|
|
81
97
|
export interface BuildAppOptions extends AppRouterProjectOptions {
|
|
98
|
+
onBuildPhaseTiming?: ((timing: BuildAppPhaseTiming) => void) | undefined;
|
|
82
99
|
outDir: string;
|
|
83
100
|
targets?: readonly AppRouterBuildTarget[] | undefined;
|
|
84
101
|
viteConfig?: Pick<UserConfig, "plugins"> | undefined;
|
|
85
102
|
}
|
|
86
103
|
|
|
104
|
+
export type BuildAppPhase =
|
|
105
|
+
| "scan"
|
|
106
|
+
| "collectFiles"
|
|
107
|
+
| "analyzeSources"
|
|
108
|
+
| "validate"
|
|
109
|
+
| "prepareOutput"
|
|
110
|
+
| "publicAssets"
|
|
111
|
+
| "serverActionManifest"
|
|
112
|
+
| "serverModules"
|
|
113
|
+
| "importPolicy"
|
|
114
|
+
| "serverModuleArtifacts"
|
|
115
|
+
| "clientBundles"
|
|
116
|
+
| "navigationRuntime"
|
|
117
|
+
| "prerender"
|
|
118
|
+
| "cloudflare"
|
|
119
|
+
| "writeManifests"
|
|
120
|
+
| "adapterArtifacts";
|
|
121
|
+
|
|
122
|
+
export interface BuildAppPhaseTiming {
|
|
123
|
+
ms: number;
|
|
124
|
+
phase: BuildAppPhase;
|
|
125
|
+
}
|
|
126
|
+
|
|
87
127
|
export interface BuildAppResult {
|
|
88
128
|
routes: AppRoute[];
|
|
89
129
|
}
|
|
@@ -112,6 +152,7 @@ export interface CloudflarePagesArtifactManifest {
|
|
|
112
152
|
|
|
113
153
|
export interface PackageAwsLambdaArtifactOptions {
|
|
114
154
|
fromDir: string;
|
|
155
|
+
handlerEntry?: string | undefined;
|
|
115
156
|
outDir: string;
|
|
116
157
|
skipRuntimeDependencyCheck?: boolean | undefined;
|
|
117
158
|
}
|
|
@@ -178,6 +219,32 @@ export interface BuiltRouteSourceAnalysisSummary {
|
|
|
178
219
|
usesRuntimeCacheControl: boolean;
|
|
179
220
|
}
|
|
180
221
|
|
|
222
|
+
interface BuildSourceAnalysis {
|
|
223
|
+
authIncludesClaims: boolean;
|
|
224
|
+
cachePolicy?: RouteCachePolicy | undefined;
|
|
225
|
+
hasGenerateStaticParams: boolean;
|
|
226
|
+
hasLoader: boolean;
|
|
227
|
+
hasMetadata: boolean;
|
|
228
|
+
hasPrerender: boolean;
|
|
229
|
+
source: string;
|
|
230
|
+
sourceHash: string;
|
|
231
|
+
usesRuntimeCacheControl: boolean;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
interface BuildRouteSourceAnalysis extends BuildSourceAnalysis {
|
|
235
|
+
clientBoundaryImports: readonly string[];
|
|
236
|
+
clientRoute: boolean;
|
|
237
|
+
file: string;
|
|
238
|
+
route: AppRoute & { kind: "page" };
|
|
239
|
+
routeCode: string;
|
|
240
|
+
streamRoute: boolean;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
interface BuildSourceAnalysisScope {
|
|
244
|
+
byFile: ReadonlyMap<string, BuildSourceAnalysis>;
|
|
245
|
+
byRouteFile: ReadonlyMap<string, BuildRouteSourceAnalysis>;
|
|
246
|
+
}
|
|
247
|
+
|
|
181
248
|
export interface BuiltServerModuleOutput {
|
|
182
249
|
bundleCode?: string;
|
|
183
250
|
code: string;
|
|
@@ -195,84 +262,217 @@ export interface BuiltPrerenderedRoute {
|
|
|
195
262
|
type StaticParams = Record<string, string | number | boolean | readonly string[]>;
|
|
196
263
|
|
|
197
264
|
export async function buildApp(options: BuildAppOptions): Promise<BuildAppResult> {
|
|
265
|
+
const timingSink = options.onBuildPhaseTiming;
|
|
198
266
|
const project = resolveAppRouterProjectOptions(options);
|
|
199
267
|
const buildTargets = resolveBuildTargets(options.targets ?? project.buildTargets);
|
|
200
268
|
const shouldBuildCloudflare = buildTargets.includes("cloudflare");
|
|
201
269
|
const shouldBuildAwsLambda = buildTargets.includes("aws-lambda");
|
|
202
|
-
const routes =
|
|
270
|
+
const routes =
|
|
271
|
+
timingSink === undefined
|
|
272
|
+
? await scanAppRoutes({ appDir: project.routesDir })
|
|
273
|
+
: await timeBuildPhase(timingSink, "scan", () =>
|
|
274
|
+
scanAppRoutes({ appDir: project.routesDir }),
|
|
275
|
+
);
|
|
203
276
|
const vitePlugins = options.viteConfig?.plugins;
|
|
204
|
-
const files =
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
277
|
+
const files =
|
|
278
|
+
timingSink === undefined
|
|
279
|
+
? await collectBuildFiles(project.projectRoot, project.allowedSourceDirs, project.routesDir)
|
|
280
|
+
: await timeBuildPhase(timingSink, "collectFiles", () =>
|
|
281
|
+
collectBuildFiles(project.projectRoot, project.allowedSourceDirs, project.routesDir),
|
|
282
|
+
);
|
|
283
|
+
const serverClientRouteInferenceCache = createClientRouteInferenceCache();
|
|
284
|
+
const serverTransformCache: ServerTransformCache = new Map();
|
|
209
285
|
const serverDir = join(options.outDir, "server");
|
|
210
286
|
const clientDir = join(options.outDir, "client");
|
|
211
287
|
const cloudflareDir = join(options.outDir, "cloudflare");
|
|
288
|
+
const sourceAnalysis =
|
|
289
|
+
timingSink === undefined
|
|
290
|
+
? await analyzeBuildRouteSources({
|
|
291
|
+
clientRouteInferenceCache: serverClientRouteInferenceCache,
|
|
292
|
+
files,
|
|
293
|
+
project,
|
|
294
|
+
projectRoot: project.projectRoot,
|
|
295
|
+
routes,
|
|
296
|
+
vitePlugins,
|
|
297
|
+
})
|
|
298
|
+
: await timeBuildPhase(timingSink, "analyzeSources", () =>
|
|
299
|
+
analyzeBuildRouteSources({
|
|
300
|
+
clientRouteInferenceCache: serverClientRouteInferenceCache,
|
|
301
|
+
files,
|
|
302
|
+
project,
|
|
303
|
+
projectRoot: project.projectRoot,
|
|
304
|
+
routes,
|
|
305
|
+
vitePlugins,
|
|
306
|
+
}),
|
|
307
|
+
);
|
|
212
308
|
|
|
213
|
-
|
|
309
|
+
if (timingSink === undefined) {
|
|
310
|
+
await validateProductionRoutes({
|
|
311
|
+
clientRouteInferenceCache: serverClientRouteInferenceCache,
|
|
312
|
+
files,
|
|
313
|
+
project,
|
|
314
|
+
projectRoot: project.projectRoot,
|
|
315
|
+
routes,
|
|
316
|
+
sourceAnalysis,
|
|
317
|
+
serverTransformCache,
|
|
318
|
+
vitePlugins,
|
|
319
|
+
});
|
|
320
|
+
} else {
|
|
321
|
+
await timeBuildPhase(timingSink, "validate", () =>
|
|
322
|
+
validateProductionRoutes({
|
|
323
|
+
clientRouteInferenceCache: serverClientRouteInferenceCache,
|
|
324
|
+
files,
|
|
325
|
+
project,
|
|
326
|
+
projectRoot: project.projectRoot,
|
|
327
|
+
routes,
|
|
328
|
+
sourceAnalysis,
|
|
329
|
+
serverTransformCache,
|
|
330
|
+
vitePlugins,
|
|
331
|
+
}),
|
|
332
|
+
);
|
|
333
|
+
}
|
|
214
334
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
335
|
+
if (timingSink === undefined) {
|
|
336
|
+
await rm(options.outDir, { force: true, recursive: true });
|
|
337
|
+
await Promise.all([
|
|
338
|
+
mkdir(serverDir, { recursive: true }),
|
|
339
|
+
mkdir(clientDir, { recursive: true }),
|
|
340
|
+
...(shouldBuildCloudflare ? [mkdir(cloudflareDir, { recursive: true })] : []),
|
|
341
|
+
mkdir(join(clientDir, ".vite"), { recursive: true }),
|
|
342
|
+
mkdir(join(clientDir, "assets", "routes"), { recursive: true }),
|
|
343
|
+
]);
|
|
344
|
+
} else {
|
|
345
|
+
await timeBuildPhase(timingSink, "prepareOutput", async () => {
|
|
346
|
+
await rm(options.outDir, { force: true, recursive: true });
|
|
347
|
+
await Promise.all([
|
|
348
|
+
mkdir(serverDir, { recursive: true }),
|
|
349
|
+
mkdir(clientDir, { recursive: true }),
|
|
350
|
+
...(shouldBuildCloudflare ? [mkdir(cloudflareDir, { recursive: true })] : []),
|
|
351
|
+
mkdir(join(clientDir, ".vite"), { recursive: true }),
|
|
352
|
+
mkdir(join(clientDir, "assets", "routes"), { recursive: true }),
|
|
353
|
+
]);
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const publicAssets =
|
|
358
|
+
timingSink === undefined
|
|
359
|
+
? await buildPublicAssetManifest(project, clientDir)
|
|
360
|
+
: await timeBuildPhase(timingSink, "publicAssets", () =>
|
|
361
|
+
buildPublicAssetManifest(project, clientDir),
|
|
362
|
+
);
|
|
232
363
|
|
|
233
|
-
const serverActionManifest = await collectBuildServerActionManifest({
|
|
234
|
-
files,
|
|
235
|
-
projectRoot: project.projectRoot,
|
|
236
|
-
routes,
|
|
237
|
-
routesDir: project.routesDir,
|
|
238
|
-
});
|
|
239
364
|
const clientRouteInferenceCache = createClientRouteInferenceCache();
|
|
240
|
-
const serverModules = await
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
365
|
+
const [serverActionManifest, serverModules, generatedImportPolicy] = await Promise.all([
|
|
366
|
+
timingSink === undefined
|
|
367
|
+
? collectBuildServerActionManifest({
|
|
368
|
+
files,
|
|
369
|
+
projectRoot: project.projectRoot,
|
|
370
|
+
routes,
|
|
371
|
+
routesDir: project.routesDir,
|
|
372
|
+
})
|
|
373
|
+
: timeBuildPhase(timingSink, "serverActionManifest", () =>
|
|
374
|
+
collectBuildServerActionManifest({
|
|
375
|
+
files,
|
|
376
|
+
projectRoot: project.projectRoot,
|
|
377
|
+
routes,
|
|
378
|
+
routesDir: project.routesDir,
|
|
379
|
+
}),
|
|
380
|
+
),
|
|
381
|
+
timingSink === undefined
|
|
382
|
+
? buildServerModuleArtifacts({
|
|
383
|
+
bundleCache: new Map(),
|
|
384
|
+
clientRouteInferenceCache: serverClientRouteInferenceCache,
|
|
385
|
+
files,
|
|
386
|
+
prebundleServerComponents: buildTargets.includes("node") || shouldBuildAwsLambda,
|
|
387
|
+
project,
|
|
388
|
+
projectRoot: project.projectRoot,
|
|
389
|
+
routes,
|
|
390
|
+
sourceAnalysis,
|
|
391
|
+
serverTransformCache,
|
|
392
|
+
vitePlugins,
|
|
393
|
+
})
|
|
394
|
+
: timeBuildPhase(timingSink, "serverModules", () =>
|
|
395
|
+
buildServerModuleArtifacts({
|
|
396
|
+
bundleCache: new Map(),
|
|
397
|
+
clientRouteInferenceCache: serverClientRouteInferenceCache,
|
|
398
|
+
files,
|
|
399
|
+
prebundleServerComponents: buildTargets.includes("node") || shouldBuildAwsLambda,
|
|
400
|
+
project,
|
|
401
|
+
projectRoot: project.projectRoot,
|
|
402
|
+
routes,
|
|
403
|
+
sourceAnalysis,
|
|
404
|
+
serverTransformCache,
|
|
405
|
+
vitePlugins,
|
|
406
|
+
}),
|
|
407
|
+
),
|
|
408
|
+
timingSink === undefined
|
|
409
|
+
? buildGeneratedImportPolicy({
|
|
410
|
+
files,
|
|
411
|
+
projectRoot: project.projectRoot,
|
|
412
|
+
routes,
|
|
413
|
+
routesDir: project.routesDir,
|
|
414
|
+
})
|
|
415
|
+
: timeBuildPhase(timingSink, "importPolicy", () =>
|
|
416
|
+
buildGeneratedImportPolicy({
|
|
417
|
+
files,
|
|
418
|
+
projectRoot: project.projectRoot,
|
|
419
|
+
routes,
|
|
420
|
+
routesDir: project.routesDir,
|
|
421
|
+
}),
|
|
422
|
+
),
|
|
423
|
+
]);
|
|
256
424
|
const serverRoutes = routes.map((route) => ({
|
|
257
425
|
...route,
|
|
258
426
|
file: relative(project.projectRoot, route.file),
|
|
259
427
|
}));
|
|
260
|
-
const clientRoutes = await
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
428
|
+
const [serverModuleArtifacts, clientRoutes] = await Promise.all([
|
|
429
|
+
timingSink === undefined
|
|
430
|
+
? writeServerModuleArtifactFiles(serverDir, serverModules, generatedImportPolicy.runtimePackages)
|
|
431
|
+
: timeBuildPhase(timingSink, "serverModuleArtifacts", () =>
|
|
432
|
+
writeServerModuleArtifactFiles(
|
|
433
|
+
serverDir,
|
|
434
|
+
serverModules,
|
|
435
|
+
generatedImportPolicy.runtimePackages,
|
|
436
|
+
),
|
|
437
|
+
),
|
|
438
|
+
timingSink === undefined
|
|
439
|
+
? writeClientRouteBundles({
|
|
440
|
+
appDir: project.routesDir,
|
|
441
|
+
assetBaseUrl: project.assetBaseUrl,
|
|
442
|
+
clientDir,
|
|
443
|
+
clientConsolePureFunctions: project.clientConsolePureFunctions,
|
|
444
|
+
clientRouteInferenceCache,
|
|
445
|
+
projectRoot: project.projectRoot,
|
|
446
|
+
routes,
|
|
447
|
+
sourceAnalysis,
|
|
448
|
+
sourceMapDir: join(options.outDir, "source-maps", "client"),
|
|
449
|
+
sourceMaps: project.clientSourceMaps,
|
|
450
|
+
vitePlugins,
|
|
451
|
+
})
|
|
452
|
+
: timeBuildPhase(timingSink, "clientBundles", () =>
|
|
453
|
+
writeClientRouteBundles({
|
|
454
|
+
appDir: project.routesDir,
|
|
455
|
+
assetBaseUrl: project.assetBaseUrl,
|
|
456
|
+
clientDir,
|
|
457
|
+
clientConsolePureFunctions: project.clientConsolePureFunctions,
|
|
458
|
+
clientRouteInferenceCache,
|
|
459
|
+
projectRoot: project.projectRoot,
|
|
460
|
+
routes,
|
|
461
|
+
sourceAnalysis,
|
|
462
|
+
sourceMapDir: join(options.outDir, "source-maps", "client"),
|
|
463
|
+
sourceMaps: project.clientSourceMaps,
|
|
464
|
+
vitePlugins,
|
|
465
|
+
}),
|
|
466
|
+
),
|
|
467
|
+
]);
|
|
272
468
|
const navigationRuntimeScript = clientRoutes.some(
|
|
273
469
|
(route) => route.navigation === true && !route.client,
|
|
274
470
|
)
|
|
275
|
-
?
|
|
471
|
+
? timingSink === undefined
|
|
472
|
+
? await writeNavigationRuntimeBundle(clientDir, project.clientConsolePureFunctions)
|
|
473
|
+
: await timeBuildPhase(timingSink, "navigationRuntime", () =>
|
|
474
|
+
writeNavigationRuntimeBundle(clientDir, project.clientConsolePureFunctions),
|
|
475
|
+
)
|
|
276
476
|
: undefined;
|
|
277
477
|
const clientManifestRoutes =
|
|
278
478
|
navigationRuntimeScript === undefined
|
|
@@ -282,27 +482,58 @@ export async function buildApp(options: BuildAppOptions): Promise<BuildAppResult
|
|
|
282
482
|
? { ...route, navigationScript: navigationRuntimeScript }
|
|
283
483
|
: route,
|
|
284
484
|
);
|
|
285
|
-
const prerenderedRoutes =
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
485
|
+
const prerenderedRoutes =
|
|
486
|
+
timingSink === undefined
|
|
487
|
+
? await prerenderStaticRoutes({
|
|
488
|
+
appDir: project.routesDir,
|
|
489
|
+
assetBaseUrl: project.assetBaseUrl,
|
|
490
|
+
clientRoutes: clientManifestRoutes,
|
|
491
|
+
project,
|
|
492
|
+
routes,
|
|
493
|
+
serverModules,
|
|
494
|
+
sourceAnalysis,
|
|
495
|
+
vitePlugins,
|
|
496
|
+
})
|
|
497
|
+
: await timeBuildPhase(timingSink, "prerender", () =>
|
|
498
|
+
prerenderStaticRoutes({
|
|
499
|
+
appDir: project.routesDir,
|
|
500
|
+
assetBaseUrl: project.assetBaseUrl,
|
|
501
|
+
clientRoutes: clientManifestRoutes,
|
|
502
|
+
project,
|
|
503
|
+
routes,
|
|
504
|
+
serverModules,
|
|
505
|
+
sourceAnalysis,
|
|
506
|
+
vitePlugins,
|
|
507
|
+
}),
|
|
508
|
+
);
|
|
294
509
|
let cloudflareRouteModules: CloudflareRouteModulesOutput | undefined;
|
|
295
510
|
if (shouldBuildCloudflare) {
|
|
296
|
-
cloudflareRouteModules =
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
511
|
+
cloudflareRouteModules =
|
|
512
|
+
timingSink === undefined
|
|
513
|
+
? await writeCloudflareRouteModules({
|
|
514
|
+
cloudflareDir,
|
|
515
|
+
files,
|
|
516
|
+
prerenderedRoutes,
|
|
517
|
+
projectRoot: project.projectRoot,
|
|
518
|
+
routesDir: project.routesDir,
|
|
519
|
+
routes,
|
|
520
|
+
serverModules,
|
|
521
|
+
sourceAnalysis,
|
|
522
|
+
vitePlugins,
|
|
523
|
+
})
|
|
524
|
+
: await timeBuildPhase(timingSink, "cloudflare", () =>
|
|
525
|
+
writeCloudflareRouteModules({
|
|
526
|
+
cloudflareDir,
|
|
527
|
+
files,
|
|
528
|
+
prerenderedRoutes,
|
|
529
|
+
projectRoot: project.projectRoot,
|
|
530
|
+
routesDir: project.routesDir,
|
|
531
|
+
routes,
|
|
532
|
+
serverModules,
|
|
533
|
+
sourceAnalysis,
|
|
534
|
+
vitePlugins,
|
|
535
|
+
}),
|
|
536
|
+
);
|
|
306
537
|
}
|
|
307
538
|
|
|
308
539
|
const routeServerActionReferences = Object.fromEntries(
|
|
@@ -343,40 +574,246 @@ export async function buildApp(options: BuildAppOptions): Promise<BuildAppResult
|
|
|
343
574
|
...(publicAssets.length === 0 ? {} : { publicAssets }),
|
|
344
575
|
routes: clientManifestRoutes,
|
|
345
576
|
};
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
await
|
|
577
|
+
const writeManifestFiles = async () => {
|
|
578
|
+
await Promise.all([
|
|
579
|
+
writeFile(join(serverDir, "manifest.json"), JSON.stringify(serverManifest, null, 2)),
|
|
580
|
+
writeFile(
|
|
581
|
+
join(serverDir, "import-policy.json"),
|
|
582
|
+
JSON.stringify(generatedImportPolicy, null, 2),
|
|
583
|
+
),
|
|
584
|
+
writeFile(join(clientDir, "manifest.json"), JSON.stringify(clientManifest, null, 2)),
|
|
585
|
+
writeFile(
|
|
586
|
+
join(clientDir, ".vite", "manifest.json"),
|
|
587
|
+
JSON.stringify(viteManifestFromClientRoutes(clientManifestRoutes), null, 2),
|
|
588
|
+
),
|
|
589
|
+
]);
|
|
590
|
+
};
|
|
591
|
+
if (timingSink === undefined) {
|
|
592
|
+
await writeManifestFiles();
|
|
593
|
+
} else {
|
|
594
|
+
await timeBuildPhase(timingSink, "writeManifests", writeManifestFiles);
|
|
364
595
|
}
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
596
|
+
|
|
597
|
+
if (timingSink === undefined) {
|
|
598
|
+
await Promise.all([
|
|
599
|
+
...(shouldBuildAwsLambda ? [writeAwsLambdaHandlerArtifact(options.outDir)] : []),
|
|
600
|
+
...(shouldBuildCloudflare
|
|
601
|
+
? [
|
|
602
|
+
writeCloudflareWorkerArtifact({
|
|
603
|
+
cloudflareDir,
|
|
604
|
+
clientManifest,
|
|
605
|
+
modulesFile: cloudflareRouteModules?.registryFile ?? "route-modules.mjs",
|
|
606
|
+
serverManifest,
|
|
607
|
+
}),
|
|
608
|
+
]
|
|
609
|
+
: []),
|
|
610
|
+
]);
|
|
611
|
+
} else {
|
|
612
|
+
await timeBuildPhase(timingSink, "adapterArtifacts", async () => {
|
|
613
|
+
await Promise.all([
|
|
614
|
+
...(shouldBuildAwsLambda ? [writeAwsLambdaHandlerArtifact(options.outDir)] : []),
|
|
615
|
+
...(shouldBuildCloudflare
|
|
616
|
+
? [
|
|
617
|
+
writeCloudflareWorkerArtifact({
|
|
618
|
+
cloudflareDir,
|
|
619
|
+
clientManifest,
|
|
620
|
+
modulesFile: cloudflareRouteModules?.registryFile ?? "route-modules.mjs",
|
|
621
|
+
serverManifest,
|
|
622
|
+
}),
|
|
623
|
+
]
|
|
624
|
+
: []),
|
|
625
|
+
]);
|
|
371
626
|
});
|
|
372
627
|
}
|
|
373
628
|
|
|
374
629
|
return { routes };
|
|
375
630
|
}
|
|
376
631
|
|
|
632
|
+
async function timeBuildPhase<T>(
|
|
633
|
+
sink: (timing: BuildAppPhaseTiming) => void,
|
|
634
|
+
phase: BuildAppPhase,
|
|
635
|
+
run: () => Promise<T>,
|
|
636
|
+
): Promise<T> {
|
|
637
|
+
const startedAt = performance.now();
|
|
638
|
+
|
|
639
|
+
try {
|
|
640
|
+
return await run();
|
|
641
|
+
} finally {
|
|
642
|
+
sink({
|
|
643
|
+
ms: roundBuildPhaseMs(performance.now() - startedAt),
|
|
644
|
+
phase,
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
function roundBuildPhaseMs(value: number): number {
|
|
650
|
+
return Math.round(value * 100) / 100;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
async function mapWithBuildConcurrency<T, R>(
|
|
654
|
+
items: readonly T[],
|
|
655
|
+
map: (item: T, index: number) => Promise<R>,
|
|
656
|
+
concurrency = defaultBuildConcurrency,
|
|
657
|
+
): Promise<R[]> {
|
|
658
|
+
if (items.length === 0) {
|
|
659
|
+
return [];
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
const workerCount = Math.max(1, Math.min(concurrency, items.length));
|
|
663
|
+
const results: R[] = [];
|
|
664
|
+
results.length = items.length;
|
|
665
|
+
let nextIndex = 0;
|
|
666
|
+
|
|
667
|
+
async function runWorker(): Promise<void> {
|
|
668
|
+
while (nextIndex < items.length) {
|
|
669
|
+
const index = nextIndex;
|
|
670
|
+
nextIndex += 1;
|
|
671
|
+
results[index] = await map(items[index] as T, index);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
await Promise.all(
|
|
676
|
+
Array.from({ length: workerCount }, async () => {
|
|
677
|
+
await runWorker();
|
|
678
|
+
}),
|
|
679
|
+
);
|
|
680
|
+
|
|
681
|
+
return results;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
export async function __mapWithBuildConcurrencyForTests<T, R>(
|
|
685
|
+
items: readonly T[],
|
|
686
|
+
map: (item: T, index: number) => Promise<R>,
|
|
687
|
+
concurrency?: number,
|
|
688
|
+
): Promise<R[]> {
|
|
689
|
+
return await mapWithBuildConcurrency(items, map, concurrency);
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
async function analyzeBuildRouteSources(options: {
|
|
693
|
+
clientRouteInferenceCache: ClientRouteInferenceCache;
|
|
694
|
+
files: Record<string, string>;
|
|
695
|
+
project: ResolvedAppRouterProject;
|
|
696
|
+
projectRoot: string;
|
|
697
|
+
routes: readonly AppRoute[];
|
|
698
|
+
vitePlugins?: readonly PluginOption[] | undefined;
|
|
699
|
+
}): Promise<BuildSourceAnalysisScope> {
|
|
700
|
+
const byFile = new Map<string, BuildSourceAnalysis>();
|
|
701
|
+
|
|
702
|
+
for (const [file, source] of Object.entries(options.files)) {
|
|
703
|
+
byFile.set(file, analyzeBuildSource(source, join(options.projectRoot, file)));
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
const routeAnalyses = await mapWithBuildConcurrency(
|
|
707
|
+
options.routes.filter((route): route is AppRoute & { kind: "page" } => route.kind === "page"),
|
|
708
|
+
async (route) => {
|
|
709
|
+
const file = relative(options.projectRoot, route.file).split(sep).join("/");
|
|
710
|
+
const source = options.files[file];
|
|
711
|
+
|
|
712
|
+
if (source === undefined) {
|
|
713
|
+
return undefined;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
const routeCode = stripRouteBuildExports(source, route.file);
|
|
717
|
+
const clientInference = await inferClientRouteModule({
|
|
718
|
+
appDir: options.project.routesDir,
|
|
719
|
+
cache: options.clientRouteInferenceCache,
|
|
720
|
+
code: stripRouteClientSource({ code: source, filename: route.file }),
|
|
721
|
+
filename: route.file,
|
|
722
|
+
routePath: route.path,
|
|
723
|
+
vitePlugins: options.vitePlugins,
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
for (const diagnostic of clientInference.diagnostics) {
|
|
727
|
+
console.warn(formatClientRouteInferenceDiagnostic(diagnostic));
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
return [
|
|
731
|
+
file,
|
|
732
|
+
{
|
|
733
|
+
...analyzeBuildSource(source, route.file),
|
|
734
|
+
clientBoundaryImports: clientInference.clientBoundaryImports,
|
|
735
|
+
clientRoute: clientInference.client,
|
|
736
|
+
file,
|
|
737
|
+
route,
|
|
738
|
+
routeCode,
|
|
739
|
+
streamRoute: shouldBuildRouteAsStream({
|
|
740
|
+
filename: file,
|
|
741
|
+
files: options.files,
|
|
742
|
+
projectRoot: options.projectRoot,
|
|
743
|
+
source,
|
|
744
|
+
}),
|
|
745
|
+
},
|
|
746
|
+
] as const;
|
|
747
|
+
},
|
|
748
|
+
);
|
|
749
|
+
const byRouteFile = new Map<string, BuildRouteSourceAnalysis>();
|
|
750
|
+
|
|
751
|
+
for (const entry of routeAnalyses) {
|
|
752
|
+
if (entry !== undefined) {
|
|
753
|
+
byRouteFile.set(entry[0], entry[1]);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
return { byFile, byRouteFile };
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
function analyzeBuildSource(source: string, filename: string): BuildSourceAnalysis {
|
|
761
|
+
const cachePolicy = routeCachePolicyFromSource(source);
|
|
762
|
+
const sourceHash = hashText(source);
|
|
763
|
+
|
|
764
|
+
if (!isSourceModuleFile(filename)) {
|
|
765
|
+
return {
|
|
766
|
+
authIncludesClaims: false,
|
|
767
|
+
...(cachePolicy === undefined ? {} : { cachePolicy }),
|
|
768
|
+
hasGenerateStaticParams: false,
|
|
769
|
+
hasLoader: false,
|
|
770
|
+
hasMetadata: false,
|
|
771
|
+
hasPrerender: false,
|
|
772
|
+
source,
|
|
773
|
+
sourceHash,
|
|
774
|
+
usesRuntimeCacheControl: usesRuntimeCacheControl(source),
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
return {
|
|
779
|
+
authIncludesClaims: authIncludesClaims(source),
|
|
780
|
+
...(cachePolicy === undefined ? {} : { cachePolicy }),
|
|
781
|
+
hasGenerateStaticParams: hasGenerateStaticParamsExport(source, filename),
|
|
782
|
+
hasLoader: hasLoaderExport(source, filename),
|
|
783
|
+
hasMetadata: hasMetadataExport(source),
|
|
784
|
+
hasPrerender: hasPrerenderExport(source, filename),
|
|
785
|
+
source,
|
|
786
|
+
sourceHash,
|
|
787
|
+
usesRuntimeCacheControl: usesRuntimeCacheControl(source),
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
function buildSourceAnalysisForFile(
|
|
792
|
+
sourceAnalysis: BuildSourceAnalysisScope,
|
|
793
|
+
projectRoot: string,
|
|
794
|
+
file: string,
|
|
795
|
+
): BuildSourceAnalysis | undefined {
|
|
796
|
+
return sourceAnalysis.byFile.get(relative(projectRoot, file).split(sep).join("/"));
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
async function buildPublicAssetManifest(
|
|
800
|
+
project: ResolvedAppRouterProject,
|
|
801
|
+
clientDir: string,
|
|
802
|
+
): Promise<string[]> {
|
|
803
|
+
const [publicAssetPaths, appConventionPublicAssets] = await Promise.all([
|
|
804
|
+
collectPublicAssetPaths(project.publicDir),
|
|
805
|
+
copyAppFileConventionAssets(project.routesDir, clientDir),
|
|
806
|
+
copyPublicAssets(project.publicDir, join(clientDir, "public")),
|
|
807
|
+
copyPublicAssets(project.publicDir, clientDir),
|
|
808
|
+
]);
|
|
809
|
+
|
|
810
|
+
return [...new Set([...publicAssetPaths, ...appConventionPublicAssets])].sort();
|
|
811
|
+
}
|
|
812
|
+
|
|
377
813
|
async function writeServerModuleArtifactFiles(
|
|
378
814
|
serverDir: string,
|
|
379
815
|
serverModules: Record<string, BuiltServerModuleArtifact>,
|
|
816
|
+
portableRuntimePackages: readonly string[] = [],
|
|
380
817
|
): Promise<{
|
|
381
818
|
files: Record<string, string>;
|
|
382
819
|
renderFiles: Record<string, string>;
|
|
@@ -386,48 +823,94 @@ async function writeServerModuleArtifactFiles(
|
|
|
386
823
|
const renderFiles: Record<string, string> = {};
|
|
387
824
|
const requestFiles: Record<string, string> = {};
|
|
388
825
|
const modulesDir = join(serverDir, "server-modules");
|
|
826
|
+
const artifactEntries = Object.entries(serverModules);
|
|
827
|
+
type WrittenServerModuleArtifact =
|
|
828
|
+
| { artifactFile: string; file: string }
|
|
829
|
+
| { file: string; renderArtifactFile: string; requestArtifactFile: string };
|
|
830
|
+
|
|
831
|
+
await Promise.all([
|
|
832
|
+
mkdir(modulesDir, { recursive: true }),
|
|
833
|
+
mkdir(join(modulesDir, "code"), { recursive: true }),
|
|
834
|
+
mkdir(join(modulesDir, "request"), { recursive: true }),
|
|
835
|
+
mkdir(join(modulesDir, "render"), { recursive: true }),
|
|
836
|
+
]);
|
|
837
|
+
|
|
838
|
+
const writtenArtifacts = await mapWithBuildConcurrency<
|
|
839
|
+
[string, BuiltServerModuleArtifact],
|
|
840
|
+
WrittenServerModuleArtifact
|
|
841
|
+
>(
|
|
842
|
+
artifactEntries,
|
|
843
|
+
async ([file, artifact]) => {
|
|
844
|
+
const externalized = await externalizeServerModuleArtifactCode(
|
|
845
|
+
serverDir,
|
|
846
|
+
artifact,
|
|
847
|
+
portableRuntimePackages,
|
|
848
|
+
);
|
|
849
|
+
const requestArtifact = requestServerModuleArtifact(externalized);
|
|
850
|
+
const renderArtifact = renderServerModuleArtifact(externalized);
|
|
851
|
+
|
|
852
|
+
if (Object.keys(requestArtifact).length > 0 && Object.keys(renderArtifact).length > 0) {
|
|
853
|
+
const requestJson = JSON.stringify(requestArtifact);
|
|
854
|
+
const requestArtifactFile = `server-modules/request/${hashText(`${file}\0request\0${requestJson}`).slice(0, 16)}.json`;
|
|
855
|
+
await writeFile(join(serverDir, requestArtifactFile), requestJson);
|
|
856
|
+
|
|
857
|
+
const renderJson = JSON.stringify(renderArtifact);
|
|
858
|
+
const renderArtifactFile = `server-modules/render/${hashText(`${file}\0render\0${renderJson}`).slice(0, 16)}.json`;
|
|
859
|
+
await writeFile(join(serverDir, renderArtifactFile), renderJson);
|
|
860
|
+
|
|
861
|
+
return { file, renderArtifactFile, requestArtifactFile };
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
const json = JSON.stringify(externalized);
|
|
865
|
+
const artifactFile = `server-modules/${hashText(`${file}\0${json}`).slice(0, 16)}.json`;
|
|
389
866
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
if (
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
await mkdir(join(modulesDir, "request"), { recursive: true });
|
|
401
|
-
await writeFile(join(serverDir, requestArtifactFile), requestJson);
|
|
402
|
-
requestFiles[file] = requestArtifactFile;
|
|
403
|
-
|
|
404
|
-
const renderJson = JSON.stringify(renderArtifact);
|
|
405
|
-
const renderArtifactFile = `server-modules/render/${hashText(`${file}\0render\0${renderJson}`).slice(0, 16)}.json`;
|
|
406
|
-
await mkdir(join(modulesDir, "render"), { recursive: true });
|
|
407
|
-
await writeFile(join(serverDir, renderArtifactFile), renderJson);
|
|
408
|
-
renderFiles[file] = renderArtifactFile;
|
|
867
|
+
await writeFile(join(serverDir, artifactFile), json);
|
|
868
|
+
return { artifactFile, file };
|
|
869
|
+
},
|
|
870
|
+
serverArtifactFilesystemConcurrency,
|
|
871
|
+
);
|
|
872
|
+
|
|
873
|
+
for (const artifact of writtenArtifacts) {
|
|
874
|
+
if ("requestArtifactFile" in artifact) {
|
|
875
|
+
requestFiles[artifact.file] = artifact.requestArtifactFile;
|
|
876
|
+
renderFiles[artifact.file] = artifact.renderArtifactFile;
|
|
409
877
|
continue;
|
|
410
878
|
}
|
|
411
879
|
|
|
412
|
-
|
|
413
|
-
const artifactFile = `server-modules/${hashText(`${file}\0${json}`).slice(0, 16)}.json`;
|
|
414
|
-
|
|
415
|
-
await writeFile(join(serverDir, artifactFile), json);
|
|
416
|
-
files[file] = artifactFile;
|
|
880
|
+
files[artifact.file] = artifact.artifactFile;
|
|
417
881
|
}
|
|
418
882
|
|
|
419
883
|
return { files, renderFiles, requestFiles };
|
|
420
884
|
}
|
|
421
885
|
|
|
886
|
+
export async function __writeServerModuleArtifactFilesForTests(
|
|
887
|
+
serverDir: string,
|
|
888
|
+
serverModules: Record<string, BuiltServerModuleArtifact>,
|
|
889
|
+
): Promise<{
|
|
890
|
+
files: Record<string, string>;
|
|
891
|
+
renderFiles: Record<string, string>;
|
|
892
|
+
requestFiles: Record<string, string>;
|
|
893
|
+
}> {
|
|
894
|
+
return await writeServerModuleArtifactFiles(serverDir, serverModules);
|
|
895
|
+
}
|
|
896
|
+
|
|
422
897
|
async function externalizeServerModuleArtifactCode(
|
|
423
898
|
serverDir: string,
|
|
424
899
|
artifact: BuiltServerModuleArtifact,
|
|
900
|
+
portableRuntimePackages: readonly string[],
|
|
425
901
|
): Promise<BuiltServerModuleArtifact> {
|
|
426
902
|
return {
|
|
427
903
|
...(artifact.analysis === undefined ? {} : { analysis: artifact.analysis }),
|
|
428
904
|
...(artifact.loader === undefined
|
|
429
905
|
? {}
|
|
430
|
-
: {
|
|
906
|
+
: {
|
|
907
|
+
loader: await externalizeServerModuleOutputCode(
|
|
908
|
+
serverDir,
|
|
909
|
+
artifact.loader,
|
|
910
|
+
"code",
|
|
911
|
+
portableRuntimePackages,
|
|
912
|
+
),
|
|
913
|
+
}),
|
|
431
914
|
...(artifact.routeMetadata === undefined
|
|
432
915
|
? {}
|
|
433
916
|
: {
|
|
@@ -435,17 +918,39 @@ async function externalizeServerModuleArtifactCode(
|
|
|
435
918
|
serverDir,
|
|
436
919
|
artifact.routeMetadata,
|
|
437
920
|
"code",
|
|
921
|
+
portableRuntimePackages,
|
|
438
922
|
),
|
|
439
923
|
}),
|
|
440
924
|
...(artifact.request === undefined
|
|
441
925
|
? {}
|
|
442
|
-
: {
|
|
926
|
+
: {
|
|
927
|
+
request: await externalizeServerModuleOutputCode(
|
|
928
|
+
serverDir,
|
|
929
|
+
artifact.request,
|
|
930
|
+
"code",
|
|
931
|
+
portableRuntimePackages,
|
|
932
|
+
),
|
|
933
|
+
}),
|
|
443
934
|
...(artifact.stream === undefined
|
|
444
935
|
? {}
|
|
445
|
-
: {
|
|
936
|
+
: {
|
|
937
|
+
stream: await externalizeServerModuleOutputCode(
|
|
938
|
+
serverDir,
|
|
939
|
+
artifact.stream,
|
|
940
|
+
"bundle",
|
|
941
|
+
portableRuntimePackages,
|
|
942
|
+
),
|
|
943
|
+
}),
|
|
446
944
|
...(artifact.string === undefined
|
|
447
945
|
? {}
|
|
448
|
-
: {
|
|
946
|
+
: {
|
|
947
|
+
string: await externalizeServerModuleOutputCode(
|
|
948
|
+
serverDir,
|
|
949
|
+
artifact.string,
|
|
950
|
+
"bundle",
|
|
951
|
+
portableRuntimePackages,
|
|
952
|
+
),
|
|
953
|
+
}),
|
|
449
954
|
};
|
|
450
955
|
}
|
|
451
956
|
|
|
@@ -453,15 +958,19 @@ async function externalizeServerModuleOutputCode(
|
|
|
453
958
|
serverDir: string,
|
|
454
959
|
output: BuiltServerModuleOutput,
|
|
455
960
|
kind: "bundle" | "code",
|
|
961
|
+
portableRuntimePackages: readonly string[],
|
|
456
962
|
): Promise<BuiltServerModuleOutput> {
|
|
457
|
-
const
|
|
963
|
+
const sourceCode = kind === "bundle" ? output.bundleCode : output.code;
|
|
964
|
+
const moduleCode =
|
|
965
|
+
sourceCode === undefined
|
|
966
|
+
? undefined
|
|
967
|
+
: rewritePortableRuntimePackageImports(sourceCode, portableRuntimePackages);
|
|
458
968
|
|
|
459
969
|
if (moduleCode === undefined || moduleCode.length === 0) {
|
|
460
970
|
return output;
|
|
461
971
|
}
|
|
462
972
|
|
|
463
973
|
const moduleFile = `server-modules/code/${hashText(moduleCode).slice(0, 16)}.mjs`;
|
|
464
|
-
await mkdir(join(serverDir, "server-modules", "code"), { recursive: true });
|
|
465
974
|
await writeFile(join(serverDir, moduleFile), moduleCode);
|
|
466
975
|
|
|
467
976
|
return {
|
|
@@ -472,6 +981,42 @@ async function externalizeServerModuleOutputCode(
|
|
|
472
981
|
};
|
|
473
982
|
}
|
|
474
983
|
|
|
984
|
+
function rewritePortableRuntimePackageImports(
|
|
985
|
+
code: string,
|
|
986
|
+
runtimePackages: readonly string[],
|
|
987
|
+
): string {
|
|
988
|
+
if (runtimePackages.length === 0 || !code.includes("file://")) {
|
|
989
|
+
return code;
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
return code.replace(
|
|
993
|
+
/\b(from\s+|import\s*)["'](?<specifier>file:\/\/[^"']+)["']/gu,
|
|
994
|
+
(match, prefix: string, specifier: string | undefined) => {
|
|
995
|
+
const packageName =
|
|
996
|
+
specifier === undefined ? undefined : runtimePackageForFileUrl(specifier, runtimePackages);
|
|
997
|
+
|
|
998
|
+
return packageName === undefined ? match : `${prefix}"${packageName}"`;
|
|
999
|
+
},
|
|
1000
|
+
);
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
function runtimePackageForFileUrl(
|
|
1004
|
+
specifier: string,
|
|
1005
|
+
runtimePackages: readonly string[],
|
|
1006
|
+
): string | undefined {
|
|
1007
|
+
let filePath: string;
|
|
1008
|
+
|
|
1009
|
+
try {
|
|
1010
|
+
filePath = fileURLToPath(specifier).split(sep).join("/");
|
|
1011
|
+
} catch {
|
|
1012
|
+
return undefined;
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
return [...runtimePackages]
|
|
1016
|
+
.sort((left, right) => right.length - left.length)
|
|
1017
|
+
.find((packageName) => filePath.includes(`/node_modules/${packageName}/`));
|
|
1018
|
+
}
|
|
1019
|
+
|
|
475
1020
|
function requestServerModuleArtifact(
|
|
476
1021
|
artifact: BuiltServerModuleArtifact,
|
|
477
1022
|
): BuiltServerModuleArtifact {
|
|
@@ -982,6 +1527,7 @@ async function prerenderStaticRoutes(options: {
|
|
|
982
1527
|
project: ResolvedAppRouterProject;
|
|
983
1528
|
routes: readonly AppRoute[];
|
|
984
1529
|
serverModules: Record<string, BuiltServerModuleArtifact>;
|
|
1530
|
+
sourceAnalysis: BuildSourceAnalysisScope;
|
|
985
1531
|
vitePlugins?: readonly PluginOption[] | undefined;
|
|
986
1532
|
}): Promise<Record<string, BuiltPrerenderedRoute>> {
|
|
987
1533
|
const clientScripts = new Map(
|
|
@@ -1002,37 +1548,48 @@ async function prerenderStaticRoutes(options: {
|
|
|
1002
1548
|
]),
|
|
1003
1549
|
);
|
|
1004
1550
|
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1551
|
+
const prerenderedEntries = await mapWithBuildConcurrency(
|
|
1552
|
+
options.routes.filter((route): route is AppRoute & { kind: "page" } => route.kind === "page"),
|
|
1553
|
+
async (route) => {
|
|
1554
|
+
const routeFile = relative(options.project.projectRoot, route.file).split(sep).join("/");
|
|
1555
|
+
const analysis = options.sourceAnalysis.byRouteFile.get(routeFile);
|
|
1009
1556
|
|
|
1010
|
-
|
|
1557
|
+
if (analysis === undefined || !analysis.hasPrerender) {
|
|
1558
|
+
return [] as Array<[string, BuiltPrerenderedRoute]>;
|
|
1559
|
+
}
|
|
1011
1560
|
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1561
|
+
const entries: Array<[string, BuiltPrerenderedRoute]> = [];
|
|
1562
|
+
for (const pathname of await prerenderPathsForRoute(route, analysis, options.vitePlugins)) {
|
|
1563
|
+
const response = await renderAppRequest({
|
|
1564
|
+
appDir: options.appDir,
|
|
1565
|
+
assetBaseUrl: options.assetBaseUrl,
|
|
1566
|
+
clientScripts,
|
|
1567
|
+
importPolicy,
|
|
1568
|
+
request: new Request(`http://mreact.local${pathname}`),
|
|
1569
|
+
serverModules: serverModuleMap,
|
|
1570
|
+
vitePlugins: options.vitePlugins,
|
|
1571
|
+
});
|
|
1572
|
+
const headers: Record<string, string> = {};
|
|
1015
1573
|
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1574
|
+
response.headers.forEach((value, key) => {
|
|
1575
|
+
headers[key] = value;
|
|
1576
|
+
});
|
|
1577
|
+
entries.push([
|
|
1578
|
+
pathname,
|
|
1579
|
+
{
|
|
1580
|
+
headers,
|
|
1581
|
+
html: await response.text(),
|
|
1582
|
+
status: response.status,
|
|
1583
|
+
},
|
|
1584
|
+
]);
|
|
1585
|
+
}
|
|
1586
|
+
return entries;
|
|
1587
|
+
},
|
|
1588
|
+
);
|
|
1027
1589
|
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
prerendered[pathname] = {
|
|
1032
|
-
headers,
|
|
1033
|
-
html: await response.text(),
|
|
1034
|
-
status: response.status,
|
|
1035
|
-
};
|
|
1590
|
+
for (const entries of prerenderedEntries) {
|
|
1591
|
+
for (const [pathname, route] of entries) {
|
|
1592
|
+
prerendered[pathname] = route;
|
|
1036
1593
|
}
|
|
1037
1594
|
}
|
|
1038
1595
|
|
|
@@ -1041,21 +1598,21 @@ async function prerenderStaticRoutes(options: {
|
|
|
1041
1598
|
|
|
1042
1599
|
async function prerenderPathsForRoute(
|
|
1043
1600
|
route: AppRoute,
|
|
1044
|
-
|
|
1601
|
+
analysis: BuildRouteSourceAnalysis,
|
|
1045
1602
|
vitePlugins: readonly PluginOption[] | undefined,
|
|
1046
1603
|
): Promise<string[]> {
|
|
1047
1604
|
if (route.segments.every((segment) => segment.kind === "static")) {
|
|
1048
1605
|
return [route.path];
|
|
1049
1606
|
}
|
|
1050
1607
|
|
|
1051
|
-
if (!
|
|
1608
|
+
if (!analysis.hasGenerateStaticParams) {
|
|
1052
1609
|
return [];
|
|
1053
1610
|
}
|
|
1054
1611
|
|
|
1055
1612
|
const module = await importAppRouterSourceModule<{
|
|
1056
1613
|
generateStaticParams?: () => Iterable<StaticParams> | PromiseLike<Iterable<StaticParams>>;
|
|
1057
1614
|
}>({
|
|
1058
|
-
code: source,
|
|
1615
|
+
code: analysis.source,
|
|
1059
1616
|
label: `generate-static-params:${route.file}`,
|
|
1060
1617
|
resolveDir: dirname(route.file),
|
|
1061
1618
|
sourcefile: route.file,
|
|
@@ -1095,12 +1652,16 @@ function routePathFromParams(route: AppRoute, params: StaticParams): string {
|
|
|
1095
1652
|
}
|
|
1096
1653
|
|
|
1097
1654
|
async function buildServerModuleArtifacts(options: {
|
|
1655
|
+
bundleCache: Map<string, Promise<RouterBundleOutput>>;
|
|
1656
|
+
cacheDir?: string | undefined;
|
|
1098
1657
|
clientRouteInferenceCache: ClientRouteInferenceCache;
|
|
1099
1658
|
files: Record<string, string>;
|
|
1100
1659
|
prebundleServerComponents: boolean;
|
|
1101
1660
|
project: ResolvedAppRouterProject;
|
|
1102
1661
|
projectRoot: string;
|
|
1103
1662
|
routes: readonly AppRoute[];
|
|
1663
|
+
sourceAnalysis: BuildSourceAnalysisScope;
|
|
1664
|
+
serverTransformCache: ServerTransformCache;
|
|
1104
1665
|
vitePlugins?: readonly PluginOption[] | undefined;
|
|
1105
1666
|
}): Promise<Record<string, BuiltServerModuleArtifact>> {
|
|
1106
1667
|
const routeByFile = new Map(
|
|
@@ -1116,9 +1677,10 @@ async function buildServerModuleArtifacts(options: {
|
|
|
1116
1677
|
} satisfies AppRouterImportPolicy;
|
|
1117
1678
|
const artifacts: Record<string, BuiltServerModuleArtifact> = {};
|
|
1118
1679
|
|
|
1119
|
-
for (const
|
|
1680
|
+
for (const file of Object.keys(options.files)) {
|
|
1120
1681
|
const absoluteFile = join(options.projectRoot, file);
|
|
1121
1682
|
const route = routeByFile.get(file);
|
|
1683
|
+
const analysis = options.sourceAnalysis.byFile.get(file);
|
|
1122
1684
|
|
|
1123
1685
|
if (isMiddlewareFile(options.project.routesDir, absoluteFile)) {
|
|
1124
1686
|
requestArtifactFiles.add(file);
|
|
@@ -1128,18 +1690,68 @@ async function buildServerModuleArtifacts(options: {
|
|
|
1128
1690
|
requestArtifactFiles.add(file);
|
|
1129
1691
|
}
|
|
1130
1692
|
|
|
1131
|
-
if (route?.kind === "page" &&
|
|
1693
|
+
if (route?.kind === "page" && analysis?.hasLoader === true) {
|
|
1132
1694
|
loaderArtifactFiles.add(file);
|
|
1133
1695
|
}
|
|
1134
1696
|
|
|
1135
|
-
if (
|
|
1697
|
+
if (isServerComponentFile(file) && analysis?.hasMetadata === true) {
|
|
1136
1698
|
metadataArtifactFiles.add(file);
|
|
1137
1699
|
}
|
|
1138
1700
|
}
|
|
1139
1701
|
|
|
1702
|
+
const requestBatchEntries: RouteRequestModuleBatchEntry[] = [];
|
|
1140
1703
|
for (const [file, source] of Object.entries(options.files)) {
|
|
1141
1704
|
const absoluteFile = join(options.projectRoot, file);
|
|
1142
1705
|
const route = routeByFile.get(file);
|
|
1706
|
+
|
|
1707
|
+
if (loaderArtifactFiles.has(file)) {
|
|
1708
|
+
requestBatchEntries.push({
|
|
1709
|
+
code: stripRouteLoaderOnlyExports(source, absoluteFile),
|
|
1710
|
+
filename: absoluteFile,
|
|
1711
|
+
key: routeRequestArtifactBatchKey(file, "loader"),
|
|
1712
|
+
label: "Loader",
|
|
1713
|
+
});
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
if (metadataArtifactFiles.has(file)) {
|
|
1717
|
+
requestBatchEntries.push({
|
|
1718
|
+
code: stripRouteMetadataOnlyExports(source, absoluteFile),
|
|
1719
|
+
filename: absoluteFile,
|
|
1720
|
+
key: routeRequestArtifactBatchKey(file, "metadata"),
|
|
1721
|
+
label: "Metadata",
|
|
1722
|
+
});
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
if (
|
|
1726
|
+
requestArtifactFiles.has(file) &&
|
|
1727
|
+
!isMiddlewareFile(options.project.routesDir, absoluteFile) &&
|
|
1728
|
+
route?.kind !== "server" &&
|
|
1729
|
+
route?.kind !== "metadata"
|
|
1730
|
+
) {
|
|
1731
|
+
requestBatchEntries.push({
|
|
1732
|
+
code: stripRouteRequestOnlyExports(source, absoluteFile),
|
|
1733
|
+
filename: absoluteFile,
|
|
1734
|
+
key: routeRequestArtifactBatchKey(file, "request"),
|
|
1735
|
+
label: "Loader",
|
|
1736
|
+
});
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
const requestBatchOutputs = requestBatchEntries.length >= 3
|
|
1741
|
+
? await bundleRouteRequestModuleBatchCode({
|
|
1742
|
+
appDir: options.project.routesDir,
|
|
1743
|
+
bundleCache: options.bundleCache,
|
|
1744
|
+
cacheDir: options.cacheDir,
|
|
1745
|
+
entries: requestBatchEntries,
|
|
1746
|
+
importPolicy: requestModuleImportPolicy,
|
|
1747
|
+
vitePlugins: options.vitePlugins,
|
|
1748
|
+
})
|
|
1749
|
+
: new Map<string, string>();
|
|
1750
|
+
|
|
1751
|
+
const artifactEntries = await mapWithBuildConcurrency(Object.entries(options.files), async ([file, source]) => {
|
|
1752
|
+
const absoluteFile = join(options.projectRoot, file);
|
|
1753
|
+
const route = routeByFile.get(file);
|
|
1754
|
+
const routeAnalysis = options.sourceAnalysis.byRouteFile.get(file);
|
|
1143
1755
|
const artifact: BuiltServerModuleArtifact = {};
|
|
1144
1756
|
|
|
1145
1757
|
if (
|
|
@@ -1148,13 +1760,16 @@ async function buildServerModuleArtifacts(options: {
|
|
|
1148
1760
|
metadataArtifactFiles.has(file)
|
|
1149
1761
|
) {
|
|
1150
1762
|
if (loaderArtifactFiles.has(file)) {
|
|
1151
|
-
const code =
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1763
|
+
const code = requestBatchOutputs.get(routeRequestArtifactBatchKey(file, "loader")) ??
|
|
1764
|
+
await bundleRouteLoaderModuleCode({
|
|
1765
|
+
appDir: options.project.routesDir,
|
|
1766
|
+
bundleCache: options.bundleCache,
|
|
1767
|
+
cacheDir: options.cacheDir,
|
|
1768
|
+
code: stripRouteLoaderOnlyExports(source, absoluteFile),
|
|
1769
|
+
filename: absoluteFile,
|
|
1770
|
+
importPolicy: requestModuleImportPolicy,
|
|
1771
|
+
vitePlugins: options.vitePlugins,
|
|
1772
|
+
});
|
|
1158
1773
|
artifact.loader = {
|
|
1159
1774
|
code,
|
|
1160
1775
|
sourceHash: hashText(source),
|
|
@@ -1162,13 +1777,17 @@ async function buildServerModuleArtifacts(options: {
|
|
|
1162
1777
|
}
|
|
1163
1778
|
|
|
1164
1779
|
if (metadataArtifactFiles.has(file)) {
|
|
1165
|
-
const code =
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1780
|
+
const code = requestBatchOutputs.get(routeRequestArtifactBatchKey(file, "metadata")) ??
|
|
1781
|
+
await bundleRouteRequestModuleCode({
|
|
1782
|
+
appDir: options.project.routesDir,
|
|
1783
|
+
bundleCache: options.bundleCache,
|
|
1784
|
+
cacheDir: options.cacheDir,
|
|
1785
|
+
code: stripRouteMetadataOnlyExports(source, absoluteFile),
|
|
1786
|
+
filename: absoluteFile,
|
|
1787
|
+
importPolicy: requestModuleImportPolicy,
|
|
1788
|
+
label: "Metadata",
|
|
1789
|
+
vitePlugins: options.vitePlugins,
|
|
1790
|
+
});
|
|
1172
1791
|
artifact.routeMetadata = {
|
|
1173
1792
|
code,
|
|
1174
1793
|
sourceHash: hashText(source),
|
|
@@ -1176,117 +1795,152 @@ async function buildServerModuleArtifacts(options: {
|
|
|
1176
1795
|
}
|
|
1177
1796
|
|
|
1178
1797
|
if (requestArtifactFiles.has(file)) {
|
|
1798
|
+
const batchedRequestCode = requestBatchOutputs.get(
|
|
1799
|
+
routeRequestArtifactBatchKey(file, "request"),
|
|
1800
|
+
);
|
|
1179
1801
|
artifact.request = {
|
|
1180
|
-
code:
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1802
|
+
code: batchedRequestCode ??
|
|
1803
|
+
await buildRequestModuleArtifactCode({
|
|
1804
|
+
appDir: options.project.routesDir,
|
|
1805
|
+
bundleCache: options.bundleCache,
|
|
1806
|
+
cacheDir: options.cacheDir,
|
|
1807
|
+
filename: absoluteFile,
|
|
1808
|
+
importPolicy: requestModuleImportPolicy,
|
|
1809
|
+
routeKind: route?.kind,
|
|
1810
|
+
source,
|
|
1811
|
+
vitePlugins: options.vitePlugins,
|
|
1812
|
+
}),
|
|
1188
1813
|
sourceHash: hashText(source),
|
|
1189
1814
|
};
|
|
1190
1815
|
}
|
|
1191
1816
|
}
|
|
1192
1817
|
|
|
1193
1818
|
if (!isServerComponentFile(file)) {
|
|
1194
|
-
|
|
1195
|
-
artifacts[file] = artifact;
|
|
1196
|
-
}
|
|
1197
|
-
continue;
|
|
1819
|
+
return Object.keys(artifact).length > 0 ? ([file, artifact] as const) : undefined;
|
|
1198
1820
|
}
|
|
1199
1821
|
|
|
1200
|
-
const closureUsesAwait =
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1822
|
+
const closureUsesAwait = routeAnalysis?.streamRoute ??
|
|
1823
|
+
shouldBuildRouteAsStream({
|
|
1824
|
+
filename: file,
|
|
1825
|
+
files: options.files,
|
|
1826
|
+
projectRoot: options.projectRoot,
|
|
1827
|
+
source,
|
|
1828
|
+
});
|
|
1206
1829
|
const streamRoute = route !== undefined && closureUsesAwait;
|
|
1207
1830
|
const serverOutputs =
|
|
1208
1831
|
streamRoute || (route === undefined && closureUsesAwait)
|
|
1209
1832
|
? (["stream", "string"] as const)
|
|
1210
1833
|
: (["string"] as const);
|
|
1211
|
-
const code =
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1834
|
+
const code = routeAnalysis?.routeCode ??
|
|
1835
|
+
(route === undefined ? source : stripRouteBuildExports(source, absoluteFile));
|
|
1836
|
+
const clientInference = routeAnalysis === undefined
|
|
1837
|
+
? await inferClientRouteModule({
|
|
1838
|
+
...(route === undefined ? {} : { appDir: options.project.routesDir }),
|
|
1839
|
+
cache: options.clientRouteInferenceCache,
|
|
1840
|
+
code:
|
|
1841
|
+
route === undefined
|
|
1842
|
+
? stripRouteClientOnlyExports(source, absoluteFile)
|
|
1843
|
+
: stripRouteClientSource({ code: source, filename: route.file }),
|
|
1844
|
+
filename: join(options.projectRoot, file),
|
|
1845
|
+
...(route === undefined ? {} : { routePath: route.path }),
|
|
1846
|
+
vitePlugins: options.vitePlugins,
|
|
1847
|
+
})
|
|
1848
|
+
: undefined;
|
|
1849
|
+
const clientBoundaryImports = routeAnalysis?.clientBoundaryImports ??
|
|
1850
|
+
clientInference?.clientBoundaryImports ??
|
|
1851
|
+
[];
|
|
1224
1852
|
|
|
1225
|
-
for (const diagnostic of clientInference
|
|
1853
|
+
for (const diagnostic of clientInference?.diagnostics ?? []) {
|
|
1226
1854
|
console.warn(formatClientRouteInferenceDiagnostic(diagnostic));
|
|
1227
1855
|
}
|
|
1228
1856
|
|
|
1229
|
-
if (
|
|
1857
|
+
if (routeAnalysis !== undefined) {
|
|
1230
1858
|
artifact.analysis = builtRouteSourceAnalysisSummary({
|
|
1231
|
-
|
|
1232
|
-
clientRoute: clientInference.client,
|
|
1233
|
-
route,
|
|
1234
|
-
routeCode: code,
|
|
1235
|
-
source,
|
|
1236
|
-
streamRoute,
|
|
1859
|
+
analysis: routeAnalysis,
|
|
1237
1860
|
});
|
|
1238
1861
|
}
|
|
1239
1862
|
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1863
|
+
const serverOutputArtifacts = await mapWithBuildConcurrency(
|
|
1864
|
+
serverOutputs,
|
|
1865
|
+
async (serverOutput) => {
|
|
1866
|
+
const output = await transformServerRouteSource({
|
|
1867
|
+
cache: options.serverTransformCache,
|
|
1868
|
+
code,
|
|
1869
|
+
clientBoundaryImports,
|
|
1870
|
+
filename: join(options.projectRoot, file),
|
|
1871
|
+
moduleContextCache: options.clientRouteInferenceCache,
|
|
1872
|
+
serverOutput,
|
|
1873
|
+
});
|
|
1874
|
+
const fatalDiagnostics = output.diagnostics.filter(
|
|
1875
|
+
(diagnostic) => diagnostic.code !== "MR_UNSUPPORTED_SERVER_EVENT_HANDLER",
|
|
1876
|
+
);
|
|
1877
|
+
|
|
1878
|
+
if (fatalDiagnostics.length > 0) {
|
|
1879
|
+
if (
|
|
1880
|
+
serverOutput === "string" &&
|
|
1881
|
+
streamRoute &&
|
|
1882
|
+
route?.kind === "page" &&
|
|
1883
|
+
fatalDiagnostics.every(
|
|
1884
|
+
(diagnostic) => diagnostic.code === "MR_UNSUPPORTED_AWAIT_INNER_COMPONENT",
|
|
1885
|
+
)
|
|
1886
|
+
) {
|
|
1887
|
+
return undefined;
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
throw new Error(
|
|
1891
|
+
fatalDiagnostics.map((diagnostic) => formatDiagnostic(file, diagnostic)).join("\n"),
|
|
1892
|
+
);
|
|
1893
|
+
}
|
|
1253
1894
|
|
|
1254
|
-
if (fatalDiagnostics.length > 0) {
|
|
1255
1895
|
if (
|
|
1256
1896
|
serverOutput === "string" &&
|
|
1257
1897
|
streamRoute &&
|
|
1258
1898
|
route?.kind === "page" &&
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
)
|
|
1899
|
+
source.includes("<Await") &&
|
|
1900
|
+
/\bLink\b/.test(source)
|
|
1262
1901
|
) {
|
|
1263
|
-
|
|
1902
|
+
// Native Link renders pre-rendered children as HTML. Keep direct
|
|
1903
|
+
// Await renderers on the stream artifact so the string artifact
|
|
1904
|
+
// cannot drop deferred Link content before the boundary resolves.
|
|
1905
|
+
return undefined;
|
|
1264
1906
|
}
|
|
1265
1907
|
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1908
|
+
return [
|
|
1909
|
+
serverOutput,
|
|
1910
|
+
{
|
|
1911
|
+
...(options.prebundleServerComponents
|
|
1912
|
+
? {
|
|
1913
|
+
bundleCode: await buildServerComponentBundleArtifactCode({
|
|
1914
|
+
clientRouteInferenceCache: options.clientRouteInferenceCache,
|
|
1915
|
+
code: output.code,
|
|
1916
|
+
filename: absoluteFile,
|
|
1917
|
+
root: options.projectRoot,
|
|
1918
|
+
serverOutput,
|
|
1919
|
+
vitePlugins: options.vitePlugins,
|
|
1920
|
+
}),
|
|
1921
|
+
}
|
|
1922
|
+
: {}),
|
|
1923
|
+
code: output.code,
|
|
1924
|
+
metadata: output.metadata,
|
|
1925
|
+
sourceHash: hashText(code),
|
|
1926
|
+
},
|
|
1927
|
+
] as const;
|
|
1928
|
+
},
|
|
1929
|
+
);
|
|
1270
1930
|
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
clientRouteInferenceCache: options.clientRouteInferenceCache,
|
|
1276
|
-
code: output.code,
|
|
1277
|
-
filename: absoluteFile,
|
|
1278
|
-
serverOutput,
|
|
1279
|
-
vitePlugins: options.vitePlugins,
|
|
1280
|
-
}),
|
|
1281
|
-
}
|
|
1282
|
-
: {}),
|
|
1283
|
-
code: output.code,
|
|
1284
|
-
metadata: output.metadata,
|
|
1285
|
-
sourceHash: hashText(code),
|
|
1286
|
-
};
|
|
1931
|
+
for (const entry of serverOutputArtifacts) {
|
|
1932
|
+
if (entry !== undefined) {
|
|
1933
|
+
artifact[entry[0]] = entry[1];
|
|
1934
|
+
}
|
|
1287
1935
|
}
|
|
1288
1936
|
|
|
1289
|
-
|
|
1937
|
+
return [file, artifact] as const;
|
|
1938
|
+
});
|
|
1939
|
+
|
|
1940
|
+
for (const entry of artifactEntries) {
|
|
1941
|
+
if (entry !== undefined) {
|
|
1942
|
+
artifacts[entry[0]] = entry[1];
|
|
1943
|
+
}
|
|
1290
1944
|
}
|
|
1291
1945
|
|
|
1292
1946
|
return artifacts;
|
|
@@ -1296,6 +1950,7 @@ async function buildServerComponentBundleArtifactCode(options: {
|
|
|
1296
1950
|
clientRouteInferenceCache: ClientRouteInferenceCache;
|
|
1297
1951
|
code: string;
|
|
1298
1952
|
filename: string;
|
|
1953
|
+
root?: string | undefined;
|
|
1299
1954
|
serverOutput: ServerOutputMode;
|
|
1300
1955
|
vitePlugins?: readonly PluginOption[] | undefined;
|
|
1301
1956
|
}): Promise<string> {
|
|
@@ -1303,6 +1958,7 @@ async function buildServerComponentBundleArtifactCode(options: {
|
|
|
1303
1958
|
code: options.code,
|
|
1304
1959
|
label: `server-component:${options.filename}`,
|
|
1305
1960
|
resolveDir: dirname(options.filename),
|
|
1961
|
+
root: options.root,
|
|
1306
1962
|
serverSourceTransform: {
|
|
1307
1963
|
clientRouteInferenceCache: options.clientRouteInferenceCache,
|
|
1308
1964
|
dev: false,
|
|
@@ -1314,27 +1970,63 @@ async function buildServerComponentBundleArtifactCode(options: {
|
|
|
1314
1970
|
});
|
|
1315
1971
|
}
|
|
1316
1972
|
|
|
1317
|
-
function
|
|
1973
|
+
async function transformServerRouteSource(options: {
|
|
1974
|
+
cache: ServerTransformCache;
|
|
1318
1975
|
clientBoundaryImports: readonly string[];
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1976
|
+
code: string;
|
|
1977
|
+
filename: string;
|
|
1978
|
+
moduleContextCache: ClientRouteInferenceCache;
|
|
1979
|
+
serverOutput: ServerOutputMode;
|
|
1980
|
+
}): Promise<ServerTransformOutput> {
|
|
1981
|
+
const cacheKey = stableCacheKey({
|
|
1982
|
+
clientBoundaryImports: options.clientBoundaryImports,
|
|
1983
|
+
codeHash: hashText(options.code),
|
|
1984
|
+
filename: resolve(options.filename),
|
|
1985
|
+
serverOutput: options.serverOutput,
|
|
1986
|
+
target: "server",
|
|
1987
|
+
});
|
|
1988
|
+
const cached = options.cache.get(cacheKey);
|
|
1989
|
+
|
|
1990
|
+
if (cached !== undefined) {
|
|
1991
|
+
return await cached;
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
const transformed = Promise.resolve().then(async () => {
|
|
1995
|
+
const moduleContext = await compilerModuleContextForSource({
|
|
1996
|
+
cache: options.moduleContextCache,
|
|
1997
|
+
code: options.code,
|
|
1998
|
+
filename: options.filename,
|
|
1999
|
+
});
|
|
1326
2000
|
|
|
2001
|
+
return transformCompilerModuleContext({
|
|
2002
|
+
code: options.code,
|
|
2003
|
+
clientBoundaryImports: options.clientBoundaryImports,
|
|
2004
|
+
dev: false,
|
|
2005
|
+
filename: options.filename,
|
|
2006
|
+
moduleContext,
|
|
2007
|
+
serverEscape: nativeEscapeTransform,
|
|
2008
|
+
serverOutput: options.serverOutput,
|
|
2009
|
+
target: "server",
|
|
2010
|
+
});
|
|
2011
|
+
});
|
|
2012
|
+
options.cache.set(cacheKey, transformed);
|
|
2013
|
+
return await transformed;
|
|
2014
|
+
}
|
|
2015
|
+
|
|
2016
|
+
function builtRouteSourceAnalysisSummary(options: {
|
|
2017
|
+
analysis: BuildRouteSourceAnalysis;
|
|
2018
|
+
}): BuiltRouteSourceAnalysisSummary {
|
|
1327
2019
|
return {
|
|
1328
|
-
authIncludesClaims:
|
|
1329
|
-
...(cachePolicy === undefined ? {} : { cachePolicy }),
|
|
1330
|
-
clientBoundaryImports: options.clientBoundaryImports,
|
|
1331
|
-
clientRoute: options.clientRoute,
|
|
1332
|
-
hasLoader:
|
|
1333
|
-
routeCode: options.routeCode,
|
|
1334
|
-
routePath: options.route.path,
|
|
1335
|
-
sourceHash:
|
|
1336
|
-
streamRoute: options.streamRoute,
|
|
1337
|
-
usesRuntimeCacheControl:
|
|
2020
|
+
authIncludesClaims: options.analysis.authIncludesClaims,
|
|
2021
|
+
...(options.analysis.cachePolicy === undefined ? {} : { cachePolicy: options.analysis.cachePolicy }),
|
|
2022
|
+
clientBoundaryImports: options.analysis.clientBoundaryImports,
|
|
2023
|
+
clientRoute: options.analysis.clientRoute,
|
|
2024
|
+
hasLoader: options.analysis.hasLoader,
|
|
2025
|
+
routeCode: options.analysis.routeCode,
|
|
2026
|
+
routePath: options.analysis.route.path,
|
|
2027
|
+
sourceHash: options.analysis.sourceHash,
|
|
2028
|
+
streamRoute: options.analysis.streamRoute,
|
|
2029
|
+
usesRuntimeCacheControl: options.analysis.usesRuntimeCacheControl,
|
|
1338
2030
|
};
|
|
1339
2031
|
}
|
|
1340
2032
|
|
|
@@ -1345,7 +2037,10 @@ function shouldBuildRouteAsStream(options: {
|
|
|
1345
2037
|
source: string;
|
|
1346
2038
|
}): boolean {
|
|
1347
2039
|
return (
|
|
1348
|
-
isStreamRouteSource(
|
|
2040
|
+
isStreamRouteSource(
|
|
2041
|
+
options.source,
|
|
2042
|
+
sourceFilenameForBuildAnalysis(options.projectRoot, options.filename),
|
|
2043
|
+
) ||
|
|
1349
2044
|
routeClosureMayUseAwaitBoundary({
|
|
1350
2045
|
filename: options.filename,
|
|
1351
2046
|
files: options.files,
|
|
@@ -1355,6 +2050,10 @@ function shouldBuildRouteAsStream(options: {
|
|
|
1355
2050
|
);
|
|
1356
2051
|
}
|
|
1357
2052
|
|
|
2053
|
+
function sourceFilenameForBuildAnalysis(projectRoot: string, filename: string): string {
|
|
2054
|
+
return filename.startsWith("/") ? filename : join(projectRoot, filename);
|
|
2055
|
+
}
|
|
2056
|
+
|
|
1358
2057
|
function resolveBuildLocalSourceImport(
|
|
1359
2058
|
files: Record<string, string>,
|
|
1360
2059
|
importer: string,
|
|
@@ -1379,12 +2078,18 @@ function usesRuntimeCacheControl(code: string): boolean {
|
|
|
1379
2078
|
return /\bcacheControl\s*\(/.test(code);
|
|
1380
2079
|
}
|
|
1381
2080
|
|
|
2081
|
+
function routeRequestArtifactBatchKey(file: string, kind: "loader" | "metadata" | "request"): string {
|
|
2082
|
+
return `${kind}:${file}`;
|
|
2083
|
+
}
|
|
2084
|
+
|
|
1382
2085
|
function authIncludesClaims(code: string): boolean {
|
|
1383
2086
|
return /\bexport\s+const\s+auth\s*=\s*["']include-claims["']\s*;?/.test(code);
|
|
1384
2087
|
}
|
|
1385
2088
|
|
|
1386
2089
|
async function buildRequestModuleArtifactCode(options: {
|
|
1387
2090
|
appDir: string;
|
|
2091
|
+
bundleCache: Map<string, Promise<RouterBundleOutput>>;
|
|
2092
|
+
cacheDir?: string | undefined;
|
|
1388
2093
|
filename: string;
|
|
1389
2094
|
importPolicy: AppRouterImportPolicy;
|
|
1390
2095
|
routeKind?: AppRoute["kind"] | undefined;
|
|
@@ -1401,53 +2106,161 @@ async function buildRequestModuleArtifactCode(options: {
|
|
|
1401
2106
|
});
|
|
1402
2107
|
}
|
|
1403
2108
|
|
|
1404
|
-
if (options.routeKind === "server" || options.routeKind === "metadata") {
|
|
1405
|
-
return await bundleAppRouterSourceModule({
|
|
1406
|
-
code: options.source,
|
|
1407
|
-
label: `server-route:${options.filename}`,
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
2109
|
+
if (options.routeKind === "server" || options.routeKind === "metadata") {
|
|
2110
|
+
return await bundleAppRouterSourceModule({
|
|
2111
|
+
code: options.source,
|
|
2112
|
+
label: `server-route:${options.filename}`,
|
|
2113
|
+
plugins: [fileImportMetaUrlPlugin()],
|
|
2114
|
+
resolveDir: dirname(options.filename),
|
|
2115
|
+
root: options.importPolicy.projectRoot,
|
|
2116
|
+
sourcefile: options.filename,
|
|
2117
|
+
vitePlugins: options.vitePlugins,
|
|
2118
|
+
});
|
|
2119
|
+
}
|
|
2120
|
+
|
|
2121
|
+
return await bundleRouteLoaderModuleCode({
|
|
2122
|
+
appDir: options.appDir,
|
|
2123
|
+
bundleCache: options.bundleCache,
|
|
2124
|
+
cacheDir: options.cacheDir,
|
|
2125
|
+
code: stripRouteRequestOnlyExports(options.source, options.filename),
|
|
2126
|
+
filename: options.filename,
|
|
2127
|
+
importPolicy: options.importPolicy,
|
|
2128
|
+
vitePlugins: options.vitePlugins,
|
|
2129
|
+
});
|
|
2130
|
+
}
|
|
2131
|
+
|
|
2132
|
+
async function bundleRouteLoaderModuleCode(options: {
|
|
2133
|
+
appDir: string;
|
|
2134
|
+
bundleCache?: Map<string, Promise<RouterBundleOutput>> | undefined;
|
|
2135
|
+
cacheDir?: string | undefined;
|
|
2136
|
+
code: string;
|
|
2137
|
+
filename: string;
|
|
2138
|
+
importPolicy?: AppRouterImportPolicy | undefined;
|
|
2139
|
+
vitePlugins?: readonly PluginOption[] | undefined;
|
|
2140
|
+
}): Promise<string> {
|
|
2141
|
+
return await bundleRouteRequestModuleCode({
|
|
2142
|
+
...options,
|
|
2143
|
+
label: "Loader",
|
|
2144
|
+
});
|
|
2145
|
+
}
|
|
2146
|
+
|
|
2147
|
+
async function bundleRouteRequestModuleBatchCode(options: {
|
|
2148
|
+
appDir: string;
|
|
2149
|
+
bundleCache?: Map<string, Promise<RouterBundleOutput>> | undefined;
|
|
2150
|
+
cacheDir?: string | undefined;
|
|
2151
|
+
entries: readonly RouteRequestModuleBatchEntry[];
|
|
2152
|
+
importPolicy?: AppRouterImportPolicy | undefined;
|
|
2153
|
+
vitePlugins?: readonly PluginOption[] | undefined;
|
|
2154
|
+
}): Promise<Map<string, string>> {
|
|
2155
|
+
if (options.entries.length === 0) {
|
|
2156
|
+
return new Map();
|
|
2157
|
+
}
|
|
2158
|
+
|
|
2159
|
+
if (options.entries.length === 1) {
|
|
2160
|
+
const entry = options.entries[0];
|
|
2161
|
+
if (entry === undefined) {
|
|
2162
|
+
return new Map();
|
|
2163
|
+
}
|
|
2164
|
+
|
|
2165
|
+
return new Map([
|
|
2166
|
+
[
|
|
2167
|
+
entry.key,
|
|
2168
|
+
await bundleRouteRequestModuleCode({
|
|
2169
|
+
appDir: options.appDir,
|
|
2170
|
+
bundleCache: options.bundleCache,
|
|
2171
|
+
cacheDir: options.cacheDir,
|
|
2172
|
+
code: entry.code,
|
|
2173
|
+
filename: entry.filename,
|
|
2174
|
+
importPolicy: options.importPolicy,
|
|
2175
|
+
label: entry.label,
|
|
2176
|
+
vitePlugins: options.vitePlugins,
|
|
2177
|
+
}),
|
|
2178
|
+
],
|
|
2179
|
+
]);
|
|
2180
|
+
}
|
|
2181
|
+
|
|
2182
|
+
const namesByKey = new Map(
|
|
2183
|
+
options.entries.map((entry, index) => [
|
|
2184
|
+
entry.key,
|
|
2185
|
+
`request-${index}-${hashText(entry.key).slice(0, 8)}`,
|
|
2186
|
+
]),
|
|
2187
|
+
);
|
|
2188
|
+
const output = await bundleRouterModules({
|
|
2189
|
+
cacheDir: options.cacheDir,
|
|
2190
|
+
chunkFileNames: "request/chunks/[name].[hash].js",
|
|
2191
|
+
entries: options.entries.map((entry) => ({
|
|
2192
|
+
code: entry.code,
|
|
2193
|
+
filename: entry.filename,
|
|
2194
|
+
name: namesByKey.get(entry.key) ?? hashText(entry.key).slice(0, 8),
|
|
2195
|
+
})),
|
|
2196
|
+
entryFileNames: "request/[name].js",
|
|
2197
|
+
platform: "node",
|
|
2198
|
+
plugins: [
|
|
2199
|
+
fileImportMetaUrlPlugin(),
|
|
2200
|
+
createAppRouterImportPolicyPlugin({
|
|
2201
|
+
appDir: options.appDir,
|
|
2202
|
+
importPolicy: options.importPolicy,
|
|
2203
|
+
label: "Request artifact",
|
|
2204
|
+
}),
|
|
2205
|
+
],
|
|
2206
|
+
root: options.importPolicy?.projectRoot ?? dirname(options.entries[0]?.filename ?? options.appDir),
|
|
2207
|
+
vitePlugins: options.vitePlugins,
|
|
2208
|
+
});
|
|
2209
|
+
|
|
2210
|
+
if (output.chunks.some((chunk) => !chunk.isEntry)) {
|
|
2211
|
+
const fallbackEntries = await mapWithBuildConcurrency(options.entries, async (entry) => [
|
|
2212
|
+
entry.key,
|
|
2213
|
+
await bundleRouteRequestModuleCode({
|
|
2214
|
+
appDir: options.appDir,
|
|
2215
|
+
bundleCache: options.bundleCache,
|
|
2216
|
+
cacheDir: options.cacheDir,
|
|
2217
|
+
code: entry.code,
|
|
2218
|
+
filename: entry.filename,
|
|
2219
|
+
importPolicy: options.importPolicy,
|
|
2220
|
+
label: entry.label,
|
|
2221
|
+
vitePlugins: options.vitePlugins,
|
|
2222
|
+
}),
|
|
2223
|
+
] as const);
|
|
2224
|
+
return new Map(fallbackEntries);
|
|
2225
|
+
}
|
|
2226
|
+
|
|
2227
|
+
const chunksByName = new Map(output.chunks.map((chunk) => [chunk.name, chunk]));
|
|
2228
|
+
return new Map(
|
|
2229
|
+
options.entries.map((entry) => {
|
|
2230
|
+
const name = namesByKey.get(entry.key);
|
|
2231
|
+
const chunk = name === undefined ? undefined : chunksByName.get(name);
|
|
1413
2232
|
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
filename: options.filename,
|
|
1418
|
-
importPolicy: options.importPolicy,
|
|
1419
|
-
vitePlugins: options.vitePlugins,
|
|
1420
|
-
});
|
|
1421
|
-
}
|
|
2233
|
+
if (chunk === undefined) {
|
|
2234
|
+
throw new Error(`Failed to compile request artifact for ${entry.filename}.`);
|
|
2235
|
+
}
|
|
1422
2236
|
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
filename: string;
|
|
1427
|
-
importPolicy?: AppRouterImportPolicy | undefined;
|
|
1428
|
-
vitePlugins?: readonly PluginOption[] | undefined;
|
|
1429
|
-
}): Promise<string> {
|
|
1430
|
-
return await bundleRouteRequestModuleCode({
|
|
1431
|
-
...options,
|
|
1432
|
-
label: "Loader",
|
|
1433
|
-
});
|
|
2237
|
+
return [entry.key, chunk.code] as const;
|
|
2238
|
+
}),
|
|
2239
|
+
);
|
|
1434
2240
|
}
|
|
1435
2241
|
|
|
1436
|
-
async function
|
|
2242
|
+
export async function __bundleRouteRequestModuleBatchForTests(options: {
|
|
1437
2243
|
appDir: string;
|
|
1438
|
-
|
|
1439
|
-
|
|
2244
|
+
cacheDir?: string | undefined;
|
|
2245
|
+
entries: readonly RouteRequestModuleBatchEntry[];
|
|
1440
2246
|
importPolicy?: AppRouterImportPolicy | undefined;
|
|
1441
2247
|
vitePlugins?: readonly PluginOption[] | undefined;
|
|
1442
|
-
}): Promise<string
|
|
1443
|
-
return
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
2248
|
+
}): Promise<Record<string, string>> {
|
|
2249
|
+
return Object.fromEntries(
|
|
2250
|
+
await bundleRouteRequestModuleBatchCode({
|
|
2251
|
+
appDir: options.appDir,
|
|
2252
|
+
cacheDir: options.cacheDir,
|
|
2253
|
+
entries: options.entries,
|
|
2254
|
+
importPolicy: options.importPolicy,
|
|
2255
|
+
vitePlugins: options.vitePlugins,
|
|
2256
|
+
}),
|
|
2257
|
+
);
|
|
1447
2258
|
}
|
|
1448
2259
|
|
|
1449
2260
|
async function bundleRouteRequestModuleCode(options: {
|
|
1450
2261
|
appDir: string;
|
|
2262
|
+
bundleCache?: Map<string, Promise<RouterBundleOutput>> | undefined;
|
|
2263
|
+
cacheDir?: string | undefined;
|
|
1451
2264
|
code: string;
|
|
1452
2265
|
filename: string;
|
|
1453
2266
|
importPolicy?: AppRouterImportPolicy | undefined;
|
|
@@ -1455,11 +2268,16 @@ async function bundleRouteRequestModuleCode(options: {
|
|
|
1455
2268
|
vitePlugins?: readonly PluginOption[] | undefined;
|
|
1456
2269
|
}): Promise<string> {
|
|
1457
2270
|
const output = await bundleRouterModule({
|
|
2271
|
+
cache: options.bundleCache,
|
|
2272
|
+
cacheDir: options.cacheDir,
|
|
2273
|
+
cacheKey: routeRequestBundleCacheKey(options),
|
|
1458
2274
|
code: options.code,
|
|
1459
2275
|
filename: options.filename,
|
|
1460
2276
|
platform: "node",
|
|
2277
|
+
root: options.importPolicy?.projectRoot,
|
|
1461
2278
|
vitePlugins: options.vitePlugins,
|
|
1462
2279
|
plugins: [
|
|
2280
|
+
fileImportMetaUrlPlugin(),
|
|
1463
2281
|
createAppRouterImportPolicyPlugin({
|
|
1464
2282
|
appDir: options.appDir,
|
|
1465
2283
|
importPolicy: options.importPolicy,
|
|
@@ -1476,6 +2294,42 @@ async function bundleRouteRequestModuleCode(options: {
|
|
|
1476
2294
|
return code;
|
|
1477
2295
|
}
|
|
1478
2296
|
|
|
2297
|
+
function routeRequestBundleCacheKey(options: {
|
|
2298
|
+
appDir: string;
|
|
2299
|
+
code: string;
|
|
2300
|
+
filename: string;
|
|
2301
|
+
importPolicy?: AppRouterImportPolicy | undefined;
|
|
2302
|
+
label: "Loader" | "Metadata";
|
|
2303
|
+
vitePlugins?: readonly PluginOption[] | undefined;
|
|
2304
|
+
}): string {
|
|
2305
|
+
return stableCacheKey({
|
|
2306
|
+
appDir: resolve(options.appDir),
|
|
2307
|
+
codeHash: hashText(options.code),
|
|
2308
|
+
filename: resolve(options.filename),
|
|
2309
|
+
importPolicy:
|
|
2310
|
+
options.importPolicy === undefined
|
|
2311
|
+
? undefined
|
|
2312
|
+
: {
|
|
2313
|
+
allowedPackages: [...(options.importPolicy.allowedPackages ?? [])].sort(),
|
|
2314
|
+
allowedSourceDirs: (options.importPolicy.allowedSourceDirs ?? [])
|
|
2315
|
+
.map((dir) => resolve(dir))
|
|
2316
|
+
.sort(),
|
|
2317
|
+
projectRoot:
|
|
2318
|
+
options.importPolicy.projectRoot === undefined
|
|
2319
|
+
? undefined
|
|
2320
|
+
: resolve(options.importPolicy.projectRoot),
|
|
2321
|
+
},
|
|
2322
|
+
label: options.label,
|
|
2323
|
+
platform: "node",
|
|
2324
|
+
target: "es2022",
|
|
2325
|
+
vitePlugins: vitePluginsCacheKey(options.vitePlugins),
|
|
2326
|
+
});
|
|
2327
|
+
}
|
|
2328
|
+
|
|
2329
|
+
function stableCacheKey(value: unknown): string {
|
|
2330
|
+
return JSON.stringify(value);
|
|
2331
|
+
}
|
|
2332
|
+
|
|
1479
2333
|
function isMiddlewareFile(appDir: string, file: string): boolean {
|
|
1480
2334
|
return file === join(appDir, "middleware.ts") || file === join(appDir, "middleware.mreact.ts");
|
|
1481
2335
|
}
|
|
@@ -1510,7 +2364,31 @@ interface CloudflareRouteModulesOutput {
|
|
|
1510
2364
|
registryFile: string;
|
|
1511
2365
|
}
|
|
1512
2366
|
|
|
2367
|
+
interface CloudflareRequiredRoute {
|
|
2368
|
+
route: AppRoute;
|
|
2369
|
+
routeFile: string;
|
|
2370
|
+
routeId: string;
|
|
2371
|
+
}
|
|
2372
|
+
|
|
2373
|
+
interface CloudflareBatchedRouteModule {
|
|
2374
|
+
code: string;
|
|
2375
|
+
fileName: string;
|
|
2376
|
+
}
|
|
2377
|
+
|
|
2378
|
+
interface CloudflareBatchedRouteModules {
|
|
2379
|
+
chunks: readonly RouterBundleChunkOutput[];
|
|
2380
|
+
entries: ReadonlyMap<string, CloudflareBatchedRouteModule>;
|
|
2381
|
+
}
|
|
2382
|
+
|
|
2383
|
+
interface RouteRequestModuleBatchEntry {
|
|
2384
|
+
code: string;
|
|
2385
|
+
filename: string;
|
|
2386
|
+
key: string;
|
|
2387
|
+
label: "Loader" | "Metadata";
|
|
2388
|
+
}
|
|
2389
|
+
|
|
1513
2390
|
async function writeCloudflareRouteModules(options: {
|
|
2391
|
+
cacheDir?: string | undefined;
|
|
1514
2392
|
cloudflareDir: string;
|
|
1515
2393
|
files: Record<string, string>;
|
|
1516
2394
|
prerenderedRoutes: Record<string, BuiltPrerenderedRoute>;
|
|
@@ -1518,32 +2396,66 @@ async function writeCloudflareRouteModules(options: {
|
|
|
1518
2396
|
routesDir: string;
|
|
1519
2397
|
routes: readonly AppRoute[];
|
|
1520
2398
|
serverModules: Record<string, BuiltServerModuleArtifact>;
|
|
2399
|
+
sourceAnalysis: BuildSourceAnalysisScope;
|
|
1521
2400
|
vitePlugins?: readonly PluginOption[] | undefined;
|
|
1522
2401
|
}): Promise<CloudflareRouteModulesOutput> {
|
|
1523
2402
|
const routesDir = join(options.cloudflareDir, "routes");
|
|
1524
|
-
const requiredRoutes =
|
|
1525
|
-
|
|
2403
|
+
const requiredRoutes = await Promise.all(
|
|
2404
|
+
options.routes
|
|
2405
|
+
.filter((route) => cloudflareRouteRequiresGeneratedModule(route, options.prerenderedRoutes))
|
|
2406
|
+
.flatMap((route): CloudflareRequiredRoute[] => {
|
|
2407
|
+
const routeFile = relative(options.projectRoot, route.file).replaceAll(sep, "/");
|
|
2408
|
+
const analysis = options.sourceAnalysis.byFile.get(routeFile);
|
|
2409
|
+
|
|
2410
|
+
return analysis === undefined
|
|
2411
|
+
? []
|
|
2412
|
+
: [{
|
|
2413
|
+
route,
|
|
2414
|
+
routeFile,
|
|
2415
|
+
routeId: routeIdForPath(route.path),
|
|
2416
|
+
}];
|
|
2417
|
+
}),
|
|
1526
2418
|
);
|
|
1527
|
-
const registryEntries: string[] = [];
|
|
1528
2419
|
|
|
1529
2420
|
await mkdir(routesDir, { recursive: true });
|
|
1530
2421
|
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
2422
|
+
const serverRouteModules = await buildCloudflareServerRouteModuleBatch({
|
|
2423
|
+
cacheDir: options.cacheDir,
|
|
2424
|
+
routes: requiredRoutes
|
|
2425
|
+
.filter(({ route }) => route.kind === "server" || route.kind === "metadata")
|
|
2426
|
+
.map(({ route, routeId }) => ({ filename: route.file, routeId })),
|
|
2427
|
+
root: options.projectRoot,
|
|
2428
|
+
vitePlugins: options.vitePlugins,
|
|
2429
|
+
});
|
|
2430
|
+
const loaderRouteModules = await buildCloudflareRouteLoaderModuleBatch({
|
|
2431
|
+
cacheDir: options.cacheDir,
|
|
2432
|
+
routes: requiredRoutes
|
|
2433
|
+
.filter(({ route, routeFile }) =>
|
|
2434
|
+
route.kind === "page" && options.sourceAnalysis.byRouteFile.get(routeFile)?.hasLoader === true
|
|
2435
|
+
)
|
|
2436
|
+
.map(({ route, routeId }) => ({ filename: route.file, routeId })),
|
|
2437
|
+
root: options.projectRoot,
|
|
2438
|
+
vitePlugins: options.vitePlugins,
|
|
2439
|
+
});
|
|
2440
|
+
|
|
2441
|
+
await Promise.all([
|
|
2442
|
+
writeCloudflareBatchedRouteModuleChunks(options.cloudflareDir, serverRouteModules),
|
|
2443
|
+
writeCloudflareBatchedRouteModuleChunks(options.cloudflareDir, loaderRouteModules),
|
|
2444
|
+
]);
|
|
2445
|
+
|
|
2446
|
+
const registryEntries = await mapWithBuildConcurrency(requiredRoutes, async ({ route, routeFile, routeId }) => {
|
|
1535
2447
|
const routeModuleFile = `routes/${routeId}.mjs`;
|
|
1536
2448
|
let routeModuleExports: string[];
|
|
1537
2449
|
|
|
1538
2450
|
if (route.kind === "server" || route.kind === "metadata") {
|
|
1539
2451
|
try {
|
|
1540
|
-
const
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
2452
|
+
const serverRouteModule = serverRouteModules.entries.get(routeId);
|
|
2453
|
+
|
|
2454
|
+
if (serverRouteModule === undefined) {
|
|
2455
|
+
throw new Error(`Missing bundled Cloudflare ${route.kind} route module.`);
|
|
2456
|
+
}
|
|
1545
2457
|
|
|
1546
|
-
|
|
2458
|
+
const serverRouteFile = serverRouteModule.fileName;
|
|
1547
2459
|
const serverRouteImport = `./${serverRouteFile.split("/").pop() ?? serverRouteFile}`;
|
|
1548
2460
|
routeModuleExports = [
|
|
1549
2461
|
`export * from ${JSON.stringify(serverRouteImport)};`,
|
|
@@ -1558,20 +2470,12 @@ async function writeCloudflareRouteModules(options: {
|
|
|
1558
2470
|
}
|
|
1559
2471
|
|
|
1560
2472
|
await writeFile(join(options.cloudflareDir, routeModuleFile), `${routeModuleExports.join("\n")}\n`);
|
|
1561
|
-
|
|
1562
|
-
`${JSON.stringify(routeFile)}: () => import(${JSON.stringify(`./${routeModuleFile}`)})`,
|
|
1563
|
-
);
|
|
1564
|
-
continue;
|
|
2473
|
+
return `${JSON.stringify(routeFile)}: () => import(${JSON.stringify(`./${routeModuleFile}`)})`;
|
|
1565
2474
|
}
|
|
1566
2475
|
|
|
1567
2476
|
const serverOutput =
|
|
1568
2477
|
options.serverModules[routeFile]?.analysis?.streamRoute === true ||
|
|
1569
|
-
|
|
1570
|
-
filename: routeFile,
|
|
1571
|
-
files: options.files,
|
|
1572
|
-
projectRoot: options.projectRoot,
|
|
1573
|
-
source,
|
|
1574
|
-
})
|
|
2478
|
+
options.sourceAnalysis.byRouteFile.get(routeFile)?.streamRoute === true
|
|
1575
2479
|
? "stream"
|
|
1576
2480
|
: "string";
|
|
1577
2481
|
|
|
@@ -1579,17 +2483,21 @@ async function writeCloudflareRouteModules(options: {
|
|
|
1579
2483
|
const componentOutput =
|
|
1580
2484
|
serverOutput === "stream"
|
|
1581
2485
|
? await buildCloudflareStreamRouteComponentModule({
|
|
2486
|
+
cacheDir: options.cacheDir,
|
|
1582
2487
|
filename: route.file,
|
|
1583
2488
|
projectRoot: options.projectRoot,
|
|
1584
2489
|
routesDir: options.routesDir,
|
|
1585
2490
|
serverModules: options.serverModules,
|
|
2491
|
+
sourceAnalysis: options.sourceAnalysis,
|
|
1586
2492
|
vitePlugins: options.vitePlugins,
|
|
1587
2493
|
})
|
|
1588
2494
|
: await buildCloudflareStringRouteComponentModule({
|
|
2495
|
+
cacheDir: options.cacheDir,
|
|
1589
2496
|
filename: route.file,
|
|
1590
2497
|
projectRoot: options.projectRoot,
|
|
1591
2498
|
routesDir: options.routesDir,
|
|
1592
2499
|
serverModules: options.serverModules,
|
|
2500
|
+
sourceAnalysis: options.sourceAnalysis,
|
|
1593
2501
|
vitePlugins: options.vitePlugins,
|
|
1594
2502
|
});
|
|
1595
2503
|
const componentFile = `routes/${routeId}.${hashText(componentOutput).slice(0, 8)}.component.mjs`;
|
|
@@ -1606,16 +2514,15 @@ async function writeCloudflareRouteModules(options: {
|
|
|
1606
2514
|
);
|
|
1607
2515
|
}
|
|
1608
2516
|
|
|
1609
|
-
if (
|
|
2517
|
+
if (options.sourceAnalysis.byRouteFile.get(routeFile)?.hasLoader === true) {
|
|
1610
2518
|
try {
|
|
1611
|
-
const
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
}
|
|
1616
|
-
const loaderFile = `routes/${routeId}.${hashText(loaderOutput).slice(0, 8)}.loader.mjs`;
|
|
2519
|
+
const loaderModule = loaderRouteModules.entries.get(routeId);
|
|
2520
|
+
|
|
2521
|
+
if (loaderModule === undefined) {
|
|
2522
|
+
throw new Error("Missing bundled Cloudflare loader module.");
|
|
2523
|
+
}
|
|
1617
2524
|
|
|
1618
|
-
|
|
2525
|
+
const loaderFile = loaderModule.fileName;
|
|
1619
2526
|
const loaderImport = `./${loaderFile.split("/").pop() ?? loaderFile}`;
|
|
1620
2527
|
routeModuleExports.push(`export { loader } from ${JSON.stringify(loaderImport)};`);
|
|
1621
2528
|
} catch (error) {
|
|
@@ -1626,10 +2533,8 @@ async function writeCloudflareRouteModules(options: {
|
|
|
1626
2533
|
}
|
|
1627
2534
|
|
|
1628
2535
|
await writeFile(join(options.cloudflareDir, routeModuleFile), `${routeModuleExports.join("\n")}\n`);
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
);
|
|
1632
|
-
}
|
|
2536
|
+
return `${JSON.stringify(routeFile)}: () => import(${JSON.stringify(`./${routeModuleFile}`)})`;
|
|
2537
|
+
});
|
|
1633
2538
|
|
|
1634
2539
|
const registrySource = [
|
|
1635
2540
|
`export const routeModules = {`,
|
|
@@ -1651,14 +2556,19 @@ interface CloudflareShellFile {
|
|
|
1651
2556
|
}
|
|
1652
2557
|
|
|
1653
2558
|
async function buildCloudflareServerComponentModule(options: {
|
|
2559
|
+
cacheDir?: string | undefined;
|
|
1654
2560
|
filename: string;
|
|
1655
2561
|
projectRoot: string;
|
|
1656
2562
|
serverOutput: ServerOutputMode;
|
|
1657
2563
|
serverModules: Record<string, BuiltServerModuleArtifact>;
|
|
2564
|
+
sourceAnalysis: BuildSourceAnalysisScope;
|
|
1658
2565
|
vitePlugins?: readonly PluginOption[] | undefined;
|
|
1659
2566
|
}): Promise<string> {
|
|
1660
2567
|
const metadataModule = await buildCloudflareRouteMetadataExportModule({
|
|
2568
|
+
cacheDir: options.cacheDir,
|
|
1661
2569
|
filename: options.filename,
|
|
2570
|
+
hasMetadata: buildSourceAnalysisForFile(options.sourceAnalysis, options.projectRoot, options.filename)?.hasMetadata,
|
|
2571
|
+
root: options.projectRoot,
|
|
1662
2572
|
vitePlugins: options.vitePlugins,
|
|
1663
2573
|
});
|
|
1664
2574
|
const entry = `import * as routeModule from ${JSON.stringify(options.filename)};
|
|
@@ -1674,6 +2584,7 @@ export const slots = routeModule.slots;`;
|
|
|
1674
2584
|
return bundleCloudflareVirtualModule({
|
|
1675
2585
|
entry,
|
|
1676
2586
|
filename: `${options.filename}.mreact-cloudflare-component.js`,
|
|
2587
|
+
cacheDir: options.cacheDir,
|
|
1677
2588
|
modules: metadataModule === undefined
|
|
1678
2589
|
? new Map()
|
|
1679
2590
|
: new Map([["mreact:metadata", metadataModule]]),
|
|
@@ -1687,43 +2598,52 @@ export const slots = routeModule.slots;`;
|
|
|
1687
2598
|
cloudflareWorkspaceRuntimePlugin(),
|
|
1688
2599
|
],
|
|
1689
2600
|
resolveDir: dirname(options.filename),
|
|
2601
|
+
root: options.projectRoot,
|
|
1690
2602
|
vitePlugins: options.vitePlugins,
|
|
1691
2603
|
});
|
|
1692
2604
|
}
|
|
1693
2605
|
|
|
1694
2606
|
async function buildCloudflareStringRouteComponentModule(options: {
|
|
2607
|
+
cacheDir?: string | undefined;
|
|
1695
2608
|
filename: string;
|
|
1696
2609
|
projectRoot: string;
|
|
1697
2610
|
routesDir: string;
|
|
1698
2611
|
serverModules: Record<string, BuiltServerModuleArtifact>;
|
|
2612
|
+
sourceAnalysis: BuildSourceAnalysisScope;
|
|
1699
2613
|
vitePlugins?: readonly PluginOption[] | undefined;
|
|
1700
2614
|
}): Promise<string> {
|
|
1701
2615
|
const shellFiles = await cloudflareShellFilesForPage(options.routesDir, options.filename);
|
|
1702
2616
|
|
|
1703
2617
|
if (shellFiles.length === 0) {
|
|
1704
2618
|
return buildCloudflareServerComponentModule({
|
|
2619
|
+
cacheDir: options.cacheDir,
|
|
1705
2620
|
filename: options.filename,
|
|
1706
2621
|
projectRoot: options.projectRoot,
|
|
1707
2622
|
serverModules: options.serverModules,
|
|
1708
2623
|
serverOutput: "string",
|
|
2624
|
+
sourceAnalysis: options.sourceAnalysis,
|
|
1709
2625
|
vitePlugins: options.vitePlugins,
|
|
1710
2626
|
});
|
|
1711
2627
|
}
|
|
1712
2628
|
|
|
1713
2629
|
const pageModule = await buildCloudflareComponentExportModule({
|
|
2630
|
+
cacheDir: options.cacheDir,
|
|
1714
2631
|
filename: options.filename,
|
|
1715
2632
|
projectRoot: options.projectRoot,
|
|
1716
2633
|
serverModules: options.serverModules,
|
|
1717
2634
|
serverOutput: "string",
|
|
2635
|
+
sourceAnalysis: options.sourceAnalysis,
|
|
1718
2636
|
vitePlugins: options.vitePlugins,
|
|
1719
2637
|
});
|
|
1720
2638
|
const shellModules = await Promise.all(
|
|
1721
2639
|
shellFiles.map((shell) =>
|
|
1722
2640
|
buildCloudflareComponentExportModule({
|
|
2641
|
+
cacheDir: options.cacheDir,
|
|
1723
2642
|
filename: shell.file,
|
|
1724
2643
|
projectRoot: options.projectRoot,
|
|
1725
2644
|
serverModules: options.serverModules,
|
|
1726
2645
|
serverOutput: "string",
|
|
2646
|
+
sourceAnalysis: options.sourceAnalysis,
|
|
1727
2647
|
vitePlugins: options.vitePlugins,
|
|
1728
2648
|
}),
|
|
1729
2649
|
),
|
|
@@ -1785,6 +2705,7 @@ ${cloudflareShellRuntimeSource()}`;
|
|
|
1785
2705
|
|
|
1786
2706
|
return bundleCloudflareVirtualModule({
|
|
1787
2707
|
entry,
|
|
2708
|
+
cacheDir: options.cacheDir,
|
|
1788
2709
|
filename: `${options.filename}.mreact-cloudflare-string-route.js`,
|
|
1789
2710
|
modules: new Map([
|
|
1790
2711
|
["mreact:page", pageModule],
|
|
@@ -1792,32 +2713,39 @@ ${cloudflareShellRuntimeSource()}`;
|
|
|
1792
2713
|
]),
|
|
1793
2714
|
plugins: [cloudflareWorkspaceRuntimePlugin()],
|
|
1794
2715
|
resolveDir: dirname(options.filename),
|
|
2716
|
+
root: options.projectRoot,
|
|
1795
2717
|
vitePlugins: options.vitePlugins,
|
|
1796
2718
|
});
|
|
1797
2719
|
}
|
|
1798
2720
|
|
|
1799
2721
|
async function buildCloudflareStreamRouteComponentModule(options: {
|
|
2722
|
+
cacheDir?: string | undefined;
|
|
1800
2723
|
filename: string;
|
|
1801
2724
|
projectRoot: string;
|
|
1802
2725
|
routesDir: string;
|
|
1803
2726
|
serverModules: Record<string, BuiltServerModuleArtifact>;
|
|
2727
|
+
sourceAnalysis: BuildSourceAnalysisScope;
|
|
1804
2728
|
vitePlugins?: readonly PluginOption[] | undefined;
|
|
1805
2729
|
}): Promise<string> {
|
|
1806
2730
|
const pageModule = await buildCloudflareComponentExportModule({
|
|
2731
|
+
cacheDir: options.cacheDir,
|
|
1807
2732
|
filename: options.filename,
|
|
1808
2733
|
projectRoot: options.projectRoot,
|
|
1809
2734
|
serverModules: options.serverModules,
|
|
1810
2735
|
serverOutput: "stream",
|
|
2736
|
+
sourceAnalysis: options.sourceAnalysis,
|
|
1811
2737
|
vitePlugins: options.vitePlugins,
|
|
1812
2738
|
});
|
|
1813
2739
|
const shellFiles = await cloudflareShellFilesForPage(options.routesDir, options.filename);
|
|
1814
2740
|
const shellModules = await Promise.all(
|
|
1815
2741
|
shellFiles.map((shell) =>
|
|
1816
2742
|
buildCloudflareComponentExportModule({
|
|
2743
|
+
cacheDir: options.cacheDir,
|
|
1817
2744
|
filename: shell.file,
|
|
1818
2745
|
projectRoot: options.projectRoot,
|
|
1819
2746
|
serverModules: options.serverModules,
|
|
1820
2747
|
serverOutput: "string",
|
|
2748
|
+
sourceAnalysis: options.sourceAnalysis,
|
|
1821
2749
|
vitePlugins: options.vitePlugins,
|
|
1822
2750
|
}),
|
|
1823
2751
|
),
|
|
@@ -1895,6 +2823,7 @@ ${cloudflareShellRuntimeSource()}`;
|
|
|
1895
2823
|
|
|
1896
2824
|
return bundleCloudflareVirtualModule({
|
|
1897
2825
|
entry,
|
|
2826
|
+
cacheDir: options.cacheDir,
|
|
1898
2827
|
filename: `${options.filename}.mreact-cloudflare-stream-route.js`,
|
|
1899
2828
|
modules: new Map([
|
|
1900
2829
|
["mreact:page", pageModule],
|
|
@@ -1902,6 +2831,7 @@ ${cloudflareShellRuntimeSource()}`;
|
|
|
1902
2831
|
]),
|
|
1903
2832
|
plugins: [cloudflareWorkspaceRuntimePlugin()],
|
|
1904
2833
|
resolveDir: dirname(options.filename),
|
|
2834
|
+
root: options.projectRoot,
|
|
1905
2835
|
vitePlugins: options.vitePlugins,
|
|
1906
2836
|
});
|
|
1907
2837
|
}
|
|
@@ -2155,14 +3085,19 @@ function escapeHtml(value) {
|
|
|
2155
3085
|
}
|
|
2156
3086
|
|
|
2157
3087
|
async function buildCloudflareComponentExportModule(options: {
|
|
3088
|
+
cacheDir?: string | undefined;
|
|
2158
3089
|
filename: string;
|
|
2159
3090
|
projectRoot: string;
|
|
2160
3091
|
serverModules: Record<string, BuiltServerModuleArtifact>;
|
|
2161
3092
|
serverOutput: ServerOutputMode;
|
|
3093
|
+
sourceAnalysis: BuildSourceAnalysisScope;
|
|
2162
3094
|
vitePlugins?: readonly PluginOption[] | undefined;
|
|
2163
3095
|
}): Promise<string> {
|
|
2164
3096
|
const metadataModule = await buildCloudflareRouteMetadataExportModule({
|
|
3097
|
+
cacheDir: options.cacheDir,
|
|
2165
3098
|
filename: options.filename,
|
|
3099
|
+
hasMetadata: buildSourceAnalysisForFile(options.sourceAnalysis, options.projectRoot, options.filename)?.hasMetadata,
|
|
3100
|
+
root: options.projectRoot,
|
|
2166
3101
|
vitePlugins: options.vitePlugins,
|
|
2167
3102
|
});
|
|
2168
3103
|
const entry = `import * as routeModule from ${JSON.stringify(options.filename)};
|
|
@@ -2177,6 +3112,7 @@ export const slots = routeModule.slots;`;
|
|
|
2177
3112
|
|
|
2178
3113
|
return bundleCloudflareVirtualModule({
|
|
2179
3114
|
entry,
|
|
3115
|
+
cacheDir: options.cacheDir,
|
|
2180
3116
|
filename: `${options.filename}.mreact-cloudflare-${options.serverOutput}-component.js`,
|
|
2181
3117
|
modules: metadataModule === undefined
|
|
2182
3118
|
? new Map()
|
|
@@ -2191,33 +3127,142 @@ export const slots = routeModule.slots;`;
|
|
|
2191
3127
|
cloudflareWorkspaceRuntimePlugin(),
|
|
2192
3128
|
],
|
|
2193
3129
|
resolveDir: dirname(options.filename),
|
|
3130
|
+
root: options.projectRoot,
|
|
2194
3131
|
vitePlugins: options.vitePlugins,
|
|
2195
3132
|
});
|
|
2196
3133
|
}
|
|
2197
3134
|
|
|
2198
|
-
async function
|
|
2199
|
-
|
|
3135
|
+
async function writeCloudflareBatchedRouteModuleChunks(
|
|
3136
|
+
cloudflareDir: string,
|
|
3137
|
+
modules: CloudflareBatchedRouteModules,
|
|
3138
|
+
): Promise<void> {
|
|
3139
|
+
if (modules.chunks.some((chunk) => chunk.fileName.startsWith("routes/chunks/"))) {
|
|
3140
|
+
await mkdir(join(cloudflareDir, "routes", "chunks"), { recursive: true });
|
|
3141
|
+
}
|
|
3142
|
+
|
|
3143
|
+
await Promise.all(
|
|
3144
|
+
modules.chunks.map((chunk) => writeFile(join(cloudflareDir, chunk.fileName), chunk.code)),
|
|
3145
|
+
);
|
|
3146
|
+
}
|
|
3147
|
+
|
|
3148
|
+
async function buildCloudflareRouteLoaderModuleBatch(options: {
|
|
3149
|
+
cacheDir?: string | undefined;
|
|
3150
|
+
routes: readonly { filename: string; routeId: string }[];
|
|
3151
|
+
root: string;
|
|
3152
|
+
vitePlugins?: readonly PluginOption[] | undefined;
|
|
3153
|
+
}): Promise<CloudflareBatchedRouteModules> {
|
|
3154
|
+
return await bundleCloudflareModuleBatch({
|
|
3155
|
+
cacheDir: options.cacheDir,
|
|
3156
|
+
entries: options.routes.map((route) => ({
|
|
3157
|
+
code: cloudflareRouteLoaderModuleEntry(route.filename),
|
|
3158
|
+
filename: `${route.filename}.mreact-cloudflare-loader.js`,
|
|
3159
|
+
name: `${route.routeId}.loader`,
|
|
3160
|
+
routeId: route.routeId,
|
|
3161
|
+
})),
|
|
3162
|
+
root: options.root,
|
|
3163
|
+
vitePlugins: options.vitePlugins,
|
|
3164
|
+
});
|
|
3165
|
+
}
|
|
3166
|
+
|
|
3167
|
+
export async function __buildCloudflareRouteLoaderModuleBatchForTests(options: {
|
|
3168
|
+
cacheDir?: string | undefined;
|
|
2200
3169
|
projectRoot: string;
|
|
3170
|
+
routes: readonly { filename: string; routeId: string }[];
|
|
2201
3171
|
vitePlugins?: readonly PluginOption[] | undefined;
|
|
2202
|
-
}): Promise<string
|
|
2203
|
-
const
|
|
3172
|
+
}): Promise<Record<string, string>> {
|
|
3173
|
+
const output = await buildCloudflareRouteLoaderModuleBatch({
|
|
3174
|
+
cacheDir: options.cacheDir,
|
|
3175
|
+
root: options.projectRoot,
|
|
3176
|
+
routes: options.routes,
|
|
3177
|
+
vitePlugins: options.vitePlugins,
|
|
3178
|
+
});
|
|
2204
3179
|
|
|
2205
|
-
return
|
|
2206
|
-
|
|
2207
|
-
|
|
3180
|
+
return Object.fromEntries(
|
|
3181
|
+
options.routes.map((route) => [route.routeId, output.entries.get(route.routeId)?.code ?? ""]),
|
|
3182
|
+
);
|
|
3183
|
+
}
|
|
3184
|
+
|
|
3185
|
+
async function buildCloudflareServerRouteModuleBatch(options: {
|
|
3186
|
+
cacheDir?: string | undefined;
|
|
3187
|
+
routes: readonly { filename: string; routeId: string }[];
|
|
3188
|
+
root: string;
|
|
3189
|
+
vitePlugins?: readonly PluginOption[] | undefined;
|
|
3190
|
+
}): Promise<CloudflareBatchedRouteModules> {
|
|
3191
|
+
return await bundleCloudflareModuleBatch({
|
|
3192
|
+
cacheDir: options.cacheDir,
|
|
3193
|
+
entries: options.routes.map((route) => ({
|
|
3194
|
+
code: cloudflareServerRouteModuleEntry(route.filename),
|
|
3195
|
+
filename: `${route.filename}.mreact-cloudflare-server-route.js`,
|
|
3196
|
+
name: `${route.routeId}.server`,
|
|
3197
|
+
routeId: route.routeId,
|
|
3198
|
+
})),
|
|
3199
|
+
root: options.root,
|
|
3200
|
+
vitePlugins: options.vitePlugins,
|
|
3201
|
+
});
|
|
3202
|
+
}
|
|
3203
|
+
|
|
3204
|
+
async function bundleCloudflareModuleBatch(options: {
|
|
3205
|
+
cacheDir?: string | undefined;
|
|
3206
|
+
entries: readonly {
|
|
3207
|
+
code: string;
|
|
3208
|
+
filename: string;
|
|
3209
|
+
name: string;
|
|
3210
|
+
routeId: string;
|
|
3211
|
+
}[];
|
|
3212
|
+
root: string;
|
|
3213
|
+
vitePlugins?: readonly PluginOption[] | undefined;
|
|
3214
|
+
}): Promise<CloudflareBatchedRouteModules> {
|
|
3215
|
+
if (options.entries.length === 0) {
|
|
3216
|
+
return { chunks: [], entries: new Map() };
|
|
3217
|
+
}
|
|
3218
|
+
|
|
3219
|
+
const output = await bundleRouterModules({
|
|
3220
|
+
cacheDir: options.cacheDir,
|
|
3221
|
+
chunkFileNames: "routes/chunks/[name].[hash].mjs",
|
|
3222
|
+
entries: options.entries,
|
|
3223
|
+
entryFileNames: "routes/[name].[hash].mjs",
|
|
3224
|
+
minify: true,
|
|
3225
|
+
platform: "node",
|
|
2208
3226
|
plugins: [cloudflareWorkspaceRuntimePlugin()],
|
|
2209
|
-
|
|
3227
|
+
root: options.root,
|
|
3228
|
+
target: "es2022",
|
|
2210
3229
|
vitePlugins: options.vitePlugins,
|
|
2211
3230
|
});
|
|
3231
|
+
const entriesByName = new Map(options.entries.map((entry) => [entry.name, entry]));
|
|
3232
|
+
const entries = new Map<string, CloudflareBatchedRouteModule>();
|
|
3233
|
+
|
|
3234
|
+
for (const chunk of output.chunks) {
|
|
3235
|
+
if (!chunk.isEntry) {
|
|
3236
|
+
continue;
|
|
3237
|
+
}
|
|
3238
|
+
|
|
3239
|
+
const entry = entriesByName.get(chunk.name);
|
|
3240
|
+
|
|
3241
|
+
if (entry === undefined) {
|
|
3242
|
+
continue;
|
|
3243
|
+
}
|
|
3244
|
+
|
|
3245
|
+
entries.set(entry.routeId, {
|
|
3246
|
+
code: chunk.code,
|
|
3247
|
+
fileName: chunk.fileName,
|
|
3248
|
+
});
|
|
3249
|
+
}
|
|
3250
|
+
|
|
3251
|
+
return { chunks: output.chunks, entries };
|
|
3252
|
+
}
|
|
3253
|
+
|
|
3254
|
+
function cloudflareRouteLoaderModuleEntry(filename: string): string {
|
|
3255
|
+
return `export { loader } from ${JSON.stringify(filename)};`;
|
|
2212
3256
|
}
|
|
2213
3257
|
|
|
2214
3258
|
async function buildCloudflareRouteMetadataExportModule(options: {
|
|
3259
|
+
cacheDir?: string | undefined;
|
|
2215
3260
|
filename: string;
|
|
3261
|
+
hasMetadata?: boolean | undefined;
|
|
3262
|
+
root?: string | undefined;
|
|
2216
3263
|
vitePlugins?: readonly PluginOption[] | undefined;
|
|
2217
3264
|
}): Promise<string | undefined> {
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
if (!hasMetadataExport(source)) {
|
|
3265
|
+
if (options.hasMetadata !== true) {
|
|
2221
3266
|
return undefined;
|
|
2222
3267
|
}
|
|
2223
3268
|
|
|
@@ -2227,18 +3272,17 @@ export const metadata = routeMetadataModule.metadata;`;
|
|
|
2227
3272
|
|
|
2228
3273
|
return bundleCloudflareModule({
|
|
2229
3274
|
entry,
|
|
3275
|
+
cacheDir: options.cacheDir,
|
|
2230
3276
|
filename: `${options.filename}.mreact-cloudflare-metadata.js`,
|
|
2231
3277
|
plugins: [cloudflareWorkspaceRuntimePlugin()],
|
|
2232
3278
|
resolveDir: dirname(options.filename),
|
|
3279
|
+
root: options.root,
|
|
2233
3280
|
vitePlugins: options.vitePlugins,
|
|
2234
3281
|
});
|
|
2235
3282
|
}
|
|
2236
3283
|
|
|
2237
|
-
|
|
2238
|
-
filename
|
|
2239
|
-
vitePlugins?: readonly PluginOption[] | undefined;
|
|
2240
|
-
}): Promise<string> {
|
|
2241
|
-
const entry = `import * as routeModule from ${JSON.stringify(options.filename)};
|
|
3284
|
+
function cloudflareServerRouteModuleEntry(filename: string): string {
|
|
3285
|
+
return `import * as routeModule from ${JSON.stringify(filename)};
|
|
2242
3286
|
|
|
2243
3287
|
export const GET = routeModule.GET;
|
|
2244
3288
|
export const HEAD = routeModule.HEAD;
|
|
@@ -2250,30 +3294,26 @@ export const OPTIONS = routeModule.OPTIONS;
|
|
|
2250
3294
|
export const ALL = routeModule.ALL;
|
|
2251
3295
|
const defaultHandler = routeModule.default;
|
|
2252
3296
|
export default defaultHandler;`;
|
|
2253
|
-
|
|
2254
|
-
return bundleCloudflareModule({
|
|
2255
|
-
entry,
|
|
2256
|
-
filename: `${options.filename}.mreact-cloudflare-server-route.js`,
|
|
2257
|
-
plugins: [cloudflareWorkspaceRuntimePlugin()],
|
|
2258
|
-
resolveDir: dirname(options.filename),
|
|
2259
|
-
vitePlugins: options.vitePlugins,
|
|
2260
|
-
});
|
|
2261
3297
|
}
|
|
2262
3298
|
|
|
2263
3299
|
async function bundleCloudflareModule(options: {
|
|
3300
|
+
cacheDir?: string | undefined;
|
|
2264
3301
|
entry: string;
|
|
2265
3302
|
filename: string;
|
|
2266
3303
|
plugins: RouterCompatPlugin[];
|
|
2267
3304
|
resolveDir: string;
|
|
3305
|
+
root?: string | undefined;
|
|
2268
3306
|
vitePlugins?: readonly PluginOption[] | undefined;
|
|
2269
3307
|
}): Promise<string> {
|
|
2270
3308
|
const output = await bundleRouterModule({
|
|
3309
|
+
cacheDir: options.cacheDir,
|
|
2271
3310
|
code: options.entry,
|
|
2272
3311
|
filename: options.filename,
|
|
2273
3312
|
minify: true,
|
|
2274
|
-
platform: "
|
|
3313
|
+
platform: "node",
|
|
2275
3314
|
preserveExports: true,
|
|
2276
3315
|
plugins: options.plugins,
|
|
3316
|
+
root: options.root,
|
|
2277
3317
|
target: "es2022",
|
|
2278
3318
|
vitePlugins: options.vitePlugins,
|
|
2279
3319
|
});
|
|
@@ -2287,14 +3327,17 @@ async function bundleCloudflareModule(options: {
|
|
|
2287
3327
|
}
|
|
2288
3328
|
|
|
2289
3329
|
async function bundleCloudflareVirtualModule(options: {
|
|
3330
|
+
cacheDir?: string | undefined;
|
|
2290
3331
|
entry: string;
|
|
2291
3332
|
filename: string;
|
|
2292
3333
|
modules: ReadonlyMap<string, string>;
|
|
2293
3334
|
plugins: RouterCompatPlugin[];
|
|
2294
3335
|
resolveDir: string;
|
|
3336
|
+
root?: string | undefined;
|
|
2295
3337
|
vitePlugins?: readonly PluginOption[] | undefined;
|
|
2296
3338
|
}): Promise<string> {
|
|
2297
3339
|
return bundleCloudflareModule({
|
|
3340
|
+
cacheDir: options.cacheDir,
|
|
2298
3341
|
entry: options.entry,
|
|
2299
3342
|
filename: options.filename,
|
|
2300
3343
|
plugins: [
|
|
@@ -2323,6 +3366,7 @@ async function bundleCloudflareVirtualModule(options: {
|
|
|
2323
3366
|
...options.plugins,
|
|
2324
3367
|
],
|
|
2325
3368
|
resolveDir: options.resolveDir,
|
|
3369
|
+
root: options.root,
|
|
2326
3370
|
vitePlugins: options.vitePlugins,
|
|
2327
3371
|
});
|
|
2328
3372
|
}
|
|
@@ -2371,7 +3415,9 @@ function cloudflareServerSourceTransformPlugin(options: {
|
|
|
2371
3415
|
}
|
|
2372
3416
|
|
|
2373
3417
|
const source = await readFile(args.path, "utf8");
|
|
2374
|
-
const serverSource = isServerComponentFile(args.path)
|
|
3418
|
+
const serverSource = isServerComponentFile(args.path)
|
|
3419
|
+
? stripRouteBuildExports(source, args.path)
|
|
3420
|
+
: source;
|
|
2375
3421
|
const sourceHash = hashText(serverSource);
|
|
2376
3422
|
const routeFile = relative(options.projectRoot, args.path).replaceAll(sep, "/");
|
|
2377
3423
|
const artifact = options.serverModules[routeFile]?.[options.serverOutput];
|
|
@@ -2616,11 +3662,13 @@ function viteManifestFromClientRoutes(routes: ClientRouteManifestEntry[]): Recor
|
|
|
2616
3662
|
async function writeClientRouteBundles(options: {
|
|
2617
3663
|
appDir: string;
|
|
2618
3664
|
assetBaseUrl?: string | undefined;
|
|
3665
|
+
cacheDir?: string | undefined;
|
|
2619
3666
|
clientDir: string;
|
|
2620
3667
|
clientConsolePureFunctions?: readonly string[] | undefined;
|
|
2621
3668
|
clientRouteInferenceCache: ClientRouteInferenceCache;
|
|
2622
3669
|
projectRoot: string;
|
|
2623
3670
|
routes: readonly AppRoute[];
|
|
3671
|
+
sourceAnalysis: BuildSourceAnalysisScope;
|
|
2624
3672
|
sourceMapDir: string;
|
|
2625
3673
|
sourceMaps: AppRouterClientSourceMapMode;
|
|
2626
3674
|
vitePlugins?: readonly PluginOption[] | undefined;
|
|
@@ -2633,6 +3681,18 @@ async function writeClientRouteBundles(options: {
|
|
|
2633
3681
|
};
|
|
2634
3682
|
type PreparedClientManifestEntry = { manifest: ClientRouteManifestEntry };
|
|
2635
3683
|
type PreparedRouteEntry = PreparedClientRouteEntry | PreparedClientManifestEntry;
|
|
3684
|
+
const pageRoutes = options.routes.filter(
|
|
3685
|
+
(route): route is AppRoute & { kind: "page" } => route.kind === "page",
|
|
3686
|
+
);
|
|
3687
|
+
const routeCssAssets = await writeRouteCssAssetBatches({
|
|
3688
|
+
appDir: options.appDir,
|
|
3689
|
+
cacheDir: options.cacheDir,
|
|
3690
|
+
clientDir: options.clientDir,
|
|
3691
|
+
pageRoutes,
|
|
3692
|
+
projectRoot: options.projectRoot,
|
|
3693
|
+
sourceAnalysis: options.sourceAnalysis,
|
|
3694
|
+
vitePlugins: options.vitePlugins,
|
|
3695
|
+
});
|
|
2636
3696
|
const entries: PreparedRouteEntry[] = await Promise.all(
|
|
2637
3697
|
options.routes.map(async (route) => {
|
|
2638
3698
|
if (route.kind !== "page") {
|
|
@@ -2641,21 +3701,26 @@ async function writeClientRouteBundles(options: {
|
|
|
2641
3701
|
};
|
|
2642
3702
|
}
|
|
2643
3703
|
|
|
2644
|
-
const css =
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
3704
|
+
const css = routeCssAssets.get(route.file) ?? [];
|
|
3705
|
+
const source = buildSourceAnalysisForFile(
|
|
3706
|
+
options.sourceAnalysis,
|
|
3707
|
+
options.projectRoot,
|
|
3708
|
+
route.file,
|
|
3709
|
+
)?.source;
|
|
3710
|
+
|
|
3711
|
+
if (source === undefined) {
|
|
3712
|
+
return {
|
|
3713
|
+
manifest: { path: route.path, kind: route.kind, client: false },
|
|
3714
|
+
};
|
|
3715
|
+
}
|
|
3716
|
+
|
|
2653
3717
|
const clientSource = stripRouteClientSource({ code: source, filename: route.file });
|
|
2654
3718
|
const references = await collectClientRouteReferences({
|
|
2655
3719
|
appDir: options.appDir,
|
|
2656
3720
|
cache: options.clientRouteInferenceCache,
|
|
2657
3721
|
code: clientSource,
|
|
2658
3722
|
filename: route.file,
|
|
3723
|
+
routePath: route.path,
|
|
2659
3724
|
vitePlugins: options.vitePlugins,
|
|
2660
3725
|
});
|
|
2661
3726
|
const navigation = await resolveNavigationRuntime({
|
|
@@ -2663,6 +3728,7 @@ async function writeClientRouteBundles(options: {
|
|
|
2663
3728
|
code: source,
|
|
2664
3729
|
filename: route.file,
|
|
2665
3730
|
references,
|
|
3731
|
+
vitePlugins: options.vitePlugins,
|
|
2666
3732
|
});
|
|
2667
3733
|
|
|
2668
3734
|
for (const diagnostic of references.diagnostics) {
|
|
@@ -2691,6 +3757,7 @@ async function writeClientRouteBundles(options: {
|
|
|
2691
3757
|
clientReferenceImports: references.clientReferenceImports,
|
|
2692
3758
|
clientReferenceManifest: references.clientReferenceManifest,
|
|
2693
3759
|
clientNavigation: detectClientNavigationHint(source),
|
|
3760
|
+
cacheDir: options.cacheDir,
|
|
2694
3761
|
dropConsoleFunctions: options.clientConsolePureFunctions,
|
|
2695
3762
|
filename: route.file,
|
|
2696
3763
|
minify: true,
|
|
@@ -2714,6 +3781,7 @@ async function writeClientRouteBundles(options: {
|
|
|
2714
3781
|
try {
|
|
2715
3782
|
output = await buildClientRouteBatchOutput({
|
|
2716
3783
|
assetBaseUrl: options.assetBaseUrl,
|
|
3784
|
+
cacheDir: options.cacheDir,
|
|
2717
3785
|
dropConsoleFunctions: options.clientConsolePureFunctions,
|
|
2718
3786
|
minify: true,
|
|
2719
3787
|
projectRoot: options.projectRoot,
|
|
@@ -2802,20 +3870,75 @@ async function writeClientRouteBundles(options: {
|
|
|
2802
3870
|
});
|
|
2803
3871
|
}
|
|
2804
3872
|
|
|
2805
|
-
async function
|
|
3873
|
+
async function writeRouteCssAssetBatches(options: {
|
|
2806
3874
|
appDir: string;
|
|
3875
|
+
cacheDir?: string | undefined;
|
|
2807
3876
|
clientDir: string;
|
|
2808
|
-
|
|
3877
|
+
pageRoutes: readonly (AppRoute & { kind: "page" })[];
|
|
2809
3878
|
projectRoot: string;
|
|
2810
|
-
|
|
3879
|
+
sourceAnalysis: BuildSourceAnalysisScope;
|
|
2811
3880
|
vitePlugins?: readonly PluginOption[] | undefined;
|
|
2812
|
-
}): Promise<string[]
|
|
2813
|
-
const
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
3881
|
+
}): Promise<Map<string, string[]>> {
|
|
3882
|
+
const cssInputs = await mapWithBuildConcurrency(options.pageRoutes, async (route) => {
|
|
3883
|
+
const cssFiles = await collectRouteCssFilesFromSources({
|
|
3884
|
+
appDir: options.appDir,
|
|
3885
|
+
pageFile: route.file,
|
|
3886
|
+
projectRoot: options.projectRoot,
|
|
3887
|
+
readSource: (file) => buildSourceAnalysisForFile(options.sourceAnalysis, options.projectRoot, file)?.source,
|
|
3888
|
+
});
|
|
3889
|
+
|
|
3890
|
+
return { cssFiles, route };
|
|
2817
3891
|
});
|
|
3892
|
+
const groups = new Map<string, { cssFiles: string[]; routeIds: string[]; routeFiles: string[] }>();
|
|
3893
|
+
|
|
3894
|
+
for (const { cssFiles, route } of cssInputs) {
|
|
3895
|
+
if (cssFiles.length === 0) {
|
|
3896
|
+
continue;
|
|
3897
|
+
}
|
|
3898
|
+
|
|
3899
|
+
const key = cssFiles.join("\0");
|
|
3900
|
+
const group = groups.get(key) ?? { cssFiles, routeFiles: [], routeIds: [] };
|
|
3901
|
+
group.routeFiles.push(route.file);
|
|
3902
|
+
group.routeIds.push(routeIdForPath(route.path));
|
|
3903
|
+
groups.set(key, group);
|
|
3904
|
+
}
|
|
2818
3905
|
|
|
3906
|
+
const writtenGroups = await mapWithBuildConcurrency([...groups.entries()], async ([key, group]) => [
|
|
3907
|
+
key,
|
|
3908
|
+
await writeRouteCssAssetsForFiles({
|
|
3909
|
+
cacheDir: options.cacheDir,
|
|
3910
|
+
clientDir: options.clientDir,
|
|
3911
|
+
cssFiles: group.cssFiles,
|
|
3912
|
+
pageFile: group.routeFiles[0] ?? options.appDir,
|
|
3913
|
+
projectRoot: options.projectRoot,
|
|
3914
|
+
routeIds: group.routeIds,
|
|
3915
|
+
vitePlugins: options.vitePlugins,
|
|
3916
|
+
}),
|
|
3917
|
+
] as const);
|
|
3918
|
+
const cssByGroup = new Map(writtenGroups);
|
|
3919
|
+
const cssByRoute = new Map<string, string[]>();
|
|
3920
|
+
|
|
3921
|
+
for (const [key, group] of groups) {
|
|
3922
|
+
const css = cssByGroup.get(key) ?? [];
|
|
3923
|
+
|
|
3924
|
+
for (const routeFile of group.routeFiles) {
|
|
3925
|
+
cssByRoute.set(routeFile, css);
|
|
3926
|
+
}
|
|
3927
|
+
}
|
|
3928
|
+
|
|
3929
|
+
return cssByRoute;
|
|
3930
|
+
}
|
|
3931
|
+
|
|
3932
|
+
async function writeRouteCssAssetsForFiles(options: {
|
|
3933
|
+
cacheDir?: string | undefined;
|
|
3934
|
+
clientDir: string;
|
|
3935
|
+
cssFiles: readonly string[];
|
|
3936
|
+
pageFile: string;
|
|
3937
|
+
projectRoot: string;
|
|
3938
|
+
routeIds: readonly string[];
|
|
3939
|
+
vitePlugins?: readonly PluginOption[] | undefined;
|
|
3940
|
+
}): Promise<string[]> {
|
|
3941
|
+
const cssFiles = [...options.cssFiles];
|
|
2819
3942
|
if (cssFiles.length === 0) {
|
|
2820
3943
|
return [];
|
|
2821
3944
|
}
|
|
@@ -2825,22 +3948,27 @@ async function writeRouteCssAssets(options: {
|
|
|
2825
3948
|
"export default undefined;",
|
|
2826
3949
|
].join("\n");
|
|
2827
3950
|
const output = await bundleRouterModule({
|
|
3951
|
+
cacheDir: options.cacheDir,
|
|
2828
3952
|
code,
|
|
2829
3953
|
filename: options.pageFile,
|
|
2830
3954
|
minify: true,
|
|
2831
3955
|
platform: "browser",
|
|
3956
|
+
root: options.projectRoot,
|
|
2832
3957
|
vitePlugins: options.vitePlugins,
|
|
2833
3958
|
});
|
|
2834
3959
|
const cssAssets = (output.assets ?? []).filter((asset) => asset.fileName.endsWith(".css"));
|
|
2835
3960
|
const written: string[] = [];
|
|
3961
|
+
const routeStem = options.routeIds.length === 1
|
|
3962
|
+
? (options.routeIds[0] ?? "index")
|
|
3963
|
+
: `shared.${hashText(cssFiles.join("\0")).slice(0, 8)}`;
|
|
2836
3964
|
|
|
2837
|
-
for (const asset of cssAssets) {
|
|
3965
|
+
for (const [index, asset] of cssAssets.entries()) {
|
|
2838
3966
|
const source =
|
|
2839
3967
|
typeof asset.source === "string"
|
|
2840
3968
|
? asset.source
|
|
2841
3969
|
: Buffer.from(asset.source).toString("utf8");
|
|
2842
3970
|
const hash = createHash("sha256").update(source).digest("hex").slice(0, 8);
|
|
2843
|
-
const cssFile = `assets/routes/${
|
|
3971
|
+
const cssFile = `assets/routes/${routeStem}${cssAssets.length === 1 ? "" : `.${index}`}.${hash}.css`;
|
|
2844
3972
|
|
|
2845
3973
|
await mkdir(dirname(join(options.clientDir, cssFile)), { recursive: true });
|
|
2846
3974
|
await writeFile(join(options.clientDir, cssFile), source);
|
|
@@ -2945,7 +4073,15 @@ export async function packageAwsLambdaArtifact(
|
|
|
2945
4073
|
fromDir: dirname(options.fromDir),
|
|
2946
4074
|
outDir: options.outDir,
|
|
2947
4075
|
});
|
|
2948
|
-
|
|
4076
|
+
if (options.handlerEntry === undefined) {
|
|
4077
|
+
await writeFile(join(options.outDir, "mreact-handler.mjs"), awsLambdaHandlerSource(".mreact"));
|
|
4078
|
+
} else {
|
|
4079
|
+
await writeAwsLambdaCustomHandlerArtifact({
|
|
4080
|
+
entry: options.handlerEntry,
|
|
4081
|
+
projectRoot: dirname(options.fromDir),
|
|
4082
|
+
outDir: options.outDir,
|
|
4083
|
+
});
|
|
4084
|
+
}
|
|
2949
4085
|
|
|
2950
4086
|
if (options.skipRuntimeDependencyCheck !== true) {
|
|
2951
4087
|
await assertAwsLambdaRuntimeDependencies(options.outDir);
|
|
@@ -2968,6 +4104,44 @@ export async function packageAwsLambdaArtifact(
|
|
|
2968
4104
|
return manifest;
|
|
2969
4105
|
}
|
|
2970
4106
|
|
|
4107
|
+
async function writeAwsLambdaCustomHandlerArtifact(options: {
|
|
4108
|
+
entry: string;
|
|
4109
|
+
outDir: string;
|
|
4110
|
+
projectRoot: string;
|
|
4111
|
+
}): Promise<void> {
|
|
4112
|
+
const entry = resolve(options.entry);
|
|
4113
|
+
const output = await bundleRouterModule({
|
|
4114
|
+
code: await readFile(entry, "utf8"),
|
|
4115
|
+
filename: entry,
|
|
4116
|
+
outfile: "mreact-handler.mjs",
|
|
4117
|
+
platform: "node",
|
|
4118
|
+
plugins: [externalizePackageImportsPlugin()],
|
|
4119
|
+
preserveExports: true,
|
|
4120
|
+
root: options.projectRoot,
|
|
4121
|
+
target: "node24",
|
|
4122
|
+
});
|
|
4123
|
+
|
|
4124
|
+
await writeFile(join(options.outDir, "mreact-handler.mjs"), output.code);
|
|
4125
|
+
}
|
|
4126
|
+
|
|
4127
|
+
function externalizePackageImportsPlugin(): RouterCompatPlugin {
|
|
4128
|
+
return {
|
|
4129
|
+
name: "mreact-router-externalize-package-imports",
|
|
4130
|
+
setup(buildApi) {
|
|
4131
|
+
buildApi.onResolve({ filter: /^(?:node:|[A-Za-z@])/ }, (args) =>
|
|
4132
|
+
isBarePackageImport(args.path) ? { external: true, path: args.path } : undefined,
|
|
4133
|
+
);
|
|
4134
|
+
},
|
|
4135
|
+
};
|
|
4136
|
+
}
|
|
4137
|
+
|
|
4138
|
+
function isBarePackageImport(specifier: string): boolean {
|
|
4139
|
+
return (
|
|
4140
|
+
specifier.startsWith("node:") ||
|
|
4141
|
+
(!specifier.startsWith(".") && !specifier.startsWith("/") && !specifier.includes("\\"))
|
|
4142
|
+
);
|
|
4143
|
+
}
|
|
4144
|
+
|
|
2971
4145
|
async function assertAwsLambdaRuntimeDependencies(outDir: string): Promise<void> {
|
|
2972
4146
|
try {
|
|
2973
4147
|
const info = await stat(join(outDir, "node_modules", "@reckona", "mreact-router", "package.json"));
|
|
@@ -3146,31 +4320,34 @@ export const handler = await createPreloadedAwsLambdaRequestHandler({
|
|
|
3146
4320
|
}
|
|
3147
4321
|
|
|
3148
4322
|
async function validateProductionRoutes(options: {
|
|
4323
|
+
clientRouteInferenceCache: ClientRouteInferenceCache;
|
|
3149
4324
|
files: Record<string, string>;
|
|
4325
|
+
project: ResolvedAppRouterProject;
|
|
3150
4326
|
projectRoot: string;
|
|
3151
4327
|
routes: readonly AppRoute[];
|
|
4328
|
+
sourceAnalysis: BuildSourceAnalysisScope;
|
|
4329
|
+
serverTransformCache: ServerTransformCache;
|
|
4330
|
+
vitePlugins?: readonly PluginOption[] | undefined;
|
|
3152
4331
|
}): Promise<void> {
|
|
3153
4332
|
for (const route of options.routes) {
|
|
3154
4333
|
if (route.kind !== "page") {
|
|
3155
4334
|
continue;
|
|
3156
4335
|
}
|
|
3157
4336
|
|
|
3158
|
-
const source = await readFile(route.file, "utf8");
|
|
3159
4337
|
const filename = relative(options.projectRoot, route.file);
|
|
3160
|
-
const
|
|
3161
|
-
|
|
3162
|
-
|
|
4338
|
+
const analysis = options.sourceAnalysis.byRouteFile.get(filename.split(sep).join("/"));
|
|
4339
|
+
|
|
4340
|
+
if (analysis === undefined) {
|
|
4341
|
+
continue;
|
|
4342
|
+
}
|
|
4343
|
+
|
|
4344
|
+
const output = await transformServerRouteSource({
|
|
4345
|
+
cache: options.serverTransformCache,
|
|
4346
|
+
code: analysis.routeCode,
|
|
4347
|
+
clientBoundaryImports: analysis.clientBoundaryImports,
|
|
3163
4348
|
filename: route.file,
|
|
3164
|
-
|
|
3165
|
-
serverOutput:
|
|
3166
|
-
filename,
|
|
3167
|
-
files: options.files,
|
|
3168
|
-
projectRoot: options.projectRoot,
|
|
3169
|
-
source,
|
|
3170
|
-
})
|
|
3171
|
-
? "stream"
|
|
3172
|
-
: "string",
|
|
3173
|
-
target: "server",
|
|
4349
|
+
moduleContextCache: options.clientRouteInferenceCache,
|
|
4350
|
+
serverOutput: analysis.streamRoute ? "stream" : "string",
|
|
3174
4351
|
});
|
|
3175
4352
|
const fatalDiagnostics = output.diagnostics.filter(
|
|
3176
4353
|
(diagnostic) => diagnostic.code !== "MR_UNSUPPORTED_SERVER_EVENT_HANDLER",
|
|
@@ -3192,18 +4369,26 @@ async function collectBuildFiles(
|
|
|
3192
4369
|
const files: Record<string, string> = {};
|
|
3193
4370
|
|
|
3194
4371
|
for (const directory of allowedSourceDirs) {
|
|
3195
|
-
|
|
4372
|
+
const sourceFiles = (await collectFiles(directory)).flatMap((file) => {
|
|
3196
4373
|
if (isAppFileConventionAsset(file, appDir)) {
|
|
3197
|
-
|
|
4374
|
+
return [];
|
|
3198
4375
|
}
|
|
3199
4376
|
|
|
3200
4377
|
const relativeFile = relative(projectRoot, file);
|
|
3201
4378
|
|
|
3202
4379
|
if (relativeFile === "" || relativeFile.startsWith("..") || relativeFile.startsWith(sep)) {
|
|
3203
|
-
|
|
4380
|
+
return [];
|
|
3204
4381
|
}
|
|
3205
4382
|
|
|
3206
|
-
|
|
4383
|
+
return [{ file, relativeFile }];
|
|
4384
|
+
});
|
|
4385
|
+
const fileContents = await mapWithBuildConcurrency(sourceFiles, async ({ file, relativeFile }) => [
|
|
4386
|
+
relativeFile,
|
|
4387
|
+
await readFile(file, "utf8"),
|
|
4388
|
+
] as const);
|
|
4389
|
+
|
|
4390
|
+
for (const [relativeFile, source] of fileContents) {
|
|
4391
|
+
files[relativeFile] = source;
|
|
3207
4392
|
}
|
|
3208
4393
|
}
|
|
3209
4394
|
|
|
@@ -3221,22 +4406,21 @@ function isAppFileConventionAsset(file: string, appDir: string): boolean {
|
|
|
3221
4406
|
|
|
3222
4407
|
async function collectFiles(directory: string): Promise<string[]> {
|
|
3223
4408
|
const entries = await readdir(directory, { withFileTypes: true });
|
|
3224
|
-
const
|
|
3225
|
-
|
|
3226
|
-
for (const entry of entries) {
|
|
4409
|
+
const nestedFiles = await mapWithBuildConcurrency(entries, async (entry) => {
|
|
3227
4410
|
const path = join(directory, entry.name);
|
|
3228
4411
|
|
|
3229
4412
|
if (entry.isDirectory()) {
|
|
3230
|
-
|
|
3231
|
-
continue;
|
|
4413
|
+
return await collectFiles(path);
|
|
3232
4414
|
}
|
|
3233
4415
|
|
|
3234
4416
|
if (entry.isFile()) {
|
|
3235
|
-
|
|
4417
|
+
return [path];
|
|
3236
4418
|
}
|
|
3237
|
-
}
|
|
3238
4419
|
|
|
3239
|
-
|
|
4420
|
+
return [];
|
|
4421
|
+
});
|
|
4422
|
+
|
|
4423
|
+
return nestedFiles.flat();
|
|
3240
4424
|
}
|
|
3241
4425
|
|
|
3242
4426
|
function hashText(text: string): string {
|