@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.
- 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 +25 -19
- 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/route-source.d.ts.map +1 -1
- package/dist/route-source.js +7 -5
- package/dist/route-source.js.map +1 -1
- 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/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 +32 -20
- 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/route-source.ts +9 -4
- package/src/routes.ts +8 -0
- package/src/serve.ts +110 -29
- package/src/vite.ts +6 -4
package/dist/render.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import {
|
|
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,
|
|
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
|
|
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("&", "&")
|
|
3120
|
-
.replaceAll("<", "<")
|
|
3121
|
-
.replaceAll(">", ">")
|
|
3122
|
-
.replaceAll('"', """);
|
|
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;
|