@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/dist/render.js CHANGED
@@ -1,13 +1,14 @@
1
- import { createHash, randomUUID } from "node:crypto";
1
+ import { randomUUID } from "node:crypto";
2
2
  import { AsyncLocalStorage } from "node:async_hooks";
3
3
  import { access, readFile, stat } from "node:fs/promises";
4
4
  import { dirname, join, relative, sep } from "node:path";
5
5
  import { formatDiagnostic, transform, } from "@reckona/mreact-compiler";
6
6
  import { createQueryClient, dehydrate, __MREACT_QUERY_STATE_SCRIPT_ID, runWithQueryClient, } from "@reckona/mreact-query";
7
7
  import { createStringSink, renderAsyncBoundary, renderOutOfOrderReorderScript, renderToReadableStream, } from "@reckona/mreact-server";
8
- import { createClientRouteInferenceCache, hydrationMarkerParts, formatClientRouteInferenceDiagnostic, inferClientRouteModule, routeIdForPath, withHydrationMarkers, withRouteMarkers, } from "./client.js";
8
+ import { createClientRouteInferenceCache, formatClientRouteInferenceDiagnostic, inferClientRouteModule, } from "./client-route-inference.js";
9
+ import { hydrationMarkerParts, routeIdForPath, withHydrationMarkers, withRouteMarkers, } from "./navigation-runtime.js";
9
10
  import { assetPath } from "./assets.js";
10
- import { escapeHtmlAttribute, escapeHtmlText as escapeHtml, } from "@reckona/mreact-shared/html-escape";
11
+ import { escapeHtmlAttribute } from "@reckona/mreact-shared/html-escape";
11
12
  import { matchRoute, scanAppRoutes } from "./routes.js";
12
13
  import { appFileConventionContentType } from "./file-conventions.js";
13
14
  import { dispatchServerActionRequest, prepareRouteServerActions, } from "./actions.js";
@@ -15,7 +16,6 @@ import { serverActionCookie } from "./csrf.js";
15
16
  import { beginRouteCacheContext, cachedRouteResponse, cacheRouteResponse, routeCacheKey, routeCachePolicyFromSource, } from "./cache.js";
16
17
  import { resolveRouterCacheLimit } from "./cache-config.js";
17
18
  import { importAppRouterBuiltFileModule, importAppRouterFileModule, importAppRouterSourceModule, fileImportMetaUrlPlugin, } from "./module-runner.js";
18
- import { contentSecurityPolicy } from "./csp.js";
19
19
  import { bytesResponse, htmlResponse } from "./http.js";
20
20
  import { isNotFoundError, isRedirectError, rewriteLocation } from "./navigation.js";
21
21
  import { createAppRouterImportPolicyPlugin } from "./import-policy.js";
@@ -24,9 +24,12 @@ import { hasLoaderExport, isStreamRouteSource, routeClosureMayUseAwaitBoundary,
24
24
  import { emitRouterLog, logDurationMs, logNow } from "./logger.js";
25
25
  import { createRouterRuntimeCacheCounters, readRouterRuntimeCacheEntry, routerRuntimeCacheStat, } from "./cache-stats.js";
26
26
  import { bundleRouterModule } from "./bundle-pipeline.js";
27
- import { routeSecurityHeaders } from "./security-headers.js";
28
27
  import { vitePluginsCacheKey } from "./vite-plugin-cache-key.js";
29
28
  import { invokeRouterInstrumentation, traceContextFromRequest, } from "./trace.js";
29
+ import { mergeRouteMiddlewareControls, middlewareMatches, parseRouteMiddlewareControl, parseStaticMiddlewareConfig, shouldSkipMiddleware, } from "./middleware.js";
30
+ import { applyFileConventionMetadata, injectHeadMetadata, mergeRouteMetadata, responseHeadersForMetadata, serializeRobots, serializeSitemap, } from "./metadata.js";
31
+ import { createSlotRenderContext, markShellBoundary, shellBoundaryId, splitLayoutSlot, warnUnconsumedRouteSlots, } from "./layout-composer.js";
32
+ import { importPolicyCacheKey, memoizedHashText, prebuiltRequestModuleArtifact, prebuiltRouteLoaderModuleArtifact, prebuiltServerComponentModuleCode, prebuiltServerModuleOutputMatches, } from "./route-module-loader.js";
30
33
  const nativeEscapeTransform = {
31
34
  batchImportName: "escapeHtmlBatch",
32
35
  batchImportSource: "@reckona/mreact-router/native-escape",
@@ -1238,6 +1241,30 @@ async function dispatchConventionAssetRoute(options) {
1238
1241
  },
1239
1242
  });
1240
1243
  }
