@reckona/mreact-router 0.0.81 → 0.0.83
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 -0
- package/dist/adapters/aws-lambda.d.ts.map +1 -1
- package/dist/adapters/aws-lambda.js +17 -22
- package/dist/adapters/aws-lambda.js.map +1 -1
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +3 -1
- package/dist/build.js.map +1 -1
- package/dist/client-route-inference.d.ts +2 -0
- package/dist/client-route-inference.d.ts.map +1 -0
- package/dist/client-route-inference.js +2 -0
- package/dist/client-route-inference.js.map +1 -0
- package/dist/client.d.ts +2 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +89 -24
- package/dist/client.js.map +1 -1
- package/dist/layout-composer.d.ts +24 -0
- package/dist/layout-composer.d.ts.map +1 -0
- package/dist/layout-composer.js +83 -0
- package/dist/layout-composer.js.map +1 -0
- package/dist/metadata.d.ts +9 -0
- package/dist/metadata.d.ts.map +1 -0
- package/dist/metadata.js +419 -0
- package/dist/metadata.js.map +1 -0
- package/dist/middleware.d.ts +22 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +94 -0
- package/dist/middleware.js.map +1 -0
- package/dist/module-runner.d.ts +2 -2
- package/dist/module-runner.d.ts.map +1 -1
- package/dist/module-runner.js +1 -1
- package/dist/module-runner.js.map +1 -1
- package/dist/navigation-runtime.d.ts +2 -0
- package/dist/navigation-runtime.d.ts.map +1 -0
- package/dist/navigation-runtime.js +2 -0
- package/dist/navigation-runtime.js.map +1 -0
- package/dist/preload-policy.d.ts +24 -0
- package/dist/preload-policy.d.ts.map +1 -0
- package/dist/preload-policy.js +45 -0
- package/dist/preload-policy.js.map +1 -0
- package/dist/render.d.ts +1 -3
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +32 -676
- package/dist/render.js.map +1 -1
- package/dist/route-hydration-contract.d.ts +11 -0
- package/dist/route-hydration-contract.d.ts.map +1 -0
- package/dist/route-hydration-contract.js +21 -0
- package/dist/route-hydration-contract.js.map +1 -0
- package/dist/route-module-loader.d.ts +10 -0
- package/dist/route-module-loader.d.ts.map +1 -0
- package/dist/route-module-loader.js +61 -0
- package/dist/route-module-loader.js.map +1 -0
- package/dist/routes.js +6 -0
- package/dist/routes.js.map +1 -1
- package/dist/serve.d.ts +13 -0
- package/dist/serve.d.ts.map +1 -1
- package/dist/serve.js +63 -28
- package/dist/serve.js.map +1 -1
- package/dist/server-action-inference.d.ts.map +1 -1
- package/dist/server-action-inference.js +37 -1
- package/dist/server-action-inference.js.map +1 -1
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +2 -1
- package/dist/vite.js.map +1 -1
- package/package.json +11 -11
- package/src/adapters/aws-lambda.ts +21 -28
- package/src/build.ts +9 -6
- package/src/client-route-inference.ts +18 -0
- package/src/client.ts +124 -24
- package/src/layout-composer.ts +142 -0
- package/src/metadata.ts +578 -0
- package/src/middleware.ts +153 -0
- package/src/module-runner.ts +3 -2
- package/src/navigation-runtime.ts +16 -0
- package/src/preload-policy.ts +89 -0
- package/src/render.ts +75 -986
- package/src/route-hydration-contract.ts +22 -0
- package/src/route-module-loader.ts +95 -0
- package/src/routes.ts +8 -0
- package/src/serve.ts +110 -29
- package/src/server-action-inference.ts +49 -0
- package/src/vite.ts +6 -4
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export {
|
|
2
|
+
collectClientRouteReferences,
|
|
3
|
+
compilerModuleContextForSource,
|
|
4
|
+
createClientRouteInferenceCache,
|
|
5
|
+
detectClientNavigationHint,
|
|
6
|
+
detectNavigationRuntimeHint,
|
|
7
|
+
formatClientRouteInferenceDiagnostic,
|
|
8
|
+
inferClientRouteModule,
|
|
9
|
+
isClientRouteModule,
|
|
10
|
+
isClientRouteSource,
|
|
11
|
+
routeToClientManifestEntry,
|
|
12
|
+
type ClientReferenceImport,
|
|
13
|
+
type ClientRouteInferenceCache,
|
|
14
|
+
type ClientRouteInferenceDiagnostic,
|
|
15
|
+
type ClientRouteInferenceResult,
|
|
16
|
+
type ClientRouteManifestEntry,
|
|
17
|
+
type ClientRouteReferenceResult,
|
|
18
|
+
} from "./client.js";
|
package/src/client.ts
CHANGED
|
@@ -3,15 +3,17 @@ import { readFile, stat } from "node:fs/promises";
|
|
|
3
3
|
import { builtinModules } from "node:module";
|
|
4
4
|
import { dirname, extname, join, relative, sep } from "node:path";
|
|
5
5
|
import {
|
|
6
|
+
analyzeBoundaryGraph,
|
|
6
7
|
collectClientRouteModuleAnalysis,
|
|
7
8
|
formatDiagnostic,
|
|
8
9
|
type ComponentMetadata,
|
|
10
|
+
type BoundaryGraphResult,
|
|
9
11
|
type ClientRouteModuleAnalysis,
|
|
10
12
|
type ClientRouteStaticImportReference,
|
|
11
|
-
type ClientReferenceMetadata,
|
|
12
13
|
type StaticImportReference,
|
|
13
14
|
type TopLevelExportRenderInfo,
|
|
14
15
|
} from "@reckona/mreact-compiler";
|
|
16
|
+
import type { ClientReferenceMetadata } from "@reckona/mreact-shared/compiler-contract";
|
|
15
17
|
import {
|
|
16
18
|
collectClientRouteModuleAnalysisFromContext,
|
|
17
19
|
createCompilerModuleContext,
|
|
@@ -29,6 +31,11 @@ import {
|
|
|
29
31
|
} from "./bundle-pipeline.js";
|
|
30
32
|
import type { AppRoute } from "./routes.js";
|
|
31
33
|
import { existingRouteShellCandidates } from "./route-shells.js";
|
|
34
|
+
import {
|
|
35
|
+
routeDataScriptIds,
|
|
36
|
+
routeDataScriptSelector,
|
|
37
|
+
routeHydrationContract,
|
|
38
|
+
} from "./route-hydration-contract.js";
|
|
32
39
|
import { stripRouteClientSource } from "./route-source.js";
|
|
33
40
|
import { hasJsxSyntax } from "./source-jsx.js";
|
|
34
41
|
import { sourceModuleCandidates } from "./source-modules.js";
|
|
@@ -103,6 +110,8 @@ export interface ClientRouteInferenceResult {
|
|
|
103
110
|
}
|
|
104
111
|
|
|
105
112
|
interface ClientRouteModuleInferenceResult extends ClientRouteInferenceResult {
|
|
113
|
+
boundaryGraphFallbackCandidate: boolean;
|
|
114
|
+
boundaryGraphFallbackRequired: boolean;
|
|
106
115
|
clientBoundaryExportNames: string[];
|
|
107
116
|
clientBoundaryModule: boolean;
|
|
108
117
|
nestedClientExportNames: string[];
|
|
@@ -211,9 +220,20 @@ export async function inferClientRouteModule(options: {
|
|
|
211
220
|
seen: new Set(),
|
|
212
221
|
sourceTransform,
|
|
213
222
|
});
|
|
223
|
+
const mergedRouteInference = routeInference.boundaryGraphFallbackRequired
|
|
224
|
+
? mergeClientRouteInference(
|
|
225
|
+
routeInference,
|
|
226
|
+
await inferClientRouteModuleBoundaryGraph({
|
|
227
|
+
cache,
|
|
228
|
+
code,
|
|
229
|
+
filename: options.filename,
|
|
230
|
+
sourceTransform,
|
|
231
|
+
}),
|
|
232
|
+
)
|
|
233
|
+
: routeInference;
|
|
214
234
|
|
|
215
235
|
if (options.appDir === undefined) {
|
|
216
|
-
return
|
|
236
|
+
return mergedRouteInference;
|
|
217
237
|
}
|
|
218
238
|
|
|
219
239
|
const shellInferences = await inferClientRouteShellModules({
|
|
@@ -224,10 +244,10 @@ export async function inferClientRouteModule(options: {
|
|
|
224
244
|
});
|
|
225
245
|
|
|
226
246
|
return {
|
|
227
|
-
client:
|
|
228
|
-
clientBoundaryImports:
|
|
247
|
+
client: mergedRouteInference.client || shellInferences.some((inference) => inference.client),
|
|
248
|
+
clientBoundaryImports: mergedRouteInference.clientBoundaryImports,
|
|
229
249
|
diagnostics: [
|
|
230
|
-
...
|
|
250
|
+
...mergedRouteInference.diagnostics,
|
|
231
251
|
...shellInferences.flatMap((inference) => inference.diagnostics),
|
|
232
252
|
],
|
|
233
253
|
};
|
|
@@ -239,6 +259,68 @@ export async function inferClientRouteModule(options: {
|
|
|
239
259
|
}
|
|
240
260
|
}
|
|
241
261
|
|
|
262
|
+
async function inferClientRouteModuleBoundaryGraph(options: {
|
|
263
|
+
cache: ClientRouteInferenceCache;
|
|
264
|
+
code: string;
|
|
265
|
+
filename: string;
|
|
266
|
+
sourceTransform?: ClientRouteSourceTransform | undefined;
|
|
267
|
+
}): Promise<ClientRouteInferenceResult> {
|
|
268
|
+
const graph = await analyzeBoundaryGraph({
|
|
269
|
+
entries: [{ file: options.filename, kind: "route-page" }],
|
|
270
|
+
readModule: async (file) => {
|
|
271
|
+
if (file === options.filename) {
|
|
272
|
+
return options.code;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return await readClientRouteSource({
|
|
276
|
+
cache: options.cache,
|
|
277
|
+
filename: file,
|
|
278
|
+
sourceTransform: options.sourceTransform,
|
|
279
|
+
});
|
|
280
|
+
},
|
|
281
|
+
resolveModule: async ({ importer, source }) =>
|
|
282
|
+
await resolveAppLocalModule({
|
|
283
|
+
allowExplicitNonSource: options.sourceTransform !== undefined,
|
|
284
|
+
cache: options.cache,
|
|
285
|
+
importer,
|
|
286
|
+
specifier: source,
|
|
287
|
+
}),
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
return clientRouteInferenceFromBoundaryGraph(graph, options.filename);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function clientRouteInferenceFromBoundaryGraph(
|
|
294
|
+
graph: BoundaryGraphResult,
|
|
295
|
+
filename: string,
|
|
296
|
+
): ClientRouteInferenceResult {
|
|
297
|
+
const clientBoundaryImports = graph.clientBoundaries
|
|
298
|
+
.filter((boundary) => boundary.importerFile === filename)
|
|
299
|
+
.map((boundary) => boundary.source);
|
|
300
|
+
const clientRoute = graph.modules.some(
|
|
301
|
+
(module) => module.file === filename && module.classification === "client-route",
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
return {
|
|
305
|
+
client: clientRoute || clientBoundaryImports.length > 0,
|
|
306
|
+
clientBoundaryImports,
|
|
307
|
+
diagnostics: [],
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function mergeClientRouteInference(
|
|
312
|
+
left: ClientRouteInferenceResult,
|
|
313
|
+
right: ClientRouteInferenceResult,
|
|
314
|
+
): ClientRouteInferenceResult {
|
|
315
|
+
return {
|
|
316
|
+
client: left.client || right.client,
|
|
317
|
+
clientBoundaryImports: Array.from(
|
|
318
|
+
new Set([...left.clientBoundaryImports, ...right.clientBoundaryImports]),
|
|
319
|
+
),
|
|
320
|
+
diagnostics: [...left.diagnostics, ...right.diagnostics],
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
242
324
|
export async function collectClientRouteReferences(options: {
|
|
243
325
|
appDir?: string | undefined;
|
|
244
326
|
cache?: ClientRouteInferenceCache | undefined;
|
|
@@ -534,6 +616,7 @@ async function inferClientRouteModuleSource(options: {
|
|
|
534
616
|
const nestedClientExportNames = new Set<string>();
|
|
535
617
|
const clientReferenceSourceFiles: string[] = [];
|
|
536
618
|
const diagnostics: ClientRouteInferenceDiagnostic[] = [];
|
|
619
|
+
let boundaryGraphFallbackRequired = false;
|
|
537
620
|
let clientProxy = false;
|
|
538
621
|
let nestedClient = false;
|
|
539
622
|
const exportInfo = analysis.topLevelExportRenderInfo;
|
|
@@ -605,6 +688,9 @@ async function inferClientRouteModuleSource(options: {
|
|
|
605
688
|
diagnostics.push(...imported.diagnostics);
|
|
606
689
|
|
|
607
690
|
if (!imported.client) {
|
|
691
|
+
if (rendered && imported.boundaryGraphFallbackCandidate) {
|
|
692
|
+
boundaryGraphFallbackRequired = true;
|
|
693
|
+
}
|
|
608
694
|
if (imported.serverOnlyClientRuntime && rendered) {
|
|
609
695
|
diagnostics.push(
|
|
610
696
|
serverOnlyClientImportReferenceDiagnostic({
|
|
@@ -706,11 +792,16 @@ async function inferClientRouteModuleSource(options: {
|
|
|
706
792
|
} else if (exported.client) {
|
|
707
793
|
nestedClient = true;
|
|
708
794
|
clientReferenceSourceFiles.push(resolved);
|
|
795
|
+
} else if (exported.boundaryGraphFallbackCandidate) {
|
|
796
|
+
boundaryGraphFallbackRequired = true;
|
|
709
797
|
}
|
|
710
798
|
}
|
|
711
799
|
}
|
|
712
800
|
|
|
713
801
|
return {
|
|
802
|
+
boundaryGraphFallbackCandidate:
|
|
803
|
+
analysis.staticExports.length > 0 || boundaryGraphFallbackRequired,
|
|
804
|
+
boundaryGraphFallbackRequired,
|
|
714
805
|
client:
|
|
715
806
|
clientBoundaryImports.length > 0 ||
|
|
716
807
|
clientBoundaryExportNames.size > 0 ||
|
|
@@ -735,6 +826,8 @@ function emptyClientRouteModuleInferenceResult(
|
|
|
735
826
|
overrides: Partial<ClientRouteModuleInferenceResult> = {},
|
|
736
827
|
): ClientRouteModuleInferenceResult {
|
|
737
828
|
return {
|
|
829
|
+
boundaryGraphFallbackCandidate: false,
|
|
830
|
+
boundaryGraphFallbackRequired: false,
|
|
738
831
|
client: false,
|
|
739
832
|
clientBoundaryImports: [],
|
|
740
833
|
clientBoundaryExportNames: [],
|
|
@@ -1287,7 +1380,7 @@ export function withHydrationMarkers(options: {
|
|
|
1287
1380
|
export function withRouteMarkers(options: { html: string; routePath: string }): string {
|
|
1288
1381
|
const routeId = routeIdForPath(options.routePath);
|
|
1289
1382
|
|
|
1290
|
-
return `<div
|
|
1383
|
+
return `<div ${routeHydrationContract.routeMarkerAttribute}="${escapeHtmlAttribute(routeId)}">${options.html}</div>`;
|
|
1291
1384
|
}
|
|
1292
1385
|
|
|
1293
1386
|
export function hydrationMarkerParts(options: {
|
|
@@ -1299,6 +1392,9 @@ export function hydrationMarkerParts(options: {
|
|
|
1299
1392
|
}): { prefix: string; suffix: string } {
|
|
1300
1393
|
const routeId = routeIdForPath(options.routePath);
|
|
1301
1394
|
const escapedRouteId = escapeHtmlAttribute(routeId);
|
|
1395
|
+
const [propsScriptId, clientReferencesScriptId] = routeDataScriptIds(routeId).map((id) =>
|
|
1396
|
+
escapeHtmlAttribute(id),
|
|
1397
|
+
);
|
|
1302
1398
|
const propsJson = escapeScriptJson(JSON.stringify(options.props));
|
|
1303
1399
|
const script = options.script ?? clientScriptForPath(options.routePath);
|
|
1304
1400
|
const scriptSrc = assetPath(script, options.assetBaseUrl ?? "/_mreact/client/");
|
|
@@ -1308,13 +1404,13 @@ export function hydrationMarkerParts(options: {
|
|
|
1308
1404
|
: escapeScriptJson(JSON.stringify(options.clientReferenceManifest));
|
|
1309
1405
|
|
|
1310
1406
|
return {
|
|
1311
|
-
prefix: `<div
|
|
1407
|
+
prefix: `<div ${routeHydrationContract.routeMarkerAttribute}="${escapedRouteId}">`,
|
|
1312
1408
|
suffix: [
|
|
1313
1409
|
"</div>",
|
|
1314
|
-
`<script type="application/json" id="
|
|
1410
|
+
`<script type="application/json" id="${propsScriptId}">${propsJson}</script>`,
|
|
1315
1411
|
clientReferencesJson === undefined
|
|
1316
1412
|
? undefined
|
|
1317
|
-
: `<script type="application/json" id="
|
|
1413
|
+
: `<script type="application/json" id="${clientReferencesScriptId}">${clientReferencesJson}</script>`,
|
|
1318
1414
|
`<script type="module" src="${escapeHtmlAttribute(scriptSrc)}"></script>`,
|
|
1319
1415
|
]
|
|
1320
1416
|
.filter((part): part is string => part !== undefined)
|
|
@@ -1670,7 +1766,7 @@ function __mreactResolveRouteNode(value) {
|
|
|
1670
1766
|
const boundaryOnlyHydrationBlock = routeRequiresFullHydration
|
|
1671
1767
|
? ""
|
|
1672
1768
|
: `${routeCellHydrationIndent}if (!__mreactHasNonSerializableClientBoundaries(__mreactMarker) && __mreactHydrateClientBoundaries(document, __mreactClientReferences, __mreactClientReferenceComponents)) {
|
|
1673
|
-
${routeCellHydrationIndent} __mreactMarker.setAttribute(
|
|
1769
|
+
${routeCellHydrationIndent} __mreactMarker.setAttribute(${JSON.stringify(routeHydrationContract.hydratedAttribute)}, "true");
|
|
1674
1770
|
${routeCellHydrationIndent} return;
|
|
1675
1771
|
${routeCellHydrationIndent}}
|
|
1676
1772
|
`;
|
|
@@ -1681,8 +1777,12 @@ ${routeCellHydrationIndent}}
|
|
|
1681
1777
|
const entry = `${routeCellEffectImport}${routeCleanupScopeImport}${emitCompatClientReferenceImportBlock(compatClientReferenceNames)}${clientReferenceImportBlock}${routeHydrationCode}
|
|
1682
1778
|
|
|
1683
1779
|
const __mreactRouteId = ${JSON.stringify(routeId)};
|
|
1684
|
-
const __mreactRouteStateSignature = ${JSON.stringify(routeStateSignature)};
|
|
1685
|
-
const
|
|
1780
|
+
const __mreactRouteStateSignature = ${JSON.stringify(routeStateSignature)};
|
|
1781
|
+
const __mreactRouteMarkerAttribute = ${JSON.stringify(routeHydrationContract.routeMarkerAttribute)};
|
|
1782
|
+
const __mreactRouteHydratedAttribute = ${JSON.stringify(routeHydrationContract.hydratedAttribute)};
|
|
1783
|
+
const __mreactPropsScriptPrefix = ${JSON.stringify(routeHydrationContract.propsScriptPrefix)};
|
|
1784
|
+
const __mreactClientReferencesScriptPrefix = ${JSON.stringify(routeHydrationContract.clientReferencesScriptPrefix)};
|
|
1785
|
+
const __mreactGlobal = globalThis;
|
|
1686
1786
|
${navigationStateDeclaration}
|
|
1687
1787
|
${routeCellStateDeclaration}
|
|
1688
1788
|
${routeCleanupStateDeclaration}
|
|
@@ -1692,9 +1792,9 @@ ${routeNodeResolver}
|
|
|
1692
1792
|
|
|
1693
1793
|
export function __mreactHydrateRoute() {
|
|
1694
1794
|
__mreactApplyOutOfOrderFragments(document);
|
|
1695
|
-
const __mreactMarker = document.querySelector(\`[
|
|
1696
|
-
const __mreactPropsElement = document.getElementById(
|
|
1697
|
-
const __mreactClientReferencesElement = document.getElementById(
|
|
1795
|
+
const __mreactMarker = document.querySelector(\`[\${__mreactRouteMarkerAttribute}="\${__mreactRouteId}"]\`);
|
|
1796
|
+
const __mreactPropsElement = document.getElementById(\`\${__mreactPropsScriptPrefix}\${__mreactRouteId}\`);
|
|
1797
|
+
const __mreactClientReferencesElement = document.getElementById(\`\${__mreactClientReferencesScriptPrefix}\${__mreactRouteId}\`);
|
|
1698
1798
|
const __mreactProps = __mreactPropsElement?.textContent === undefined
|
|
1699
1799
|
? {}
|
|
1700
1800
|
: JSON.parse(__mreactPropsElement.textContent);
|
|
@@ -1711,7 +1811,7 @@ export function __mreactHydrateRoute() {
|
|
|
1711
1811
|
${routeCellHydrationStart}${routeCleanupHydrationStart}${boundaryOnlyHydrationBlock}${routeComponentGuard}${routeCellHydrationIndent}const __mreactNode = ${routeNodeExpression};
|
|
1712
1812
|
${routeCellHydrationIndent}__mreactResumeRoute(__mreactMarker, __mreactNode);
|
|
1713
1813
|
${routeCellHydrationIndent}__mreactHydrateClientBoundaries(document, __mreactClientReferences, __mreactClientReferenceComponents);
|
|
1714
|
-
${routeCellHydrationIndent}__mreactMarker.setAttribute(
|
|
1814
|
+
${routeCellHydrationIndent}__mreactMarker.setAttribute(__mreactRouteHydratedAttribute, "true");
|
|
1715
1815
|
${routeCellHydrationEnd}}
|
|
1716
1816
|
${routeCellDropFunction}
|
|
1717
1817
|
${routeCleanupFunction}
|
|
@@ -2098,15 +2198,15 @@ function __mreactApplyNavigationHtml(html, url) {
|
|
|
2098
2198
|
const template = document.createElement("template");
|
|
2099
2199
|
template.innerHTML = html.replace(/^\\s*<!doctype html>/i, "");
|
|
2100
2200
|
__mreactApplyOutOfOrderFragments(template.content);
|
|
2101
|
-
const nextMarker = template.content.querySelector("[
|
|
2102
|
-
const currentMarker = document.querySelector("[
|
|
2201
|
+
const nextMarker = template.content.querySelector("[${routeHydrationContract.routeMarkerAttribute}]");
|
|
2202
|
+
const currentMarker = document.querySelector("[${routeHydrationContract.routeMarkerAttribute}]");
|
|
2103
2203
|
|
|
2104
2204
|
if (nextMarker === null || currentMarker === null) {
|
|
2105
2205
|
return false;
|
|
2106
2206
|
}
|
|
2107
2207
|
|
|
2108
|
-
const currentRouteId = currentMarker.getAttribute("
|
|
2109
|
-
const nextRouteId = nextMarker.getAttribute("
|
|
2208
|
+
const currentRouteId = currentMarker.getAttribute("${routeHydrationContract.routeMarkerAttribute}");
|
|
2209
|
+
const nextRouteId = nextMarker.getAttribute("${routeHydrationContract.routeMarkerAttribute}");
|
|
2110
2210
|
|
|
2111
2211
|
__mreactSyncHeadMetadata(template.content, html);
|
|
2112
2212
|
__mreactResumeNode(currentMarker, nextMarker);
|
|
@@ -2114,7 +2214,7 @@ ${routeCleanupNavigationDispose} __mreactSyncRouteDataScripts(template.content,
|
|
|
2114
2214
|
|
|
2115
2215
|
const script = template.content.querySelector('script[type="module"][src]')?.getAttribute("src");
|
|
2116
2216
|
if (script !== null && script !== undefined) {
|
|
2117
|
-
void import(/* @vite-ignore */ script).then((module) => module.
|
|
2217
|
+
void import(/* @vite-ignore */ script).then((module) => module.${routeHydrationContract.routeHydrateExport}?.());
|
|
2118
2218
|
}
|
|
2119
2219
|
|
|
2120
2220
|
__mreactApplyOutOfOrderFragments(document);
|
|
@@ -2207,15 +2307,15 @@ function __mreactRouteDataScriptIds(...routeIds) {
|
|
|
2207
2307
|
continue;
|
|
2208
2308
|
}
|
|
2209
2309
|
|
|
2210
|
-
ids.add(
|
|
2211
|
-
ids.add(
|
|
2310
|
+
ids.add(\`${routeHydrationContract.propsScriptPrefix}\${routeId}\`);
|
|
2311
|
+
ids.add(\`${routeHydrationContract.clientReferencesScriptPrefix}\${routeId}\`);
|
|
2212
2312
|
}
|
|
2213
2313
|
|
|
2214
2314
|
return ids;
|
|
2215
2315
|
}
|
|
2216
2316
|
|
|
2217
2317
|
function __mreactRouteDataScriptSelector() {
|
|
2218
|
-
return
|
|
2318
|
+
return ${JSON.stringify(routeDataScriptSelector())};
|
|
2219
2319
|
}
|
|
2220
2320
|
|
|
2221
2321
|
function __mreactCurrentHistoryState(url) {
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { relative, sep } from "node:path";
|
|
2
|
+
import { escapeHtmlAttribute } from "@reckona/mreact-shared/html-escape";
|
|
3
|
+
|
|
4
|
+
export interface SlotRenderContext {
|
|
5
|
+
consumedSlots: Set<string>;
|
|
6
|
+
namedSlots: Readonly<Record<string, string>>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface ShellFile {
|
|
10
|
+
file: string;
|
|
11
|
+
id: string;
|
|
12
|
+
kind: "layout" | "template";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface RenderedShellParts {
|
|
16
|
+
prefix: string;
|
|
17
|
+
suffix: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function createSlotRenderContext(
|
|
21
|
+
namedSlots: Readonly<Record<string, string>> = {},
|
|
22
|
+
): SlotRenderContext {
|
|
23
|
+
return {
|
|
24
|
+
consumedSlots: new Set(),
|
|
25
|
+
namedSlots,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function splitLayoutSlot(
|
|
30
|
+
layoutHtml: string,
|
|
31
|
+
slotContext: SlotRenderContext = createSlotRenderContext(),
|
|
32
|
+
): RenderedShellParts {
|
|
33
|
+
const html = replaceNamedLayoutSlots(layoutHtml, slotContext);
|
|
34
|
+
const match = findDefaultLayoutSlot(html);
|
|
35
|
+
|
|
36
|
+
if (match === null) {
|
|
37
|
+
return { prefix: html, suffix: "" };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
prefix: html.slice(0, match.index),
|
|
42
|
+
suffix: html.slice(match.index + match[0].length),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function markShellBoundary(html: string, shell: ShellFile): string {
|
|
47
|
+
const attributeName =
|
|
48
|
+
shell.kind === "layout" ? "data-mreact-layout-boundary" : "data-mreact-template-boundary";
|
|
49
|
+
|
|
50
|
+
if (html.includes(`${attributeName}=`)) {
|
|
51
|
+
return html;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return html.replace(
|
|
55
|
+
/<([A-Za-z][^\s/>]*)([^>]*)>/,
|
|
56
|
+
`<$1$2 ${attributeName}="${escapeHtmlAttribute(shell.id)}">`,
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function shellBoundaryId(appDir: string, directory: string): string {
|
|
61
|
+
const relativeDirectory = relative(appDir, directory);
|
|
62
|
+
|
|
63
|
+
return relativeDirectory === ""
|
|
64
|
+
? "root"
|
|
65
|
+
: relativeDirectory.replaceAll(sep, "/").replace(/[^A-Za-z0-9_$/-]/g, "_");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function warnUnconsumedRouteSlots(options: {
|
|
69
|
+
appDir: string;
|
|
70
|
+
pageFile: string;
|
|
71
|
+
serverModuleCacheVersion: string | undefined;
|
|
72
|
+
slotContext: SlotRenderContext;
|
|
73
|
+
}): void {
|
|
74
|
+
if (options.serverModuleCacheVersion !== undefined) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const slotNames = Object.keys(options.slotContext.namedSlots);
|
|
79
|
+
if (slotNames.length === 0) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const routeLabel = relative(options.appDir, options.pageFile).replaceAll(sep, "/");
|
|
84
|
+
|
|
85
|
+
for (const name of slotNames) {
|
|
86
|
+
if (name === "default") {
|
|
87
|
+
console.warn(
|
|
88
|
+
`[mreact] ${routeLabel}: slots.default does not target <Slot />; use the page body for default slot content.`,
|
|
89
|
+
);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (!options.slotContext.consumedSlots.has(name)) {
|
|
94
|
+
console.warn(
|
|
95
|
+
`[mreact] ${routeLabel}: slots.{${name}} is not consumed by any ancestor layout or template.`,
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function replaceNamedLayoutSlots(layoutHtml: string, slotContext: SlotRenderContext): string {
|
|
102
|
+
return layoutHtml.replace(SLOT_TAG_PATTERN, (source, openAttributes: string) => {
|
|
103
|
+
const name = readSlotName(openAttributes);
|
|
104
|
+
|
|
105
|
+
if (name === undefined || name === "default") {
|
|
106
|
+
return source;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (Object.hasOwn(slotContext.namedSlots, name)) {
|
|
110
|
+
slotContext.consumedSlots.add(name);
|
|
111
|
+
return slotContext.namedSlots[name] ?? "";
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return "";
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const SLOT_TAG_PATTERN = /<slot\b([^>]*)>(?:<\/slot\s*>)?/g;
|
|
119
|
+
|
|
120
|
+
function findDefaultLayoutSlot(html: string): RegExpExecArray | null {
|
|
121
|
+
SLOT_TAG_PATTERN.lastIndex = 0;
|
|
122
|
+
|
|
123
|
+
for (;;) {
|
|
124
|
+
const match = SLOT_TAG_PATTERN.exec(html);
|
|
125
|
+
|
|
126
|
+
if (match === null) {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const name = readSlotName(match[1] ?? "");
|
|
131
|
+
|
|
132
|
+
if (name === undefined || name === "default") {
|
|
133
|
+
return match;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function readSlotName(attributes: string): string | undefined {
|
|
139
|
+
const match = /\bname\s*=\s*(?:"([^"]*)"|'([^']*)')/.exec(attributes);
|
|
140
|
+
|
|
141
|
+
return match?.[1] ?? match?.[2];
|
|
142
|
+
}
|