@reckona/mreact-router 0.0.208 → 0.0.210

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +2 -2
  2. package/dist/adapters/cloudflare.d.ts +3 -3
  3. package/dist/adapters/cloudflare.d.ts.map +1 -1
  4. package/dist/adapters/cloudflare.js +11 -4
  5. package/dist/adapters/cloudflare.js.map +1 -1
  6. package/dist/boundaries.d.ts +37 -1
  7. package/dist/boundaries.d.ts.map +1 -1
  8. package/dist/boundaries.js +125 -4
  9. package/dist/boundaries.js.map +1 -1
  10. package/dist/build.d.ts +6 -0
  11. package/dist/build.d.ts.map +1 -1
  12. package/dist/build.js +265 -24
  13. package/dist/build.js.map +1 -1
  14. package/dist/bundle-pipeline.d.ts +1 -0
  15. package/dist/bundle-pipeline.d.ts.map +1 -1
  16. package/dist/bundle-pipeline.js +4 -1
  17. package/dist/bundle-pipeline.js.map +1 -1
  18. package/dist/client.d.ts +1 -0
  19. package/dist/client.d.ts.map +1 -1
  20. package/dist/client.js +15 -11
  21. package/dist/client.js.map +1 -1
  22. package/dist/config.d.ts +7 -0
  23. package/dist/config.d.ts.map +1 -1
  24. package/dist/config.js +17 -0
  25. package/dist/config.js.map +1 -1
  26. package/dist/index.d.ts +5 -5
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +1 -1
  29. package/dist/index.js.map +1 -1
  30. package/dist/reactive-devtools-stub.d.ts +3 -0
  31. package/dist/reactive-devtools-stub.d.ts.map +1 -0
  32. package/dist/reactive-devtools-stub.js +10 -0
  33. package/dist/reactive-devtools-stub.js.map +1 -0
  34. package/dist/render.d.ts.map +1 -1
  35. package/dist/render.js +23 -23
  36. package/dist/render.js.map +1 -1
  37. package/dist/request-header-tracking.d.ts +19 -0
  38. package/dist/request-header-tracking.d.ts.map +1 -1
  39. package/dist/request-header-tracking.js +48 -0
  40. package/dist/request-header-tracking.js.map +1 -1
  41. package/dist/tailwind-source.d.ts.map +1 -1
  42. package/dist/tailwind-source.js +147 -1
  43. package/dist/tailwind-source.js.map +1 -1
  44. package/dist/types.d.ts +14 -2
  45. package/dist/types.d.ts.map +1 -1
  46. package/dist/types.js.map +1 -1
  47. package/dist/vite.d.ts +1 -0
  48. package/dist/vite.d.ts.map +1 -1
  49. package/dist/vite.js +2 -7
  50. package/dist/vite.js.map +1 -1
  51. package/package.json +11 -11
  52. package/src/adapters/cloudflare.ts +14 -5
  53. package/src/boundaries.ts +224 -4
  54. package/src/build.ts +379 -24
  55. package/src/bundle-pipeline.ts +9 -4
  56. package/src/client.ts +19 -12
  57. package/src/config.ts +32 -0
  58. package/src/index.ts +10 -0
  59. package/src/reactive-devtools-stub.ts +9 -0
  60. package/src/render.ts +30 -16
  61. package/src/request-header-tracking.ts +61 -0
  62. package/src/tailwind-source.ts +164 -1
  63. package/src/types.ts +15 -2
  64. package/src/vite.ts +3 -7
package/src/build.ts CHANGED
@@ -16,6 +16,7 @@ import { builtinModules } from "node:module";
16
16
  import { availableParallelism } from "node:os";
17
17
  import { dirname, join, relative, resolve, sep } from "node:path";
18
18
  import { fileURLToPath } from "node:url";
19
+ import { gzipSync } from "node:zlib";
19
20
  import type { DehydrateOptions } from "@reckona/mreact-query";
