@reckona/mreact-router 0.0.82 → 0.0.84

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 (80) hide show
  1. package/dist/adapters/aws-lambda.d.ts.map +1 -1
  2. package/dist/adapters/aws-lambda.js +17 -22
  3. package/dist/adapters/aws-lambda.js.map +1 -1
  4. package/dist/build.d.ts.map +1 -1
  5. package/dist/build.js +3 -1
  6. package/dist/build.js.map +1 -1
  7. package/dist/client-route-inference.d.ts +2 -0
  8. package/dist/client-route-inference.d.ts.map +1 -0
  9. package/dist/client-route-inference.js +2 -0
  10. package/dist/client-route-inference.js.map +1 -0
  11. package/dist/client.d.ts +2 -1
  12. package/dist/client.d.ts.map +1 -1
  13. package/dist/client.js +25 -19
  14. package/dist/client.js.map +1 -1
  15. package/dist/layout-composer.d.ts +24 -0
  16. package/dist/layout-composer.d.ts.map +1 -0
  17. package/dist/layout-composer.js +83 -0
  18. package/dist/layout-composer.js.map +1 -0
  19. package/dist/metadata.d.ts +9 -0
  20. package/dist/metadata.d.ts.map +1 -0
  21. package/dist/metadata.js +419 -0
  22. package/dist/metadata.js.map +1 -0
  23. package/dist/middleware.d.ts +22 -0
  24. package/dist/middleware.d.ts.map +1 -0
  25. package/dist/middleware.js +94 -0
  26. package/dist/middleware.js.map +1 -0
  27. package/dist/module-runner.d.ts +2 -2
  28. package/dist/module-runner.d.ts.map +1 -1
  29. package/dist/module-runner.js +1 -1
  30. package/dist/module-runner.js.map +1 -1
  31. package/dist/navigation-runtime.d.ts +2 -0
  32. package/dist/navigation-runtime.d.ts.map +1 -0
  33. package/dist/navigation-runtime.js +2 -0
  34. package/dist/navigation-runtime.js.map +1 -0
  35. package/dist/preload-policy.d.ts +24 -0
  36. package/dist/preload-policy.d.ts.map +1 -0
  37. package/dist/preload-policy.js +45 -0
  38. package/dist/preload-policy.js.map +1 -0
  39. package/dist/render.d.ts +1 -3
  40. package/dist/render.d.ts.map +1 -1
  41. package/dist/render.js +32 -676
  42. package/dist/render.js.map +1 -1
  43. package/dist/route-hydration-contract.d.ts +11 -0
  44. package/dist/route-hydration-contract.d.ts.map +1 -0
  45. package/dist/route-hydration-contract.js +21 -0
  46. package/dist/route-hydration-contract.js.map +1 -0
  47. package/dist/route-module-loader.d.ts +10 -0
  48. package/dist/route-module-loader.d.ts.map +1 -0
  49. package/dist/route-module-loader.js +61 -0
  50. package/dist/route-module-loader.js.map +1 -0
  51. package/dist/route-source.d.ts.map +1 -1
  52. package/dist/route-source.js +7 -5
  53. package/dist/route-source.js.map +1 -1
  54. package/dist/routes.js +6 -0
  55. package/dist/routes.js.map +1 -1
  56. package/dist/serve.d.ts +13 -0
  57. package/dist/serve.d.ts.map +1 -1
  58. package/dist/serve.js +63 -28
  59. package/dist/serve.js.map +1 -1
  60. package/dist/vite.d.ts.map +1 -1
  61. package/dist/vite.js +2 -1
  62. package/dist/vite.js.map +1 -1
  63. package/package.json +11 -11
  64. package/src/adapters/aws-lambda.ts +21 -28
  65. package/src/build.ts +9 -6
  66. package/src/client-route-inference.ts +18 -0
  67. package/src/client.ts +32 -20
  68. package/src/layout-composer.ts +142 -0
  69. package/src/metadata.ts +578 -0
  70. package/src/middleware.ts +153 -0
  71. package/src/module-runner.ts +3 -2
  72. package/src/navigation-runtime.ts +16 -0
  73. package/src/preload-policy.ts +89 -0
  74. package/src/render.ts +75 -986
  75. package/src/route-hydration-contract.ts +22 -0
  76. package/src/route-module-loader.ts +95 -0
  77. package/src/route-source.ts +9 -4
  78. package/src/routes.ts +8 -0
  79. package/src/serve.ts +110 -29
  80. package/src/vite.ts +6 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reckona/mreact-router",
