@reckona/mreact-router 0.0.202 → 0.0.203
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 +5 -2
- package/dist/actions.d.ts +2 -3
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +61 -41
- package/dist/actions.js.map +1 -1
- package/dist/adapters/cloudflare.d.ts +3 -1
- package/dist/adapters/cloudflare.d.ts.map +1 -1
- package/dist/adapters/cloudflare.js +26 -10
- package/dist/adapters/cloudflare.js.map +1 -1
- package/dist/build.d.ts +3 -1
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +98 -12
- package/dist/build.js.map +1 -1
- package/dist/cache.d.ts +1 -1
- package/dist/cache.d.ts.map +1 -1
- package/dist/cache.js +5 -4
- 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 +2 -2
- 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 +11 -2
- package/dist/client.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 +294 -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/prerender-entry.d.ts +3 -1
- package/dist/prerender-entry.d.ts.map +1 -1
- package/dist/prerender-entry.js +61 -3
- package/dist/prerender-entry.js.map +1 -1
- package/dist/render.d.ts +7 -1
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +51 -23
- package/dist/render.js.map +1 -1
- package/dist/serve.d.ts +3 -0
- package/dist/serve.d.ts.map +1 -1
- package/dist/serve.js +54 -30
- package/dist/serve.js.map +1 -1
- package/package.json +11 -11
- package/src/actions.ts +72 -56
- package/src/adapters/cloudflare.ts +40 -10
- package/src/build.ts +107 -10
- package/src/cache.ts +10 -4
- package/src/cli-options.ts +15 -2
- package/src/cli.ts +2 -1
- package/src/client.ts +15 -2
- package/src/navigation-marker.ts +353 -0
- package/src/navigation-runtime.ts +1 -0
- package/src/prerender-entry.ts +78 -5
- package/src/render.ts +80 -24
- package/src/serve.ts +71 -34
package/dist/build.js
CHANGED
|
@@ -9,13 +9,13 @@ import { collectStaticImportReferences, collectTopLevelValueExportNames, formatD
|
|
|
9
9
|
import { transformCompilerModuleContext } from "@reckona/mreact-compiler/internal";
|
|
10
10
|
import { compilerModuleContextForSource, collectClientRouteReferences, createClientRouteInferenceCache, detectAnchorElementUsage, detectClientNavigationOverride, formatClientRouteInferenceDiagnostic, inferClientRouteModule, navigationRuntimeLinkDisabledDiagnostic, resolveNavigationRuntime, } from "./client-route-inference.js";
|
|
11
11
|
import { createBoundaryReport } from "./boundaries.js";
|
|
12
|
-
import { buildClientRouteBatchOutput, buildNavigationRuntimeBundle, clientScriptForPath, routeIdForPath, } from "./navigation-runtime.js";
|
|
12
|
+
import { buildClientRouteBatchOutput, buildNavigationRuntimeBundle, clientScriptForPath, routeIdForPath, routeMarkerParts, } from "./navigation-runtime.js";
|
|
13
13
|
import { COMPAT_VENDOR_PLACEHOLDER_PREFIX, bundleAppRouterSourceModule, fileImportMetaUrlPlugin, importAppRouterSourceModule, resolveCompatVendorEntryFiles, sourceReferencesCompatVendorSpecifier, } from "./module-runner.js";
|
|
14
14
|
import { compileRouteMatcherArtifact, scanAppRoutes } from "./routes.js";
|
|
15
15
|
import { appFileConventionForRootFilename } from "./file-conventions.js";
|
|
16
16
|
import { resolveAppRouterProjectOptions, resolveBuildTargets, } from "./config.js";
|
|
17
17
|
import { routeCachePolicyFromSource } from "./cache.js";
|
|
18
|
-
import { bundleMiddlewareModuleCode, renderAppRequest, } from "./render.js";
|
|
18
|
+
import { bundleMiddlewareModuleCode, prerenderVariantMarkerParts, renderAppRequest, } from "./render.js";
|
|
19
19
|
import { createAppRouterImportPolicyPlugin } from "./import-policy.js";
|
|
20
20
|
import { hasGenerateStaticParamsExport, hasLoaderExport, hasPrerenderExport, isStreamRouteSource, routeClosureMayUseAwaitBoundary, routeClosureMayUseRequestInput, stripRouteBuildExports, stripRouteClientOnlyExports, stripRouteClientSource, stripRouteLoaderOnlyExports, stripRouteMetadataOnlyExports, stripRouteRequestOnlyExports, } from "./route-source.js";
|
|
21
21
|
import { bundleRouterModule, bundleRouterModules, } from "./bundle-pipeline.js";
|
|
@@ -23,7 +23,7 @@ import { collectRouteCssFilesFromSources, collectSpecialBoundaryFiles } from "./
|
|
|
23
23
|
import { existingRouteShellCandidates } from "./route-shells.js";
|
|
24
24
|
import { sourceModuleCandidates } from "./source-modules.js";
|
|
25
25
|
import { collectBuildInferredServerActions } from "./server-action-inference.js";
|
|
26
|
-
import { isVisitorDependentResponse, PRERENDERED_ROUTE_SCHEMA_VERSION, } from "./prerender-entry.js";
|
|
26
|
+
import { isVisitorDependentResponse, PRERENDERED_ROUTE_SCHEMA_VERSION, storedPrerenderedRouteHeaders, } from "./prerender-entry.js";
|
|
27
27
|
import { prepareRouteServerActionPlaceholders } from "./actions.js";
|
|
28
28
|
import { viteDefineCacheKey, vitePluginsCacheKey } from "./vite-plugin-cache-key.js";
|
|
29
29
|
import { workspacePackageFile } from "./workspace-packages.js";
|
|
@@ -1605,6 +1605,7 @@ async function prerenderStaticRoutes(options) {
|
|
|
1605
1605
|
for (const pathname of await prerenderPathsForRoute(route, analysis, options.vitePlugins)) {
|
|
1606
1606
|
const renderSignals = {
|
|
1607
1607
|
headerDependent: () => true,
|
|
1608
|
+
strictTransportSecurity: () => undefined,
|
|
1608
1609
|
};
|
|
1609
1610
|
const renderOptions = {
|
|
1610
1611
|
appDir: options.appDir,
|
|
@@ -1614,28 +1615,46 @@ async function prerenderStaticRoutes(options) {
|
|
|
1614
1615
|
define: options.define,
|
|
1615
1616
|
importPolicy,
|
|
1616
1617
|
navigationScripts,
|
|
1617
|
-
request: new Request(`http://mreact.local${pathname}
|
|
1618
|
+
request: new Request(`http://mreact.local${pathname}`, analysis.clientRoute
|
|
1619
|
+
? undefined
|
|
1620
|
+
: { headers: { "x-mreact-prerender-variant-capture": "1" } }),
|
|
1618
1621
|
renderSignals,
|
|
1619
1622
|
serverModuleCacheVersion,
|
|
1620
1623
|
serverModules: serverModuleMap,
|
|
1621
1624
|
vitePlugins: options.vitePlugins,
|
|
1622
1625
|
};
|
|
1623
1626
|
const response = await renderAppRequest(renderOptions);
|
|
1624
|
-
const
|
|
1627
|
+
const renderedHtml = await response.text();
|
|
1625
1628
|
if (renderSignals.headerDependent() || isVisitorDependentResponse(response)) {
|
|
1626
1629
|
continue;
|
|
1627
1630
|
}
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1631
|
+
let html = renderedHtml;
|
|
1632
|
+
let navigationHtml = renderedHtml;
|
|
1633
|
+
if (!analysis.clientRoute) {
|
|
1634
|
+
const capture = prerenderVariantMarkerParts(route.path);
|
|
1635
|
+
const start = renderedHtml.indexOf(capture.prefix);
|
|
1636
|
+
const end = renderedHtml.lastIndexOf(capture.suffix);
|
|
1637
|
+
if (start === -1 || end < start + capture.prefix.length) {
|
|
1638
|
+
throw new Error(`Failed to capture prerender response variants for ${pathname}.`);
|
|
1639
|
+
}
|
|
1640
|
+
const before = renderedHtml.slice(0, start);
|
|
1641
|
+
const content = renderedHtml.slice(start + capture.prefix.length, end);
|
|
1642
|
+
const after = renderedHtml.slice(end + capture.suffix.length);
|
|
1643
|
+
const navigationMarker = routeMarkerParts(route.path);
|
|
1644
|
+
html = `${before}${content}${after}`;
|
|
1645
|
+
navigationHtml = `${before}${navigationMarker.prefix}${content}${navigationMarker.suffix}${after}`;
|
|
1646
|
+
}
|
|
1647
|
+
const headers = storedPrerenderedRouteHeaders(response.headers);
|
|
1648
|
+
const strictTransportSecurity = renderSignals.strictTransportSecurity();
|
|
1632
1649
|
entries.push([
|
|
1633
1650
|
pathname,
|
|
1634
1651
|
{
|
|
1635
1652
|
headers,
|
|
1636
1653
|
html,
|
|
1654
|
+
navigationHtml,
|
|
1637
1655
|
schemaVersion: PRERENDERED_ROUTE_SCHEMA_VERSION,
|
|
1638
1656
|
status: response.status,
|
|
1657
|
+
...(strictTransportSecurity === undefined ? {} : { strictTransportSecurity }),
|
|
1639
1658
|
},
|
|
1640
1659
|
]);
|
|
1641
1660
|
}
|
|
@@ -2532,6 +2551,7 @@ function cloudflarePageRouteFacadeModuleSource(componentImport) {
|
|
|
2532
2551
|
const componentSlots = readComponentModuleExport(componentModule, "slots");
|
|
2533
2552
|
const componentGenerateMetadata = readComponentModuleExport(componentModule, "generateMetadata");
|
|
2534
2553
|
const componentMetadata = readComponentModuleExport(componentModule, "metadata");
|
|
2554
|
+
const componentSecurityHeadersApplied = readComponentModuleExport(componentModule, "__mreactSecurityHeadersApplied");
|
|
2535
2555
|
|
|
2536
2556
|
export function App(props) {
|
|
2537
2557
|
return renderCloudflareRouteComponent(props);
|
|
@@ -2546,6 +2566,7 @@ export const slots = componentSlots === undefined ? undefined : { ...componentSl
|
|
|
2546
2566
|
export const generateMetadata =
|
|
2547
2567
|
typeof componentGenerateMetadata === "function" ? componentGenerateMetadata : undefined;
|
|
2548
2568
|
export const metadata = componentMetadata;
|
|
2569
|
+
export const __mreactSecurityHeadersApplied = componentSecurityHeadersApplied === true;
|
|
2549
2570
|
|
|
2550
2571
|
function renderCloudflareRouteComponent(props) {
|
|
2551
2572
|
const routeComponent = resolveCloudflareRouteComponent();
|
|
@@ -3115,7 +3136,9 @@ ${cloudflareShellRuntimeSource()}`;
|
|
|
3115
3136
|
});
|
|
3116
3137
|
}
|
|
3117
3138
|
function cloudflareShellRuntimeSource() {
|
|
3118
|
-
return `
|
|
3139
|
+
return `export const __mreactSecurityHeadersApplied = true;
|
|
3140
|
+
|
|
3141
|
+
async function renderLayoutShells(shells, props, namedSlots) {
|
|
3119
3142
|
const slotContext = { consumedSlots: new Set(), namedSlots };
|
|
3120
3143
|
const rendered = [];
|
|
3121
3144
|
for (const shell of shells) {
|
|
@@ -3194,7 +3217,7 @@ function cloudflareHydrationMarkerParts(props) {
|
|
|
3194
3217
|
const escapedRouteId = escapeHtmlAttribute(routeId);
|
|
3195
3218
|
const propsJson = escapeScriptJson(JSON.stringify({
|
|
3196
3219
|
params: props.params,
|
|
3197
|
-
request: { url: props.request.url },
|
|
3220
|
+
request: { url: new URL(props.request.url).pathname },
|
|
3198
3221
|
data: props.data,
|
|
3199
3222
|
}));
|
|
3200
3223
|
const clientReferencesJson = route.clientReferenceManifest === undefined || route.clientReferenceManifest.length === 0
|
|
@@ -3617,17 +3640,63 @@ function routeSecurityHeaders(security, request) {
|
|
|
3617
3640
|
} else {
|
|
3618
3641
|
headers["referrer-policy"] = validateHeaderValue(security?.referrerPolicy ?? "strict-origin-when-cross-origin");
|
|
3619
3642
|
}
|
|
3643
|
+
if (security?.permissionsPolicy === null) {
|
|
3644
|
+
delete headers["permissions-policy"];
|
|
3645
|
+
} else {
|
|
3646
|
+
const permissionsPolicy = serializePermissionsPolicy(security?.permissionsPolicy);
|
|
3647
|
+
if (permissionsPolicy === undefined) {
|
|
3648
|
+
delete headers["permissions-policy"];
|
|
3649
|
+
} else {
|
|
3650
|
+
headers["permissions-policy"] = permissionsPolicy;
|
|
3651
|
+
}
|
|
3652
|
+
}
|
|
3620
3653
|
if (security?.frameOptions === null) {
|
|
3621
3654
|
delete headers["x-frame-options"];
|
|
3622
3655
|
} else if (security?.frameOptions !== undefined) {
|
|
3623
3656
|
headers["x-frame-options"] = validateHeaderValue(security.frameOptions);
|
|
3624
3657
|
}
|
|
3625
3658
|
if (request.url.startsWith("https://") && security?.hsts !== undefined && security.hsts !== false && security.hsts !== null) {
|
|
3626
|
-
headers["strict-transport-security"] =
|
|
3659
|
+
headers["strict-transport-security"] = serializeHsts(security.hsts);
|
|
3627
3660
|
}
|
|
3628
3661
|
return headers;
|
|
3629
3662
|
}
|
|
3630
3663
|
|
|
3664
|
+
function serializeHsts(hsts) {
|
|
3665
|
+
if (hsts === false) {
|
|
3666
|
+
throw new TypeError("Invalid security header value for hsts.");
|
|
3667
|
+
}
|
|
3668
|
+
const maxAge = Math.trunc(hsts.maxAge);
|
|
3669
|
+
if (!Number.isFinite(maxAge) || maxAge < 0) {
|
|
3670
|
+
throw new TypeError("Invalid security header value for hsts.maxAge.");
|
|
3671
|
+
}
|
|
3672
|
+
const parts = [\`max-age=\${maxAge}\`];
|
|
3673
|
+
if (hsts.includeSubDomains === true) {
|
|
3674
|
+
parts.push("includeSubDomains");
|
|
3675
|
+
}
|
|
3676
|
+
if (hsts.preload === true) {
|
|
3677
|
+
parts.push("preload");
|
|
3678
|
+
}
|
|
3679
|
+
return parts.join("; ");
|
|
3680
|
+
}
|
|
3681
|
+
|
|
3682
|
+
function serializePermissionsPolicy(policy) {
|
|
3683
|
+
if (policy === undefined) {
|
|
3684
|
+
return "camera=(), microphone=(), geolocation=()";
|
|
3685
|
+
}
|
|
3686
|
+
const directives = [];
|
|
3687
|
+
for (const [directive, allowlist] of Object.entries(policy)) {
|
|
3688
|
+
if (allowlist === null || allowlist === undefined) {
|
|
3689
|
+
continue;
|
|
3690
|
+
}
|
|
3691
|
+
validateToken(directive, "permissionsPolicy directive");
|
|
3692
|
+
for (const value of allowlist) {
|
|
3693
|
+
validatePermissionAllowlistValue(value);
|
|
3694
|
+
}
|
|
3695
|
+
directives.push(\`\${directive}=(\${allowlist.join(" ")})\`);
|
|
3696
|
+
}
|
|
3697
|
+
return directives.length === 0 ? undefined : directives.join(", ");
|
|
3698
|
+
}
|
|
3699
|
+
|
|
3631
3700
|
function validateHeaderValue(value) {
|
|
3632
3701
|
for (let index = 0; index < value.length; index += 1) {
|
|
3633
3702
|
const code = value.charCodeAt(index);
|
|
@@ -3638,6 +3707,22 @@ function validateHeaderValue(value) {
|
|
|
3638
3707
|
return value;
|
|
3639
3708
|
}
|
|
3640
3709
|
|
|
3710
|
+
function validateToken(value, label) {
|
|
3711
|
+
if (!/^[A-Za-z][A-Za-z0-9-]*$/.test(value)) {
|
|
3712
|
+
throw new TypeError(\`Invalid security header value for \${label}: \${JSON.stringify(value)}\`);
|
|
3713
|
+
}
|
|
3714
|
+
}
|
|
3715
|
+
|
|
3716
|
+
function validatePermissionAllowlistValue(value) {
|
|
3717
|
+
if (value === "self" || value === "*" || /^[A-Za-z][A-Za-z0-9+.-]*:$/.test(value)) {
|
|
3718
|
+
return;
|
|
3719
|
+
}
|
|
3720
|
+
if (/^https:\\/\\/[A-Za-z0-9.-]+(?::[0-9]+)?$/.test(value)) {
|
|
3721
|
+
return;
|
|
3722
|
+
}
|
|
3723
|
+
throw new TypeError(\`Invalid security header value for permissionsPolicy allowlist: \${JSON.stringify(value)}\`);
|
|
3724
|
+
}
|
|
3725
|
+
|
|
3641
3726
|
function mergeRouteMetadata(metadata) {
|
|
3642
3727
|
if (metadata.length === 0) {
|
|
3643
3728
|
return undefined;
|
|
@@ -4359,6 +4444,7 @@ async function writeClientRouteBundles(options) {
|
|
|
4359
4444
|
clientNavigation: detectClientNavigationOverride(source) ??
|
|
4360
4445
|
(navigation || detectAnchorElementUsage(clientSource, route.file)),
|
|
4361
4446
|
routeMayUseOutOfOrderFragments: options.sourceAnalysis.byRouteFile.get(relative(options.projectRoot, route.file).split(sep).join("/"))?.streamRoute === true,
|
|
4447
|
+
restoreRequestUrl: options.sourceAnalysis.byRouteFile.get(relative(options.projectRoot, route.file).split(sep).join("/"))?.usesRequestInput === true,
|
|
4362
4448
|
cacheDir: options.cacheDir,
|
|
4363
4449
|
dropConsoleFunctions: options.clientConsolePureFunctions,
|
|
4364
4450
|
filename: route.file,
|