1244
+ function textConventionResponse(body) {
1245
+ return htmlResponse(body, {
1246
+ headers: {
1247
+ "cache-control": "public, max-age=3600",
1248
+ "content-type": "text/plain; charset=utf-8",
1249
+ },
1250
+ });
1251
+ }
1252
+ function xmlConventionResponse(body) {
1253
+ return htmlResponse(body, {
1254
+ headers: {
1255
+ "cache-control": "no-cache",
1256
+ "content-type": "application/xml; charset=utf-8",
1257
+ },
1258
+ });
1259
+ }
1260
+ function jsonConventionResponse(body) {
1261
+ return htmlResponse(JSON.stringify(body), {
1262
+ headers: {
1263
+ "cache-control": "public, max-age=3600",
1264
+ "content-type": "application/manifest+json; charset=utf-8",
1265
+ },
1266
+ });
1267
+ }
1241
1268
  async function dispatchMetadataRoute(options) {
1242
1269
  if (options.request.method !== "GET" && options.request.method !== "HEAD") {
1243
1270
  return new Response("Method Not Allowed", {
@@ -1415,44 +1442,6 @@ async function runMiddleware(options) {
1415
1442
  }
1416
1443
  return undefined;
1417
1444
  }
1418
- function shouldSkipMiddleware(config, control) {
1419
- if (control?.skip === true) {
1420
- return true;
1421
- }
1422
- if (!Array.isArray(control?.skip)) {
1423
- return false;
1424
- }
1425
- return typeof config?.id === "string" && control.skip.includes(config.id);
1426
- }
1427
- function parseStaticMiddlewareConfig(code) {
1428
- const configBody = /\bexport\s+const\s+config\s*=\s*\{(?<body>[\s\S]*?)\}\s*;?/.exec(code)?.groups
1429
- ?.body;
1430
- if (configBody === undefined) {
1431
- return { hasMatcher: false };
1432
- }
1433
- const id = /\bid\s*:\s*["'](?<id>[^"']+)["']/.exec(configBody)?.groups?.id;
1434
- const stringMatcher = /\bmatcher\s*:\s*["'](?<matcher>[^"']+)["']/.exec(configBody)?.groups
1435
- ?.matcher;
1436
- if (stringMatcher !== undefined) {
1437
- return {
1438
- hasMatcher: true,
1439
- ...(id === undefined ? {} : { id }),
1440
- matcher: stringMatcher,
1441
- };
1442
- }
1443
- const matcherArray = /\bmatcher\s*:\s*\[(?<items>[\s\S]*?)\]/.exec(configBody)?.groups?.items;
1444
- if (matcherArray !== undefined) {
1445
- return {
1446
- hasMatcher: true,
1447
- ...(id === undefined ? {} : { id }),
1448
- matcher: Array.from(matcherArray.matchAll(/["'](?<matcher>[^"']+)["']/g), (match) => match.groups?.matcher).filter((matcher) => matcher !== undefined),
1449
- };
1450
- }
1451
- return {
1452
- hasMatcher: /\bmatcher\s*:/.test(configBody),
1453
- ...(id === undefined ? {} : { id }),
1454
- };
1455
- }
1456
1445
  async function loadMiddlewareModule(options) {
1457
1446
  const code = options.code ??
1458
1447
  (await readServerSourceFile(options.file, options.serverModuleCacheVersion, options.serverSourceFiles));
@@ -1532,33 +1521,6 @@ async function loadBundledMiddlewareModule(options) {
1532
1521
  vitePlugins: options.vitePlugins,
1533
1522
  });
1534
1523
  }
1535
- function middlewareMatches(config, pathname) {
1536
- const matcher = config?.matcher;
1537
- if (matcher === undefined) {
1538
- return true;
1539
- }
1540
- if (matcher instanceof RegExp) {
1541
- return matcher.test(pathname);
1542
- }
1543
- if (Array.isArray(matcher)) {
1544
- return matcher.some((item) => middlewarePatternMatches(item, pathname));
1545
- }
1546
- return typeof matcher === "string" && middlewarePatternMatches(matcher, pathname);
1547
- }
1548
- function middlewarePatternMatches(pattern, pathname) {
1549
- if (pattern === pathname) {
1550
- return true;
1551
- }
1552
- if (pattern.endsWith("/:path*")) {
1553
- const prefix = pattern.slice(0, -"/:path*".length);
1554
- return pathname === prefix || pathname.startsWith(`${prefix}/`);
1555
- }
1556
- if (pattern.endsWith("*")) {
1557
- const prefix = pattern.slice(0, -1);
1558
- return pathname.startsWith(prefix);
1559
- }
1560
- return false;
1561
- }
1562
1524
  function transformServerModule(options) {
1563
1525
  const sourceHash = memoizedHashText(options.code);
1564
1526
  const artifact = options.serverModules?.get(options.filename)?.[options.serverOutput];
@@ -1672,28 +1634,6 @@ function routeSourceFilesForAnalysis(options) {
1672
1634
  ? { [options.filename]: options.code }
1673
1635
  : { ...Object.fromEntries(options.serverSourceFiles), [options.filename]: options.code };
1674
1636
  }
1675
- // Per-request hashText (SHA-256) is one of the hot path's dominant
1676
- // costs. Cache hashes for `code` strings we have already seen this
1677
- // process (common case: the prepared code is identical across requests
1678
- // when the source file is unchanged).
1679
- const codeHashCache = new Map();
1680
- const MAX_CODE_HASH_ENTRIES = 256;
1681
- function memoizedHashText(code) {
1682
- const cached = codeHashCache.get(code);
1683
- if (cached !== undefined) {
1684
- return cached;
1685
- }
1686
- const hash = hashText(code);
1687
- if (codeHashCache.size >= MAX_CODE_HASH_ENTRIES) {
1688
- // Simple LRU eviction: drop the oldest entry (Map keeps insertion order).
1689
- const oldestKey = codeHashCache.keys().next().value;
1690
- if (oldestKey !== undefined) {
1691
- codeHashCache.delete(oldestKey);
1692
- }
1693
- }
1694
- codeHashCache.set(code, hash);
1695
- return hash;
1696
- }
1697
1637
  async function runServerModule(code, props, sourcefile, serverModules, serverModuleCacheVersion, vitePlugins) {
1698
1638
  const component = await loadServerComponent(code, sourcefile, serverModules, serverModuleCacheVersion, vitePlugins);
1699
1639
  return component(props);
@@ -1750,18 +1690,6 @@ async function loadServerModule(code, sourcefile, serverModules, serverModuleCac
1750
1690
  vitePlugins,
1751
1691
  });
1752
1692
  }
1753
- function prebuiltServerComponentModuleCode(artifact, code, codeHash) {
1754
- if (artifact === undefined) {
1755
- return undefined;
1756
- }
1757
- if (!prebuiltServerModuleOutputMatches(artifact, code, codeHash)) {
1758
- return undefined;
1759
- }
1760
- return artifact.bundleCode;
1761
- }
1762
- function prebuiltServerModuleOutputMatches(artifact, code, codeHash) {
1763
- return artifact.sourceHash === codeHash || artifact.code === code;
1764
- }
1765
1693
  async function importBuiltServerModuleFile(options) {
1766
1694
  return await importAppRouterBuiltFileModule({
1767
1695
  ...(options.serverModuleCacheVersion === undefined
@@ -2214,17 +2142,6 @@ async function renderShellStreamComponent(component, props) {
2214
2142
  await sink.drain();
2215
2143
  return sink.toString();
2216
2144
  }
2217
- function splitLayoutSlot(layoutHtml, slotContext = createSlotRenderContext()) {
2218
- const html = replaceNamedLayoutSlots(layoutHtml, slotContext);
2219
- const match = findDefaultLayoutSlot(html);
2220
- if (match === null) {
2221
- return { prefix: html, suffix: "" };
2222
- }
2223
- return {
2224
- prefix: html.slice(0, match.index),
2225
- suffix: html.slice(match.index + match[0].length),
2226
- };
2227
- }
2228
2145
  // Layout/template files for a given page do not change during a server's
2229
2146
  // lifetime in production. Each cache miss costs up to N×4 filesystem
2230
2147
  // `access()` syscalls (~5-10μs each on a fast SSD), making this one of
@@ -2283,12 +2200,6 @@ function withRouteCacheHeader(response, policy) {
2283
2200
  }
2284
2201
  return response;
2285
2202
  }
2286
- function shellBoundaryId(appDir, directory) {
2287
- const relativeDirectory = relative(appDir, directory);
2288
- return relativeDirectory === ""
2289
- ? "root"
2290
- : relativeDirectory.replaceAll(sep, "/").replace(/[^A-Za-z0-9_$/-]/g, "_");
2291
- }
2292
2203
  async function hasAppMiddleware(options) {
2293
2204
  const middlewareFiles = [
2294
2205
  join(options.appDir, "middleware.ts"),
@@ -2398,97 +2309,6 @@ async function loadRouteMiddlewareControlFile(options) {
2398
2309
  });
2399
2310
  return control;
2400
2311
  }
2401
- function parseRouteMiddlewareControl(code) {
2402
- if (!/\bexport\s+const\s+middleware\s*=/.test(code)) {
2403
- return undefined;
2404
- }
2405
- if (/\bmiddleware\s*=\s*\{[\s\S]*?\bskip\s*:\s*true\b/.test(code)) {
2406
- return { skip: true };
2407
- }
2408
- const skipArray = /\bmiddleware\s*=\s*\{[\s\S]*?\bskip\s*:\s*\[([\s\S]*?)\]/.exec(code);
2409
- if (skipArray === null) {
2410
- return undefined;
2411
- }
2412
- const ids = Array.from(skipArray[1]?.matchAll(/["']([^"']+)["']/g) ?? [], (match) => match[1]).filter((id) => id !== undefined);
2413
- return ids.length === 0 ? undefined : { skip: ids };
2414
- }
2415
- function mergeRouteMiddlewareControls(controls) {
2416
- const skippedIds = new Set();
2417
- for (const control of controls) {
2418
- if (control?.skip === true) {
2419
- return { skip: true };
2420
- }
2421
- if (Array.isArray(control?.skip)) {
2422
- for (const id of control.skip) {
2423
- skippedIds.add(id);
2424
- }
2425
- }
2426
- }
2427
- return skippedIds.size === 0 ? undefined : { skip: [...skippedIds] };
2428
- }
2429
- function markShellBoundary(html, shell) {
2430
- const attributeName = shell.kind === "layout" ? "data-mreact-layout-boundary" : "data-mreact-template-boundary";
2431
- if (html.includes(`${attributeName}=`)) {
2432
- return html;
2433
- }
2434
- return html.replace(/<([A-Za-z][^\s/>]*)([^>]*)>/, `<$1$2 ${attributeName}="${escapeHtmlAttribute(shell.id)}">`);
2435
- }
2436
- function replaceNamedLayoutSlots(layoutHtml, slotContext) {
2437
- return layoutHtml.replace(SLOT_TAG_PATTERN, (source, openAttributes) => {
2438
- const name = readSlotName(openAttributes);
2439
- if (name === undefined || name === "default") {
2440
- return source;
2441
- }
2442
- if (Object.hasOwn(slotContext.namedSlots, name)) {
2443
- slotContext.consumedSlots.add(name);
2444
- return slotContext.namedSlots[name] ?? "";
2445
- }
2446
- return "";
2447
- });
2448
- }
2449
- const SLOT_TAG_PATTERN = /<slot\b([^>]*)>(?:<\/slot\s*>)?/g;
2450
- function findDefaultLayoutSlot(html) {
2451
- SLOT_TAG_PATTERN.lastIndex = 0;
2452
- for (;;) {
2453
- const match = SLOT_TAG_PATTERN.exec(html);
2454
- if (match === null) {
2455
- return null;
2456
- }
2457
- const name = readSlotName(match[1] ?? "");
2458
- if (name === undefined || name === "default") {
2459
- return match;
2460
- }
2461
- }
2462
- }
2463
- function readSlotName(attributes) {
2464
- const match = /\bname\s*=\s*(?:"([^"]*)"|'([^']*)')/.exec(attributes);
2465
- return match?.[1] ?? match?.[2];
2466
- }
2467
- function createSlotRenderContext(namedSlots = {}) {
2468
- return {
2469
- consumedSlots: new Set(),
2470
- namedSlots,
2471
- };
2472
- }
2473
- function warnUnconsumedRouteSlots(options) {
2474
- if (options.serverModuleCacheVersion !== undefined) {
2475
- return;
2476
- }
2477
- const slotNames = Object.keys(options.slotContext.namedSlots);
2478
- if (slotNames.length === 0) {
2479
- return;
2480
- }
2481
- const routeLabel = relative(options.appDir, options.pageFile).replaceAll(sep, "/");
2482
- for (const name of slotNames) {
2483
- if (name === "default") {
2484
- console.warn(`[mreact] ${routeLabel}: slots.default does not target <Slot />; use the page body for default slot content.`);
2485
- continue;
2486
- }
2487
- if (!options.slotContext.consumedSlots.has(name)) {
2488
- console.warn(`[mreact] ${routeLabel}: slots.{${name}} is not consumed by any ancestor layout or template.`);
2489
- }
2490
- }
2491
- }
2492
2312
  async function loadRouteData(options) {
2493
2313
  if (!hasLoaderExport(options.code)) {
2494
2314
  return undefined;
@@ -2613,28 +2433,6 @@ async function loadBundledRouteLoaderModule(options) {
2613
2433
  label: `loader:${options.filename}`,
2614
2434
  });
2615
2435
  }
2616
- function prebuiltRequestModuleArtifact(serverModules, file, source, kind = "request") {
2617
- const artifact = serverModules?.get(file)?.[kind];
2618
- return artifact !== undefined && artifact.sourceHash === memoizedHashText(source)
2619
- ? artifact
2620
- : undefined;
2621
- }
2622
- function prebuiltRouteLoaderModuleArtifact(serverModules, file, source) {
2623
- const artifact = serverModules?.get(file)?.loader;
2624
- return artifact !== undefined && artifact.sourceHash === memoizedHashText(source)
2625
- ? artifact
2626
- : prebuiltRequestModuleArtifact(serverModules, file, source);
2627
- }
2628
- function importPolicyCacheKey(policy) {
2629
- if (policy === undefined) {
2630
- return "";
2631
- }
2632
- return JSON.stringify({
2633
- allowedPackages: [...(policy.allowedPackages ?? [])].sort(),
2634
- allowedSourceDirs: [...(policy.allowedSourceDirs ?? [])].sort(),
2635
- projectRoot: policy.projectRoot ?? "",
2636
- });
2637
- }
2638
2436
  async function loadRouteMetadata(options) {
2639
2437
  if (!hasMetadataExport(options.code)) {
2640
2438
  return undefined;
@@ -2767,179 +2565,6 @@ async function loadComposedRouteMetadataUncached(options) {
2767
2565
  metadata: applyFileConventionMetadata(mergeRouteMetadata(metadata), options.routes, options.filename, options.context.params),
2768
2566
  };
2769
2567
  }
2770
- function mergeRouteMetadata(metadata) {
2771
- if (metadata.length === 0) {
2772
- return undefined;
2773
- }
2774
- return metadata.reduce((merged, next) => {
2775
- const mergedMetadata = { ...merged, ...next };
2776
- const alternates = mergeObject(merged.alternates, next.alternates);
2777
- const csp = mergeCspMetadata(merged.csp, next.csp);
2778
- const head = mergeReadonlyArrays(merged.head, next.head);
2779
- const icons = mergeObject(merged.icons, next.icons);
2780
- const openGraph = mergeOpenGraphMetadata(merged.openGraph, next.openGraph);
2781
- if (alternates !== undefined) {
2782
- mergedMetadata.alternates = alternates;
2783
- }
2784
- if (csp !== undefined) {
2785
- mergedMetadata.csp = csp;
2786
- }
2787
- if (head !== undefined) {
2788
- mergedMetadata.head = head;
2789
- }
2790
- if (icons !== undefined) {
2791
- mergedMetadata.icons = icons;
2792
- }
2793
- if (openGraph !== undefined) {
2794
- mergedMetadata.openGraph = openGraph;
2795
- }
2796
- return mergedMetadata;
2797
- }, {});
2798
- }
2799
- function applyFileConventionMetadata(metadata, routes, filename, params) {
2800
- const next = metadata === undefined ? {} : { ...metadata };
2801
- const iconRoute = routes.find((route) => route.kind === "asset" && route.convention === "icon");
2802
- const appleIconRoute = routes.find((route) => route.kind === "asset" && route.convention === "apple-icon");
2803
- const openGraphImagePath = fileConventionMetadataRoutePath(routes, filename, params, "opengraph-image");
2804
- if (iconRoute !== undefined && next.icons?.icon === undefined) {
2805
- next.icons = { ...next.icons, icon: iconRoute.path };
2806
- }
2807
- if (appleIconRoute !== undefined && next.icons?.apple === undefined) {
2808
- next.icons = { ...next.icons, apple: appleIconRoute.path };
2809
- }
2810
- if (openGraphImagePath !== undefined &&
2811
- next.openGraph?.image === undefined &&
2812
- (next.openGraph?.images === undefined || next.openGraph.images.length === 0)) {
2813
- next.openGraph = { ...next.openGraph, image: openGraphImagePath };
2814
- }
2815
- return Object.keys(next).length === 0 ? undefined : next;
2816
- }
2817
- function fileConventionMetadataRoutePath(routes, filename, params, convention) {
2818
- const pageRoute = routes.find((route) => route.kind === "page" && route.file === filename);
2819
- const candidateRoutes = routes.filter((route) => (route.kind === "asset" || route.kind === "metadata") &&
2820
- route.convention === convention);
2821
- if (pageRoute !== undefined) {
2822
- const expectedPath = pageRoute.path === "/" ? `/${convention}` : `${pageRoute.path}/${convention}`;
2823
- const routeLocal = candidateRoutes.find((route) => route.path === expectedPath);
2824
- const routeLocalPath = routeLocal === undefined ? undefined : concreteRoutePath(routeLocal.path, params);
2825
- if (routeLocalPath !== undefined) {
2826
- return routeLocalPath;
2827
- }
2828
- }
2829
- return candidateRoutes.find((route) => route.path === `/${convention}`)?.path;
2830
- }
2831
- function concreteRoutePath(path, params) {
2832
- const segments = path === "/" ? [] : path.slice(1).split("/");
2833
- const concrete = [];
2834
- for (const segment of segments) {
2835
- if (segment.startsWith(":...")) {
2836
- const value = params[segment.slice(4)];
2837
- const values = Array.isArray(value)
2838
- ? value
2839
- : typeof value === "string"
2840
- ? value.split("/").filter((part) => part !== "")
2841
- : undefined;
2842
- if (values === undefined) {
2843
- return undefined;
2844
- }
2845
- concrete.push(...values.map((part) => encodeURIComponent(part)));
2846
- continue;
2847
- }
2848
- if (segment.startsWith(":")) {
2849
- const value = params[segment.slice(1)];
2850
- const stringValue = Array.isArray(value) ? value[0] : value;
2851
- if (typeof stringValue !== "string") {
2852
- return undefined;
2853
- }
2854
- concrete.push(encodeURIComponent(stringValue));
2855
- continue;
2856
- }
2857
- concrete.push(segment);
2858
- }
2859
- return `/${concrete.join("/")}`;
2860
- }
2861
- function mergeObject(left, right) {
2862
- if (left === undefined) {
2863
- return right;
2864
- }
2865
- if (right === undefined) {
2866
- return left;
2867
- }
2868
- return { ...left, ...right };
2869
- }
2870
- function mergeReadonlyArrays(left, right) {
2871
- if (left === undefined || left.length === 0) {
2872
- return right;
2873
- }
2874
- if (right === undefined || right.length === 0) {
2875
- return left;
2876
- }
2877
- return [...left, ...right];
2878
- }
2879
- function mergeCspMetadata(left, right) {
2880
- if (right?.disable === true) {
2881
- return { disable: true };
2882
- }
2883
- if (left === undefined) {
2884
- if (right === undefined) {
2885
- return undefined;
2886
- }
2887
- const merged = { ...right };
2888
- const directives = applyCspOverrides(undefined, right);
2889
- if (directives !== undefined) {
2890
- merged.directives = directives;
2891
- }
2892
- else {
2893
- delete merged.directives;
2894
- }
2895
- return merged;
2896
- }
2897
- if (right === undefined) {
2898
- return left;
2899
- }
2900
- const merged = {
2901
- ...left,
2902
- ...right,
2903
- };
2904
- const directives = applyCspOverrides(left.directives, right);
2905
- if (directives !== undefined) {
2906
- merged.directives = directives;
2907
- }
2908
- else {
2909
- delete merged.directives;
2910
- }
2911
- return merged;
2912
- }
2913
- function applyCspOverrides(left, right) {
2914
- if (right === undefined) {
2915
- return left;
2916
- }
2917
- const merged = { ...left, ...right.directives };
2918
- for (const [name, value] of Object.entries(right.replace ?? {})) {
2919
- merged[name] = value;
2920
- }
2921
- for (const name of right.remove ?? []) {
2922
- delete merged[name];
2923
- }
2924
- return Object.keys(merged).length === 0 ? undefined : merged;
2925
- }
2926
- function mergeOpenGraphMetadata(left, right) {
2927
- if (left === undefined) {
2928
- return right;
2929
- }
2930
- if (right === undefined) {
2931
- return left;
2932
- }
2933
- const merged = {
2934
- ...left,
2935
- ...right,
2936
- };
2937
- const images = mergeReadonlyArrays(openGraphImages(left), openGraphImages(right));
2938
- if (images !== undefined && images.length > 0) {
2939
- merged.images = images;
2940
- }
2941
- return merged;
2942
- }
2943
2568
  function hasMetadataExport(code) {
2944
2569
  return /\bexport\s+const\s+metadata\s*=/.test(code) || hasGenerateMetadataExport(code);
2945
2570
  }
@@ -2950,177 +2575,6 @@ function hasGenerateMetadataExport(code) {
2950
2575
  function usesRuntimeCacheControl(code) {
2951
2576
  return /\bcacheControl\s*\(/.test(code);
2952
2577
  }
2953
- function injectHeadMetadata(html, metadata) {
2954
- if (metadata === undefined) {
2955
- return html;
2956
- }
2957
- let nextHtml = metadata.lang === undefined
2958
- ? html
2959
- : injectHtmlLangAttribute(html, metadataString(metadata.lang, "lang"));
2960
- const tags = [
2961
- metadata.title === undefined
2962
- ? undefined
2963
- : `<title>${escapeHtml(metadataString(metadata.title, "title"))}</title>`,
2964
- metadata.description === undefined
2965
- ? undefined
2966
- : `<meta name="description" content="${escapeHtmlAttribute(metadataString(metadata.description, "description"))}">`,
2967
- metadata.alternates?.canonical === undefined
2968
- ? undefined
2969
- : `<link rel="canonical" href="${escapeHtmlAttribute(metadataString(metadata.alternates.canonical, "alternates.canonical"))}">`,
2970
- metadata.openGraph?.title === undefined
2971
- ? undefined
2972
- : `<meta property="og:title" content="${escapeHtmlAttribute(metadataString(metadata.openGraph.title, "openGraph.title"))}">`,
2973
- metadata.openGraph?.description === undefined
2974
- ? undefined
2975
- : `<meta property="og:description" content="${escapeHtmlAttribute(metadataString(metadata.openGraph.description, "openGraph.description"))}">`,
2976
- ...openGraphImages(metadata.openGraph).map((image) => `<meta property="og:image" content="${escapeHtmlAttribute(image)}">`),
2977
- metadata.icons?.icon === undefined
2978
- ? undefined
2979
- : `<link rel="icon" href="${escapeHtmlAttribute(metadataString(metadata.icons.icon, "icons.icon"))}">`,
2980
- metadata.icons?.apple === undefined
2981
- ? undefined
2982
- : `<link rel="apple-touch-icon" href="${escapeHtmlAttribute(metadataString(metadata.icons.apple, "icons.apple"))}">`,
2983
- metadata.robots === undefined
2984
- ? undefined
2985
- : `<meta name="robots" content="${escapeHtmlAttribute(robotsContent(metadata.robots))}">`,
2986
- metadata.themeColor === undefined ? undefined : themeColorTag(metadata.themeColor),
2987
- metadata.viewport === undefined
2988
- ? undefined
2989
- : `<meta name="viewport" content="${escapeHtmlAttribute(viewportContent(metadata.viewport))}">`,
2990
- ...headDescriptorTags(metadata.head, metadata.csp?.nonce),
2991
- ]
2992
- .filter((tag) => tag !== undefined)
2993
- .join("");
2994
- if (tags === "") {
2995
- return nextHtml;
2996
- }
2997
- if (/<head(?:\s[^>]*)?>/i.test(nextHtml)) {
2998
- return nextHtml.replace(/<head(\s[^>]*)?>/i, (match) => `${match}${tags}`);
2999
- }
3000
- if (/<html(?:\s[^>]*)?>/i.test(nextHtml)) {
3001
- return nextHtml.replace(/<html(\s[^>]*)?>/i, (match) => `${match}<head>${tags}</head>`);
3002
- }
3003
- return `<head>${tags}</head>${nextHtml}`;
3004
- }
3005
- function injectHtmlLangAttribute(html, lang) {
3006
- const escapedLang = escapeHtmlAttribute(lang);
3007
- if (!/<html(?:\s[^>]*)?>/i.test(html)) {
3008
- return html;
3009
- }
3010
- return html.replace(/<html(\s[^>]*)?>/i, (_match, attrs = "") => {
3011
- const strippedAttrs = String(attrs).replace(/\s+lang=(?:"[^"]*"|'[^']*'|[^\s>]+)/i, "");
3012
- return `<html lang="${escapedLang}"${strippedAttrs}>`;
3013
- });
3014
- }
3015
- const DEFAULT_HTML_RESPONSE_HEADERS = Object.freeze({
3016
- "content-type": "text/html; charset=utf-8",
3017
- });
3018
- function responseHeadersForMetadata(metadata, request, extra) {
3019
- const csp = contentSecurityPolicy(metadata?.csp);
3020
- const security = routeSecurityHeaders({
3021
- request,
3022
- security: metadata?.security,
3023
- });
3024
- if (csp === undefined && extra === undefined) {
3025
- return {
3026
- ...DEFAULT_HTML_RESPONSE_HEADERS,
3027
- ...security,
3028
- };
3029
- }
3030
- return {
3031
- ...DEFAULT_HTML_RESPONSE_HEADERS,
3032
- ...security,
3033
- ...(csp === undefined ? undefined : { "content-security-policy": csp }),
3034
- ...extra,
3035
- };
3036
- }
3037
- function textConventionResponse(body) {
3038
- return htmlResponse(body, {
3039
- headers: {
3040
- "cache-control": "public, max-age=3600",
3041
- "content-type": "text/plain; charset=utf-8",
3042
- },
3043
- });
3044
- }
3045
- function xmlConventionResponse(body) {
3046
- return htmlResponse(body, {
3047
- headers: {
3048
- "cache-control": "no-cache",
3049
- "content-type": "application/xml; charset=utf-8",
3050
- },
3051
- });
3052
- }
3053
- function jsonConventionResponse(body) {
3054
- return htmlResponse(JSON.stringify(body), {
3055
- headers: {
3056
- "cache-control": "public, max-age=3600",
3057
- "content-type": "application/manifest+json; charset=utf-8",
3058
- },
3059
- });
3060
- }
3061
- function serializeRobots(manifest) {
3062
- const lines = [];
3063
- const rules = manifest.rules === undefined
3064
- ? []
3065
- : Array.isArray(manifest.rules)
3066
- ? manifest.rules
3067
- : [manifest.rules];
3068
- for (const rule of rules) {
3069
- for (const userAgent of arrayValue(rule.userAgent)) {
3070
- lines.push(`User-agent: ${userAgent}`);
3071
- }
3072
- for (const allow of arrayValue(rule.allow)) {
3073
- lines.push(`Allow: ${allow}`);
3074
- }
3075
- for (const disallow of arrayValue(rule.disallow)) {
3076
- lines.push(`Disallow: ${disallow}`);
3077
- }
3078
- }
3079
- for (const sitemap of arrayValue(manifest.sitemap)) {
3080
- lines.push(`Sitemap: ${sitemap}`);
3081
- }
3082
- if (manifest.host !== undefined) {
3083
- lines.push(`Host: ${manifest.host}`);
3084
- }
3085
- return `${lines.join("\n")}\n`;
3086
- }
3087
- function serializeSitemap(entries) {
3088
- const urls = entries
3089
- .map((entry) => {
3090
- const fields = [
3091
- `<loc>${escapeXml(entry.url)}</loc>`,
3092
- entry.lastModified === undefined
3093
- ? undefined
3094
- : `<lastmod>${escapeXml(sitemapDate(entry.lastModified))}</lastmod>`,
3095
- entry.changeFrequency === undefined
3096
- ? undefined
3097
- : `<changefreq>${escapeXml(entry.changeFrequency)}</changefreq>`,
3098
- entry.priority === undefined ? undefined : `<priority>${entry.priority}</priority>`,
3099
- ].filter((field) => field !== undefined);
3100
- return `<url>${fields.join("")}</url>`;
3101
- })
3102
- .join("");
3103
- return `<?xml version="1.0" encoding="UTF-8"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">${urls}</urlset>`;
3104
- }
3105
- function sitemapDate(value) {
3106
- if (value instanceof Date) {
3107
- return value.toISOString();
3108
- }
3109
- return typeof value === "number" ? new Date(value).toISOString() : value;
3110
- }
3111
- function arrayValue(value) {
3112
- if (value === undefined) {
3113
- return [];
3114
- }
3115
- return Array.isArray(value) ? value : [value];
3116
- }
3117
- function escapeXml(value) {
3118
- return value
3119
- .replaceAll("&", "&amp;")
3120
- .replaceAll("<", "&lt;")
3121
- .replaceAll(">", "&gt;")
3122
- .replaceAll('"', "&quot;");
3123
- }
3124
2578
  function injectQueryState(html, state) {
3125
2579
  if (state.queries.length === 0) {
3126
2580
  return html;
@@ -3159,101 +2613,6 @@ function escapeJsonForHtml(value) {
3159
2613
  .replaceAll("\u2028", "\\u2028")
3160
2614
  .replaceAll("\u2029", "\\u2029");
3161
2615
  }
3162
- function headDescriptorTags(descriptors, nonce) {
3163
- return (descriptors ?? []).flatMap((descriptor) => {
3164
- const descriptorNonce = descriptor.nonce === true ? nonce : descriptor.nonce || undefined;
3165
- const attrs = {
3166
- ...descriptor.attrs,
3167
- ...(descriptorNonce === undefined ? {} : { nonce: descriptorNonce }),
3168
- };
3169
- const attrText = Object.entries(attrs)
3170
- .flatMap(([name, value]) => {
3171
- if (value === undefined || value === false) {
3172
- return [];
3173
- }
3174
- return value === true
3175
- ? [escapeHtmlAttribute(name)]
3176
- : [`${escapeHtmlAttribute(name)}="${escapeHtmlAttribute(String(value))}"`];
3177
- })
3178
- .join(" ");
3179
- const open = attrText === "" ? `<${descriptor.tag}>` : `<${descriptor.tag} ${attrText}>`;
3180
- if (descriptor.tag === "meta" || descriptor.tag === "link" || descriptor.tag === "base") {
3181
- return [open.slice(0, -1) + ">"];
3182
- }
3183
- return [`${open}${escapeHeadTextContent(descriptor.content ?? "")}</${descriptor.tag}>`];
3184
- });
3185
- }
3186
- function escapeHeadTextContent(value) {
3187
- return value.replaceAll("<", "\\u003c");
3188
- }
3189
- function metadataString(value, path) {
3190
- if (isMetadataScalar(value)) {
3191
- return String(value);
3192
- }
3193
- throw new Error(`Invalid metadata field ${path}: expected string, number, or boolean.`);
3194
- }
3195
- function metadataKebabName(name) {
3196
- return name.replace(/[A-Z]/g, (char) => `-${char.toLowerCase()}`);
3197
- }
3198
- function viewportContent(viewport) {
3199
- if (isMetadataScalar(viewport)) {
3200
- return metadataString(viewport, "viewport");
3201
- }
3202
- return Object.entries(viewport)
3203
- .flatMap(([key, value]) => {
3204
- if (value === undefined || value === null || value === false) {
3205
- return [];
3206
- }
3207
- return [`${metadataKebabName(key)}=${metadataString(value, `viewport.${key}`)}`];
3208
- })
3209
- .join(", ");
3210
- }
3211
- function themeColorTag(themeColor) {
3212
- if (isMetadataScalar(themeColor)) {
3213
- return `<meta name="theme-color" content="${escapeHtmlAttribute(metadataString(themeColor, "themeColor"))}">`;
3214
- }
3215
- const content = themeColor.color;
3216
- if (!isMetadataScalar(content)) {
3217
- throw new Error("Invalid metadata field themeColor.color: expected string, number, or boolean.");
3218
- }
3219
- const media = themeColor.media === undefined
3220
- ? ""
3221
- : ` media="${escapeHtmlAttribute(metadataString(metadataScalarField(themeColor.media, "themeColor.media"), "themeColor.media"))}"`;
3222
- return `<meta name="theme-color"${media} content="${escapeHtmlAttribute(metadataString(content, "themeColor.color"))}">`;
3223
- }
3224
- function metadataScalarField(value, path) {
3225
- if (isMetadataScalar(value)) {
3226
- return value;
3227
- }
3228
- throw new Error(`Invalid metadata field ${path}: expected string, number, or boolean.`);
3229
- }
3230
- function isMetadataScalar(value) {
3231
- return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
3232
- }
3233
- function openGraphImages(openGraph) {
3234
- if (openGraph?.images !== undefined) {
3235
- return openGraph.images.map((image, index) => metadataImageUrl(image, `openGraph.images.${index}`));
3236
- }
3237
- return openGraph?.image === undefined ? [] : [metadataImageUrl(openGraph.image, "openGraph.image")];
3238
- }
3239
- function metadataImageUrl(value, path) {
3240
- if (isMetadataScalar(value)) {
3241
- return metadataString(value, path);
3242
- }
3243
- if (typeof value === "object" && value !== null && "url" in value) {
3244
- return metadataString(value.url, `${path}.url`);
3245
- }
3246
- throw new Error(`Invalid metadata field ${path}: expected string, number, boolean, or object with url.`);
3247
- }
3248
- function robotsContent(robots) {
3249
- if (typeof robots === "string") {
3250
- return robots;
3251
- }
3252
- return [
3253
- robots.index === false ? "noindex" : "index",
3254
- robots.follow === false ? "nofollow" : "follow",
3255
- ].join(",");
3256
- }
3257
2616
  function readServerSourceFile(file, serverModuleCacheVersion, serverSourceFiles) {
3258
2617
  const manifestSource = serverSourceFiles?.get(file);
3259
2618
  if (manifestSource !== undefined) {
@@ -3274,9 +2633,6 @@ function readServerSourceFile(file, serverModuleCacheVersion, serverSourceFiles)
3274
2633
  setBoundedCacheEntry(serverSourceFileCache, key, loaded, maxServerSourceFileCacheEntries, serverSourceFileCacheCounters);
3275
2634
  return loaded;
3276
2635
  }
3277
- function hashText(text) {
3278
- return createHash("sha256").update(text).digest("hex").slice(0, 16);
3279
- }
3280
2636
  function setBoundedCacheEntry(cache, key, value, maxEntries, counters) {
3281
2637
  if (cache.size >= maxEntries) {
3282
2638
  const oldestKey = cache.keys().next().value;