@reckona/mreact-router 0.0.202 → 0.0.204
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -11
- package/dist/actions.d.ts +8 -3
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +125 -45
- package/dist/actions.js.map +1 -1
- package/dist/adapters/aws-lambda.d.ts +15 -4
- package/dist/adapters/aws-lambda.d.ts.map +1 -1
- package/dist/adapters/aws-lambda.js +301 -63
- package/dist/adapters/aws-lambda.js.map +1 -1
- package/dist/adapters/cloudflare.d.ts +5 -2
- package/dist/adapters/cloudflare.d.ts.map +1 -1
- package/dist/adapters/cloudflare.js +35 -11
- package/dist/adapters/cloudflare.js.map +1 -1
- package/dist/adapters/edge.d.ts +1 -1
- package/dist/adapters/edge.d.ts.map +1 -1
- package/dist/adapters/edge.js.map +1 -1
- package/dist/adapters/node.d.ts +5 -3
- package/dist/adapters/node.d.ts.map +1 -1
- package/dist/adapters/node.js +2 -1
- package/dist/adapters/node.js.map +1 -1
- package/dist/adapters/static.d.ts.map +1 -1
- package/dist/adapters/static.js +24 -2
- package/dist/adapters/static.js.map +1 -1
- package/dist/build.d.ts +4 -1
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +185 -13
- package/dist/build.js.map +1 -1
- package/dist/built-runtime.d.ts +2 -0
- package/dist/built-runtime.d.ts.map +1 -1
- package/dist/built-runtime.js +16 -0
- package/dist/built-runtime.js.map +1 -1
- package/dist/cache.d.ts +4 -2
- package/dist/cache.d.ts.map +1 -1
- package/dist/cache.js +216 -17
- package/dist/cache.js.map +1 -1
- package/dist/cli-options.d.ts +3 -0
- package/dist/cli-options.d.ts.map +1 -1
- package/dist/cli-options.js +10 -2
- package/dist/cli-options.js.map +1 -1
- package/dist/cli.js +14 -3
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts +5 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +43 -3
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +3 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +21 -2
- package/dist/config.js.map +1 -1
- package/dist/csp.d.ts +1 -0
- package/dist/csp.d.ts.map +1 -1
- package/dist/csp.js +9 -0
- package/dist/csp.js.map +1 -1
- package/dist/dehydrate-policy.d.ts +3 -0
- package/dist/dehydrate-policy.d.ts.map +1 -0
- package/dist/dehydrate-policy.js +16 -0
- package/dist/dehydrate-policy.js.map +1 -0
- package/dist/dev-server.d.ts +7 -2
- package/dist/dev-server.d.ts.map +1 -1
- package/dist/dev-server.js +120 -11
- package/dist/dev-server.js.map +1 -1
- package/dist/http.d.ts +1 -1
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +147 -7
- package/dist/http.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +22 -1
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +22 -8
- package/dist/logger.js.map +1 -1
- package/dist/metadata.d.ts +8 -0
- package/dist/metadata.d.ts.map +1 -1
- package/dist/metadata.js +60 -22
- package/dist/metadata.js.map +1 -1
- package/dist/navigation-marker.d.ts +2 -0
- package/dist/navigation-marker.d.ts.map +1 -0
- package/dist/navigation-marker.js +357 -0
- package/dist/navigation-marker.js.map +1 -0
- package/dist/navigation-runtime.d.ts +1 -1
- package/dist/navigation-runtime.d.ts.map +1 -1
- package/dist/navigation-runtime.js +1 -1
- package/dist/navigation-runtime.js.map +1 -1
- package/dist/navigation.d.ts +1 -0
- package/dist/navigation.d.ts.map +1 -1
- package/dist/navigation.js +1 -1
- package/dist/navigation.js.map +1 -1
- package/dist/node-server.d.ts +5 -2
- package/dist/node-server.d.ts.map +1 -1
- package/dist/node-server.js +75 -6
- package/dist/node-server.js.map +1 -1
- package/dist/prerender-entry.d.ts +17 -1
- package/dist/prerender-entry.d.ts.map +1 -1
- package/dist/prerender-entry.js +98 -3
- package/dist/prerender-entry.js.map +1 -1
- package/dist/render.d.ts +16 -2
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +142 -135
- package/dist/render.js.map +1 -1
- package/dist/serve.d.ts +11 -2
- package/dist/serve.d.ts.map +1 -1
- package/dist/serve.js +67 -41
- package/dist/serve.js.map +1 -1
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +52 -19
- package/dist/session.js.map +1 -1
- package/dist/upgrade.d.ts +59 -2
- package/dist/upgrade.d.ts.map +1 -1
- package/dist/upgrade.js +282 -1
- package/dist/upgrade.js.map +1 -1
- package/dist/vite-config.d.ts +2 -0
- package/dist/vite-config.d.ts.map +1 -1
- package/dist/vite-config.js +2 -0
- package/dist/vite-config.js.map +1 -1
- package/dist/vite.d.ts +4 -1
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +117 -6
- package/dist/vite.js.map +1 -1
- package/package.json +11 -11
- package/src/actions.ts +146 -60
- package/src/adapters/aws-lambda.ts +426 -115
- package/src/adapters/cloudflare.ts +58 -12
- package/src/adapters/edge.ts +2 -0
- package/src/adapters/node.ts +8 -10
- package/src/adapters/static.ts +31 -2
- package/src/build.ts +217 -13
- package/src/built-runtime.ts +28 -0
- package/src/cache.ts +272 -19
- package/src/cli-options.ts +15 -2
- package/src/cli.ts +16 -2
- package/src/client.ts +47 -3
- package/src/config.ts +43 -6
- package/src/csp.ts +11 -0
- package/src/dehydrate-policy.ts +23 -0
- package/src/dev-server.ts +188 -15
- package/src/http.ts +172 -6
- package/src/index.ts +14 -1
- package/src/logger.ts +47 -10
- package/src/metadata.ts +125 -60
- package/src/navigation-marker.ts +423 -0
- package/src/navigation-runtime.ts +1 -0
- package/src/navigation.ts +1 -1
- package/src/node-server.ts +128 -22
- package/src/prerender-entry.ts +127 -5
- package/src/render.ts +859 -749
- package/src/serve.ts +112 -82
- package/src/session.ts +56 -25
- package/src/upgrade.ts +402 -3
- package/src/vite-config.ts +6 -0
- package/src/vite.ts +157 -5
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
isQueryClientScopeUnavailableError,
|
|
11
11
|
runWithQueryClient,
|
|
12
12
|
type DehydratedQueryClient,
|
|
13
|
+
type DehydrateOptions,
|
|
13
14
|
type QueryAsyncStorage,
|
|
14
15
|
type QueryClient,
|
|
15
16
|
} from "@reckona/mreact-query";
|
|
@@ -32,13 +33,17 @@ import { middlewareMatches, type MiddlewareModule } from "../middleware.js";
|
|
|
32
33
|
import { normalizeRoutePath } from "../route-path.js";
|
|
33
34
|
import type { AppRoute } from "../routes.js";
|
|
34
35
|
import { contentSecurityPolicy } from "../csp.js";
|
|
35
|
-
import { isNotFoundError, rewriteLocation } from "../navigation.js";
|
|
36
|
+
import { isNotFoundError, isRedirectError, rewriteLocation } from "../navigation.js";
|
|
36
37
|
import { validateRouteMetadata } from "../metadata.js";
|
|
37
38
|
import { routeSecurityHeaders } from "../security-headers.js";
|
|
38
39
|
import type { AppRouterPrerenderStore } from "../serve.js";
|
|
39
40
|
import { emitRouterDevtoolsEvent } from "./devtools.js";
|
|
40
41
|
import { escapeHtmlAttribute, escapeHtmlText } from "@reckona/mreact-shared/html-escape";
|
|
41
|
-
import {
|
|
42
|
+
import {
|
|
43
|
+
isCurrentPrerenderedRoute,
|
|
44
|
+
replayedPrerenderedRouteHeaders,
|
|
45
|
+
validatedPrerenderedNavigationHtml,
|
|
46
|
+
} from "../prerender-entry.js";
|
|
42
47
|
|
|
43
48
|
/** Re-exports build manifest contracts used by Cloudflare handlers. */
|
|
44
49
|
export type {
|
|
@@ -66,6 +71,8 @@ export type {
|
|
|
66
71
|
AppRouterRequestStartLogEvent,
|
|
67
72
|
AppRouterRequestTimingLogEvent,
|
|
68
73
|
AppRouterRuntime,
|
|
74
|
+
AppRouterUpgradeErrorLogEvent,
|
|
75
|
+
AppRouterUpgradeRejectedLogEvent,
|
|
69
76
|
} from "../logger.js";
|
|
70
77
|
/** Re-exports response hook contracts used by Cloudflare handlers. */
|
|
71
78
|
export type { AppRouterResponseHook, AppRouterResponseHookContext } from "../render.js";
|
|
@@ -212,6 +219,7 @@ export type CloudflareRouteModuleComponent<Data = unknown, Env = unknown> = (
|
|
|
212
219
|
* Defines the exports accepted from a Cloudflare page route module.
|
|
213
220
|
*/
|
|
214
221
|
export interface CloudflareRouteModule<Data = unknown, Env = unknown> {
|
|
222
|
+
[exportName: string]: unknown;
|
|
215
223
|
App?: CloudflareRouteModuleComponent<Data, Env> | undefined;
|
|
216
224
|
CloudflareRouteComponent?: CloudflareRouteModuleComponent<Data, Env> | undefined;
|
|
217
225
|
default?: CloudflareRouteModuleComponent<Data, Env> | undefined;
|
|
@@ -291,6 +299,7 @@ export type CloudflareRouteModuleRegistry<Env = unknown> = Record<
|
|
|
291
299
|
* Configures the Cloudflare route module renderer.
|
|
292
300
|
*/
|
|
293
301
|
export interface CloudflareRouteModuleRendererOptions<Env = unknown> {
|
|
302
|
+
dehydrateOptions?: DehydrateOptions | undefined;
|
|
294
303
|
document?:
|
|
295
304
|
| ((
|
|
296
305
|
context: CloudflareRouteModuleComponentProps<unknown, Env> & {
|
|
@@ -496,7 +505,7 @@ export function createCloudflareRouteModuleRenderer<Env = unknown>(
|
|
|
496
505
|
const prerendered = prerenderedResponse(
|
|
497
506
|
context.serverManifest.prerenderedRoutes,
|
|
498
507
|
normalizeRoutePath(new URL(request.url).pathname),
|
|
499
|
-
request
|
|
508
|
+
request,
|
|
500
509
|
isCloudflareNavigationRequest(request),
|
|
501
510
|
);
|
|
502
511
|
|
|
@@ -570,12 +579,24 @@ export function createCloudflareRouteModuleRenderer<Env = unknown>(
|
|
|
570
579
|
return error;
|
|
571
580
|
}
|
|
572
581
|
|
|
582
|
+
if (isRedirectError(error)) {
|
|
583
|
+
return new Response(null, {
|
|
584
|
+
headers: { location: error.location },
|
|
585
|
+
status: error.status,
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
|
|
573
589
|
if (isNotFoundError(error)) {
|
|
574
590
|
return cloudflareNotFoundResponse(request);
|
|
575
591
|
}
|
|
576
592
|
|
|
577
593
|
throw error;
|
|
578
594
|
}
|
|
595
|
+
|
|
596
|
+
if (data instanceof Response) {
|
|
597
|
+
return data;
|
|
598
|
+
}
|
|
599
|
+
|
|
579
600
|
const props = {
|
|
580
601
|
...context,
|
|
581
602
|
data,
|
|
@@ -587,6 +608,13 @@ export function createCloudflareRouteModuleRenderer<Env = unknown>(
|
|
|
587
608
|
try {
|
|
588
609
|
rendered = await runWithCloudflareQueryClient(queryClient, () => component(props));
|
|
589
610
|
} catch (error) {
|
|
611
|
+
if (isRedirectError(error)) {
|
|
612
|
+
return new Response(null, {
|
|
613
|
+
headers: { location: error.location },
|
|
614
|
+
status: error.status,
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
|
|
590
618
|
if (isNotFoundError(error)) {
|
|
591
619
|
return cloudflareNotFoundResponse(request);
|
|
592
620
|
}
|
|
@@ -599,6 +627,16 @@ export function createCloudflareRouteModuleRenderer<Env = unknown>(
|
|
|
599
627
|
);
|
|
600
628
|
|
|
601
629
|
if (rendered instanceof Response) {
|
|
630
|
+
if (
|
|
631
|
+
(
|
|
632
|
+
pageModule as CloudflareRouteModule<unknown, Env> & {
|
|
633
|
+
__mreactSecurityHeadersApplied?: boolean | undefined;
|
|
634
|
+
}
|
|
635
|
+
).__mreactSecurityHeadersApplied === true
|
|
636
|
+
) {
|
|
637
|
+
return rendered;
|
|
638
|
+
}
|
|
639
|
+
|
|
602
640
|
return withDefaultSecurityHeaders(rendered, request, metadata);
|
|
603
641
|
}
|
|
604
642
|
|
|
@@ -623,7 +661,7 @@ export function createCloudflareRouteModuleRenderer<Env = unknown>(
|
|
|
623
661
|
);
|
|
624
662
|
const documentedWithQueryState = await injectCloudflareQueryState(
|
|
625
663
|
documented,
|
|
626
|
-
dehydrate(queryClient),
|
|
664
|
+
dehydrate(queryClient, options.dehydrateOptions),
|
|
627
665
|
);
|
|
628
666
|
|
|
629
667
|
return withDefaultSecurityHeaders(
|
|
@@ -722,6 +760,9 @@ async function runWithCloudflareQueryClient<T>(
|
|
|
722
760
|
if (isQueryClientScopeUnavailableError(error)) {
|
|
723
761
|
installQueryAsyncStorage(cloudflareQueryClientStorage);
|
|
724
762
|
cloudflareQueryClientFallbackInstalled = true;
|
|
763
|
+
console.warn(
|
|
764
|
+
'[mreact] Cloudflare AsyncLocalStorage is unavailable. Enable the "nodejs_compat" compatibility flag; rendering is serialized until native request-local storage is available.',
|
|
765
|
+
);
|
|
725
766
|
return await runWithSerializedCloudflareQueryClient(queryClient, fn);
|
|
726
767
|
}
|
|
727
768
|
|
|
@@ -733,6 +774,11 @@ const cloudflareQueryClientStorage = createCloudflareQueryClientStorage();
|
|
|
733
774
|
let cloudflareQueryClientFallbackQueue: Promise<void> = Promise.resolve();
|
|
734
775
|
let cloudflareQueryClientFallbackInstalled = false;
|
|
735
776
|
|
|
777
|
+
export function __resetCloudflareQueryClientFallbackForTesting(): void {
|
|
778
|
+
cloudflareQueryClientFallbackQueue = Promise.resolve();
|
|
779
|
+
cloudflareQueryClientFallbackInstalled = false;
|
|
780
|
+
}
|
|
781
|
+
|
|
736
782
|
async function runWithSerializedCloudflareQueryClient<T>(
|
|
737
783
|
queryClient: QueryClient,
|
|
738
784
|
fn: () => T,
|
|
@@ -1234,7 +1280,7 @@ async function handleCloudflareRequest<Env>(
|
|
|
1234
1280
|
: prerenderedResponse(
|
|
1235
1281
|
options.serverManifest.prerenderedRoutes,
|
|
1236
1282
|
normalizeRoutePath(url.pathname),
|
|
1237
|
-
request
|
|
1283
|
+
request,
|
|
1238
1284
|
isCloudflareNavigationRequest(request),
|
|
1239
1285
|
);
|
|
1240
1286
|
|
|
@@ -1377,25 +1423,25 @@ function builtServerManifestHasMiddleware(manifest: {
|
|
|
1377
1423
|
function prerenderedResponse(
|
|
1378
1424
|
prerenderedRoutes: Record<string, BuiltPrerenderedRoute> | undefined,
|
|
1379
1425
|
path: string,
|
|
1380
|
-
|
|
1426
|
+
request: Request,
|
|
1381
1427
|
isNavigation: boolean,
|
|
1382
1428
|
): Response | undefined {
|
|
1383
|
-
if (method !== "GET" && method !== "HEAD") {
|
|
1429
|
+
if (request.method !== "GET" && request.method !== "HEAD") {
|
|
1384
1430
|
return undefined;
|
|
1385
1431
|
}
|
|
1386
1432
|
|
|
1387
1433
|
const prerendered = prerenderedRoutes?.[path];
|
|
1388
|
-
|
|
1389
1434
|
if (!isCurrentPrerenderedRoute(prerendered)) {
|
|
1390
1435
|
return undefined;
|
|
1391
1436
|
}
|
|
1392
1437
|
|
|
1393
|
-
|
|
1438
|
+
const html = isNavigation ? validatedPrerenderedNavigationHtml(prerendered) : prerendered.html;
|
|
1439
|
+
if (html === undefined) {
|
|
1394
1440
|
return cloudflareDocumentReloadNavigationResponse();
|
|
1395
1441
|
}
|
|
1396
1442
|
|
|
1397
|
-
return new Response(method === "HEAD" ? null :
|
|
1398
|
-
headers: prerendered
|
|
1443
|
+
return new Response(request.method === "HEAD" ? null : html, {
|
|
1444
|
+
headers: replayedPrerenderedRouteHeaders(prerendered, request),
|
|
1399
1445
|
status: prerendered.status,
|
|
1400
1446
|
});
|
|
1401
1447
|
}
|
|
@@ -1622,7 +1668,7 @@ function cloudflareHydrationMarkerParts(options: {
|
|
|
1622
1668
|
const propsJson = escapeScriptJson(
|
|
1623
1669
|
JSON.stringify({
|
|
1624
1670
|
params: options.params,
|
|
1625
|
-
request: { url: options.request.url },
|
|
1671
|
+
request: { url: new URL(options.request.url).pathname },
|
|
1626
1672
|
data: options.data,
|
|
1627
1673
|
}),
|
|
1628
1674
|
);
|
package/src/adapters/edge.ts
CHANGED
package/src/adapters/node.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
import type { DehydrateOptions } from "@reckona/mreact-query";
|
|
2
3
|
import type { AppRouterCache } from "../cache.js";
|
|
3
4
|
import { nodeRequestToWebRequest, sendResponse } from "../http.js";
|
|
4
5
|
import type { AppRouterServerActionOptions } from "../actions.js";
|
|
@@ -19,10 +20,7 @@ import { resolveNodeRequestProtocol } from "../node-server.js";
|
|
|
19
20
|
/** Re-exports cache contracts used by Node handlers. */
|
|
20
21
|
export type { AppRouterCache, AppRouterCacheEntry } from "../cache.js";
|
|
21
22
|
/** Re-exports server action contracts used by Node handlers. */
|
|
22
|
-
export type {
|
|
23
|
-
AppRouterAllowedServerAction,
|
|
24
|
-
AppRouterServerActionOptions,
|
|
25
|
-
} from "../actions.js";
|
|
23
|
+
export type { AppRouterAllowedServerAction, AppRouterServerActionOptions } from "../actions.js";
|
|
26
24
|
/** Re-exports import policy contracts used by Node handlers. */
|
|
27
25
|
export type { AppRouterImportPolicy } from "../import-policy.js";
|
|
28
26
|
/** Re-exports logger contracts used by Node handlers. */
|
|
@@ -37,6 +35,8 @@ export type {
|
|
|
37
35
|
AppRouterRequestStartLogEvent,
|
|
38
36
|
AppRouterRequestTimingLogEvent,
|
|
39
37
|
AppRouterRuntime,
|
|
38
|
+
AppRouterUpgradeErrorLogEvent,
|
|
39
|
+
AppRouterUpgradeRejectedLogEvent,
|
|
40
40
|
} from "../logger.js";
|
|
41
41
|
/** Re-exports response hook contracts used by Node handlers. */
|
|
42
42
|
export type { AppRouterResponseHook, AppRouterResponseHookContext } from "../render.js";
|
|
@@ -63,17 +63,14 @@ import {
|
|
|
63
63
|
} from "../serve.js";
|
|
64
64
|
|
|
65
65
|
/** Re-exports request and rendering contracts used by Node handlers. */
|
|
66
|
-
export type {
|
|
67
|
-
AppRouterPrerenderStore,
|
|
68
|
-
RequestHostPolicy,
|
|
69
|
-
ResponseSinkStrategy,
|
|
70
|
-
} from "../serve.js";
|
|
66
|
+
export type { AppRouterPrerenderStore, RequestHostPolicy, ResponseSinkStrategy } from "../serve.js";
|
|
71
67
|
|
|
72
68
|
/**
|
|
73
69
|
* Configures a Node request handler for built app-router output.
|
|
74
70
|
*/
|
|
75
71
|
export interface NodeRequestHandlerOptions {
|
|
76
72
|
allowedHosts?: readonly string[] | undefined;
|
|
73
|
+
dehydrateOptions?: DehydrateOptions | undefined;
|
|
77
74
|
errorHandler?:
|
|
78
75
|
| ((error: unknown) => {
|
|
79
76
|
body: string;
|
|
@@ -139,13 +136,14 @@ export function createNodeRequestHandler(options: NodeRequestHandlerOptions): No
|
|
|
139
136
|
forwardedProto: incoming.headers["x-forwarded-proto"],
|
|
140
137
|
trustForwardedProto: options.trustForwardedProto,
|
|
141
138
|
});
|
|
142
|
-
const request = nodeRequestToWebRequest(incoming, `${protocol}://${host}
|
|
139
|
+
const request = nodeRequestToWebRequest(incoming, `${protocol}://${host}`, outgoing);
|
|
143
140
|
const logFields = requestLogFields(request, "node");
|
|
144
141
|
emitRouterLog(options.logger, "info", {
|
|
145
142
|
...logFields,
|
|
146
143
|
type: "router:request:start",
|
|
147
144
|
});
|
|
148
145
|
const response = await renderBuiltAppRequest({
|
|
146
|
+
dehydrateOptions: options.dehydrateOptions,
|
|
149
147
|
outDir: options.outDir,
|
|
150
148
|
importPolicy: options.importPolicy,
|
|
151
149
|
instrumentation: options.instrumentation,
|
package/src/adapters/static.ts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { cp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
2
2
|
import { dirname, join, resolve, sep } from "node:path";
|
|
3
3
|
import type { BuiltPrerenderedRoute, BuiltServerManifest } from "../build.js";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
isCurrentPrerenderedRoute,
|
|
6
|
+
validatedPrerenderedNavigationHtml,
|
|
7
|
+
} from "../prerender-entry.js";
|
|
8
|
+
|
|
9
|
+
const staticNavigationBase = "/_mreact/navigation";
|
|
10
|
+
const staticNavigationMetadata = `<meta name="mreact-static-navigation" content="${staticNavigationBase}">`;
|
|
5
11
|
|
|
6
12
|
/**
|
|
7
13
|
* Configures static export from a built app-router output directory.
|
|
@@ -104,8 +110,31 @@ async function writePrerenderedRoute(
|
|
|
104
110
|
}
|
|
105
111
|
|
|
106
112
|
const file = routeToHtmlFile(exportDir, route);
|
|
113
|
+
const navigationHtml = validatedPrerenderedNavigationHtml(entry);
|
|
114
|
+
if (navigationHtml === undefined) {
|
|
115
|
+
throw new Error(`Cannot export route ${route} without a valid navigation variant.`);
|
|
116
|
+
}
|
|
117
|
+
const navigationFile = routeToHtmlFile(join(exportDir, staticNavigationBase), route);
|
|
107
118
|
await mkdir(dirname(file), { recursive: true });
|
|
108
|
-
await
|
|
119
|
+
await mkdir(dirname(navigationFile), { recursive: true });
|
|
120
|
+
await writeFile(file, withStaticNavigationMetadata(entry.html));
|
|
121
|
+
await writeFile(navigationFile, navigationHtml);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function withStaticNavigationMetadata(html: string): string {
|
|
125
|
+
const head = /<head(?:\s[^>]*)?>/i.exec(html);
|
|
126
|
+
if (head?.index !== undefined) {
|
|
127
|
+
const insertion = head.index + head[0].length;
|
|
128
|
+
return `${html.slice(0, insertion)}${staticNavigationMetadata}${html.slice(insertion)}`;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const doctype = /^\s*<!doctype\s+html\s*>/i.exec(html);
|
|
132
|
+
if (doctype !== null) {
|
|
133
|
+
const insertion = doctype[0].length;
|
|
134
|
+
return `${html.slice(0, insertion)}${staticNavigationMetadata}${html.slice(insertion)}`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return `${staticNavigationMetadata}${html}`;
|
|
109
138
|
}
|
|
110
139
|
|
|
111
140
|
function routeToHtmlFile(exportDir: string, route: string): string {
|