20
21
  import {
21
22
  collectStaticImportReferences,
@@ -41,7 +42,11 @@ import {
41
42
  type ClientRouteInferenceDiagnostic,
42
43
  type ClientRouteInferenceCache,
43
44
  } from "./client-route-inference.js";
44
- import { createBoundaryReport, type BoundaryReport } from "./boundaries.js";
45
+ import {
46
+ createBoundaryReport,
47
+ validateBoundaryExecutionContracts,
48
+ type BoundaryReport,
49
+ } from "./boundaries.js";
45
50
  import {
46
51
  buildClientRouteBatchOutput,
47
52
  buildNavigationRuntimeBundle,
@@ -140,12 +145,19 @@ export type AwsLambdaGeneratedHandlerPreloadMode =
140
145
  | "middleware"
141
146
  | "none";
142
147
 
148
+ /** Supplies observed dynamic imports when boundary navigation costs are measured. */
149
+ export interface BoundaryCostOptions {
150
+ /** Maps a route path to dynamic import entry files fetched during navigation. */
151
+ fetchedDynamicImports?: Readonly<Record<string, readonly string[]>> | undefined;
152
+ }
153
+
143
154
  /**
144
155
  * Configures an app-router production build and its deployment targets.
145
156
  */
146
157
  export interface BuildAppOptions extends AppRouterProjectOptions {
147
158
  awsLambdaPreload?: AwsLambdaGeneratedHandlerPreloadMode | undefined;
148
159
  awsLambdaPreloadRoutes?: readonly string[] | undefined;
160
+ boundaryCost?: BoundaryCostOptions | undefined;
149
161
  /** Receives the full route and component boundary report after source analysis. */
150
162
  onBoundaryReport?: ((report: BoundaryReport) => void) | undefined;
151
163
  onBuildProgress?: ((event: BuildAppProgressEvent) => void) | undefined;
@@ -263,9 +275,15 @@ interface IncrementalBuildServerManifestOutputs {
263
275
 
264
276
  interface IncrementalBuildClientManifestOutputs {
265
277
  assets?: readonly string[];
278
+ chunks?: readonly {
279
+ dynamicImports?: readonly string[];
280
+ file: string;
281
+ imports?: readonly string[];
282
+ }[];
266
283
  publicAssets?: readonly string[];
267
284
  routes: readonly {
268
285
  css?: readonly string[];
286
+ dynamicImports?: readonly string[];
269
287
  imports?: readonly string[];
270
288
  navigationScript?: string | undefined;
271
289
  script?: string | undefined;
@@ -522,7 +540,7 @@ async function buildAppWithResolvedProject(
522
540
  vitePlugins,
523
541
  }),
524
542
  );
525
- options.onBoundaryReport?.(sourceAnalysis.boundaryReport);
543
+ validateBoundaryExecutionContracts(sourceAnalysis.boundaryReport, project.executionContracts);
526
544
 
527
545
  if (shouldTrackBuildPhases === false) {
528
546
  await validateProductionRoutes({
@@ -558,6 +576,19 @@ async function buildAppWithResolvedProject(
558
576
  outDir: options.outDir,
559
577
  }))
560
578
  ) {
579
+ if (options.onBoundaryReport !== undefined) {
580
+ const clientManifest = await readJsonBuildOutput<ClientArtifactManifest>(
581
+ join(options.outDir, "client", "manifest.json"),
582
+ );
583
+ options.onBoundaryReport(
584
+ await boundaryReportWithArtifactCosts({
585
+ boundaryCost: options.boundaryCost,
586
+ clientDir,
587
+ manifest: clientManifest,
588
+ report: sourceAnalysis.boundaryReport,
589
+ }),
590
+ );
591
+ }
561
592
  return { routes };
562
593
  }
563
594
 
@@ -852,6 +883,7 @@ async function buildAppWithResolvedProject(
852
883
  } satisfies BuiltServerManifest;
853
884
  const clientManifest = {
854
885
  ...(clientManifestAssets.length === 0 ? {} : { assets: clientManifestAssets }),
886
+ ...(clientBundle.chunks.length === 0 ? {} : { chunks: clientBundle.chunks }),
855
887
  ...(publicAssets.length === 0 ? {} : { publicAssets }),
856
888
  routes: clientManifestRoutes,
857
889
  ...(clientBundle.styles.length === 0 ? {} : { styles: clientBundle.styles }),
@@ -933,9 +965,296 @@ async function buildAppWithResolvedProject(
933
965
  await writeIncrementalBuildCacheManifest(options.outDir, incrementalBuildFingerprint);
934
966
  }
935
967
 
968
+ if (options.onBoundaryReport !== undefined) {
969
+ options.onBoundaryReport(
970
+ await boundaryReportWithArtifactCosts({
971
+ boundaryCost: options.boundaryCost,
972
+ clientDir,
973
+ manifest: clientManifest,
974
+ report: sourceAnalysis.boundaryReport,
975
+ }),
976
+ );
977
+ }
978
+
936
979
  return { routes };
937
980
  }
938
981
 
982
+ async function boundaryReportWithArtifactCosts(options: {
983
+ boundaryCost: BoundaryCostOptions | undefined;
984
+ clientDir: string;
985
+ manifest: ClientArtifactManifest | undefined;
986
+ report: BoundaryReport;
987
+ }): Promise<BoundaryReport> {
988
+ if (options.manifest === undefined) {
989
+ return {
990
+ ...options.report,
991
+ routes: options.report.routes.map((route) => ({
992
+ ...route,
993
+ cost: {
994
+ reason: "Production client manifest is unavailable.",
995
+ status: "unavailable",
996
+ },
997
+ })),
998
+ };
999
+ }
1000
+
1001
+ const manifest = options.manifest;
1002
+ const chunks = new Map((manifest.chunks ?? []).map((chunk) => [chunk.file, chunk]));
1003
+ const routeClosures = options.report.routes.map((route) => {
1004
+ const manifestRoute = manifest.routes.find((candidate) => candidate.path === route.path);
1005
+
1006
+ if (manifestRoute === undefined) {
1007
+ return { initial: undefined, navigation: undefined, route };
1008
+ }
1009
+
1010
+ const initial = clientArtifactStaticClosure(manifestRoute, chunks);
1011
+ const navigation = new Set(initial);
1012
+ addFetchedDynamicImportClosures({
1013
+ chunks,
1014
+ fetchedDynamicImports: options.boundaryCost?.fetchedDynamicImports?.[route.path] ?? [],
1015
+ navigation,
1016
+ routeDynamicImports: manifestRoute.dynamicImports ?? [],
1017
+ });
1018
+
1019
+ return { initial, navigation, route };
1020
+ });
1021
+ const availableClosures = routeClosures.flatMap((entry) =>
1022
+ entry.initial === undefined ? [] : [entry.initial],
1023
+ );
1024
+ const sharedPaths = intersectionOfPathSets(availableClosures);
1025
+ const allPaths = new Set<string>();
1026
+ for (const entry of routeClosures) {
1027
+ for (const path of entry.navigation ?? []) {
1028
+ allPaths.add(path);
1029
+ }
1030
+ }
1031
+ const measurements = await measureClientArtifactPaths(options.clientDir, allPaths);
1032
+
1033
+ return {
1034
+ ...options.report,
1035
+ routes: routeClosures.map((entry) => {
1036
+ if (entry.initial === undefined || entry.navigation === undefined) {
1037
+ return {
1038
+ ...entry.route,
1039
+ cost: {
1040
+ reason: "No client route artifact was generated for this route.",
1041
+ status: "unavailable",
1042
+ },
1043
+ };
1044
+ }
1045
+
1046
+ const routePaths = new Set(entry.navigation);
1047
+ const unavailable = [...routePaths]
1048
+ .filter((path) => measurements.get(path)?.available !== true)
1049
+ .sort();
1050
+ if (unavailable.length > 0) {
1051
+ return {
1052
+ ...entry.route,
1053
+ cost: {
1054
+ reason: `Missing client artifacts: ${unavailable.join(", ")}.`,
1055
+ status: "unavailable",
1056
+ },
1057
+ };
1058
+ }
1059
+
1060
+ const initial = byteCostForPaths(entry.initial, measurements);
1061
+ const navigation = byteCostForPaths(
1062
+ new Set([...entry.navigation].filter((path) => !sharedPaths.has(path))),
1063
+ measurements,
1064
+ );
1065
+ const shared = byteCostForPaths(sharedPaths, measurements);
1066
+
1067
+ return {
1068
+ ...entry.route,
1069
+ cost: {
1070
+ baseline: {
1071
+ initialGzipDeltaBytes: initial.gzipEstimateBytes - shared.gzipEstimateBytes,
1072
+ navigationGzipDeltaBytes: navigation.gzipEstimateBytes,
1073
+ },
1074
+ initial,
1075
+ navigation,
1076
+ status: "available",
1077
+ },
1078
+ };
1079
+ }),
1080
+ };
1081
+ }
1082
+
1083
+ interface ClientArtifactMeasurement {
1084
+ available: boolean;
1085
+ gzipEstimateBytes?: number;
1086
+ rawBytes?: number;
1087
+ }
1088
+
1089
+ function clientArtifactStaticClosure(
1090
+ route: ClientRouteManifestEntry,
1091
+ chunks: ReadonlyMap<string, ClientArtifactChunkManifest>,
1092
+ ): Set<string> {
1093
+ const paths = new Set<string>();
1094
+ for (const path of route.css ?? []) {
1095
+ paths.add(path);
1096
+ }
1097
+ if (route.navigation === true && route.navigationScript !== undefined) {
1098
+ paths.add(route.navigationScript);
1099
+ }
1100
+ for (const path of route.imports ?? []) {
1101
+ addClientArtifactStaticClosure(paths, path, chunks);
1102
+ }
1103
+ if (route.script !== undefined) {
1104
+ addClientArtifactStaticClosure(paths, route.script, chunks);
1105
+ }
1106
+ return paths;
1107
+ }
1108
+
1109
+ function addFetchedDynamicImportClosures(options: {
1110
+ chunks: ReadonlyMap<string, ClientArtifactChunkManifest>;
1111
+ fetchedDynamicImports: readonly string[];
1112
+ navigation: Set<string>;
1113
+ routeDynamicImports: readonly string[];
1114
+ }): void {
1115
+ const fetchedDynamicImports = new Set(options.fetchedDynamicImports);
1116
+ const acceptedDynamicImports = new Set<string>();
1117
+ const reachableDynamicImports = new Set(options.routeDynamicImports);
1118
+
1119
+ for (;;) {
1120
+ for (const dynamicImport of dynamicImportsReachableFromStaticClosure(
1121
+ options.navigation,
1122
+ options.chunks,
1123
+ )) {
1124
+ reachableDynamicImports.add(dynamicImport);
1125
+ }
1126
+
1127
+ let added = false;
1128
+ for (const dynamicImport of fetchedDynamicImports) {
1129
+ if (
1130
+ acceptedDynamicImports.has(dynamicImport) ||
1131
+ !reachableDynamicImports.has(dynamicImport) ||
1132
+ !options.chunks.has(dynamicImport)
1133
+ ) {
1134
+ continue;
1135
+ }
1136
+
1137
+ acceptedDynamicImports.add(dynamicImport);
1138
+ addClientArtifactStaticClosure(options.navigation, dynamicImport, options.chunks);
1139
+ added = true;
1140
+ }
1141
+
1142
+ if (!added) {
1143
+ return;
1144
+ }
1145
+ }
1146
+ }
1147
+
1148
+ function dynamicImportsReachableFromStaticClosure(
1149
+ paths: ReadonlySet<string>,
1150
+ chunks: ReadonlyMap<string, ClientArtifactChunkManifest>,
1151
+ ): Set<string> {
1152
+ const pending = [...paths];
1153
+ const visited = new Set<string>();
1154
+ const dynamicImports = new Set<string>();
1155
+
1156
+ while (pending.length > 0) {
1157
+ const path = pending.pop();
1158
+ if (path === undefined || visited.has(path)) {
1159
+ continue;
1160
+ }
1161
+
1162
+ visited.add(path);
1163
+ const chunk = chunks.get(path);
1164
+ if (chunk === undefined) {
1165
+ continue;
1166
+ }
1167
+
1168
+ for (const imported of chunk.imports ?? []) {
1169
+ pending.push(imported);
1170
+ }
1171
+ for (const dynamicImport of chunk.dynamicImports ?? []) {
1172
+ dynamicImports.add(dynamicImport);
1173
+ }
1174
+ }
1175
+
1176
+ return dynamicImports;
1177
+ }
1178
+
1179
+ function addClientArtifactStaticClosure(
1180
+ paths: Set<string>,
1181
+ path: string,
1182
+ chunks: ReadonlyMap<string, ClientArtifactChunkManifest>,
1183
+ ): void {
1184
+ if (path === "" || paths.has(path)) {
1185
+ return;
1186
+ }
1187
+
1188
+ paths.add(path);
1189
+ for (const imported of chunks.get(path)?.imports ?? []) {
1190
+ addClientArtifactStaticClosure(paths, imported, chunks);
1191
+ }
1192
+ }
1193
+
1194
+ function intersectionOfPathSets(sets: readonly Set<string>[]): Set<string> {
1195
+ const [first, ...rest] = sets;
1196
+ if (first === undefined || sets.length < 2) {
1197
+ return new Set();
1198
+ }
1199
+
1200
+ return new Set([...first].filter((path) => rest.every((set) => set.has(path))));
1201
+ }
1202
+
1203
+ async function measureClientArtifactPaths(
1204
+ clientDir: string,
1205
+ paths: ReadonlySet<string>,
1206
+ ): Promise<ReadonlyMap<string, ClientArtifactMeasurement>> {
1207
+ const measurements = await Promise.all(
1208
+ [...paths].map(async (path) => {
1209
+ const absolutePath = safeClientArtifactPath(clientDir, path);
1210
+ if (absolutePath === undefined) {
1211
+ return [path, { available: false }] as [string, ClientArtifactMeasurement];
1212
+ }
1213
+
1214
+ try {
1215
+ const content = await readFile(absolutePath);
1216
+ return [
1217
+ path,
1218
+ {
1219
+ available: true,
1220
+ gzipEstimateBytes: gzipSync(content).byteLength,
1221
+ rawBytes: content.byteLength,
1222
+ },
1223
+ ] as [string, ClientArtifactMeasurement];
1224
+ } catch {
1225
+ return [path, { available: false }] as [string, ClientArtifactMeasurement];
1226
+ }
1227
+ }),
1228
+ );
1229
+
1230
+ return new Map(measurements);
1231
+ }
1232
+
1233
+ function safeClientArtifactPath(clientDir: string, path: string): string | undefined {
1234
+ const absolutePath = resolve(clientDir, path);
1235
+ const relativePath = relative(resolve(clientDir), absolutePath);
1236
+
1237
+ return relativePath === "" || relativePath === ".." || relativePath.startsWith(`..${sep}`)
1238
+ ? undefined
1239
+ : absolutePath;
1240
+ }
1241
+
1242
+ function byteCostForPaths(
1243
+ paths: ReadonlySet<string>,
1244
+ measurements: ReadonlyMap<string, ClientArtifactMeasurement>,
1245
+ ): { gzipEstimateBytes: number; rawBytes: number } {
1246
+ return [...paths].reduce(
1247
+ (cost, path) => {
1248
+ const measurement = measurements.get(path);
1249
+ return {
1250
+ gzipEstimateBytes: cost.gzipEstimateBytes + (measurement?.gzipEstimateBytes ?? 0),
1251
+ rawBytes: cost.rawBytes + (measurement?.rawBytes ?? 0),
1252
+ };
1253
+ },
1254
+ { gzipEstimateBytes: 0, rawBytes: 0 },
1255
+ );
1256
+ }
1257
+
939
1258
  const incrementalBuildCacheFilename = "build-cache.json";
940
1259
  const incrementalBuildCacheVersion = 2;
941
1260
 
@@ -1090,6 +1409,11 @@ function collectClientManifestOutputFiles(
1090
1409
  files.add(asset);
1091
1410
  files.add(`public/${asset}`);
1092
1411
  }
1412
+ for (const chunk of manifest.chunks ?? []) {
1413
+ for (const file of [chunk.file, ...(chunk.imports ?? []), ...(chunk.dynamicImports ?? [])]) {
1414
+ files.add(file);
1415
+ }
1416
+ }
1093
1417
  for (const route of manifest.routes) {
1094
1418
  for (const file of [
1095
1419
  route.script,
@@ -1097,6 +1421,7 @@ function collectClientManifestOutputFiles(
1097
1421
  route.navigationScript,
1098
1422
  ...(route.css ?? []),
1099
1423
  ...(route.imports ?? []),
1424
+ ...(route.dynamicImports ?? []),
1100
1425
  ]) {
1101
1426
  if (file !== undefined) {
1102
1427
  files.add(file);
@@ -1473,6 +1798,7 @@ async function analyzeBuildRouteSources(options: {
1473
1798
  diagnostics: analysis.diagnostics,
1474
1799
  entry: analysis.route.file,
1475
1800
  path: analysis.route.path,
1801
+ source: analysis.source,
1476
1802
  })),
1477
1803
  }),
1478
1804
  byFile,
@@ -4056,14 +4382,14 @@ const componentGenerateMetadata = readComponentModuleExport(componentModule, "ge
4056
4382
  const componentMetadata = readComponentModuleExport(componentModule, "metadata");
4057
4383
  const componentSecurityHeadersApplied = readComponentModuleExport(componentModule, "__mreactSecurityHeadersApplied");
4058
4384
 
4059
- export function App(props) {
4060
- return renderCloudflareRouteComponent(props);
4385
+ export function App(props, request) {
4386
+ return renderCloudflareRouteComponent(props, request);
4061
4387
  }
4062
- export default function CloudflareDefaultRouteComponent(props) {
4063
- return renderCloudflareRouteComponent(props);
4388
+ export default function CloudflareDefaultRouteComponent(props, request) {
4389
+ return renderCloudflareRouteComponent(props, request);
4064
4390
  }
4065
- export function CloudflareRouteComponent(props) {
4066
- return renderCloudflareRouteComponent(props);
4391
+ export function CloudflareRouteComponent(props, request) {
4392
+ return renderCloudflareRouteComponent(props, request);
4067
4393
  }
4068
4394
  export const slots = componentSlots === undefined ? undefined : { ...componentSlots };
4069
4395
  export const generateMetadata =
@@ -4071,13 +4397,13 @@ export const generateMetadata =
4071
4397
  export const metadata = componentMetadata;
4072
4398
  export const __mreactSecurityHeadersApplied = componentSecurityHeadersApplied === true;
4073
4399
 
4074
- function renderCloudflareRouteComponent(props) {
4400
+ function renderCloudflareRouteComponent(props, request) {
4075
4401
  const routeComponent = resolveCloudflareRouteComponent();
4076
4402
  if (routeComponent === undefined) {
4077
4403
  throw new Error("No Cloudflare component export was found for ${componentImport}.");
4078
4404
  }
4079
4405
 
4080
- return routeComponent(props);
4406
+ return routeComponent(props, request);
4081
4407
  }
4082
4408
 
4083
4409
  function resolveCloudflareRouteComponent() {
@@ -4443,14 +4769,14 @@ ${shellModules.join("\n")}
4443
4769
  const pageComponent = selectComponent(pageModule, ${JSON.stringify(options.filename)});
4444
4770
  const shells = [${shellDefinitions.join(", ")}];
4445
4771
  export const slots = pageModule.slots;
4446
- export default function CloudflareStringRouteComponent(props) {
4447
- return renderCloudflareStringRoute(props);
4772
+ export default function CloudflareStringRouteComponent(props, request) {
4773
+ return renderCloudflareStringRoute(props, request);
4448
4774
  }
4449
4775
 
4450
- async function renderCloudflareStringRoute(props) {
4776
+ async function renderCloudflareStringRoute(props, request) {
4451
4777
  const slotHtml = await renderRouteSlots(pageModule.slots, props);
4452
4778
  const layoutShells = await renderLayoutShells(shells, props, slotHtml);
4453
- const metadata = await resolveRouteMetadata([...shells.map((shell) => shell.module), pageModule], props);
4779
+ const metadata = await resolveRouteMetadata([...shells.map((shell) => shell.module), pageModule], { ...props, request });
4454
4780
  let html = "<!DOCTYPE html>";
4455
4781
  for (const shell of layoutShells) {
4456
4782
  html += shell.prefix;
@@ -4461,7 +4787,7 @@ async function renderCloudflareStringRoute(props) {
4461
4787
  }
4462
4788
  html = injectCloudflareHead(html, metadata, cloudflareRouteHeadTags(props.clientManifest, props.route.path));
4463
4789
  return new Response(html, {
4464
- headers: cloudflareMetadataHeaders(metadata, props.request, {
4790
+ headers: cloudflareMetadataHeaders(metadata, request, {
4465
4791
  "content-type": "text/html; charset=utf-8"
4466
4792
  })
4467
4793
  });
@@ -4627,14 +4953,14 @@ ${shellImports.join("\n")}
4627
4953
  const pageComponent = selectComponent(pageModule, ${JSON.stringify(options.filename)});
4628
4954
  const shells = [${shellDefinitions.join(", ")}];
4629
4955
  export const slots = pageModule.slots;
4630
- export default function CloudflareStringRouteComponent(props) {
4631
- return renderCloudflareStringRoute(props);
4956
+ export default function CloudflareStringRouteComponent(props, request) {
4957
+ return renderCloudflareStringRoute(props, request);
4632
4958
  }
4633
4959
 
4634
- async function renderCloudflareStringRoute(props) {
4960
+ async function renderCloudflareStringRoute(props, request) {
4635
4961
  const slotHtml = await renderRouteSlots(pageModule.slots, props);
4636
4962
  const layoutShells = await renderLayoutShells(shells, props, slotHtml);
4637
- const metadata = await resolveRouteMetadata([...shells.map((shell) => shell.module), pageModule], props);
4963
+ const metadata = await resolveRouteMetadata([...shells.map((shell) => shell.module), pageModule], { ...props, request });
4638
4964
  let html = "<!DOCTYPE html>";
4639
4965
  for (const shell of layoutShells) {
4640
4966
  html += shell.prefix;
@@ -4645,7 +4971,7 @@ async function renderCloudflareStringRoute(props) {
4645
4971
  }
4646
4972
  html = injectCloudflareHead(html, metadata, cloudflareRouteHeadTags(props.clientManifest, props.route.path));
4647
4973
  return new Response(html, {
4648
- headers: cloudflareMetadataHeaders(metadata, props.request, {
4974
+ headers: cloudflareMetadataHeaders(metadata, request, {
4649
4975
  "content-type": "text/html; charset=utf-8"
4650
4976
  })
4651
4977
  });
@@ -4740,8 +5066,8 @@ export const slots = pageModule.slots;
4740
5066
  export const App = renderCloudflareStreamRoute;
4741
5067
  export default renderCloudflareStreamRoute;
4742
5068
 
4743
- async function renderCloudflareStreamRoute(props) {
4744
- const metadata = await resolveRouteMetadata([...shells.map((shell) => shell.module), pageModule], props);
5069
+ async function renderCloudflareStreamRoute(props, request) {
5070
+ const metadata = await resolveRouteMetadata([...shells.map((shell) => shell.module), pageModule], { ...props, request });
4745
5071
  const body = renderToReadableStream(async ($sink) => {
4746
5072
  const slotHtml = await renderRouteSlots(pageModule.slots, props);
4747
5073
  const layoutShells = await renderLayoutShells(shells, props, slotHtml);
@@ -4764,7 +5090,7 @@ async function renderCloudflareStreamRoute(props) {
4764
5090
  }
4765
5091
  });
4766
5092
  return new Response(body, {
4767
- headers: cloudflareMetadataHeaders(metadata, props.request, {
5093
+ headers: cloudflareMetadataHeaders(metadata, request, {
4768
5094
  "content-type": "text/html; charset=utf-8",
4769
5095
  "x-mreact-stream": "1"
4770
5096
  })
@@ -4895,9 +5221,15 @@ function cloudflareHydrationMarkerParts(props) {
4895
5221
  }
4896
5222
  const routeId = route.routeId ?? cloudflareRouteIdForPath(props.route.path);
4897
5223
  const escapedRouteId = escapeHtmlAttribute(routeId);
5224
+ const requestUrl = new URL(props.request.url);
4898
5225
  const propsJson = escapeScriptJson(JSON.stringify({
4899
5226
  params: props.params,
4900
- request: { url: new URL(props.request.url).pathname },
5227
+ request: {
5228
+ hash: "",
5229
+ pathname: requestUrl.pathname,
5230
+ search: "",
5231
+ url: requestUrl.pathname,
5232
+ },
4901
5233
  data: props.data,
4902
5234
  }));
4903
5235
  const clientReferencesJson = route.clientReferenceManifest === undefined || route.clientReferenceManifest.length === 0
@@ -6279,10 +6611,24 @@ function viteManifestFromClientRoutes(routes: ClientRouteManifestEntry[]): Recor
6279
6611
 
6280
6612
  interface ClientRouteBundleManifest {
6281
6613
  assets: string[];
6614
+ chunks: ClientArtifactChunkManifest[];
6282
6615
  routes: ClientRouteManifestEntry[];
6283
6616
  styles: ClientStyleManifestEntry[];
6284
6617
  }
6285
6618
 
6619
+ interface ClientArtifactChunkManifest {
6620
+ dynamicImports?: readonly string[];
6621
+ file: string;
6622
+ imports?: readonly string[];
6623
+ }
6624
+
6625
+ interface ClientArtifactManifest {
6626
+ assets?: readonly string[];
6627
+ chunks?: readonly ClientArtifactChunkManifest[];
6628
+ routes: readonly ClientRouteManifestEntry[];
6629
+ styles?: readonly ClientStyleManifestEntry[];
6630
+ }
6631
+
6286
6632
  interface ClientStyleManifestEntry {
6287
6633
  css: string[];
6288
6634
  file: string;
@@ -6442,6 +6788,7 @@ async function writeClientRouteBundles(options: {
6442
6788
  if (clientEntries.length === 0) {
6443
6789
  return {
6444
6790
  assets: [...routeCssAssets.assets, ...specialCssAssets.assets].sort(),
6791
+ chunks: [],
6445
6792
  routes: entries.flatMap((entry) => ("manifest" in entry ? [entry.manifest] : [])),
6446
6793
  styles: specialCssAssets.styles,
6447
6794
  };
@@ -6552,6 +6899,9 @@ async function writeClientRouteBundles(options: {
6552
6899
  ? {}
6553
6900
  : { clientReferenceManifest: entry.build.clientReferenceManifest }),
6554
6901
  ...(entry.css.length === 0 ? {} : { css: entry.css }),
6902
+ ...(routeOutput.chunk.dynamicImports.length === 0
6903
+ ? {}
6904
+ : { dynamicImports: routeOutput.chunk.dynamicImports }),
6555
6905
  ...(routeOutput.chunk.imports.length === 0 ? {} : { imports: routeOutput.chunk.imports }),
6556
6906
  ...(navigation ? { navigation } : {}),
6557
6907
  routeId,
@@ -6563,6 +6913,11 @@ async function writeClientRouteBundles(options: {
6563
6913
 
6564
6914
  return {
6565
6915
  assets: Array.from(generatedAssets).sort(),
6916
+ chunks: output.chunks.map((chunk) => ({
6917
+ ...(chunk.dynamicImports.length === 0 ? {} : { dynamicImports: chunk.dynamicImports }),
6918
+ file: chunk.fileName,
6919
+ ...(chunk.imports.length === 0 ? {} : { imports: chunk.imports }),
6920
+ })),
6566
6921
  routes,
6567
6922
  styles: specialCssAssets.styles,
6568
6923
  };
@@ -73,6 +73,7 @@ export interface RouterBundleModulesOutput {
73
73
 
74
74
  export interface RouterBundleChunkOutput {
75
75
  code: string;
76
+ dynamicImports: string[];
76
77
  fileName: string;
77
78
  imports: string[];
78
79
  isEntry: boolean;
@@ -87,6 +88,7 @@ export interface RouterBundleAssetOutput {
87
88
 
88
89
  interface RouterBundlerChunk {
89
90
  code: string;
91
+ dynamicImports?: string[] | undefined;
90
92
  fileName: string;
91
93
  imports?: string[] | undefined;
92
94
  isEntry?: boolean | undefined;
@@ -282,7 +284,8 @@ async function bundleRouterModuleUncached(
282
284
  plugins: [
283
285
  nodeBuiltinsForBrowserPlugin(options.platform, options.nodeBuiltins ?? "reject"),
284
286
  mreactJsxRuntimeAliasPlugin({
285
- externalize: options.platform === "node" && options.externalizeMreactRuntimeAliases === true,
287
+ externalize:
288
+ options.platform === "node" && options.externalizeMreactRuntimeAliases === true,
286
289
  }),
287
290
  ...(options.vitePlugins ?? []),
288
291
  virtualEntryPlugin(entryId, options.code),
@@ -369,8 +372,7 @@ function cloneRouterBundleOutput(output: RouterBundleOutput): RouterBundleOutput
369
372
  : {
370
373
  assets: output.assets.map((asset) => ({
371
374
  fileName: asset.fileName,
372
- source:
373
- typeof asset.source === "string" ? asset.source : new Uint8Array(asset.source),
375
+ source: typeof asset.source === "string" ? asset.source : new Uint8Array(asset.source),
374
376
  })),
375
377
  }),
376
378
  code: output.code,
@@ -459,6 +461,7 @@ export async function bundleRouterModules(
459
461
  options.sourceRegionModulePaths,
460
462
  options.root ?? dirname(options.entries[0]?.filename ?? process.cwd()),
461
463
  ),
464
+ dynamicImports: chunk.dynamicImports ?? [],
462
465
  fileName: chunk.fileName,
463
466
  imports: chunk.imports ?? [],
464
467
  isEntry: chunk.isEntry === true,
@@ -490,7 +493,9 @@ function mreactJsxRuntimeAliasPlugin(options?: { externalize?: boolean | undefin
490
493
  return undefined;
491
494
  }
492
495
 
493
- return options?.externalize === true ? { external: true, id: pathToFileURL(path).href } : path;
496
+ return options?.externalize === true
497
+ ? { external: true, id: pathToFileURL(path).href }
498
+ : path;
494
499
  },
495
500
  };
496
501
  }