3
- "version": "0.0.82",
3
+ "version": "0.0.84",
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-compiler": "0.0.82",
109
- "@reckona/mreact-query": "0.0.82",
110
- "@reckona/mreact-reactive-core": "0.0.82",
111
- "@reckona/mreact-compat": "0.0.82",
112
- "@reckona/mreact-devtools": "0.0.82",
113
- "@reckona/mreact": "0.0.82",
114
- "@reckona/mreact-reactive-dom": "0.0.82",
115
- "@reckona/mreact-server": "0.0.82",
116
- "@reckona/mreact-shared": "0.0.82"
108
+ "@reckona/mreact-compat": "0.0.84",
109
+ "@reckona/mreact": "0.0.84",
110
+ "@reckona/mreact-devtools": "0.0.84",
111
+ "@reckona/mreact-query": "0.0.84",
112
+ "@reckona/mreact-reactive-core": "0.0.84",
113
+ "@reckona/mreact-compiler": "0.0.84",
114
+ "@reckona/mreact-reactive-dom": "0.0.84",
115
+ "@reckona/mreact-shared": "0.0.84",
116
+ "@reckona/mreact-server": "0.0.84"
117
117
  },
118
118
  "peerDependencies": {
119
119
  "vite": ">=8 <9"
120
120
  },
121
121
  "optionalDependencies": {
122
- "@reckona/mreact-router-native": "0.0.82"
122
+ "@reckona/mreact-router-native": "0.0.84"
123
123
  }
124
124
  }
@@ -6,6 +6,10 @@ import { dirname, join } from "node:path";
6
6
  import type { AppRouterServerActionOptions } from "../actions.js";
7
7
  import type { AppRouterCache } from "../cache.js";
8
8
  import type { AppRouterImportPolicy } from "../import-policy.js";
