@reckona/mreact-router 0.0.203 → 0.0.205
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 +53 -9
- package/dist/actions.d.ts +6 -0
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +70 -10
- 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 +2 -1
- package/dist/adapters/cloudflare.d.ts.map +1 -1
- package/dist/adapters/cloudflare.js +18 -10
- 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 +1 -0
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +91 -5
- 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 +3 -1
- package/dist/cache.d.ts.map +1 -1
- package/dist/cache.js +211 -13
- package/dist/cache.js.map +1 -1
- package/dist/cli.js +12 -1
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +32 -1
- 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 +11 -6
- 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.map +1 -1
- package/dist/navigation-marker.js +72 -9
- package/dist/navigation-marker.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 +14 -0
- package/dist/prerender-entry.d.ts.map +1 -1
- package/dist/prerender-entry.js +40 -3
- package/dist/prerender-entry.js.map +1 -1
- package/dist/render.d.ts +9 -1
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +102 -123
- package/dist/render.js.map +1 -1
- package/dist/serve.d.ts +8 -2
- package/dist/serve.d.ts.map +1 -1
- package/dist/serve.js +22 -20
- 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 +81 -11
- package/src/adapters/aws-lambda.ts +426 -115
- package/src/adapters/cloudflare.ts +29 -13
- 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 +117 -10
- package/src/built-runtime.ts +28 -0
- package/src/cache.ts +262 -15
- package/src/cli.ts +14 -1
- package/src/client.ts +32 -1
- package/src/config.ts +43 -6
- package/src/csp.ts +14 -11
- 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 +79 -9
- package/src/navigation.ts +1 -1
- package/src/node-server.ts +128 -22
- package/src/prerender-entry.ts +52 -3
- package/src/render.ts +807 -753
- package/src/serve.ts +49 -56
- 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
|
@@ -33,14 +33,17 @@ import { middlewareMatches, type MiddlewareModule } from "../middleware.js";
|
|
|
33
33
|
import { normalizeRoutePath } from "../route-path.js";
|
|
34
34
|
import type { AppRoute } from "../routes.js";
|
|
35
35
|
import { contentSecurityPolicy } from "../csp.js";
|
|
36
|
-
import { isNotFoundError, rewriteLocation } from "../navigation.js";
|
|
36
|
+
import { isNotFoundError, isRedirectError, rewriteLocation } from "../navigation.js";
|
|
37
37
|
import { validateRouteMetadata } from "../metadata.js";
|
|
38
38
|
import { routeSecurityHeaders } from "../security-headers.js";
|
|
39
39
|
import type { AppRouterPrerenderStore } from "../serve.js";
|
|
40
40
|
import { emitRouterDevtoolsEvent } from "./devtools.js";
|
|
41
41
|
import { escapeHtmlAttribute, escapeHtmlText } from "@reckona/mreact-shared/html-escape";
|
|
42
|
-
import {
|
|
43
|
-
|
|
42
|
+
import {
|
|
43
|
+
isCurrentPrerenderedRoute,
|
|
44
|
+
replayedPrerenderedRouteHeaders,
|
|
45
|
+
validatedPrerenderedNavigationHtml,
|
|
46
|
+
} from "../prerender-entry.js";
|
|
44
47
|
|
|
45
48
|
/** Re-exports build manifest contracts used by Cloudflare handlers. */
|
|
46
49
|
export type {
|
|
@@ -68,6 +71,8 @@ export type {
|
|
|
68
71
|
AppRouterRequestStartLogEvent,
|
|
69
72
|
AppRouterRequestTimingLogEvent,
|
|
70
73
|
AppRouterRuntime,
|
|
74
|
+
AppRouterUpgradeErrorLogEvent,
|
|
75
|
+
AppRouterUpgradeRejectedLogEvent,
|
|
71
76
|
} from "../logger.js";
|
|
72
77
|
/** Re-exports response hook contracts used by Cloudflare handlers. */
|
|
73
78
|
export type { AppRouterResponseHook, AppRouterResponseHookContext } from "../render.js";
|
|
@@ -214,6 +219,7 @@ export type CloudflareRouteModuleComponent<Data = unknown, Env = unknown> = (
|
|
|
214
219
|
* Defines the exports accepted from a Cloudflare page route module.
|
|
215
220
|
*/
|
|
216
221
|
export interface CloudflareRouteModule<Data = unknown, Env = unknown> {
|
|
222
|
+
[exportName: string]: unknown;
|
|
217
223
|
App?: CloudflareRouteModuleComponent<Data, Env> | undefined;
|
|
218
224
|
CloudflareRouteComponent?: CloudflareRouteModuleComponent<Data, Env> | undefined;
|
|
219
225
|
default?: CloudflareRouteModuleComponent<Data, Env> | undefined;
|
|
@@ -573,12 +579,24 @@ export function createCloudflareRouteModuleRenderer<Env = unknown>(
|
|
|
573
579
|
return error;
|
|
574
580
|
}
|
|
575
581
|
|
|
582
|
+
if (isRedirectError(error)) {
|
|
583
|
+
return new Response(null, {
|
|
584
|
+
headers: { location: error.location },
|
|
585
|
+
status: error.status,
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
|
|
576
589
|
if (isNotFoundError(error)) {
|
|
577
590
|
return cloudflareNotFoundResponse(request);
|
|
578
591
|
}
|
|
579
592
|
|
|
580
593
|
throw error;
|
|
581
594
|
}
|
|
595
|
+
|
|
596
|
+
if (data instanceof Response) {
|
|
597
|
+
return data;
|
|
598
|
+
}
|
|
599
|
+
|
|
582
600
|
const props = {
|
|
583
601
|
...context,
|
|
584
602
|
data,
|
|
@@ -590,6 +608,13 @@ export function createCloudflareRouteModuleRenderer<Env = unknown>(
|
|
|
590
608
|
try {
|
|
591
609
|
rendered = await runWithCloudflareQueryClient(queryClient, () => component(props));
|
|
592
610
|
} catch (error) {
|
|
611
|
+
if (isRedirectError(error)) {
|
|
612
|
+
return new Response(null, {
|
|
613
|
+
headers: { location: error.location },
|
|
614
|
+
status: error.status,
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
|
|
593
618
|
if (isNotFoundError(error)) {
|
|
594
619
|
return cloudflareNotFoundResponse(request);
|
|
595
620
|
}
|
|
@@ -1406,20 +1431,11 @@ function prerenderedResponse(
|
|
|
1406
1431
|
}
|
|
1407
1432
|
|
|
1408
1433
|
const prerendered = prerenderedRoutes?.[path];
|
|
1409
|
-
const candidateNavigationHtml = prerendered?.navigationHtml;
|
|
1410
|
-
|
|
1411
1434
|
if (!isCurrentPrerenderedRoute(prerendered)) {
|
|
1412
|
-
if (
|
|
1413
|
-
isNavigation &&
|
|
1414
|
-
typeof candidateNavigationHtml === "string" &&
|
|
1415
|
-
!hasNavigationRouteMarker(candidateNavigationHtml)
|
|
1416
|
-
) {
|
|
1417
|
-
return cloudflareDocumentReloadNavigationResponse();
|
|
1418
|
-
}
|
|
1419
1435
|
return undefined;
|
|
1420
1436
|
}
|
|
1421
1437
|
|
|
1422
|
-
const html = isNavigation ? prerendered
|
|
1438
|
+
const html = isNavigation ? validatedPrerenderedNavigationHtml(prerendered) : prerendered.html;
|
|
1423
1439
|
if (html === undefined) {
|
|
1424
1440
|
return cloudflareDocumentReloadNavigationResponse();
|
|
1425
1441
|
}
|
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 {
|
package/src/build.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { builtinModules } from "node:module";
|
|
|
16
16
|
import { availableParallelism } from "node:os";
|
|
17
17
|
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
18
18
|
import { fileURLToPath } from "node:url";
|
|
19
|
+
import type { DehydrateOptions } from "@reckona/mreact-query";
|
|
19
20
|
import {
|
|
20
21
|
collectStaticImportReferences,
|
|
21
22
|
collectTopLevelValueExportNames,
|
|
@@ -57,6 +58,7 @@ import {
|
|
|
57
58
|
resolveCompatVendorEntryFiles,
|
|
58
59
|
sourceReferencesCompatVendorSpecifier,
|
|
59
60
|
} from "./module-runner.js";
|
|
61
|
+
import { dehydrateOptionsFromModule } from "./dehydrate-policy.js";
|
|
60
62
|
import { compileRouteMatcherArtifact, scanAppRoutes } from "./routes.js";
|
|
61
63
|
import type { AppRoute, CompiledRouteMatcherArtifact } from "./routes.js";
|
|
62
64
|
import { appFileConventionForRootFilename } from "./file-conventions.js";
|
|
@@ -249,10 +251,11 @@ export interface BuildAppResult {
|
|
|
249
251
|
|
|
250
252
|
interface IncrementalBuildCacheManifest {
|
|
251
253
|
fingerprint: string;
|
|
252
|
-
version:
|
|
254
|
+
version: 2;
|
|
253
255
|
}
|
|
254
256
|
|
|
255
257
|
interface IncrementalBuildServerManifestOutputs {
|
|
258
|
+
dehydratePolicyModule?: string;
|
|
256
259
|
serverModuleFiles?: Record<string, string>;
|
|
257
260
|
serverModuleRenderFiles?: Record<string, string>;
|
|
258
261
|
serverModuleRequestFiles?: Record<string, string>;
|
|
@@ -326,6 +329,7 @@ export interface PackageCloudflarePagesArtifactOptions {
|
|
|
326
329
|
export interface BuiltServerManifest {
|
|
327
330
|
allowedSourceDirs?: readonly string[];
|
|
328
331
|
assetBaseUrl?: string;
|
|
332
|
+
dehydratePolicyModule?: string;
|
|
329
333
|
version: 1;
|
|
330
334
|
files: Record<string, string>;
|
|
331
335
|
prerenderedRoutes?: Record<string, BuiltPrerenderedRoute>;
|
|
@@ -456,6 +460,13 @@ async function buildAppWithResolvedProject(
|
|
|
456
460
|
options: BuildAppOptions,
|
|
457
461
|
project: ResolvedAppRouterProject,
|
|
458
462
|
): Promise<BuildAppResult> {
|
|
463
|
+
if (
|
|
464
|
+
(options as BuildAppOptions & { dehydrateOptions?: unknown }).dehydrateOptions !== undefined
|
|
465
|
+
) {
|
|
466
|
+
throw new Error(
|
|
467
|
+
"buildApp dehydration policies must use dehydratePolicyModule so the same executable policy is available to prerendering and built runtimes.",
|
|
468
|
+
);
|
|
469
|
+
}
|
|
459
470
|
const timingSink = options.onBuildPhaseTiming;
|
|
460
471
|
const progressSink = options.onBuildProgress;
|
|
461
472
|
const shouldTrackBuildPhases = timingSink !== undefined || progressSink !== undefined;
|
|
@@ -572,6 +583,24 @@ async function buildAppWithResolvedProject(
|
|
|
572
583
|
});
|
|
573
584
|
}
|
|
574
585
|
|
|
586
|
+
const builtDehydratePolicy =
|
|
587
|
+
project.dehydratePolicyModule === undefined
|
|
588
|
+
? undefined
|
|
589
|
+
: await buildDehydratePolicyModule({
|
|
590
|
+
define: viteDefine,
|
|
591
|
+
file: project.dehydratePolicyModule,
|
|
592
|
+
projectRoot: project.projectRoot,
|
|
593
|
+
vitePlugins,
|
|
594
|
+
});
|
|
595
|
+
const dehydratePolicyArtifact =
|
|
596
|
+
builtDehydratePolicy === undefined
|
|
597
|
+
? undefined
|
|
598
|
+
: `dehydrate-policy.${hashText(builtDehydratePolicy.code).slice(0, 16)}.mjs`;
|
|
599
|
+
if (builtDehydratePolicy !== undefined && dehydratePolicyArtifact !== undefined) {
|
|
600
|
+
await writeFile(join(serverDir, dehydratePolicyArtifact), builtDehydratePolicy.code);
|
|
601
|
+
}
|
|
602
|
+
const dehydrateOptions = builtDehydratePolicy?.dehydrateOptions;
|
|
603
|
+
|
|
575
604
|
const publicAssets =
|
|
576
605
|
shouldTrackBuildPhases === false
|
|
577
606
|
? await buildPublicAssetManifest(project, clientDir)
|
|
@@ -729,6 +758,7 @@ async function buildAppWithResolvedProject(
|
|
|
729
758
|
assetBaseUrl: project.assetBaseUrl,
|
|
730
759
|
clientRoutes: clientManifestRoutes,
|
|
731
760
|
define: viteDefine,
|
|
761
|
+
dehydrateOptions,
|
|
732
762
|
project,
|
|
733
763
|
routes,
|
|
734
764
|
serverModules,
|
|
@@ -741,6 +771,7 @@ async function buildAppWithResolvedProject(
|
|
|
741
771
|
assetBaseUrl: project.assetBaseUrl,
|
|
742
772
|
clientRoutes: clientManifestRoutes,
|
|
743
773
|
define: viteDefine,
|
|
774
|
+
dehydrateOptions,
|
|
744
775
|
project,
|
|
745
776
|
routes,
|
|
746
777
|
serverModules,
|
|
@@ -790,6 +821,9 @@ async function buildAppWithResolvedProject(
|
|
|
790
821
|
relative(project.projectRoot, directory),
|
|
791
822
|
),
|
|
792
823
|
...(project.assetBaseUrl === undefined ? {} : { assetBaseUrl: project.assetBaseUrl }),
|
|
824
|
+
...(dehydratePolicyArtifact === undefined
|
|
825
|
+
? {}
|
|
826
|
+
: { dehydratePolicyModule: dehydratePolicyArtifact }),
|
|
793
827
|
version: 1,
|
|
794
828
|
routes: serverRoutes,
|
|
795
829
|
routeMatcher: compileRouteMatcherArtifact(serverRoutes),
|
|
@@ -903,7 +937,7 @@ async function buildAppWithResolvedProject(
|
|
|
903
937
|
}
|
|
904
938
|
|
|
905
939
|
const incrementalBuildCacheFilename = "build-cache.json";
|
|
906
|
-
const incrementalBuildCacheVersion =
|
|
940
|
+
const incrementalBuildCacheVersion = 2;
|
|
907
941
|
|
|
908
942
|
async function createIncrementalBuildCacheFingerprint(options: {
|
|
909
943
|
buildTargets: readonly AppRouterBuildTarget[];
|
|
@@ -949,6 +983,12 @@ async function createIncrementalBuildCacheFingerprint(options: {
|
|
|
949
983
|
assetBaseUrl: options.project.assetBaseUrl ?? null,
|
|
950
984
|
clientConsolePureFunctions: options.project.clientConsolePureFunctions ?? [],
|
|
951
985
|
clientSourceMaps: options.project.clientSourceMaps,
|
|
986
|
+
dehydratePolicyModule:
|
|
987
|
+
options.project.dehydratePolicyModule === undefined
|
|
988
|
+
? null
|
|
989
|
+
: normalizeBuildInputPath(
|
|
990
|
+
relative(options.project.projectRoot, options.project.dehydratePolicyModule),
|
|
991
|
+
),
|
|
952
992
|
define,
|
|
953
993
|
nodeEnv: process.env.NODE_ENV ?? null,
|
|
954
994
|
publicAssetBaseUrl: options.project.publicAssetBaseUrl ?? null,
|
|
@@ -1015,6 +1055,9 @@ async function hasRequiredIncrementalBuildOutputs(
|
|
|
1015
1055
|
}
|
|
1016
1056
|
|
|
1017
1057
|
for (const file of [
|
|
1058
|
+
...(serverManifest.dehydratePolicyModule === undefined
|
|
1059
|
+
? []
|
|
1060
|
+
: [serverManifest.dehydratePolicyModule]),
|
|
1018
1061
|
...Object.values(serverManifest.serverModuleFiles ?? {}),
|
|
1019
1062
|
...Object.values(serverManifest.serverModuleRenderFiles ?? {}),
|
|
1020
1063
|
...Object.values(serverManifest.serverModuleRequestFiles ?? {}),
|
|
@@ -1466,7 +1509,7 @@ async function buildRouteUsesRequestInput(options: {
|
|
|
1466
1509
|
files: options.files,
|
|
1467
1510
|
projectRoot: options.projectRoot,
|
|
1468
1511
|
source: entry.source,
|
|
1469
|
-
})
|
|
1512
|
+
}),
|
|
1470
1513
|
);
|
|
1471
1514
|
}
|
|
1472
1515
|
|
|
@@ -2560,6 +2603,38 @@ function isReservedPublicAssetPath(pathname: string): boolean {
|
|
|
2560
2603
|
return pathname === "/_mreact" || pathname.startsWith("/_mreact/");
|
|
2561
2604
|
}
|
|
2562
2605
|
|
|
2606
|
+
async function buildDehydratePolicyModule(options: {
|
|
2607
|
+
define?: UserConfig["define"] | undefined;
|
|
2608
|
+
file: string;
|
|
2609
|
+
projectRoot: string;
|
|
2610
|
+
vitePlugins?: readonly PluginOption[] | undefined;
|
|
2611
|
+
}): Promise<{ code: string; dehydrateOptions: DehydrateOptions }> {
|
|
2612
|
+
const source = await readFile(options.file, "utf8");
|
|
2613
|
+
const code = await bundleAppRouterSourceModule({
|
|
2614
|
+
code: source,
|
|
2615
|
+
define: options.define,
|
|
2616
|
+
label: `dehydrate-policy:${options.file}`,
|
|
2617
|
+
plugins: [fileImportMetaUrlPlugin()],
|
|
2618
|
+
resolveDir: dirname(options.file),
|
|
2619
|
+
root: options.projectRoot,
|
|
2620
|
+
sourcefile: options.file,
|
|
2621
|
+
vitePlugins: options.vitePlugins,
|
|
2622
|
+
});
|
|
2623
|
+
const policyModule = await importAppRouterSourceModule<unknown>({
|
|
2624
|
+
code,
|
|
2625
|
+
label: `dehydrate-policy-runtime:${options.file}`,
|
|
2626
|
+
sourcefile: options.file,
|
|
2627
|
+
});
|
|
2628
|
+
|
|
2629
|
+
return {
|
|
2630
|
+
code,
|
|
2631
|
+
dehydrateOptions: dehydrateOptionsFromModule(
|
|
2632
|
+
policyModule,
|
|
2633
|
+
`mreactRouter dehydratePolicyModule ${options.file}`,
|
|
2634
|
+
),
|
|
2635
|
+
};
|
|
2636
|
+
}
|
|
2637
|
+
|
|
2563
2638
|
function isNodeError(error: unknown): error is NodeJS.ErrnoException {
|
|
2564
2639
|
return error instanceof Error && "code" in error;
|
|
2565
2640
|
}
|
|
@@ -2568,6 +2643,7 @@ async function prerenderStaticRoutes(options: {
|
|
|
2568
2643
|
appDir: string;
|
|
2569
2644
|
assetBaseUrl?: string | undefined;
|
|
2570
2645
|
clientRoutes: readonly ClientRouteManifestEntry[];
|
|
2646
|
+
dehydrateOptions?: DehydrateOptions | undefined;
|
|
2571
2647
|
define?: UserConfig["define"] | undefined;
|
|
2572
2648
|
project: ResolvedAppRouterProject;
|
|
2573
2649
|
routes: readonly AppRoute[];
|
|
@@ -2633,14 +2709,11 @@ async function prerenderStaticRoutes(options: {
|
|
|
2633
2709
|
clientScripts,
|
|
2634
2710
|
clientStyles,
|
|
2635
2711
|
define: options.define,
|
|
2712
|
+
dehydrateOptions: options.dehydrateOptions,
|
|
2636
2713
|
importPolicy,
|
|
2637
2714
|
navigationScripts,
|
|
2638
|
-
request: new Request(
|
|
2639
|
-
|
|
2640
|
-
analysis.clientRoute
|
|
2641
|
-
? undefined
|
|
2642
|
-
: { headers: { "x-mreact-prerender-variant-capture": "1" } },
|
|
2643
|
-
),
|
|
2715
|
+
request: new Request(`http://mreact.local${pathname}`),
|
|
2716
|
+
prerenderVariantCapture: !analysis.clientRoute,
|
|
2644
2717
|
renderSignals,
|
|
2645
2718
|
serverModuleCacheVersion,
|
|
2646
2719
|
serverModules: serverModuleMap,
|
|
@@ -5006,9 +5079,43 @@ function validateOptionalHeadMetadata(value, path) {
|
|
|
5006
5079
|
}
|
|
5007
5080
|
}
|
|
5008
5081
|
}
|
|
5082
|
+
validateMetaRefreshHeadDescriptor(descriptor, descriptorPath);
|
|
5009
5083
|
});
|
|
5010
5084
|
}
|
|
5011
5085
|
|
|
5086
|
+
function validateMetaRefreshHeadDescriptor(descriptor, path) {
|
|
5087
|
+
if (descriptor.tag !== "meta" || descriptor.attrs === undefined) {
|
|
5088
|
+
return;
|
|
5089
|
+
}
|
|
5090
|
+
const attrs = Object.entries(descriptor.attrs);
|
|
5091
|
+
const hasRefresh = attrs.some(([name, value]) => {
|
|
5092
|
+
const canonicalName = name.toLowerCase();
|
|
5093
|
+
return (canonicalName === "http-equiv" || canonicalName === "httpequiv")
|
|
5094
|
+
&& typeof value === "string"
|
|
5095
|
+
&& value.toLowerCase() === "refresh";
|
|
5096
|
+
});
|
|
5097
|
+
if (!hasRefresh) {
|
|
5098
|
+
return;
|
|
5099
|
+
}
|
|
5100
|
+
for (const [name, value] of attrs) {
|
|
5101
|
+
if (name.toLowerCase() !== "content" || typeof value !== "string") {
|
|
5102
|
+
continue;
|
|
5103
|
+
}
|
|
5104
|
+
const match = /^[^;]*;\\s*url\\s*=\\s*([\\s\\S]+)$/iu.exec(value);
|
|
5105
|
+
if (match === null || match[1] === undefined) {
|
|
5106
|
+
continue;
|
|
5107
|
+
}
|
|
5108
|
+
let target = match[1].trim();
|
|
5109
|
+
const quote = target[0];
|
|
5110
|
+
if ((quote === String.fromCharCode(34) || quote === "'") && target[target.length - 1] === quote) {
|
|
5111
|
+
target = target.slice(1, -1).trim();
|
|
5112
|
+
}
|
|
5113
|
+
if (isUnsafeUrlValueForName("href", target)) {
|
|
5114
|
+
throw new Error(\`Invalid metadata field \${path}.attrs.\${name}: unsafe refresh URL value.\`);
|
|
5115
|
+
}
|
|
5116
|
+
}
|
|
5117
|
+
}
|
|
5118
|
+
|
|
5012
5119
|
function validateHeadAttribute(name, value, path) {
|
|
5013
5120
|
if (!isSafeHeadAttributeName(name)) {
|
|
5014
5121
|
throw new Error(\`Invalid metadata field \${path}: expected safe HTML attribute name.\`);
|
|
@@ -5070,7 +5177,7 @@ function isUnsafeUrlValueForName(name, value) {
|
|
|
5070
5177
|
}
|
|
5071
5178
|
const scheme = match[1].toLowerCase();
|
|
5072
5179
|
if (scheme === "data" && (name === "src" || name === "poster")) {
|
|
5073
|
-
return !/^data:image\\/(?!svg\\+xml(?:[;,]|$))/i.test(canonical);
|
|
5180
|
+
return !/^data:image\\/(?!svg\\+xml\\s*(?:[;,]|$))/i.test(canonical);
|
|
5074
5181
|
}
|
|
5075
5182
|
return scheme === "javascript" || scheme === "data" || scheme === "vbscript" || scheme === "livescript" || scheme === "mhtml" || scheme === "file";
|
|
5076
5183
|
}
|
package/src/built-runtime.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import { dirname, isAbsolute, join, normalize } from "node:path";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
import type { DehydrateOptions } from "@reckona/mreact-query";
|
|
4
6
|
import type {
|
|
5
7
|
BuiltPrerenderedRoute,
|
|
6
8
|
BuiltServerManifest,
|
|
@@ -10,6 +12,7 @@ import { builtClientAssetPaths } from "./built-assets.js";
|
|
|
10
12
|
import type { BuiltServerModuleArtifactRuntime } from "./built-server-module-artifacts.js";
|
|
11
13
|
import type { ClientRouteManifestEntry } from "./client-route-inference.js";
|
|
12
14
|
import type { AppRouterImportPolicy } from "./import-policy.js";
|
|
15
|
+
import { dehydrateOptionsFromModule } from "./dehydrate-policy.js";
|
|
13
16
|
import { createRouteMatcher, type AppRoute, type RouteMatcher } from "./routes.js";
|
|
14
17
|
|
|
15
18
|
export interface BuiltRuntime extends BuiltServerModuleArtifactRuntime {
|
|
@@ -19,6 +22,7 @@ export interface BuiltRuntime extends BuiltServerModuleArtifactRuntime {
|
|
|
19
22
|
clientScripts: ReadonlyMap<string, string>;
|
|
20
23
|
clientStylesByFile: ReadonlyMap<string, readonly string[]>;
|
|
21
24
|
clientStyles: ReadonlyMap<string, readonly string[]>;
|
|
25
|
+
dehydrateOptions?: DehydrateOptions | undefined;
|
|
22
26
|
generatedImportPolicy?: AppRouterImportPolicy | undefined;
|
|
23
27
|
hasMiddleware: boolean;
|
|
24
28
|
navigationScripts: ReadonlyMap<string, string>;
|
|
@@ -160,6 +164,13 @@ export async function materializeBuiltRuntime(options: {
|
|
|
160
164
|
options.importPolicyText,
|
|
161
165
|
options.importPolicyPath,
|
|
162
166
|
);
|
|
167
|
+
const dehydrateOptions =
|
|
168
|
+
serverManifest.dehydratePolicyModule === undefined
|
|
169
|
+
? undefined
|
|
170
|
+
: await loadBuiltDehydratePolicy(
|
|
171
|
+
join(options.outDir, "server", safeManifestFilePath(serverManifest.dehydratePolicyModule)),
|
|
172
|
+
serverManifest.dehydratePolicyModule,
|
|
173
|
+
);
|
|
163
174
|
|
|
164
175
|
return {
|
|
165
176
|
appDir: routesDir,
|
|
@@ -171,6 +182,7 @@ export async function materializeBuiltRuntime(options: {
|
|
|
171
182
|
clientScripts,
|
|
172
183
|
clientStylesByFile,
|
|
173
184
|
clientStyles,
|
|
185
|
+
...(dehydrateOptions === undefined ? {} : { dehydrateOptions }),
|
|
174
186
|
...(generatedImportPolicy === undefined ? {} : { generatedImportPolicy }),
|
|
175
187
|
hasMiddleware,
|
|
176
188
|
navigationScripts,
|
|
@@ -198,6 +210,22 @@ export async function materializeBuiltRuntime(options: {
|
|
|
198
210
|
};
|
|
199
211
|
}
|
|
200
212
|
|
|
213
|
+
async function loadBuiltDehydratePolicy(
|
|
214
|
+
policyPath: string,
|
|
215
|
+
manifestPath: string,
|
|
216
|
+
): Promise<DehydrateOptions> {
|
|
217
|
+
let policyModule: unknown;
|
|
218
|
+
try {
|
|
219
|
+
policyModule = await import(pathToFileURL(policyPath).href);
|
|
220
|
+
} catch (error) {
|
|
221
|
+
throw builtArtifactReadError("built app dehydration policy", policyPath, error);
|
|
222
|
+
}
|
|
223
|
+
return dehydrateOptionsFromModule(
|
|
224
|
+
policyModule,
|
|
225
|
+
`Built dehydration policy ${manifestPath}`,
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
201
229
|
export async function readBuiltImportPolicyText(outDir: string): Promise<string | undefined> {
|
|
202
230
|
const policyPath = join(outDir, "server", "import-policy.json");
|
|
203
231
|
|