@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.
- package/README.md +2 -2
- package/dist/adapters/cloudflare.d.ts +3 -3
- package/dist/adapters/cloudflare.d.ts.map +1 -1
- package/dist/adapters/cloudflare.js +11 -4
- package/dist/adapters/cloudflare.js.map +1 -1
- package/dist/boundaries.d.ts +37 -1
- package/dist/boundaries.d.ts.map +1 -1
- package/dist/boundaries.js +125 -4
- package/dist/boundaries.js.map +1 -1
- package/dist/build.d.ts +6 -0
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +265 -24
- package/dist/build.js.map +1 -1
- package/dist/bundle-pipeline.d.ts +1 -0
- package/dist/bundle-pipeline.d.ts.map +1 -1
- package/dist/bundle-pipeline.js +4 -1
- package/dist/bundle-pipeline.js.map +1 -1
- package/dist/client.d.ts +1 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +15 -11
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +7 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +17 -0
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/reactive-devtools-stub.d.ts +3 -0
- package/dist/reactive-devtools-stub.d.ts.map +1 -0
- package/dist/reactive-devtools-stub.js +10 -0
- package/dist/reactive-devtools-stub.js.map +1 -0
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +23 -23
- package/dist/render.js.map +1 -1
- package/dist/request-header-tracking.d.ts +19 -0
- package/dist/request-header-tracking.d.ts.map +1 -1
- package/dist/request-header-tracking.js +48 -0
- package/dist/request-header-tracking.js.map +1 -1
- package/dist/tailwind-source.d.ts.map +1 -1
- package/dist/tailwind-source.js +147 -1
- package/dist/tailwind-source.js.map +1 -1
- package/dist/types.d.ts +14 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/vite.d.ts +1 -0
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +2 -7
- package/dist/vite.js.map +1 -1
- package/package.json +11 -11
- package/src/adapters/cloudflare.ts +14 -5
- package/src/boundaries.ts +224 -4
- package/src/build.ts +379 -24
- package/src/bundle-pipeline.ts +9 -4
- package/src/client.ts +19 -12
- package/src/config.ts +32 -0
- package/src/index.ts +10 -0
- package/src/reactive-devtools-stub.ts +9 -0
- package/src/render.ts +30 -16
- package/src/request-header-tracking.ts +61 -0
- package/src/tailwind-source.ts +164 -1
- package/src/types.ts +15 -2
- 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 {
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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: {
|
|
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
|
};
|
package/src/bundle-pipeline.ts
CHANGED
|
@@ -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:
|
|
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
|
|
496
|
+
return options?.externalize === true
|
|
497
|
+
? { external: true, id: pathToFileURL(path).href }
|
|
498
|
+
: path;
|
|
494
499
|
},
|
|
495
500
|
};
|
|
496
501
|
}
|