9
+ import {
10
+ normalizeBuiltAppRuntimePreloadStrategy,
11
+ type NormalizedBuiltAppRuntimePreloadStrategy,
12
+ } from "../preload-policy.js";
9
13
  import {
10
14
  emitRouterLog,
11
15
  logDurationMs,
@@ -21,11 +25,10 @@ import type { RouterInstrumentation } from "../trace.js";
21
25
  export type { RouterInstrumentation } from "../trace.js";
22
26
  export type { AppRouterImportPolicy } from "../import-policy.js";
23
27
  import {
28
+ createBuiltRequestRuntime,
24
29
  preloadBuiltAppRuntime,
25
- renderBuiltAppRequest,
26
30
  resolveRequestHost,
27
31
  warnIfImplicitHostTrust,
28
- type BuiltAppRuntimePreloadStrategy,
29
32
  type AppRouterPrerenderStore,
30
33
  type RequestHostPolicy,
31
34
  type ResponseSinkStrategy,
@@ -110,9 +113,7 @@ export type AwsLambdaPreloadStrategy =
110
113
  wait?: "background" | "before-render" | "first-request" | undefined;
111
114
  };
112
115
 
113
- type NormalizedAwsLambdaPreloadStrategy = BuiltAppRuntimePreloadStrategy & {
114
- wait: "background" | "before-render" | "first-request";
115
- };
116
+ type NormalizedAwsLambdaPreloadStrategy = NormalizedBuiltAppRuntimePreloadStrategy;
116
117
 
117
118
  type AwsLambdaDefaultPreloadMode = "all" | "middleware";
118
119
 
@@ -186,22 +187,24 @@ function createAwsLambdaRequestHandlerFromRuntime(
186
187
  );
187
188
  const importPolicy = await resolveAwsLambdaImportPolicy(options);
188
189
  const renderStartedAt = phaseStartedAt(phases);
190
+ const builtRuntime = await createBuiltRequestRuntime({
191
+ immutableRuntime: true,
192
+ importPolicy,
193
+ outDir: options.outDir,
194
+ runtimeDir,
195
+ });
189
196
  const preload = awsLambdaRenderPreload(
190
197
  options,
191
198
  runtimePreloadPromise,
192
199
  defaultPreloadMode,
193
200
  );
194
- const response = await renderBuiltAppRequest({
195
- outDir: options.outDir,
196
- importPolicy,
201
+ const response = await builtRuntime.render(request, {
197
202
  instrumentation: options.instrumentation,
198
203
  logger: awsLambdaRenderLogger(options),
199
204
  onResponse: options.onResponse,
200
205
  ...(preload === undefined ? {} : { preload }),
201
206
  prerenderStore: options.prerenderStore,
202
- request,
203
207
  routeCache: options.routeCache,
204
- runtimeDir,
205
208
  serverActions: options.serverActions,
206
209
  ...(options.sinkStrategy === undefined ? {} : { sinkStrategy: options.sinkStrategy }),
207
210
  });
@@ -416,19 +419,7 @@ function normalizeAwsLambdaPreload(
416
419
  strategy: AwsLambdaPreloadStrategy | undefined,
417
420
  defaultMode: AwsLambdaDefaultPreloadMode = "all",
418
421
  ): NormalizedAwsLambdaPreloadStrategy {
419
- if (strategy === undefined) {
420
- return { mode: defaultMode, wait: "background" };
421
- }
422
-
423
- if (typeof strategy === "string") {
424
- return { mode: strategy, wait: "background" };
425
- }
426
-
427
- return {
428
- mode: strategy.mode,
429
- ...(strategy.routes === undefined ? {} : { routes: strategy.routes }),
430
- wait: strategy.wait ?? "background",
431
- };
422
+ return normalizeBuiltAppRuntimePreloadStrategy(strategy, defaultMode);
432
423
  }
433
424
 
434
425
  function createAwsLambdaStreamingRequestHandlerFromRuntime<TContext = unknown>(
@@ -462,22 +453,24 @@ function createAwsLambdaStreamingRequestHandlerFromRuntime<TContext = unknown>(
462
453
  );
463
454
  const importPolicy = await resolveAwsLambdaImportPolicy(options);
464
455
  const renderStartedAt = phaseStartedAt(phases);
456
+ const builtRuntime = await createBuiltRequestRuntime({
457
+ immutableRuntime: true,
458
+ importPolicy,
459
+ outDir: options.outDir,
460
+ runtimeDir,
461
+ });
465
462
  const preload = awsLambdaRenderPreload(
466
463
  options,
467
464
  runtimePreloadPromise,
468
465
  defaultPreloadMode,
469
466
  );
470
- const response = await renderBuiltAppRequest({
471
- outDir: options.outDir,
472
- importPolicy,
467
+ const response = await builtRuntime.render(request, {
473
468
  instrumentation: options.instrumentation,
474
469
  logger: awsLambdaRenderLogger(options),
475
470
  onResponse: options.onResponse,
476
471
  ...(preload === undefined ? {} : { preload }),
477
472
  prerenderStore: options.prerenderStore,
478
- request,
479
473
  routeCache: options.routeCache,
480
- runtimeDir,
481
474
  serverActions: options.serverActions,
482
475
  ...(options.sinkStrategy === undefined ? {} : { sinkStrategy: options.sinkStrategy }),
483
476
  });
package/src/build.ts CHANGED
@@ -13,9 +13,6 @@ import {
13
13
  import { transformCompilerModuleContext } from "@reckona/mreact-compiler/internal";
14
14
  import type { ServerOutputMode } from "@reckona/mreact-compiler";
15
15
  import {
16
- buildClientRouteBatchOutput,
17
- buildNavigationRuntimeBundle,
18
- clientScriptForPath,
19
16
  compilerModuleContextForSource,
20
17
  collectClientRouteReferences,
21
18
  createClientRouteInferenceCache,
@@ -23,11 +20,16 @@ import {
23
20
  detectNavigationRuntimeHint,
24
21
  formatClientRouteInferenceDiagnostic,
25
22
  inferClientRouteModule,
26
- routeIdForPath,
27
- type BuildClientRouteOutputOptions,
28
23
  type ClientRouteManifestEntry,
29
24
  type ClientRouteInferenceCache,
30
- } from "./client.js";
25
+ } from "./client-route-inference.js";
26
+ import {
27
+ buildClientRouteBatchOutput,
28
+ buildNavigationRuntimeBundle,
29
+ clientScriptForPath,
30
+ routeIdForPath,
31
+ type BuildClientRouteOutputOptions,
32
+ } from "./navigation-runtime.js";
31
33
  import { bundleAppRouterSourceModule, importAppRouterSourceModule } from "./module-runner.js";
32
34
  import { scanAppRoutes } from "./routes.js";
33
35
  import type { AppRoute } from "./routes.js";
@@ -3099,6 +3101,7 @@ const here = dirname(fileURLToPath(import.meta.url));
3099
3101
  export const handler = await createPreloadedAwsLambdaRequestHandler({
3100
3102
  importPolicy: "generated",
3101
3103
  outDir: resolve(here, ${JSON.stringify(outDirRelativeToHandler)}),
3104
+ preload: { mode: "all" },
3102
3105
  timings: process.env.MREACT_ROUTER_TIMINGS === "1",
3103
3106
  });
3104
3107
  `;
@@ -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
@@ -10,10 +10,10 @@ import {
10
10
  type BoundaryGraphResult,
11
11
  type ClientRouteModuleAnalysis,
12
12
  type ClientRouteStaticImportReference,
13
- type ClientReferenceMetadata,
14
13
  type StaticImportReference,
15
14
  type TopLevelExportRenderInfo,
16
15
  } from "@reckona/mreact-compiler";
16
+ import type { ClientReferenceMetadata } from "@reckona/mreact-shared/compiler-contract";
17
17
  import {
18
18
  collectClientRouteModuleAnalysisFromContext,
19
19
  createCompilerModuleContext,
@@ -31,6 +31,11 @@ import {
31
31
  } from "./bundle-pipeline.js";
32
32
  import type { AppRoute } from "./routes.js";
33
33
  import { existingRouteShellCandidates } from "./route-shells.js";
34
+ import {
35
+ routeDataScriptIds,
36
+ routeDataScriptSelector,
37
+ routeHydrationContract,
38
+ } from "./route-hydration-contract.js";
34
39
  import { stripRouteClientSource } from "./route-source.js";
35
40
  import { hasJsxSyntax } from "./source-jsx.js";
36
41
  import { sourceModuleCandidates } from "./source-modules.js";
@@ -1375,7 +1380,7 @@ export function withHydrationMarkers(options: {
1375
1380
  export function withRouteMarkers(options: { html: string; routePath: string }): string {
1376
1381
  const routeId = routeIdForPath(options.routePath);
1377
1382
 
1378
- return `<div data-mreact-route-id="${escapeHtmlAttribute(routeId)}">${options.html}</div>`;
1383
+ return `<div ${routeHydrationContract.routeMarkerAttribute}="${escapeHtmlAttribute(routeId)}">${options.html}</div>`;
1379
1384
  }
1380
1385
 
1381
1386
  export function hydrationMarkerParts(options: {
@@ -1387,6 +1392,9 @@ export function hydrationMarkerParts(options: {
1387
1392
  }): { prefix: string; suffix: string } {
1388
1393
  const routeId = routeIdForPath(options.routePath);
1389
1394
  const escapedRouteId = escapeHtmlAttribute(routeId);
1395
+ const [propsScriptId, clientReferencesScriptId] = routeDataScriptIds(routeId).map((id) =>
1396
+ escapeHtmlAttribute(id),
1397
+ );
1390
1398
  const propsJson = escapeScriptJson(JSON.stringify(options.props));
1391
1399
  const script = options.script ?? clientScriptForPath(options.routePath);
1392
1400
  const scriptSrc = assetPath(script, options.assetBaseUrl ?? "/_mreact/client/");
@@ -1396,13 +1404,13 @@ export function hydrationMarkerParts(options: {
1396
1404
  : escapeScriptJson(JSON.stringify(options.clientReferenceManifest));
1397
1405
 
1398
1406
  return {
1399
- prefix: `<div data-mreact-route-id="${escapedRouteId}">`,
1407
+ prefix: `<div ${routeHydrationContract.routeMarkerAttribute}="${escapedRouteId}">`,
1400
1408
  suffix: [
1401
1409
  "</div>",
1402
- `<script type="application/json" id="mreact-props-${escapedRouteId}">${propsJson}</script>`,
1410
+ `<script type="application/json" id="${propsScriptId}">${propsJson}</script>`,
1403
1411
  clientReferencesJson === undefined
1404
1412
  ? undefined
1405
- : `<script type="application/json" id="mreact-client-references-${escapedRouteId}">${clientReferencesJson}</script>`,
1413
+ : `<script type="application/json" id="${clientReferencesScriptId}">${clientReferencesJson}</script>`,
1406
1414
  `<script type="module" src="${escapeHtmlAttribute(scriptSrc)}"></script>`,
1407
1415
  ]
1408
1416
  .filter((part): part is string => part !== undefined)
@@ -1758,7 +1766,7 @@ function __mreactResolveRouteNode(value) {
1758
1766
  const boundaryOnlyHydrationBlock = routeRequiresFullHydration
1759
1767
  ? ""
1760
1768
  : `${routeCellHydrationIndent}if (!__mreactHasNonSerializableClientBoundaries(__mreactMarker) && __mreactHydrateClientBoundaries(document, __mreactClientReferences, __mreactClientReferenceComponents)) {
1761
- ${routeCellHydrationIndent} __mreactMarker.setAttribute("data-mreact-hydrated", "true");
1769
+ ${routeCellHydrationIndent} __mreactMarker.setAttribute(${JSON.stringify(routeHydrationContract.hydratedAttribute)}, "true");
1762
1770
  ${routeCellHydrationIndent} return;
1763
1771
  ${routeCellHydrationIndent}}
1764
1772
  `;
@@ -1769,8 +1777,12 @@ ${routeCellHydrationIndent}}
1769
1777
  const entry = `${routeCellEffectImport}${routeCleanupScopeImport}${emitCompatClientReferenceImportBlock(compatClientReferenceNames)}${clientReferenceImportBlock}${routeHydrationCode}
1770
1778
 
1771
1779
  const __mreactRouteId = ${JSON.stringify(routeId)};
1772
- const __mreactRouteStateSignature = ${JSON.stringify(routeStateSignature)};
1773
- const __mreactGlobal = globalThis;
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;
1774
1786
  ${navigationStateDeclaration}
1775
1787
  ${routeCellStateDeclaration}
1776
1788
  ${routeCleanupStateDeclaration}
@@ -1780,9 +1792,9 @@ ${routeNodeResolver}
1780
1792
 
1781
1793
  export function __mreactHydrateRoute() {
1782
1794
  __mreactApplyOutOfOrderFragments(document);
1783
- const __mreactMarker = document.querySelector(\`[data-mreact-route-id="\${__mreactRouteId}"]\`);
1784
- const __mreactPropsElement = document.getElementById(\`mreact-props-\${__mreactRouteId}\`);
1785
- const __mreactClientReferencesElement = document.getElementById(\`mreact-client-references-\${__mreactRouteId}\`);
1795
+ const __mreactMarker = document.querySelector(\`[\${__mreactRouteMarkerAttribute}="\${__mreactRouteId}"]\`);
1796
+ const __mreactPropsElement = document.getElementById(\`\${__mreactPropsScriptPrefix}\${__mreactRouteId}\`);
1797
+ const __mreactClientReferencesElement = document.getElementById(\`\${__mreactClientReferencesScriptPrefix}\${__mreactRouteId}\`);
1786
1798
  const __mreactProps = __mreactPropsElement?.textContent === undefined
1787
1799
  ? {}
1788
1800
  : JSON.parse(__mreactPropsElement.textContent);
@@ -1799,7 +1811,7 @@ export function __mreactHydrateRoute() {
1799
1811
  ${routeCellHydrationStart}${routeCleanupHydrationStart}${boundaryOnlyHydrationBlock}${routeComponentGuard}${routeCellHydrationIndent}const __mreactNode = ${routeNodeExpression};
1800
1812
  ${routeCellHydrationIndent}__mreactResumeRoute(__mreactMarker, __mreactNode);
1801
1813
  ${routeCellHydrationIndent}__mreactHydrateClientBoundaries(document, __mreactClientReferences, __mreactClientReferenceComponents);
1802
- ${routeCellHydrationIndent}__mreactMarker.setAttribute("data-mreact-hydrated", "true");
1814
+ ${routeCellHydrationIndent}__mreactMarker.setAttribute(__mreactRouteHydratedAttribute, "true");
1803
1815
  ${routeCellHydrationEnd}}
1804
1816
  ${routeCellDropFunction}
1805
1817
  ${routeCleanupFunction}
@@ -2186,15 +2198,15 @@ function __mreactApplyNavigationHtml(html, url) {
2186
2198
  const template = document.createElement("template");
2187
2199
  template.innerHTML = html.replace(/^\\s*<!doctype html>/i, "");
2188
2200
  __mreactApplyOutOfOrderFragments(template.content);
2189
- const nextMarker = template.content.querySelector("[data-mreact-route-id]");
2190
- const currentMarker = document.querySelector("[data-mreact-route-id]");
2201
+ const nextMarker = template.content.querySelector("[${routeHydrationContract.routeMarkerAttribute}]");
2202
+ const currentMarker = document.querySelector("[${routeHydrationContract.routeMarkerAttribute}]");
2191
2203
 
2192
2204
  if (nextMarker === null || currentMarker === null) {
2193
2205
  return false;
2194
2206
  }
2195
2207
 
2196
- const currentRouteId = currentMarker.getAttribute("data-mreact-route-id");
2197
- const nextRouteId = nextMarker.getAttribute("data-mreact-route-id");
2208
+ const currentRouteId = currentMarker.getAttribute("${routeHydrationContract.routeMarkerAttribute}");
2209
+ const nextRouteId = nextMarker.getAttribute("${routeHydrationContract.routeMarkerAttribute}");
2198
2210
 
2199
2211
  __mreactSyncHeadMetadata(template.content, html);
2200
2212
  __mreactResumeNode(currentMarker, nextMarker);
@@ -2202,7 +2214,7 @@ ${routeCleanupNavigationDispose} __mreactSyncRouteDataScripts(template.content,
2202
2214
 
2203
2215
  const script = template.content.querySelector('script[type="module"][src]')?.getAttribute("src");
2204
2216
  if (script !== null && script !== undefined) {
2205
- void import(/* @vite-ignore */ script).then((module) => module.__mreactHydrateRoute?.());
2217
+ void import(/* @vite-ignore */ script).then((module) => module.${routeHydrationContract.routeHydrateExport}?.());
2206
2218
  }
2207
2219
 
2208
2220
  __mreactApplyOutOfOrderFragments(document);
@@ -2295,15 +2307,15 @@ function __mreactRouteDataScriptIds(...routeIds) {
2295
2307
  continue;
2296
2308
  }
2297
2309
 
2298
- ids.add(\`mreact-props-\${routeId}\`);
2299
- ids.add(\`mreact-client-references-\${routeId}\`);
2310
+ ids.add(\`${routeHydrationContract.propsScriptPrefix}\${routeId}\`);
2311
+ ids.add(\`${routeHydrationContract.clientReferencesScriptPrefix}\${routeId}\`);
2300
2312
  }
2301
2313
 
2302
2314
  return ids;
2303
2315
  }
2304
2316
 
2305
2317
  function __mreactRouteDataScriptSelector() {
2306
- return 'script[type="application/json"][id^="mreact-props-"], script[type="application/json"][id^="mreact-client-references-"]';
2318
+ return ${JSON.stringify(routeDataScriptSelector())};
2307
2319
  }
2308
2320
 
2309
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
+ }