@reckona/mreact-router 0.0.115 → 0.0.117
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 +1 -1
- package/dist/adapters/cloudflare.d.ts +1 -0
- package/dist/adapters/cloudflare.d.ts.map +1 -1
- package/dist/adapters/cloudflare.js +64 -2
- package/dist/adapters/cloudflare.js.map +1 -1
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +83 -5
- package/dist/build.js.map +1 -1
- package/dist/client.d.ts +1 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js.map +1 -1
- package/package.json +11 -11
- package/src/adapters/cloudflare.ts +94 -2
- package/src/build.ts +95 -6
- package/src/client.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reckona/mreact-router",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.117",
|
|
4
4
|
"description": "File-system app router, SSR, actions, and deployment adapters for mreact.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jsx",
|
|
@@ -105,20 +105,20 @@
|
|
|
105
105
|
},
|
|
106
106
|
"dependencies": {
|
|
107
107
|
"typescript": "^6.0.3",
|
|
108
|
-
"@reckona/mreact": "0.0.
|
|
109
|
-
"@reckona/mreact-
|
|
110
|
-
"@reckona/mreact-
|
|
111
|
-
"@reckona/mreact-query": "0.0.
|
|
112
|
-
"@reckona/mreact-reactive-core": "0.0.
|
|
113
|
-
"@reckona/mreact-
|
|
114
|
-
"@reckona/mreact-
|
|
115
|
-
"@reckona/mreact-server": "0.0.
|
|
116
|
-
"@reckona/mreact-shared": "0.0.
|
|
108
|
+
"@reckona/mreact": "0.0.117",
|
|
109
|
+
"@reckona/mreact-devtools": "0.0.117",
|
|
110
|
+
"@reckona/mreact-compiler": "0.0.117",
|
|
111
|
+
"@reckona/mreact-query": "0.0.117",
|
|
112
|
+
"@reckona/mreact-reactive-core": "0.0.117",
|
|
113
|
+
"@reckona/mreact-reactive-dom": "0.0.117",
|
|
114
|
+
"@reckona/mreact-compat": "0.0.117",
|
|
115
|
+
"@reckona/mreact-server": "0.0.117",
|
|
116
|
+
"@reckona/mreact-shared": "0.0.117"
|
|
117
117
|
},
|
|
118
118
|
"peerDependencies": {
|
|
119
119
|
"vite": ">=8 <9"
|
|
120
120
|
},
|
|
121
121
|
"optionalDependencies": {
|
|
122
|
-
"@reckona/mreact-router-native": "0.0.
|
|
122
|
+
"@reckona/mreact-router-native": "0.0.117"
|
|
123
123
|
}
|
|
124
124
|
}
|
|
@@ -190,6 +190,7 @@ export interface CloudflareBuiltRequestHandlerOptions<Env = unknown> extends Omi
|
|
|
190
190
|
}
|
|
191
191
|
|
|
192
192
|
export interface CloudflareClientManifest {
|
|
193
|
+
assets?: readonly string[] | undefined;
|
|
193
194
|
publicAssets?: readonly string[] | undefined;
|
|
194
195
|
routes: ClientRouteManifestEntry[];
|
|
195
196
|
}
|
|
@@ -380,12 +381,20 @@ export function createCloudflareRouteModuleRenderer<Env = unknown>(
|
|
|
380
381
|
|
|
381
382
|
const modulePreload = cloudflareModulePreloadTag(context.clientManifest, context.route.path);
|
|
382
383
|
const metadata = await resolveCloudflareRouteMetadata([pageModule], props);
|
|
384
|
+
const body = withCloudflareHydrationMarkers({
|
|
385
|
+
data,
|
|
386
|
+
html: rendered,
|
|
387
|
+
manifest: context.clientManifest,
|
|
388
|
+
params: context.params,
|
|
389
|
+
request,
|
|
390
|
+
routePath: context.route.path,
|
|
391
|
+
});
|
|
383
392
|
const documented =
|
|
384
393
|
options.document === undefined
|
|
385
|
-
? defaultCloudflareDocument(
|
|
394
|
+
? defaultCloudflareDocument(body, modulePreload, metadata)
|
|
386
395
|
: await options.document({
|
|
387
396
|
...props,
|
|
388
|
-
body
|
|
397
|
+
body,
|
|
389
398
|
modulePreload,
|
|
390
399
|
});
|
|
391
400
|
|
|
@@ -709,6 +718,14 @@ export function cloudflareClientAssetPaths(
|
|
|
709
718
|
}
|
|
710
719
|
}
|
|
711
720
|
|
|
721
|
+
for (const asset of manifest.assets ?? []) {
|
|
722
|
+
const path = safeClientAssetPath(prefix, asset);
|
|
723
|
+
|
|
724
|
+
if (path !== undefined) {
|
|
725
|
+
paths.add(path);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
712
729
|
for (const extraPath of options.extraPaths ?? []) {
|
|
713
730
|
const path = safeClientAssetPath(prefix, extraPath);
|
|
714
731
|
|
|
@@ -1145,6 +1162,81 @@ function safeClientAssetPath(prefix: string, asset: string | undefined): string
|
|
|
1145
1162
|
return `${prefix}${segments.join("/")}`;
|
|
1146
1163
|
}
|
|
1147
1164
|
|
|
1165
|
+
function withCloudflareHydrationMarkers(options: {
|
|
1166
|
+
data: unknown;
|
|
1167
|
+
html: string;
|
|
1168
|
+
manifest: CloudflareClientManifest;
|
|
1169
|
+
params: Record<string, readonly string[] | string>;
|
|
1170
|
+
request: Request;
|
|
1171
|
+
routePath: string;
|
|
1172
|
+
}): string {
|
|
1173
|
+
const marker = cloudflareHydrationMarkerParts(options);
|
|
1174
|
+
|
|
1175
|
+
return marker.prefix === "" && marker.suffix === ""
|
|
1176
|
+
? options.html
|
|
1177
|
+
: `${marker.prefix}${options.html}${marker.suffix}`;
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
function cloudflareHydrationMarkerParts(options: {
|
|
1181
|
+
data: unknown;
|
|
1182
|
+
manifest: CloudflareClientManifest;
|
|
1183
|
+
params: Record<string, readonly string[] | string>;
|
|
1184
|
+
request: Request;
|
|
1185
|
+
routePath: string;
|
|
1186
|
+
}): { prefix: string; suffix: string } {
|
|
1187
|
+
const route = options.manifest.routes.find(
|
|
1188
|
+
(route) => route.path === options.routePath && route.client === true,
|
|
1189
|
+
);
|
|
1190
|
+
|
|
1191
|
+
if (route?.script === undefined) {
|
|
1192
|
+
return { prefix: "", suffix: "" };
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
const routeId = route.routeId ?? cloudflareRouteIdForPath(options.routePath);
|
|
1196
|
+
const escapedRouteId = escapeHtmlAttribute(routeId);
|
|
1197
|
+
const propsJson = escapeScriptJson(
|
|
1198
|
+
JSON.stringify({
|
|
1199
|
+
params: options.params,
|
|
1200
|
+
request: { url: options.request.url },
|
|
1201
|
+
data: options.data,
|
|
1202
|
+
}),
|
|
1203
|
+
);
|
|
1204
|
+
const clientReferencesJson =
|
|
1205
|
+
route.clientReferenceManifest === undefined || route.clientReferenceManifest.length === 0
|
|
1206
|
+
? undefined
|
|
1207
|
+
: escapeScriptJson(JSON.stringify(route.clientReferenceManifest));
|
|
1208
|
+
|
|
1209
|
+
return {
|
|
1210
|
+
prefix: `<div data-mreact-route-id="${escapedRouteId}">`,
|
|
1211
|
+
suffix: [
|
|
1212
|
+
"</div>",
|
|
1213
|
+
`<script type="application/json" id="mreact-props-${escapedRouteId}">${propsJson}</script>`,
|
|
1214
|
+
clientReferencesJson === undefined
|
|
1215
|
+
? undefined
|
|
1216
|
+
: `<script type="application/json" id="mreact-client-references-${escapedRouteId}">${clientReferencesJson}</script>`,
|
|
1217
|
+
`<script type="module" src="/_mreact/client/${escapeHtmlAttribute(route.script)}"></script>`,
|
|
1218
|
+
]
|
|
1219
|
+
.filter((part): part is string => part !== undefined)
|
|
1220
|
+
.join(""),
|
|
1221
|
+
};
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
function escapeScriptJson(json: string): string {
|
|
1225
|
+
return json.replaceAll("<", "\\u003c");
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
function cloudflareRouteIdForPath(path: string): string {
|
|
1229
|
+
if (path === "/") {
|
|
1230
|
+
return "index";
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
return path
|
|
1234
|
+
.slice(1)
|
|
1235
|
+
.replaceAll("/", "_")
|
|
1236
|
+
.replaceAll(":", "_")
|
|
1237
|
+
.replace(/[^A-Za-z0-9_$-]/g, "_");
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1148
1240
|
function unsafeAssetSegment(segment: string): boolean {
|
|
1149
1241
|
if (segment === "" || segment === "." || segment === "..") {
|
|
1150
1242
|
return true;
|
package/src/build.ts
CHANGED
|
@@ -425,7 +425,7 @@ export async function buildApp(options: BuildAppOptions): Promise<BuildAppResult
|
|
|
425
425
|
...route,
|
|
426
426
|
file: relative(project.projectRoot, route.file),
|
|
427
427
|
}));
|
|
428
|
-
const [serverModuleArtifacts,
|
|
428
|
+
const [serverModuleArtifacts, clientBundle] = await Promise.all([
|
|
429
429
|
timingSink === undefined
|
|
430
430
|
? writeServerModuleArtifactFiles(serverDir, serverModules, generatedImportPolicy.runtimePackages)
|
|
431
431
|
: timeBuildPhase(timingSink, "serverModuleArtifacts", () =>
|
|
@@ -465,6 +465,7 @@ export async function buildApp(options: BuildAppOptions): Promise<BuildAppResult
|
|
|
465
465
|
}),
|
|
466
466
|
),
|
|
467
467
|
]);
|
|
468
|
+
const clientRoutes = clientBundle.routes;
|
|
468
469
|
const navigationRuntimeScript = clientRoutes.some(
|
|
469
470
|
(route) => route.navigation === true && !route.client,
|
|
470
471
|
)
|
|
@@ -482,6 +483,12 @@ export async function buildApp(options: BuildAppOptions): Promise<BuildAppResult
|
|
|
482
483
|
? { ...route, navigationScript: navigationRuntimeScript }
|
|
483
484
|
: route,
|
|
484
485
|
);
|
|
486
|
+
const clientManifestAssets = Array.from(
|
|
487
|
+
new Set([
|
|
488
|
+
...clientBundle.assets,
|
|
489
|
+
...(navigationRuntimeScript === undefined ? [] : [navigationRuntimeScript]),
|
|
490
|
+
]),
|
|
491
|
+
).sort();
|
|
485
492
|
const prerenderedRoutes =
|
|
486
493
|
timingSink === undefined
|
|
487
494
|
? await prerenderStaticRoutes({
|
|
@@ -571,6 +578,7 @@ export async function buildApp(options: BuildAppOptions): Promise<BuildAppResult
|
|
|
571
578
|
: { serverModuleRenderFiles: serverModuleArtifacts.renderFiles }),
|
|
572
579
|
} satisfies BuiltServerManifest;
|
|
573
580
|
const clientManifest = {
|
|
581
|
+
...(clientManifestAssets.length === 0 ? {} : { assets: clientManifestAssets }),
|
|
574
582
|
...(publicAssets.length === 0 ? {} : { publicAssets }),
|
|
575
583
|
routes: clientManifestRoutes,
|
|
576
584
|
};
|
|
@@ -2999,7 +3007,7 @@ async function renderCloudflareStringRoute(props) {
|
|
|
2999
3007
|
for (const shell of layoutShells) {
|
|
3000
3008
|
html += shell.prefix;
|
|
3001
3009
|
}
|
|
3002
|
-
html += String(await pageComponent(props) ?? "");
|
|
3010
|
+
html += withCloudflareHydrationMarkers(props, String(await pageComponent(props) ?? ""));
|
|
3003
3011
|
for (const shell of [...layoutShells].reverse()) {
|
|
3004
3012
|
html += shell.suffix;
|
|
3005
3013
|
}
|
|
@@ -3171,7 +3179,7 @@ async function renderCloudflareStringRoute(props) {
|
|
|
3171
3179
|
for (const shell of layoutShells) {
|
|
3172
3180
|
html += shell.prefix;
|
|
3173
3181
|
}
|
|
3174
|
-
html += String(await pageComponent(props) ?? "");
|
|
3182
|
+
html += withCloudflareHydrationMarkers(props, String(await pageComponent(props) ?? ""));
|
|
3175
3183
|
for (const shell of [...layoutShells].reverse()) {
|
|
3176
3184
|
html += shell.suffix;
|
|
3177
3185
|
}
|
|
@@ -3280,7 +3288,10 @@ function renderCloudflareStreamRoute(props) {
|
|
|
3280
3288
|
const shell = layoutShells[index];
|
|
3281
3289
|
$sink.append(index === 0 ? injectCloudflareHead(shell.prefix, metadata, routeHeadTags) : shell.prefix);
|
|
3282
3290
|
}
|
|
3291
|
+
const hydrationMarker = cloudflareHydrationMarkerParts(props);
|
|
3292
|
+
$sink.append(hydrationMarker.prefix);
|
|
3283
3293
|
await pageComponent($sink, props);
|
|
3294
|
+
$sink.append(hydrationMarker.suffix);
|
|
3284
3295
|
renderOutOfOrderReorderScript($sink);
|
|
3285
3296
|
for (const shell of [...layoutShells].reverse()) {
|
|
3286
3297
|
$sink.append(shell.suffix);
|
|
@@ -3403,6 +3414,54 @@ function readSlotName(attributes) {
|
|
|
3403
3414
|
return match?.[1] ?? match?.[2];
|
|
3404
3415
|
}
|
|
3405
3416
|
|
|
3417
|
+
function withCloudflareHydrationMarkers(props, html) {
|
|
3418
|
+
const marker = cloudflareHydrationMarkerParts(props);
|
|
3419
|
+
return marker.prefix === "" && marker.suffix === "" ? html : \`\${marker.prefix}\${html}\${marker.suffix}\`;
|
|
3420
|
+
}
|
|
3421
|
+
|
|
3422
|
+
function cloudflareHydrationMarkerParts(props) {
|
|
3423
|
+
const route = props.clientManifest.routes.find((route) => route.path === props.route.path && route.client === true);
|
|
3424
|
+
if (route?.script === undefined) {
|
|
3425
|
+
return { prefix: "", suffix: "" };
|
|
3426
|
+
}
|
|
3427
|
+
const routeId = route.routeId ?? cloudflareRouteIdForPath(props.route.path);
|
|
3428
|
+
const escapedRouteId = escapeHtmlAttribute(routeId);
|
|
3429
|
+
const propsJson = escapeScriptJson(JSON.stringify({
|
|
3430
|
+
params: props.params,
|
|
3431
|
+
request: { url: props.request.url },
|
|
3432
|
+
data: props.data,
|
|
3433
|
+
}));
|
|
3434
|
+
const clientReferencesJson = route.clientReferenceManifest === undefined || route.clientReferenceManifest.length === 0
|
|
3435
|
+
? undefined
|
|
3436
|
+
: escapeScriptJson(JSON.stringify(route.clientReferenceManifest));
|
|
3437
|
+
return {
|
|
3438
|
+
prefix: \`<div data-mreact-route-id="\${escapedRouteId}">\`,
|
|
3439
|
+
suffix: [
|
|
3440
|
+
"</div>",
|
|
3441
|
+
\`<script type="application/json" id="mreact-props-\${escapedRouteId}">\${propsJson}</script>\`,
|
|
3442
|
+
clientReferencesJson === undefined
|
|
3443
|
+
? undefined
|
|
3444
|
+
: \`<script type="application/json" id="mreact-client-references-\${escapedRouteId}">\${clientReferencesJson}</script>\`,
|
|
3445
|
+
\`<script type="module" src="/_mreact/client/\${escapeHtmlAttribute(route.script)}"></script>\`,
|
|
3446
|
+
].filter((part) => part !== undefined).join(""),
|
|
3447
|
+
};
|
|
3448
|
+
}
|
|
3449
|
+
|
|
3450
|
+
function escapeScriptJson(json) {
|
|
3451
|
+
return json.replaceAll("<", "\\\\u003c");
|
|
3452
|
+
}
|
|
3453
|
+
|
|
3454
|
+
function cloudflareRouteIdForPath(path) {
|
|
3455
|
+
if (path === "/") {
|
|
3456
|
+
return "index";
|
|
3457
|
+
}
|
|
3458
|
+
return path
|
|
3459
|
+
.slice(1)
|
|
3460
|
+
.replaceAll("/", "_")
|
|
3461
|
+
.replaceAll(":", "_")
|
|
3462
|
+
.replace(/[^A-Za-z0-9_$-]/g, "_");
|
|
3463
|
+
}
|
|
3464
|
+
|
|
3406
3465
|
async function resolveRouteMetadata(modules, props) {
|
|
3407
3466
|
const metadata = [];
|
|
3408
3467
|
const context = {
|
|
@@ -4178,6 +4237,11 @@ function viteManifestFromClientRoutes(routes: ClientRouteManifestEntry[]): Recor
|
|
|
4178
4237
|
return manifest;
|
|
4179
4238
|
}
|
|
4180
4239
|
|
|
4240
|
+
interface ClientRouteBundleManifest {
|
|
4241
|
+
assets: string[];
|
|
4242
|
+
routes: ClientRouteManifestEntry[];
|
|
4243
|
+
}
|
|
4244
|
+
|
|
4181
4245
|
async function writeClientRouteBundles(options: {
|
|
4182
4246
|
appDir: string;
|
|
4183
4247
|
assetBaseUrl?: string | undefined;
|
|
@@ -4191,7 +4255,7 @@ async function writeClientRouteBundles(options: {
|
|
|
4191
4255
|
sourceMapDir: string;
|
|
4192
4256
|
sourceMaps: AppRouterClientSourceMapMode;
|
|
4193
4257
|
vitePlugins?: readonly PluginOption[] | undefined;
|
|
4194
|
-
}): Promise<
|
|
4258
|
+
}): Promise<ClientRouteBundleManifest> {
|
|
4195
4259
|
type PreparedClientRouteEntry = {
|
|
4196
4260
|
build: BuildClientRouteOutputOptions;
|
|
4197
4261
|
css: string[];
|
|
@@ -4292,7 +4356,10 @@ async function writeClientRouteBundles(options: {
|
|
|
4292
4356
|
);
|
|
4293
4357
|
|
|
4294
4358
|
if (clientEntries.length === 0) {
|
|
4295
|
-
return
|
|
4359
|
+
return {
|
|
4360
|
+
assets: [],
|
|
4361
|
+
routes: entries.flatMap((entry) => ("manifest" in entry ? [entry.manifest] : [])),
|
|
4362
|
+
};
|
|
4296
4363
|
}
|
|
4297
4364
|
|
|
4298
4365
|
let output: Awaited<ReturnType<typeof buildClientRouteBatchOutput>>;
|
|
@@ -4354,7 +4421,20 @@ async function writeClientRouteBundles(options: {
|
|
|
4354
4421
|
await writeFile(join(options.clientDir, asset.fileName), source);
|
|
4355
4422
|
}
|
|
4356
4423
|
|
|
4357
|
-
|
|
4424
|
+
const generatedAssets = new Set<string>();
|
|
4425
|
+
|
|
4426
|
+
for (const chunk of output.chunks) {
|
|
4427
|
+
generatedAssets.add(chunk.fileName);
|
|
4428
|
+
if (options.sourceMaps === "linked" && chunk.map !== undefined) {
|
|
4429
|
+
generatedAssets.add(`${chunk.fileName}.map`);
|
|
4430
|
+
}
|
|
4431
|
+
}
|
|
4432
|
+
|
|
4433
|
+
for (const asset of output.assets ?? []) {
|
|
4434
|
+
generatedAssets.add(asset.fileName);
|
|
4435
|
+
}
|
|
4436
|
+
|
|
4437
|
+
const routes = entries.map((entry) => {
|
|
4358
4438
|
if (!("build" in entry)) {
|
|
4359
4439
|
return entry.manifest;
|
|
4360
4440
|
}
|
|
@@ -4378,6 +4458,10 @@ async function writeClientRouteBundles(options: {
|
|
|
4378
4458
|
path: entry.route.path,
|
|
4379
4459
|
kind: entry.route.kind,
|
|
4380
4460
|
client: true,
|
|
4461
|
+
...(entry.build.clientReferenceManifest === undefined ||
|
|
4462
|
+
entry.build.clientReferenceManifest.length === 0
|
|
4463
|
+
? {}
|
|
4464
|
+
: { clientReferenceManifest: entry.build.clientReferenceManifest }),
|
|
4381
4465
|
...(entry.css.length === 0 ? {} : { css: entry.css }),
|
|
4382
4466
|
...(routeOutput.chunk.imports.length === 0 ? {} : { imports: routeOutput.chunk.imports }),
|
|
4383
4467
|
...(entry.navigation ? { navigation: entry.navigation } : {}),
|
|
@@ -4387,6 +4471,11 @@ async function writeClientRouteBundles(options: {
|
|
|
4387
4471
|
devScript: clientScriptForPath(entry.route.path),
|
|
4388
4472
|
};
|
|
4389
4473
|
});
|
|
4474
|
+
|
|
4475
|
+
return {
|
|
4476
|
+
assets: Array.from(generatedAssets).sort(),
|
|
4477
|
+
routes,
|
|
4478
|
+
};
|
|
4390
4479
|
}
|
|
4391
4480
|
|
|
4392
4481
|
async function writeRouteCssAssetBatches(options: {
|
package/src/client.ts
CHANGED
|
@@ -57,6 +57,7 @@ export interface ClientRouteManifestEntry {
|
|
|
57
57
|
path: string;
|
|
58
58
|
kind: AppRoute["kind"];
|
|
59
59
|
client: boolean;
|
|
60
|
+
clientReferenceManifest?: readonly ClientReferenceMetadata[] | undefined;
|
|
60
61
|
devScript?: string;
|
|
61
62
|
imports?: readonly string[];
|
|
62
63
|
navigation?: boolean;
|