@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.
Files changed (60) hide show
  1. package/README.md +5 -2
  2. package/dist/actions.d.ts +2 -3
  3. package/dist/actions.d.ts.map +1 -1
  4. package/dist/actions.js +61 -41
  5. package/dist/actions.js.map +1 -1
  6. package/dist/adapters/cloudflare.d.ts +3 -1
  7. package/dist/adapters/cloudflare.d.ts.map +1 -1
  8. package/dist/adapters/cloudflare.js +26 -10
  9. package/dist/adapters/cloudflare.js.map +1 -1
  10. package/dist/build.d.ts +3 -1
  11. package/dist/build.d.ts.map +1 -1
  12. package/dist/build.js +98 -12
  13. package/dist/build.js.map +1 -1
  14. package/dist/cache.d.ts +1 -1
  15. package/dist/cache.d.ts.map +1 -1
  16. package/dist/cache.js +5 -4
  17. package/dist/cache.js.map +1 -1
  18. package/dist/cli-options.d.ts +3 -0
  19. package/dist/cli-options.d.ts.map +1 -1
  20. package/dist/cli-options.js +10 -2
  21. package/dist/cli-options.js.map +1 -1
  22. package/dist/cli.js +2 -2
  23. package/dist/cli.js.map +1 -1
  24. package/dist/client.d.ts +5 -0
  25. package/dist/client.d.ts.map +1 -1
  26. package/dist/client.js +11 -2
  27. package/dist/client.js.map +1 -1
  28. package/dist/navigation-marker.d.ts +2 -0
  29. package/dist/navigation-marker.d.ts.map +1 -0
  30. package/dist/navigation-marker.js +294 -0
  31. package/dist/navigation-marker.js.map +1 -0
  32. package/dist/navigation-runtime.d.ts +1 -1
  33. package/dist/navigation-runtime.d.ts.map +1 -1
  34. package/dist/navigation-runtime.js +1 -1
  35. package/dist/navigation-runtime.js.map +1 -1
  36. package/dist/prerender-entry.d.ts +3 -1
  37. package/dist/prerender-entry.d.ts.map +1 -1
  38. package/dist/prerender-entry.js +61 -3
  39. package/dist/prerender-entry.js.map +1 -1
  40. package/dist/render.d.ts +7 -1
  41. package/dist/render.d.ts.map +1 -1
  42. package/dist/render.js +51 -23
  43. package/dist/render.js.map +1 -1
  44. package/dist/serve.d.ts +3 -0
  45. package/dist/serve.d.ts.map +1 -1
  46. package/dist/serve.js +54 -30
  47. package/dist/serve.js.map +1 -1
  48. package/package.json +11 -11
  49. package/src/actions.ts +72 -56
  50. package/src/adapters/cloudflare.ts +40 -10
  51. package/src/build.ts +107 -10
  52. package/src/cache.ts +10 -4
  53. package/src/cli-options.ts +15 -2
  54. package/src/cli.ts +2 -1
  55. package/src/client.ts +15 -2
  56. package/src/navigation-marker.ts +353 -0
  57. package/src/navigation-runtime.ts +1 -0
  58. package/src/prerender-entry.ts +78 -5
  59. package/src/render.ts +80 -24
  60. 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 html = await response.text();
1627
+ const renderedHtml = await response.text();
1625
1628
  if (renderSignals.headerDependent() || isVisitorDependentResponse(response)) {
1626
1629
  continue;
1627
1630
  }
1628
- const headers = {};
1629
- response.headers.forEach((value, key) => {
1630
- headers[key] = value;
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 `async function renderLayoutShells(shells, props, namedSlots) {
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"] = \`max-age=\${Math.trunc(security.hsts.maxAge)}\${security.hsts.includeSubDomains === true ? "; includeSubDomains" : ""}\${security.hsts.preload === true ? "; preload" : ""}\`;
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,