@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
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,
|
|
@@ -46,6 +47,7 @@ import {
|
|
|
46
47
|
buildNavigationRuntimeBundle,
|
|
47
48
|
clientScriptForPath,
|
|
48
49
|
routeIdForPath,
|
|
50
|
+
routeMarkerParts,
|
|
49
51
|
type BuildClientRouteOutputOptions,
|
|
50
52
|
} from "./navigation-runtime.js";
|
|
51
53
|
import {
|
|
@@ -56,6 +58,7 @@ import {
|
|
|
56
58
|
resolveCompatVendorEntryFiles,
|
|
57
59
|
sourceReferencesCompatVendorSpecifier,
|
|
58
60
|
} from "./module-runner.js";
|
|
61
|
+
import { dehydrateOptionsFromModule } from "./dehydrate-policy.js";
|
|
59
62
|
import { compileRouteMatcherArtifact, scanAppRoutes } from "./routes.js";
|
|
60
63
|
import type { AppRoute, CompiledRouteMatcherArtifact } from "./routes.js";
|
|
61
64
|
import { appFileConventionForRootFilename } from "./file-conventions.js";
|
|
@@ -72,6 +75,7 @@ import type { RouteCachePolicy } from "./cache.js";
|
|
|
72
75
|
import { routeCachePolicyFromSource } from "./cache.js";
|
|
73
76
|
import {
|
|
74
77
|
bundleMiddlewareModuleCode,
|
|
78
|
+
prerenderVariantMarkerParts,
|
|
75
79
|
renderAppRequest,
|
|
76
80
|
type RenderAppRequestRuntimeOptions,
|
|
77
81
|
} from "./render.js";
|
|
@@ -105,6 +109,7 @@ import { collectBuildInferredServerActions } from "./server-action-inference.js"
|
|
|
105
109
|
import {
|
|
106
110
|
isVisitorDependentResponse,
|
|
107
111
|
PRERENDERED_ROUTE_SCHEMA_VERSION,
|
|
112
|
+
storedPrerenderedRouteHeaders,
|
|
108
113
|
} from "./prerender-entry.js";
|
|
109
114
|
import { prepareRouteServerActionPlaceholders } from "./actions.js";
|
|
110
115
|
import { viteDefineCacheKey, vitePluginsCacheKey } from "./vite-plugin-cache-key.js";
|
|
@@ -246,10 +251,11 @@ export interface BuildAppResult {
|
|
|
246
251
|
|
|
247
252
|
interface IncrementalBuildCacheManifest {
|
|
248
253
|
fingerprint: string;
|
|
249
|
-
version:
|
|
254
|
+
version: 2;
|
|
250
255
|
}
|
|
251
256
|
|
|
252
257
|
interface IncrementalBuildServerManifestOutputs {
|
|
258
|
+
dehydratePolicyModule?: string;
|
|
253
259
|
serverModuleFiles?: Record<string, string>;
|
|
254
260
|
serverModuleRenderFiles?: Record<string, string>;
|
|
255
261
|
serverModuleRequestFiles?: Record<string, string>;
|
|
@@ -323,6 +329,7 @@ export interface PackageCloudflarePagesArtifactOptions {
|
|
|
323
329
|
export interface BuiltServerManifest {
|
|
324
330
|
allowedSourceDirs?: readonly string[];
|
|
325
331
|
assetBaseUrl?: string;
|
|
332
|
+
dehydratePolicyModule?: string;
|
|
326
333
|
version: 1;
|
|
327
334
|
files: Record<string, string>;
|
|
328
335
|
prerenderedRoutes?: Record<string, BuiltPrerenderedRoute>;
|
|
@@ -423,9 +430,11 @@ export interface BuiltServerModuleOutput {
|
|
|
423
430
|
export interface BuiltPrerenderedRoute {
|
|
424
431
|
headers: Record<string, string>;
|
|
425
432
|
html: string;
|
|
433
|
+
navigationHtml?: string | undefined;
|
|
426
434
|
/** Identifies entries that satisfy the complete current prerender contract. */
|
|
427
|
-
schemaVersion?:
|
|
435
|
+
schemaVersion?: 4 | undefined;
|
|
428
436
|
status: number;
|
|
437
|
+
strictTransportSecurity?: string | undefined;
|
|
429
438
|
}
|
|
430
439
|
|
|
431
440
|
type StaticParams = Record<string, string | number | boolean | readonly string[]>;
|
|
@@ -451,6 +460,13 @@ async function buildAppWithResolvedProject(
|
|
|
451
460
|
options: BuildAppOptions,
|
|
452
461
|
project: ResolvedAppRouterProject,
|
|
453
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
|
+
}
|
|
454
470
|
const timingSink = options.onBuildPhaseTiming;
|
|
455
471
|
const progressSink = options.onBuildProgress;
|
|
456
472
|
const shouldTrackBuildPhases = timingSink !== undefined || progressSink !== undefined;
|
|
@@ -567,6 +583,24 @@ async function buildAppWithResolvedProject(
|
|
|
567
583
|
});
|
|
568
584
|
}
|
|
569
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
|
+
|
|
570
604
|
const publicAssets =
|
|
571
605
|
shouldTrackBuildPhases === false
|
|
572
606
|
? await buildPublicAssetManifest(project, clientDir)
|
|
@@ -724,6 +758,7 @@ async function buildAppWithResolvedProject(
|
|
|
724
758
|
assetBaseUrl: project.assetBaseUrl,
|
|
725
759
|
clientRoutes: clientManifestRoutes,
|
|
726
760
|
define: viteDefine,
|
|
761
|
+
dehydrateOptions,
|
|
727
762
|
project,
|
|
728
763
|
routes,
|
|
729
764
|
serverModules,
|
|
@@ -736,6 +771,7 @@ async function buildAppWithResolvedProject(
|
|
|
736
771
|
assetBaseUrl: project.assetBaseUrl,
|
|
737
772
|
clientRoutes: clientManifestRoutes,
|
|
738
773
|
define: viteDefine,
|
|
774
|
+
dehydrateOptions,
|
|
739
775
|
project,
|
|
740
776
|
routes,
|
|
741
777
|
serverModules,
|
|
@@ -785,6 +821,9 @@ async function buildAppWithResolvedProject(
|
|
|
785
821
|
relative(project.projectRoot, directory),
|
|
786
822
|
),
|
|
787
823
|
...(project.assetBaseUrl === undefined ? {} : { assetBaseUrl: project.assetBaseUrl }),
|
|
824
|
+
...(dehydratePolicyArtifact === undefined
|
|
825
|
+
? {}
|
|
826
|
+
: { dehydratePolicyModule: dehydratePolicyArtifact }),
|
|
788
827
|
version: 1,
|
|
789
828
|
routes: serverRoutes,
|
|
790
829
|
routeMatcher: compileRouteMatcherArtifact(serverRoutes),
|
|
@@ -898,7 +937,7 @@ async function buildAppWithResolvedProject(
|
|
|
898
937
|
}
|
|
899
938
|
|
|
900
939
|
const incrementalBuildCacheFilename = "build-cache.json";
|
|
901
|
-
const incrementalBuildCacheVersion =
|
|
940
|
+
const incrementalBuildCacheVersion = 2;
|
|
902
941
|
|
|
903
942
|
async function createIncrementalBuildCacheFingerprint(options: {
|
|
904
943
|
buildTargets: readonly AppRouterBuildTarget[];
|
|
@@ -944,6 +983,12 @@ async function createIncrementalBuildCacheFingerprint(options: {
|
|
|
944
983
|
assetBaseUrl: options.project.assetBaseUrl ?? null,
|
|
945
984
|
clientConsolePureFunctions: options.project.clientConsolePureFunctions ?? [],
|
|
946
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
|
+
),
|
|
947
992
|
define,
|
|
948
993
|
nodeEnv: process.env.NODE_ENV ?? null,
|
|
949
994
|
publicAssetBaseUrl: options.project.publicAssetBaseUrl ?? null,
|
|
@@ -1010,6 +1055,9 @@ async function hasRequiredIncrementalBuildOutputs(
|
|
|
1010
1055
|
}
|
|
1011
1056
|
|
|
1012
1057
|
for (const file of [
|
|
1058
|
+
...(serverManifest.dehydratePolicyModule === undefined
|
|
1059
|
+
? []
|
|
1060
|
+
: [serverManifest.dehydratePolicyModule]),
|
|
1013
1061
|
...Object.values(serverManifest.serverModuleFiles ?? {}),
|
|
1014
1062
|
...Object.values(serverManifest.serverModuleRenderFiles ?? {}),
|
|
1015
1063
|
...Object.values(serverManifest.serverModuleRequestFiles ?? {}),
|
|
@@ -1461,7 +1509,7 @@ async function buildRouteUsesRequestInput(options: {
|
|
|
1461
1509
|
files: options.files,
|
|
1462
1510
|
projectRoot: options.projectRoot,
|
|
1463
1511
|
source: entry.source,
|
|
1464
|
-
})
|
|
1512
|
+
}),
|
|
1465
1513
|
);
|
|
1466
1514
|
}
|
|
1467
1515
|
|
|
@@ -2555,6 +2603,38 @@ function isReservedPublicAssetPath(pathname: string): boolean {
|
|
|
2555
2603
|
return pathname === "/_mreact" || pathname.startsWith("/_mreact/");
|
|
2556
2604
|
}
|
|
2557
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
|
+
|
|
2558
2638
|
function isNodeError(error: unknown): error is NodeJS.ErrnoException {
|
|
2559
2639
|
return error instanceof Error && "code" in error;
|
|
2560
2640
|
}
|
|
@@ -2563,6 +2643,7 @@ async function prerenderStaticRoutes(options: {
|
|
|
2563
2643
|
appDir: string;
|
|
2564
2644
|
assetBaseUrl?: string | undefined;
|
|
2565
2645
|
clientRoutes: readonly ClientRouteManifestEntry[];
|
|
2646
|
+
dehydrateOptions?: DehydrateOptions | undefined;
|
|
2566
2647
|
define?: UserConfig["define"] | undefined;
|
|
2567
2648
|
project: ResolvedAppRouterProject;
|
|
2568
2649
|
routes: readonly AppRoute[];
|
|
@@ -2620,6 +2701,7 @@ async function prerenderStaticRoutes(options: {
|
|
|
2620
2701
|
for (const pathname of await prerenderPathsForRoute(route, analysis, options.vitePlugins)) {
|
|
2621
2702
|
const renderSignals = {
|
|
2622
2703
|
headerDependent: () => true,
|
|
2704
|
+
strictTransportSecurity: () => undefined as string | undefined,
|
|
2623
2705
|
};
|
|
2624
2706
|
const renderOptions = {
|
|
2625
2707
|
appDir: options.appDir,
|
|
@@ -2627,33 +2709,51 @@ async function prerenderStaticRoutes(options: {
|
|
|
2627
2709
|
clientScripts,
|
|
2628
2710
|
clientStyles,
|
|
2629
2711
|
define: options.define,
|
|
2712
|
+
dehydrateOptions: options.dehydrateOptions,
|
|
2630
2713
|
importPolicy,
|
|
2631
2714
|
navigationScripts,
|
|
2632
2715
|
request: new Request(`http://mreact.local${pathname}`),
|
|
2716
|
+
prerenderVariantCapture: !analysis.clientRoute,
|
|
2633
2717
|
renderSignals,
|
|
2634
2718
|
serverModuleCacheVersion,
|
|
2635
2719
|
serverModules: serverModuleMap,
|
|
2636
2720
|
vitePlugins: options.vitePlugins,
|
|
2637
2721
|
} satisfies RenderAppRequestRuntimeOptions;
|
|
2638
2722
|
const response = await renderAppRequest(renderOptions);
|
|
2639
|
-
const
|
|
2723
|
+
const renderedHtml = await response.text();
|
|
2640
2724
|
|
|
2641
2725
|
if (renderSignals.headerDependent() || isVisitorDependentResponse(response)) {
|
|
2642
2726
|
continue;
|
|
2643
2727
|
}
|
|
2644
2728
|
|
|
2645
|
-
|
|
2729
|
+
let html = renderedHtml;
|
|
2730
|
+
let navigationHtml = renderedHtml;
|
|
2731
|
+
if (!analysis.clientRoute) {
|
|
2732
|
+
const capture = prerenderVariantMarkerParts(route.path);
|
|
2733
|
+
const start = renderedHtml.indexOf(capture.prefix);
|
|
2734
|
+
const end = renderedHtml.lastIndexOf(capture.suffix);
|
|
2735
|
+
if (start === -1 || end < start + capture.prefix.length) {
|
|
2736
|
+
throw new Error(`Failed to capture prerender response variants for ${pathname}.`);
|
|
2737
|
+
}
|
|
2738
|
+
const before = renderedHtml.slice(0, start);
|
|
2739
|
+
const content = renderedHtml.slice(start + capture.prefix.length, end);
|
|
2740
|
+
const after = renderedHtml.slice(end + capture.suffix.length);
|
|
2741
|
+
const navigationMarker = routeMarkerParts(route.path);
|
|
2742
|
+
html = `${before}${content}${after}`;
|
|
2743
|
+
navigationHtml = `${before}${navigationMarker.prefix}${content}${navigationMarker.suffix}${after}`;
|
|
2744
|
+
}
|
|
2646
2745
|
|
|
2647
|
-
response.headers
|
|
2648
|
-
|
|
2649
|
-
});
|
|
2746
|
+
const headers = storedPrerenderedRouteHeaders(response.headers);
|
|
2747
|
+
const strictTransportSecurity = renderSignals.strictTransportSecurity();
|
|
2650
2748
|
entries.push([
|
|
2651
2749
|
pathname,
|
|
2652
2750
|
{
|
|
2653
2751
|
headers,
|
|
2654
2752
|
html,
|
|
2753
|
+
navigationHtml,
|
|
2655
2754
|
schemaVersion: PRERENDERED_ROUTE_SCHEMA_VERSION,
|
|
2656
2755
|
status: response.status,
|
|
2756
|
+
...(strictTransportSecurity === undefined ? {} : { strictTransportSecurity }),
|
|
2657
2757
|
},
|
|
2658
2758
|
]);
|
|
2659
2759
|
}
|
|
@@ -3954,6 +4054,7 @@ function cloudflarePageRouteFacadeModuleSource(componentImport: string): string
|
|
|
3954
4054
|
const componentSlots = readComponentModuleExport(componentModule, "slots");
|
|
3955
4055
|
const componentGenerateMetadata = readComponentModuleExport(componentModule, "generateMetadata");
|
|
3956
4056
|
const componentMetadata = readComponentModuleExport(componentModule, "metadata");
|
|
4057
|
+
const componentSecurityHeadersApplied = readComponentModuleExport(componentModule, "__mreactSecurityHeadersApplied");
|
|
3957
4058
|
|
|
3958
4059
|
export function App(props) {
|
|
3959
4060
|
return renderCloudflareRouteComponent(props);
|
|
@@ -3968,6 +4069,7 @@ export const slots = componentSlots === undefined ? undefined : { ...componentSl
|
|
|
3968
4069
|
export const generateMetadata =
|
|
3969
4070
|
typeof componentGenerateMetadata === "function" ? componentGenerateMetadata : undefined;
|
|
3970
4071
|
export const metadata = componentMetadata;
|
|
4072
|
+
export const __mreactSecurityHeadersApplied = componentSecurityHeadersApplied === true;
|
|
3971
4073
|
|
|
3972
4074
|
function renderCloudflareRouteComponent(props) {
|
|
3973
4075
|
const routeComponent = resolveCloudflareRouteComponent();
|
|
@@ -4714,7 +4816,9 @@ ${cloudflareShellRuntimeSource()}`;
|
|
|
4714
4816
|
}
|
|
4715
4817
|
|
|
4716
4818
|
function cloudflareShellRuntimeSource(): string {
|
|
4717
|
-
return `
|
|
4819
|
+
return `export const __mreactSecurityHeadersApplied = true;
|
|
4820
|
+
|
|
4821
|
+
async function renderLayoutShells(shells, props, namedSlots) {
|
|
4718
4822
|
const slotContext = { consumedSlots: new Set(), namedSlots };
|
|
4719
4823
|
const rendered = [];
|
|
4720
4824
|
for (const shell of shells) {
|
|
@@ -4793,7 +4897,7 @@ function cloudflareHydrationMarkerParts(props) {
|
|
|
4793
4897
|
const escapedRouteId = escapeHtmlAttribute(routeId);
|
|
4794
4898
|
const propsJson = escapeScriptJson(JSON.stringify({
|
|
4795
4899
|
params: props.params,
|
|
4796
|
-
request: { url: props.request.url },
|
|
4900
|
+
request: { url: new URL(props.request.url).pathname },
|
|
4797
4901
|
data: props.data,
|
|
4798
4902
|
}));
|
|
4799
4903
|
const clientReferencesJson = route.clientReferenceManifest === undefined || route.clientReferenceManifest.length === 0
|
|
@@ -4975,9 +5079,43 @@ function validateOptionalHeadMetadata(value, path) {
|
|
|
4975
5079
|
}
|
|
4976
5080
|
}
|
|
4977
5081
|
}
|
|
5082
|
+
validateMetaRefreshHeadDescriptor(descriptor, descriptorPath);
|
|
4978
5083
|
});
|
|
4979
5084
|
}
|
|
4980
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
|
+
|
|
4981
5119
|
function validateHeadAttribute(name, value, path) {
|
|
4982
5120
|
if (!isSafeHeadAttributeName(name)) {
|
|
4983
5121
|
throw new Error(\`Invalid metadata field \${path}: expected safe HTML attribute name.\`);
|
|
@@ -5039,7 +5177,7 @@ function isUnsafeUrlValueForName(name, value) {
|
|
|
5039
5177
|
}
|
|
5040
5178
|
const scheme = match[1].toLowerCase();
|
|
5041
5179
|
if (scheme === "data" && (name === "src" || name === "poster")) {
|
|
5042
|
-
return !/^data:image\\/(?!svg\\+xml(?:[;,]|$))/i.test(canonical);
|
|
5180
|
+
return !/^data:image\\/(?!svg\\+xml\\s*(?:[;,]|$))/i.test(canonical);
|
|
5043
5181
|
}
|
|
5044
5182
|
return scheme === "javascript" || scheme === "data" || scheme === "vbscript" || scheme === "livescript" || scheme === "mhtml" || scheme === "file";
|
|
5045
5183
|
}
|
|
@@ -5216,17 +5354,63 @@ function routeSecurityHeaders(security, request) {
|
|
|
5216
5354
|
} else {
|
|
5217
5355
|
headers["referrer-policy"] = validateHeaderValue(security?.referrerPolicy ?? "strict-origin-when-cross-origin");
|
|
5218
5356
|
}
|
|
5357
|
+
if (security?.permissionsPolicy === null) {
|
|
5358
|
+
delete headers["permissions-policy"];
|
|
5359
|
+
} else {
|
|
5360
|
+
const permissionsPolicy = serializePermissionsPolicy(security?.permissionsPolicy);
|
|
5361
|
+
if (permissionsPolicy === undefined) {
|
|
5362
|
+
delete headers["permissions-policy"];
|
|
5363
|
+
} else {
|
|
5364
|
+
headers["permissions-policy"] = permissionsPolicy;
|
|
5365
|
+
}
|
|
5366
|
+
}
|
|
5219
5367
|
if (security?.frameOptions === null) {
|
|
5220
5368
|
delete headers["x-frame-options"];
|
|
5221
5369
|
} else if (security?.frameOptions !== undefined) {
|
|
5222
5370
|
headers["x-frame-options"] = validateHeaderValue(security.frameOptions);
|
|
5223
5371
|
}
|
|
5224
5372
|
if (request.url.startsWith("https://") && security?.hsts !== undefined && security.hsts !== false && security.hsts !== null) {
|
|
5225
|
-
headers["strict-transport-security"] =
|
|
5373
|
+
headers["strict-transport-security"] = serializeHsts(security.hsts);
|
|
5226
5374
|
}
|
|
5227
5375
|
return headers;
|
|
5228
5376
|
}
|
|
5229
5377
|
|
|
5378
|
+
function serializeHsts(hsts) {
|
|
5379
|
+
if (hsts === false) {
|
|
5380
|
+
throw new TypeError("Invalid security header value for hsts.");
|
|
5381
|
+
}
|
|
5382
|
+
const maxAge = Math.trunc(hsts.maxAge);
|
|
5383
|
+
if (!Number.isFinite(maxAge) || maxAge < 0) {
|
|
5384
|
+
throw new TypeError("Invalid security header value for hsts.maxAge.");
|
|
5385
|
+
}
|
|
5386
|
+
const parts = [\`max-age=\${maxAge}\`];
|
|
5387
|
+
if (hsts.includeSubDomains === true) {
|
|
5388
|
+
parts.push("includeSubDomains");
|
|
5389
|
+
}
|
|
5390
|
+
if (hsts.preload === true) {
|
|
5391
|
+
parts.push("preload");
|
|
5392
|
+
}
|
|
5393
|
+
return parts.join("; ");
|
|
5394
|
+
}
|
|
5395
|
+
|
|
5396
|
+
function serializePermissionsPolicy(policy) {
|
|
5397
|
+
if (policy === undefined) {
|
|
5398
|
+
return "camera=(), microphone=(), geolocation=()";
|
|
5399
|
+
}
|
|
5400
|
+
const directives = [];
|
|
5401
|
+
for (const [directive, allowlist] of Object.entries(policy)) {
|
|
5402
|
+
if (allowlist === null || allowlist === undefined) {
|
|
5403
|
+
continue;
|
|
5404
|
+
}
|
|
5405
|
+
validateToken(directive, "permissionsPolicy directive");
|
|
5406
|
+
for (const value of allowlist) {
|
|
5407
|
+
validatePermissionAllowlistValue(value);
|
|
5408
|
+
}
|
|
5409
|
+
directives.push(\`\${directive}=(\${allowlist.join(" ")})\`);
|
|
5410
|
+
}
|
|
5411
|
+
return directives.length === 0 ? undefined : directives.join(", ");
|
|
5412
|
+
}
|
|
5413
|
+
|
|
5230
5414
|
function validateHeaderValue(value) {
|
|
5231
5415
|
for (let index = 0; index < value.length; index += 1) {
|
|
5232
5416
|
const code = value.charCodeAt(index);
|
|
@@ -5237,6 +5421,22 @@ function validateHeaderValue(value) {
|
|
|
5237
5421
|
return value;
|
|
5238
5422
|
}
|
|
5239
5423
|
|
|
5424
|
+
function validateToken(value, label) {
|
|
5425
|
+
if (!/^[A-Za-z][A-Za-z0-9-]*$/.test(value)) {
|
|
5426
|
+
throw new TypeError(\`Invalid security header value for \${label}: \${JSON.stringify(value)}\`);
|
|
5427
|
+
}
|
|
5428
|
+
}
|
|
5429
|
+
|
|
5430
|
+
function validatePermissionAllowlistValue(value) {
|
|
5431
|
+
if (value === "self" || value === "*" || /^[A-Za-z][A-Za-z0-9+.-]*:$/.test(value)) {
|
|
5432
|
+
return;
|
|
5433
|
+
}
|
|
5434
|
+
if (/^https:\\/\\/[A-Za-z0-9.-]+(?::[0-9]+)?$/.test(value)) {
|
|
5435
|
+
return;
|
|
5436
|
+
}
|
|
5437
|
+
throw new TypeError(\`Invalid security header value for permissionsPolicy allowlist: \${JSON.stringify(value)}\`);
|
|
5438
|
+
}
|
|
5439
|
+
|
|
5240
5440
|
function mergeRouteMetadata(metadata) {
|
|
5241
5441
|
if (metadata.length === 0) {
|
|
5242
5442
|
return undefined;
|
|
@@ -6216,6 +6416,10 @@ async function writeClientRouteBundles(options: {
|
|
|
6216
6416
|
options.sourceAnalysis.byRouteFile.get(
|
|
6217
6417
|
relative(options.projectRoot, route.file).split(sep).join("/"),
|
|
6218
6418
|
)?.streamRoute === true,
|
|
6419
|
+
restoreRequestUrl:
|
|
6420
|
+
options.sourceAnalysis.byRouteFile.get(
|
|
6421
|
+
relative(options.projectRoot, route.file).split(sep).join("/"),
|
|
6422
|
+
)?.usesRequestInput === true,
|
|
6219
6423
|
cacheDir: options.cacheDir,
|
|
6220
6424
|
dropConsoleFunctions: options.clientConsolePureFunctions,
|
|
6221
6425
|
filename: route.file,
|
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
|
|