@reckona/mreact-router 0.0.120 → 0.0.122

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/src/build.ts CHANGED
@@ -39,9 +39,7 @@ import {
39
39
  } from "./module-runner.js";
40
40
  import { scanAppRoutes } from "./routes.js";
41
41
  import type { AppRoute } from "./routes.js";
42
- import {
43
- appFileConventionForRootFilename,
44
- } from "./file-conventions.js";
42
+ import { appFileConventionForRootFilename } from "./file-conventions.js";
45
43
  import {
46
44
  resolveAppRouterProjectOptions,
47
45
  resolveBuildTargets,
@@ -75,7 +73,7 @@ import {
75
73
  type RouterBundleChunkOutput,
76
74
  type RouterBundleOutput,
77
75
  } from "./bundle-pipeline.js";
78
- import { collectRouteCssFilesFromSources } from "./route-styles.js";
76
+ import { collectRouteCssFilesFromSources, collectSpecialBoundaryFiles } from "./route-styles.js";
79
77
  import { existingRouteShellCandidates } from "./route-shells.js";
80
78
  import { sourceModuleCandidates } from "./source-modules.js";
81
79
  import { collectBuildInferredServerActions } from "./server-action-inference.js";
@@ -210,6 +208,7 @@ export interface BuiltRouteSourceAnalysisSummary {
210
208
  authIncludesClaims: boolean;
211
209
  cachePolicy?: RouteCachePolicy | undefined;
212
210
  clientBoundaryImports: readonly string[];
211
+ clientBoundaryFallbackImports: readonly string[];
213
212
  clientRoute: boolean;
214
213
  hasLoader: boolean;
215
214
  routeCode: string;
@@ -233,6 +232,7 @@ interface BuildSourceAnalysis {
233
232
 
234
233
  interface BuildRouteSourceAnalysis extends BuildSourceAnalysis {
235
234
  clientBoundaryImports: readonly string[];
235
+ clientBoundaryFallbackImports: readonly string[];
236
236
  clientRoute: boolean;
237
237
  file: string;
238
238
  route: AppRoute & { kind: "page" };
@@ -428,7 +428,11 @@ export async function buildApp(options: BuildAppOptions): Promise<BuildAppResult
428
428
  }));
429
429
  const [serverModuleArtifacts, clientBundle] = await Promise.all([
430
430
  timingSink === undefined
431
- ? writeServerModuleArtifactFiles(serverDir, serverModules, generatedImportPolicy.runtimePackages)
431
+ ? writeServerModuleArtifactFiles(
432
+ serverDir,
433
+ serverModules,
434
+ generatedImportPolicy.runtimePackages,
435
+ )
432
436
  : timeBuildPhase(timingSink, "serverModuleArtifacts", () =>
433
437
  writeServerModuleArtifactFiles(
434
438
  serverDir,
@@ -487,6 +491,7 @@ export async function buildApp(options: BuildAppOptions): Promise<BuildAppResult
487
491
  const clientManifestAssets = Array.from(
488
492
  new Set([
489
493
  ...clientBundle.assets,
494
+ ...clientBundle.styles.flatMap((style) => style.css),
490
495
  ...(navigationRuntimeScript === undefined ? [] : [navigationRuntimeScript]),
491
496
  ]),
492
497
  ).sort();
@@ -584,6 +589,7 @@ export async function buildApp(options: BuildAppOptions): Promise<BuildAppResult
584
589
  ...(clientManifestAssets.length === 0 ? {} : { assets: clientManifestAssets }),
585
590
  ...(publicAssets.length === 0 ? {} : { publicAssets }),
586
591
  routes: clientManifestRoutes,
592
+ ...(clientBundle.styles.length === 0 ? {} : { styles: clientBundle.styles }),
587
593
  };
588
594
  const writeManifestFiles = async () => {
589
595
  await Promise.all([
@@ -743,6 +749,7 @@ async function analyzeBuildRouteSources(options: {
743
749
  {
744
750
  ...analyzeBuildSource(source, route.file),
745
751
  clientBoundaryImports: clientInference.clientBoundaryImports,
752
+ clientBoundaryFallbackImports: clientInference.clientBoundaryFallbackImports,
746
753
  clientRoute: clientInference.client,
747
754
  file,
748
755
  route,
@@ -1204,7 +1211,11 @@ async function collectRuntimeOptionalPackages(options: {
1204
1211
  { packageName: options.packageName, optional: false, startDir: options.projectRoot },
1205
1212
  ];
1206
1213
 
1207
- for (let index = 0; index < queue.length && seenPackageJson.size < maxRuntimePackageManifestReads; index += 1) {
1214
+ for (
1215
+ let index = 0;
1216
+ index < queue.length && seenPackageJson.size < maxRuntimePackageManifestReads;
1217
+ index += 1
1218
+ ) {
1208
1219
  const item = queue[index];
1209
1220
  if (item === undefined || !isValidRuntimePackageName(item.packageName)) {
1210
1221
  continue;
@@ -1300,7 +1311,11 @@ async function findRuntimePackageJson(
1300
1311
  }
1301
1312
 
1302
1313
  function isRuntimePackageSpecifier(specifier: string): boolean {
1303
- if (specifier.startsWith(".") || specifier.startsWith("/") || /^[A-Za-z][A-Za-z0-9+.-]*:/.test(specifier)) {
1314
+ if (
1315
+ specifier.startsWith(".") ||
1316
+ specifier.startsWith("/") ||
1317
+ /^[A-Za-z][A-Za-z0-9+.-]*:/.test(specifier)
1318
+ ) {
1304
1319
  return false;
1305
1320
  }
1306
1321
 
@@ -1428,7 +1443,11 @@ function isSourceModuleFile(file: string): boolean {
1428
1443
  }
1429
1444
 
1430
1445
  function isAppRelativeFile(file: string, relativeRoutesDir: string): boolean {
1431
- return relativeRoutesDir === "" || file === relativeRoutesDir || file.startsWith(`${relativeRoutesDir}/`);
1446
+ return (
1447
+ relativeRoutesDir === "" ||
1448
+ file === relativeRoutesDir ||
1449
+ file.startsWith(`${relativeRoutesDir}/`)
1450
+ );
1432
1451
  }
1433
1452
 
1434
1453
  function moduleIdForBuildFile(file: string, relativeRoutesDir: string): string {
@@ -1468,10 +1487,7 @@ async function collectPublicAssetPaths(publicDir: string): Promise<string[]> {
1468
1487
  return paths.sort();
1469
1488
  }
1470
1489
 
1471
- async function copyAppFileConventionAssets(
1472
- appDir: string,
1473
- outDir: string,
1474
- ): Promise<string[]> {
1490
+ async function copyAppFileConventionAssets(appDir: string, outDir: string): Promise<string[]> {
1475
1491
  let entries: Dirent[];
1476
1492
  try {
1477
1493
  entries = await readdir(appDir, { withFileTypes: true });
@@ -1496,9 +1512,7 @@ async function copyAppFileConventionAssets(
1496
1512
  continue;
1497
1513
  }
1498
1514
 
1499
- const outputPath = convention.path.startsWith("/")
1500
- ? convention.path.slice(1)
1501
- : convention.path;
1515
+ const outputPath = convention.path.startsWith("/") ? convention.path.slice(1) : convention.path;
1502
1516
  await copyFile(join(appDir, entry.name), join(outDir, outputPath));
1503
1517
  paths.push(convention.path);
1504
1518
  }
@@ -1748,205 +1762,219 @@ async function buildServerModuleArtifacts(options: {
1748
1762
  }
1749
1763
  }
1750
1764
 
1751
- const requestBatchOutputs = requestBatchEntries.length >= 3
1752
- ? await bundleRouteRequestModuleBatchCode({
1753
- appDir: options.project.routesDir,
1754
- bundleCache: options.bundleCache,
1755
- cacheDir: options.cacheDir,
1756
- entries: requestBatchEntries,
1757
- importPolicy: requestModuleImportPolicy,
1758
- vitePlugins: options.vitePlugins,
1759
- })
1760
- : new Map<string, string>();
1761
-
1762
- const artifactEntries = await mapWithBuildConcurrency(Object.entries(options.files), async ([file, source]) => {
1763
- const absoluteFile = join(options.projectRoot, file);
1764
- const route = routeByFile.get(file);
1765
- const routeAnalysis = options.sourceAnalysis.byRouteFile.get(file);
1766
- const artifact: BuiltServerModuleArtifact = {};
1765
+ const requestBatchOutputs =
1766
+ requestBatchEntries.length >= 3
1767
+ ? await bundleRouteRequestModuleBatchCode({
1768
+ appDir: options.project.routesDir,
1769
+ bundleCache: options.bundleCache,
1770
+ cacheDir: options.cacheDir,
1771
+ entries: requestBatchEntries,
1772
+ importPolicy: requestModuleImportPolicy,
1773
+ vitePlugins: options.vitePlugins,
1774
+ })
1775
+ : new Map<string, string>();
1767
1776
 
1768
- if (
1769
- requestArtifactFiles.has(file) ||
1770
- loaderArtifactFiles.has(file) ||
1771
- metadataArtifactFiles.has(file)
1772
- ) {
1773
- if (loaderArtifactFiles.has(file)) {
1774
- const code = requestBatchOutputs.get(routeRequestArtifactBatchKey(file, "loader")) ??
1775
- await bundleRouteLoaderModuleCode({
1776
- appDir: options.project.routesDir,
1777
- bundleCache: options.bundleCache,
1778
- cacheDir: options.cacheDir,
1779
- code: stripRouteLoaderOnlyExports(source, absoluteFile),
1780
- filename: absoluteFile,
1781
- importPolicy: requestModuleImportPolicy,
1782
- vitePlugins: options.vitePlugins,
1783
- });
1784
- artifact.loader = {
1785
- code,
1786
- sourceHash: hashText(source),
1787
- };
1788
- }
1777
+ const artifactEntries = await mapWithBuildConcurrency(
1778
+ Object.entries(options.files),
1779
+ async ([file, source]) => {
1780
+ const absoluteFile = join(options.projectRoot, file);
1781
+ const route = routeByFile.get(file);
1782
+ const routeAnalysis = options.sourceAnalysis.byRouteFile.get(file);
1783
+ const artifact: BuiltServerModuleArtifact = {};
1789
1784
 
1790
- if (metadataArtifactFiles.has(file)) {
1791
- const code = requestBatchOutputs.get(routeRequestArtifactBatchKey(file, "metadata")) ??
1792
- await bundleRouteRequestModuleCode({
1793
- appDir: options.project.routesDir,
1794
- bundleCache: options.bundleCache,
1795
- cacheDir: options.cacheDir,
1796
- code: stripRouteMetadataOnlyExports(source, absoluteFile),
1797
- filename: absoluteFile,
1798
- importPolicy: requestModuleImportPolicy,
1799
- label: "Metadata",
1800
- vitePlugins: options.vitePlugins,
1801
- });
1802
- artifact.routeMetadata = {
1803
- code,
1804
- sourceHash: hashText(source),
1805
- };
1806
- }
1785
+ if (
1786
+ requestArtifactFiles.has(file) ||
1787
+ loaderArtifactFiles.has(file) ||
1788
+ metadataArtifactFiles.has(file)
1789
+ ) {
1790
+ if (loaderArtifactFiles.has(file)) {
1791
+ const code =
1792
+ requestBatchOutputs.get(routeRequestArtifactBatchKey(file, "loader")) ??
1793
+ (await bundleRouteLoaderModuleCode({
1794
+ appDir: options.project.routesDir,
1795
+ bundleCache: options.bundleCache,
1796
+ cacheDir: options.cacheDir,
1797
+ code: stripRouteLoaderOnlyExports(source, absoluteFile),
1798
+ filename: absoluteFile,
1799
+ importPolicy: requestModuleImportPolicy,
1800
+ vitePlugins: options.vitePlugins,
1801
+ }));
1802
+ artifact.loader = {
1803
+ code,
1804
+ sourceHash: hashText(source),
1805
+ };
1806
+ }
1807
1807
 
1808
- if (requestArtifactFiles.has(file)) {
1809
- const batchedRequestCode = requestBatchOutputs.get(
1810
- routeRequestArtifactBatchKey(file, "request"),
1811
- );
1812
- artifact.request = {
1813
- code: batchedRequestCode ??
1814
- await buildRequestModuleArtifactCode({
1808
+ if (metadataArtifactFiles.has(file)) {
1809
+ const code =
1810
+ requestBatchOutputs.get(routeRequestArtifactBatchKey(file, "metadata")) ??
1811
+ (await bundleRouteRequestModuleCode({
1815
1812
  appDir: options.project.routesDir,
1816
1813
  bundleCache: options.bundleCache,
1817
1814
  cacheDir: options.cacheDir,
1815
+ code: stripRouteMetadataOnlyExports(source, absoluteFile),
1818
1816
  filename: absoluteFile,
1819
1817
  importPolicy: requestModuleImportPolicy,
1820
- routeKind: route?.kind,
1821
- source,
1818
+ label: "Metadata",
1822
1819
  vitePlugins: options.vitePlugins,
1823
- }),
1824
- sourceHash: hashText(source),
1825
- };
1820
+ }));
1821
+ artifact.routeMetadata = {
1822
+ code,
1823
+ sourceHash: hashText(source),
1824
+ };
1825
+ }
1826
+
1827
+ if (requestArtifactFiles.has(file)) {
1828
+ const batchedRequestCode = requestBatchOutputs.get(
1829
+ routeRequestArtifactBatchKey(file, "request"),
1830
+ );
1831
+ artifact.request = {
1832
+ code:
1833
+ batchedRequestCode ??
1834
+ (await buildRequestModuleArtifactCode({
1835
+ appDir: options.project.routesDir,
1836
+ bundleCache: options.bundleCache,
1837
+ cacheDir: options.cacheDir,
1838
+ filename: absoluteFile,
1839
+ importPolicy: requestModuleImportPolicy,
1840
+ routeKind: route?.kind,
1841
+ source,
1842
+ vitePlugins: options.vitePlugins,
1843
+ })),
1844
+ sourceHash: hashText(source),
1845
+ };
1846
+ }
1826
1847
  }
1827
- }
1828
1848
 
1829
- if (!isServerComponentFile(file)) {
1830
- return Object.keys(artifact).length > 0 ? ([file, artifact] as const) : undefined;
1831
- }
1849
+ if (!isServerComponentFile(file)) {
1850
+ return Object.keys(artifact).length > 0 ? ([file, artifact] as const) : undefined;
1851
+ }
1832
1852
 
1833
- const closureUsesAwait = routeAnalysis?.streamRoute ??
1834
- shouldBuildRouteAsStream({
1835
- filename: file,
1836
- files: options.files,
1837
- projectRoot: options.projectRoot,
1838
- source,
1839
- });
1840
- const streamRoute = route !== undefined && closureUsesAwait;
1841
- const serverOutputs =
1842
- streamRoute || (route === undefined && closureUsesAwait)
1843
- ? (["stream", "string"] as const)
1844
- : (["string"] as const);
1845
- const code = routeAnalysis?.routeCode ??
1846
- (route === undefined ? source : stripRouteBuildExports(source, absoluteFile));
1847
- const clientInference = routeAnalysis === undefined
1848
- ? await inferClientRouteModule({
1849
- ...(route === undefined ? {} : { appDir: options.project.routesDir }),
1850
- cache: options.clientRouteInferenceCache,
1851
- code:
1852
- route === undefined
1853
- ? stripRouteClientOnlyExports(source, absoluteFile)
1854
- : stripRouteClientSource({ code: source, filename: route.file }),
1855
- filename: join(options.projectRoot, file),
1856
- ...(route === undefined ? {} : { routePath: route.path }),
1857
- vitePlugins: options.vitePlugins,
1858
- })
1859
- : undefined;
1860
- const clientBoundaryImports = routeAnalysis?.clientBoundaryImports ??
1861
- clientInference?.clientBoundaryImports ??
1862
- [];
1853
+ const closureUsesAwait =
1854
+ routeAnalysis?.streamRoute ??
1855
+ shouldBuildRouteAsStream({
1856
+ filename: file,
1857
+ files: options.files,
1858
+ projectRoot: options.projectRoot,
1859
+ source,
1860
+ });
1861
+ const streamRoute = route !== undefined && closureUsesAwait;
1862
+ const serverOutputs =
1863
+ streamRoute || (route === undefined && closureUsesAwait)
1864
+ ? (["stream", "string"] as const)
1865
+ : (["string"] as const);
1866
+ const code =
1867
+ routeAnalysis?.routeCode ??
1868
+ (route === undefined ? source : stripRouteBuildExports(source, absoluteFile));
1869
+ const clientInference =
1870
+ routeAnalysis === undefined
1871
+ ? await inferClientRouteModule({
1872
+ ...(route === undefined ? {} : { appDir: options.project.routesDir }),
1873
+ cache: options.clientRouteInferenceCache,
1874
+ code:
1875
+ route === undefined
1876
+ ? stripRouteClientOnlyExports(source, absoluteFile)
1877
+ : stripRouteClientSource({ code: source, filename: route.file }),
1878
+ filename: join(options.projectRoot, file),
1879
+ ...(route === undefined ? {} : { routePath: route.path }),
1880
+ vitePlugins: options.vitePlugins,
1881
+ })
1882
+ : undefined;
1883
+ const clientBoundaryImports =
1884
+ routeAnalysis?.clientBoundaryImports ?? clientInference?.clientBoundaryImports ?? [];
1885
+ const clientBoundaryFallbackImports =
1886
+ routeAnalysis?.clientBoundaryFallbackImports ??
1887
+ clientInference?.clientBoundaryFallbackImports ??
1888
+ [];
1889
+
1890
+ for (const diagnostic of clientInference?.diagnostics ?? []) {
1891
+ console.warn(formatClientRouteInferenceDiagnostic(diagnostic));
1892
+ }
1863
1893
 
1864
- for (const diagnostic of clientInference?.diagnostics ?? []) {
1865
- console.warn(formatClientRouteInferenceDiagnostic(diagnostic));
1866
- }
1894
+ if (routeAnalysis !== undefined) {
1895
+ artifact.analysis = builtRouteSourceAnalysisSummary({
1896
+ analysis: routeAnalysis,
1897
+ });
1898
+ }
1867
1899
 
1868
- if (routeAnalysis !== undefined) {
1869
- artifact.analysis = builtRouteSourceAnalysisSummary({
1870
- analysis: routeAnalysis,
1871
- });
1872
- }
1900
+ const serverOutputArtifacts = await mapWithBuildConcurrency(
1901
+ serverOutputs,
1902
+ async (serverOutput) => {
1903
+ const output = await transformServerRouteSource({
1904
+ cache: options.serverTransformCache,
1905
+ code,
1906
+ clientBoundaryImports,
1907
+ clientBoundaryFallbackImports,
1908
+ filename: join(options.projectRoot, file),
1909
+ moduleContextCache: options.clientRouteInferenceCache,
1910
+ serverOutput,
1911
+ });
1912
+ const fatalDiagnostics = output.diagnostics.filter(
1913
+ (diagnostic) => diagnostic.code !== "MR_UNSUPPORTED_SERVER_EVENT_HANDLER",
1914
+ );
1873
1915
 
1874
- const serverOutputArtifacts = await mapWithBuildConcurrency(
1875
- serverOutputs,
1876
- async (serverOutput) => {
1877
- const output = await transformServerRouteSource({
1878
- cache: options.serverTransformCache,
1879
- code,
1880
- clientBoundaryImports,
1881
- filename: join(options.projectRoot, file),
1882
- moduleContextCache: options.clientRouteInferenceCache,
1883
- serverOutput,
1884
- });
1885
- const fatalDiagnostics = output.diagnostics.filter(
1886
- (diagnostic) => diagnostic.code !== "MR_UNSUPPORTED_SERVER_EVENT_HANDLER",
1887
- );
1916
+ if (fatalDiagnostics.length > 0) {
1917
+ if (
1918
+ serverOutput === "string" &&
1919
+ streamRoute &&
1920
+ route?.kind === "page" &&
1921
+ fatalDiagnostics.every(
1922
+ (diagnostic) => diagnostic.code === "MR_UNSUPPORTED_AWAIT_INNER_COMPONENT",
1923
+ )
1924
+ ) {
1925
+ return undefined;
1926
+ }
1927
+
1928
+ throw new Error(
1929
+ fatalDiagnostics.map((diagnostic) => formatDiagnostic(file, diagnostic)).join("\n"),
1930
+ );
1931
+ }
1888
1932
 
1889
- if (fatalDiagnostics.length > 0) {
1890
1933
  if (
1891
1934
  serverOutput === "string" &&
1892
1935
  streamRoute &&
1893
1936
  route?.kind === "page" &&
1894
- fatalDiagnostics.every(
1895
- (diagnostic) => diagnostic.code === "MR_UNSUPPORTED_AWAIT_INNER_COMPONENT",
1896
- )
1937
+ source.includes("<Await") &&
1938
+ /\bLink\b/.test(source)
1897
1939
  ) {
1940
+ // Native Link renders pre-rendered children as HTML. Keep direct
1941
+ // Await renderers on the stream artifact so the string artifact
1942
+ // cannot drop deferred Link content before the boundary resolves.
1898
1943
  return undefined;
1899
1944
  }
1900
1945
 
1901
- throw new Error(
1902
- fatalDiagnostics.map((diagnostic) => formatDiagnostic(file, diagnostic)).join("\n"),
1903
- );
1904
- }
1946
+ return [
1947
+ serverOutput,
1948
+ {
1949
+ ...(options.prebundleServerComponents
1950
+ ? {
1951
+ bundleCode: await buildServerComponentBundleArtifactCode({
1952
+ clientRouteInferenceCache: options.clientRouteInferenceCache,
1953
+ code: output.code,
1954
+ filename: absoluteFile,
1955
+ root: options.projectRoot,
1956
+ serverOutput,
1957
+ vitePlugins: options.vitePlugins,
1958
+ }),
1959
+ }
1960
+ : {}),
1961
+ code: output.code,
1962
+ metadata: output.metadata,
1963
+ sourceHash: hashText(code),
1964
+ },
1965
+ ] as const;
1966
+ },
1967
+ );
1905
1968
 
1906
- if (
1907
- serverOutput === "string" &&
1908
- streamRoute &&
1909
- route?.kind === "page" &&
1910
- source.includes("<Await") &&
1911
- /\bLink\b/.test(source)
1912
- ) {
1913
- // Native Link renders pre-rendered children as HTML. Keep direct
1914
- // Await renderers on the stream artifact so the string artifact
1915
- // cannot drop deferred Link content before the boundary resolves.
1916
- return undefined;
1969
+ for (const entry of serverOutputArtifacts) {
1970
+ if (entry !== undefined) {
1971
+ artifact[entry[0]] = entry[1];
1917
1972
  }
1918
-
1919
- return [
1920
- serverOutput,
1921
- {
1922
- ...(options.prebundleServerComponents
1923
- ? {
1924
- bundleCode: await buildServerComponentBundleArtifactCode({
1925
- clientRouteInferenceCache: options.clientRouteInferenceCache,
1926
- code: output.code,
1927
- filename: absoluteFile,
1928
- root: options.projectRoot,
1929
- serverOutput,
1930
- vitePlugins: options.vitePlugins,
1931
- }),
1932
- }
1933
- : {}),
1934
- code: output.code,
1935
- metadata: output.metadata,
1936
- sourceHash: hashText(code),
1937
- },
1938
- ] as const;
1939
- },
1940
- );
1941
-
1942
- for (const entry of serverOutputArtifacts) {
1943
- if (entry !== undefined) {
1944
- artifact[entry[0]] = entry[1];
1945
1973
  }
1946
- }
1947
1974
 
1948
- return [file, artifact] as const;
1949
- });
1975
+ return [file, artifact] as const;
1976
+ },
1977
+ );
1950
1978
 
1951
1979
  for (const entry of artifactEntries) {
1952
1980
  if (entry !== undefined) {
@@ -1984,6 +2012,7 @@ async function buildServerComponentBundleArtifactCode(options: {
1984
2012
  async function transformServerRouteSource(options: {
1985
2013
  cache: ServerTransformCache;
1986
2014
  clientBoundaryImports: readonly string[];
2015
+ clientBoundaryFallbackImports?: readonly string[];
1987
2016
  code: string;
1988
2017
  filename: string;
1989
2018
  moduleContextCache: ClientRouteInferenceCache;
@@ -1991,6 +2020,7 @@ async function transformServerRouteSource(options: {
1991
2020
  }): Promise<ServerTransformOutput> {
1992
2021
  const cacheKey = stableCacheKey({
1993
2022
  clientBoundaryImports: options.clientBoundaryImports,
2023
+ clientBoundaryFallbackImports: options.clientBoundaryFallbackImports ?? [],
1994
2024
  codeHash: hashText(options.code),
1995
2025
  filename: resolve(options.filename),
1996
2026
  serverOutput: options.serverOutput,
@@ -2012,6 +2042,7 @@ async function transformServerRouteSource(options: {
2012
2042
  return transformCompilerModuleContext({
2013
2043
  code: options.code,
2014
2044
  clientBoundaryImports: options.clientBoundaryImports,
2045
+ clientBoundaryFallbackImports: options.clientBoundaryFallbackImports ?? [],
2015
2046
  dev: false,
2016
2047
  filename: options.filename,
2017
2048
  moduleContext,
@@ -2029,8 +2060,11 @@ function builtRouteSourceAnalysisSummary(options: {
2029
2060
  }): BuiltRouteSourceAnalysisSummary {
2030
2061
  return {
2031
2062
  authIncludesClaims: options.analysis.authIncludesClaims,
2032
- ...(options.analysis.cachePolicy === undefined ? {} : { cachePolicy: options.analysis.cachePolicy }),
2063
+ ...(options.analysis.cachePolicy === undefined
2064
+ ? {}
2065
+ : { cachePolicy: options.analysis.cachePolicy }),
2033
2066
  clientBoundaryImports: options.analysis.clientBoundaryImports,
2067
+ clientBoundaryFallbackImports: options.analysis.clientBoundaryFallbackImports,
2034
2068
  clientRoute: options.analysis.clientRoute,
2035
2069
  hasLoader: options.analysis.hasLoader,
2036
2070
  routeCode: options.analysis.routeCode,
@@ -2089,7 +2123,10 @@ function usesRuntimeCacheControl(code: string): boolean {
2089
2123
  return /\bcacheControl\s*\(/.test(code);
2090
2124
  }
2091
2125
 
2092
- function routeRequestArtifactBatchKey(file: string, kind: "loader" | "metadata" | "request"): string {
2126
+ function routeRequestArtifactBatchKey(
2127
+ file: string,
2128
+ kind: "loader" | "metadata" | "request",
2129
+ ): string {
2093
2130
  return `${kind}:${file}`;
2094
2131
  }
2095
2132
 
@@ -2129,15 +2166,15 @@ async function buildRequestModuleArtifactCode(options: {
2129
2166
  });
2130
2167
  }
2131
2168
 
2132
- return await bundleRouteLoaderModuleCode({
2133
- appDir: options.appDir,
2134
- bundleCache: options.bundleCache,
2135
- cacheDir: options.cacheDir,
2136
- code: stripRouteRequestOnlyExports(options.source, options.filename),
2137
- filename: options.filename,
2138
- importPolicy: options.importPolicy,
2139
- vitePlugins: options.vitePlugins,
2140
- });
2169
+ return await bundleRouteLoaderModuleCode({
2170
+ appDir: options.appDir,
2171
+ bundleCache: options.bundleCache,
2172
+ cacheDir: options.cacheDir,
2173
+ code: stripRouteRequestOnlyExports(options.source, options.filename),
2174
+ filename: options.filename,
2175
+ importPolicy: options.importPolicy,
2176
+ vitePlugins: options.vitePlugins,
2177
+ });
2141
2178
  }
2142
2179
 
2143
2180
  async function bundleRouteLoaderModuleCode(options: {
@@ -2214,24 +2251,29 @@ async function bundleRouteRequestModuleBatchCode(options: {
2214
2251
  label: "Request artifact",
2215
2252
  }),
2216
2253
  ],
2217
- root: options.importPolicy?.projectRoot ?? dirname(options.entries[0]?.filename ?? options.appDir),
2254
+ root:
2255
+ options.importPolicy?.projectRoot ?? dirname(options.entries[0]?.filename ?? options.appDir),
2218
2256
  vitePlugins: options.vitePlugins,
2219
2257
  });
2220
2258
 
2221
2259
  if (output.chunks.some((chunk) => !chunk.isEntry)) {
2222
- const fallbackEntries = await mapWithBuildConcurrency(options.entries, async (entry) => [
2223
- entry.key,
2224
- await bundleRouteRequestModuleCode({
2225
- appDir: options.appDir,
2226
- bundleCache: options.bundleCache,
2227
- cacheDir: options.cacheDir,
2228
- code: entry.code,
2229
- filename: entry.filename,
2230
- importPolicy: options.importPolicy,
2231
- label: entry.label,
2232
- vitePlugins: options.vitePlugins,
2233
- }),
2234
- ] as const);
2260
+ const fallbackEntries = await mapWithBuildConcurrency(
2261
+ options.entries,
2262
+ async (entry) =>
2263
+ [
2264
+ entry.key,
2265
+ await bundleRouteRequestModuleCode({
2266
+ appDir: options.appDir,
2267
+ bundleCache: options.bundleCache,
2268
+ cacheDir: options.cacheDir,
2269
+ code: entry.code,
2270
+ filename: entry.filename,
2271
+ importPolicy: options.importPolicy,
2272
+ label: entry.label,
2273
+ vitePlugins: options.vitePlugins,
2274
+ }),
2275
+ ] as const,
2276
+ );
2235
2277
  return new Map(fallbackEntries);
2236
2278
  }
2237
2279
 
@@ -2346,9 +2388,11 @@ function isMiddlewareFile(appDir: string, file: string): boolean {
2346
2388
  }
2347
2389
 
2348
2390
  function hasMetadataExport(code: string): boolean {
2349
- return /\bexport\s+const\s+metadata\s*=/.test(code) ||
2391
+ return (
2392
+ /\bexport\s+const\s+metadata\s*=/.test(code) ||
2350
2393
  /\bexport\s+(?:async\s+)?function\s+generateMetadata\b/.test(code) ||
2351
- /\bexport\s*\{[^}]*\bgenerateMetadata\b[^}]*\}/.test(code);
2394
+ /\bexport\s*\{[^}]*\bgenerateMetadata\b[^}]*\}/.test(code)
2395
+ );
2352
2396
  }
2353
2397
 
2354
2398
  async function readDeclaredProjectPackages(projectRoot: string): Promise<string[]> {
@@ -2432,11 +2476,13 @@ async function writeCloudflareRouteModules(options: {
2432
2476
 
2433
2477
  return analysis === undefined
2434
2478
  ? []
2435
- : [{
2436
- route,
2437
- routeFile,
2438
- routeId: routeIdForPath(route.path),
2439
- }];
2479
+ : [
2480
+ {
2481
+ route,
2482
+ routeFile,
2483
+ routeId: routeIdForPath(route.path),
2484
+ },
2485
+ ];
2440
2486
  }),
2441
2487
  );
2442
2488
 
@@ -2455,8 +2501,10 @@ async function writeCloudflareRouteModules(options: {
2455
2501
  cacheDir: options.cacheDir,
2456
2502
  define: options.define,
2457
2503
  routes: requiredRoutes
2458
- .filter(({ route, routeFile }) =>
2459
- route.kind === "page" && options.sourceAnalysis.byRouteFile.get(routeFile)?.hasLoader === true
2504
+ .filter(
2505
+ ({ route, routeFile }) =>
2506
+ route.kind === "page" &&
2507
+ options.sourceAnalysis.byRouteFile.get(routeFile)?.hasLoader === true,
2460
2508
  )
2461
2509
  .map(({ route, routeId }) => ({ filename: route.file, routeId })),
2462
2510
  root: options.projectRoot,
@@ -2504,114 +2552,120 @@ async function writeCloudflareRouteModules(options: {
2504
2552
  writeCloudflareBatchedRouteModuleChunks(options.cloudflareDir, stringShellComponentModules),
2505
2553
  ]);
2506
2554
 
2507
- const registryEntries = await mapWithBuildConcurrency(requiredRoutes, async ({ route, routeFile, routeId }) => {
2508
- const routeModuleFile = `routes/${routeId}.mjs`;
2509
- let routeModuleExports: string[];
2555
+ const registryEntries = await mapWithBuildConcurrency(
2556
+ requiredRoutes,
2557
+ async ({ route, routeFile, routeId }) => {
2558
+ const routeModuleFile = `routes/${routeId}.mjs`;
2559
+ let routeModuleExports: string[];
2510
2560
 
2511
- if (route.kind === "server" || route.kind === "metadata") {
2512
- try {
2513
- const serverRouteModule = serverRouteModules.entries.get(routeId);
2561
+ if (route.kind === "server" || route.kind === "metadata") {
2562
+ try {
2563
+ const serverRouteModule = serverRouteModules.entries.get(routeId);
2564
+
2565
+ if (serverRouteModule === undefined) {
2566
+ throw new Error(`Missing bundled Cloudflare ${route.kind} route module.`);
2567
+ }
2514
2568
 
2515
- if (serverRouteModule === undefined) {
2516
- throw new Error(`Missing bundled Cloudflare ${route.kind} route module.`);
2569
+ const serverRouteFile = serverRouteModule.fileName;
2570
+ const serverRouteImport = `./${serverRouteFile.split("/").pop() ?? serverRouteFile}`;
2571
+ routeModuleExports = [
2572
+ `export * from ${JSON.stringify(serverRouteImport)};`,
2573
+ ...(route.kind === "metadata"
2574
+ ? [`export { default } from ${JSON.stringify(serverRouteImport)};`]
2575
+ : []),
2576
+ ];
2577
+ } catch (error) {
2578
+ throw new Error(
2579
+ `Failed to build Cloudflare ${route.kind} route module for ${routeFile}: ${errorMessage(error)}`,
2580
+ );
2517
2581
  }
2518
2582
 
2519
- const serverRouteFile = serverRouteModule.fileName;
2520
- const serverRouteImport = `./${serverRouteFile.split("/").pop() ?? serverRouteFile}`;
2521
- routeModuleExports = [
2522
- `export * from ${JSON.stringify(serverRouteImport)};`,
2523
- ...(route.kind === "metadata"
2524
- ? [`export { default } from ${JSON.stringify(serverRouteImport)};`]
2525
- : []),
2526
- ];
2527
- } catch (error) {
2528
- throw new Error(
2529
- `Failed to build Cloudflare ${route.kind} route module for ${routeFile}: ${errorMessage(error)}`,
2583
+ await writeFile(
2584
+ join(options.cloudflareDir, routeModuleFile),
2585
+ `${routeModuleExports.join("\n")}\n`,
2530
2586
  );
2587
+ return `${JSON.stringify(routeFile)}: () => import(${JSON.stringify(`./${routeModuleFile}`)})`;
2531
2588
  }
2532
2589
 
2533
- await writeFile(join(options.cloudflareDir, routeModuleFile), `${routeModuleExports.join("\n")}\n`);
2534
- return `${JSON.stringify(routeFile)}: () => import(${JSON.stringify(`./${routeModuleFile}`)})`;
2535
- }
2536
-
2537
- const serverOutput =
2538
- options.serverModules[routeFile]?.analysis?.streamRoute === true ||
2539
- options.sourceAnalysis.byRouteFile.get(routeFile)?.streamRoute === true
2540
- ? "stream"
2541
- : "string";
2590
+ const serverOutput =
2591
+ options.serverModules[routeFile]?.analysis?.streamRoute === true ||
2592
+ options.sourceAnalysis.byRouteFile.get(routeFile)?.streamRoute === true
2593
+ ? "stream"
2594
+ : "string";
2542
2595
 
2543
- try {
2544
- const batchedComponent =
2545
- stringShellComponentRouteIds.has(routeId)
2596
+ try {
2597
+ const batchedComponent = stringShellComponentRouteIds.has(routeId)
2546
2598
  ? stringShellComponentModules.entries.get(routeId)
2547
2599
  : directComponentRouteIds.has(routeId)
2548
2600
  ? directComponentModules.entries.get(routeId)
2549
2601
  : undefined;
2550
- let componentFile = batchedComponent?.fileName;
2551
-
2552
- if (batchedComponent === undefined) {
2553
- const componentOutput =
2554
- serverOutput === "stream"
2555
- ? await buildCloudflareStreamRouteComponentModule({
2556
- cacheDir: options.cacheDir,
2557
- define: options.define,
2558
- filename: route.file,
2559
- projectRoot: options.projectRoot,
2560
- routesDir: options.routesDir,
2561
- serverModules: options.serverModules,
2562
- sourceAnalysis: options.sourceAnalysis,
2563
- vitePlugins: options.vitePlugins,
2564
- })
2565
- : await buildCloudflareStringRouteComponentModule({
2566
- cacheDir: options.cacheDir,
2567
- define: options.define,
2568
- filename: route.file,
2569
- projectRoot: options.projectRoot,
2570
- routesDir: options.routesDir,
2571
- serverModules: options.serverModules,
2572
- sourceAnalysis: options.sourceAnalysis,
2573
- vitePlugins: options.vitePlugins,
2574
- });
2602
+ let componentFile = batchedComponent?.fileName;
2603
+
2604
+ if (batchedComponent === undefined) {
2605
+ const componentOutput =
2606
+ serverOutput === "stream"
2607
+ ? await buildCloudflareStreamRouteComponentModule({
2608
+ cacheDir: options.cacheDir,
2609
+ define: options.define,
2610
+ filename: route.file,
2611
+ projectRoot: options.projectRoot,
2612
+ routesDir: options.routesDir,
2613
+ serverModules: options.serverModules,
2614
+ sourceAnalysis: options.sourceAnalysis,
2615
+ vitePlugins: options.vitePlugins,
2616
+ })
2617
+ : await buildCloudflareStringRouteComponentModule({
2618
+ cacheDir: options.cacheDir,
2619
+ define: options.define,
2620
+ filename: route.file,
2621
+ projectRoot: options.projectRoot,
2622
+ routesDir: options.routesDir,
2623
+ serverModules: options.serverModules,
2624
+ sourceAnalysis: options.sourceAnalysis,
2625
+ vitePlugins: options.vitePlugins,
2626
+ });
2627
+
2628
+ componentFile = `routes/${routeId}.${hashText(componentOutput).slice(0, 8)}.component.mjs`;
2629
+ await writeFile(join(options.cloudflareDir, componentFile), componentOutput);
2630
+ }
2575
2631
 
2576
- componentFile = `routes/${routeId}.${hashText(componentOutput).slice(0, 8)}.component.mjs`;
2577
- await writeFile(join(options.cloudflareDir, componentFile), componentOutput);
2578
- }
2632
+ if (componentFile === undefined) {
2633
+ throw new Error("Missing bundled Cloudflare component module.");
2634
+ }
2579
2635
 
2580
- if (componentFile === undefined) {
2581
- throw new Error("Missing bundled Cloudflare component module.");
2636
+ const componentImport = `./${componentFile.split("/").pop() ?? componentFile}`;
2637
+ routeModuleExports = [cloudflarePageRouteFacadeModuleSource(componentImport)];
2638
+ } catch (error) {
2639
+ throw new Error(
2640
+ `Failed to build Cloudflare route module for ${routeFile}: ${errorMessage(error)}`,
2641
+ );
2582
2642
  }
2583
2643
 
2584
- const componentImport = `./${componentFile.split("/").pop() ?? componentFile}`;
2585
- routeModuleExports = [
2586
- cloudflarePageRouteFacadeModuleSource(componentImport),
2587
- ];
2588
- } catch (error) {
2589
- throw new Error(
2590
- `Failed to build Cloudflare route module for ${routeFile}: ${errorMessage(error)}`,
2591
- );
2592
- }
2644
+ if (options.sourceAnalysis.byRouteFile.get(routeFile)?.hasLoader === true) {
2645
+ try {
2646
+ const loaderModule = loaderRouteModules.entries.get(routeId);
2593
2647
 
2594
- if (options.sourceAnalysis.byRouteFile.get(routeFile)?.hasLoader === true) {
2595
- try {
2596
- const loaderModule = loaderRouteModules.entries.get(routeId);
2648
+ if (loaderModule === undefined) {
2649
+ throw new Error("Missing bundled Cloudflare loader module.");
2650
+ }
2597
2651
 
2598
- if (loaderModule === undefined) {
2599
- throw new Error("Missing bundled Cloudflare loader module.");
2652
+ const loaderFile = loaderModule.fileName;
2653
+ const loaderImport = `./${loaderFile.split("/").pop() ?? loaderFile}`;
2654
+ routeModuleExports.push(`export { loader } from ${JSON.stringify(loaderImport)};`);
2655
+ } catch (error) {
2656
+ throw new Error(
2657
+ `Failed to build Cloudflare loader module for ${routeFile}: ${errorMessage(error)}`,
2658
+ );
2600
2659
  }
2601
-
2602
- const loaderFile = loaderModule.fileName;
2603
- const loaderImport = `./${loaderFile.split("/").pop() ?? loaderFile}`;
2604
- routeModuleExports.push(`export { loader } from ${JSON.stringify(loaderImport)};`);
2605
- } catch (error) {
2606
- throw new Error(
2607
- `Failed to build Cloudflare loader module for ${routeFile}: ${errorMessage(error)}`,
2608
- );
2609
2660
  }
2610
- }
2611
2661
 
2612
- await writeFile(join(options.cloudflareDir, routeModuleFile), `${routeModuleExports.join("\n")}\n`);
2613
- return `${JSON.stringify(routeFile)}: () => import(${JSON.stringify(`./${routeModuleFile}`)})`;
2614
- });
2662
+ await writeFile(
2663
+ join(options.cloudflareDir, routeModuleFile),
2664
+ `${routeModuleExports.join("\n")}\n`,
2665
+ );
2666
+ return `${JSON.stringify(routeFile)}: () => import(${JSON.stringify(`./${routeModuleFile}`)})`;
2667
+ },
2668
+ );
2615
2669
 
2616
2670
  const registrySource = [
2617
2671
  `export const routeModules = {`,
@@ -2893,7 +2947,9 @@ async function buildCloudflareStringRouteComponentModuleBatch(options: {
2893
2947
  contents: pageMetadataModule,
2894
2948
  resolveDir: dirname(route.filename),
2895
2949
  });
2896
- metadataImports.push(`import * as pageMetadataModule from ${JSON.stringify(pageMetadataId)};`);
2950
+ metadataImports.push(
2951
+ `import * as pageMetadataModule from ${JSON.stringify(pageMetadataId)};`,
2952
+ );
2897
2953
  }
2898
2954
 
2899
2955
  const shellMetadataImports = await Promise.all(
@@ -3084,7 +3140,11 @@ async function buildCloudflareServerComponentModule(options: {
3084
3140
  cacheDir: options.cacheDir,
3085
3141
  define: options.define,
3086
3142
  filename: options.filename,
3087
- hasMetadata: buildSourceAnalysisForFile(options.sourceAnalysis, options.projectRoot, options.filename)?.hasMetadata,
3143
+ hasMetadata: buildSourceAnalysisForFile(
3144
+ options.sourceAnalysis,
3145
+ options.projectRoot,
3146
+ options.filename,
3147
+ )?.hasMetadata,
3088
3148
  root: options.projectRoot,
3089
3149
  vitePlugins: options.vitePlugins,
3090
3150
  });
@@ -3098,9 +3158,8 @@ async function buildCloudflareServerComponentModule(options: {
3098
3158
  filename: `${options.filename}.mreact-cloudflare-component.js`,
3099
3159
  cacheDir: options.cacheDir,
3100
3160
  define: options.define,
3101
- modules: metadataModule === undefined
3102
- ? new Map()
3103
- : new Map([["mreact:metadata", metadataModule]]),
3161
+ modules:
3162
+ metadataModule === undefined ? new Map() : new Map([["mreact:metadata", metadataModule]]),
3104
3163
  plugins: [
3105
3164
  cloudflareServerSourceTransformPlugin({
3106
3165
  projectRoot: options.projectRoot,
@@ -3180,7 +3239,9 @@ async function buildCloudflareStringRouteComponentModule(options: {
3180
3239
  }),
3181
3240
  ),
3182
3241
  );
3183
- const shellImports = shellFiles.map((_, index) => `import * as shell${index} from "mreact:shell-${index}";`);
3242
+ const shellImports = shellFiles.map(
3243
+ (_, index) => `import * as shell${index} from "mreact:shell-${index}";`,
3244
+ );
3184
3245
  const shellDefinitions = shellFiles.map(
3185
3246
  (shell, index) =>
3186
3247
  `{ component: selectComponent(shell${index}, ${JSON.stringify(shell.file)}), id: ${JSON.stringify(shell.id)}, kind: ${JSON.stringify(shell.kind)}, module: shell${index} }`,
@@ -3287,7 +3348,9 @@ async function buildCloudflareStreamRouteComponentModule(options: {
3287
3348
  }),
3288
3349
  ),
3289
3350
  );
3290
- const shellImports = shellFiles.map((_, index) => `import * as shell${index} from "mreact:shell-${index}";`);
3351
+ const shellImports = shellFiles.map(
3352
+ (_, index) => `import * as shell${index} from "mreact:shell-${index}";`,
3353
+ );
3291
3354
  const shellDefinitions = shellFiles.map(
3292
3355
  (shell, index) =>
3293
3356
  `{ component: selectComponent(shell${index}, ${JSON.stringify(shell.file)}), id: ${JSON.stringify(shell.id)}, kind: ${JSON.stringify(shell.kind)}, module: shell${index} }`,
@@ -3687,7 +3750,11 @@ async function buildCloudflareComponentExportModule(options: {
3687
3750
  cacheDir: options.cacheDir,
3688
3751
  define: options.define,
3689
3752
  filename: options.filename,
3690
- hasMetadata: buildSourceAnalysisForFile(options.sourceAnalysis, options.projectRoot, options.filename)?.hasMetadata,
3753
+ hasMetadata: buildSourceAnalysisForFile(
3754
+ options.sourceAnalysis,
3755
+ options.projectRoot,
3756
+ options.filename,
3757
+ )?.hasMetadata,
3691
3758
  root: options.projectRoot,
3692
3759
  vitePlugins: options.vitePlugins,
3693
3760
  });
@@ -3706,9 +3773,8 @@ export const slots = routeModule.slots;`;
3706
3773
  cacheDir: options.cacheDir,
3707
3774
  define: options.define,
3708
3775
  filename: `${options.filename}.mreact-cloudflare-${options.serverOutput}-component.js`,
3709
- modules: metadataModule === undefined
3710
- ? new Map()
3711
- : new Map([["mreact:metadata", metadataModule]]),
3776
+ modules:
3777
+ metadataModule === undefined ? new Map() : new Map([["mreact:metadata", metadataModule]]),
3712
3778
  plugins: [
3713
3779
  cloudflareServerSourceTransformPlugin({
3714
3780
  projectRoot: options.projectRoot,
@@ -4093,6 +4159,7 @@ async function transformCloudflareServerSource(options: {
4093
4159
  const output = transformCompilerModuleContext({
4094
4160
  code: options.source,
4095
4161
  clientBoundaryImports: clientInference.clientBoundaryImports,
4162
+ clientBoundaryFallbackImports: clientInference.clientBoundaryFallbackImports,
4096
4163
  dev: false,
4097
4164
  filename: options.filename,
4098
4165
  moduleContext,
@@ -4106,7 +4173,9 @@ async function transformCloudflareServerSource(options: {
4106
4173
 
4107
4174
  if (fatalDiagnostics.length > 0) {
4108
4175
  throw new Error(
4109
- fatalDiagnostics.map((diagnostic) => formatDiagnostic(options.filename, diagnostic)).join("\n"),
4176
+ fatalDiagnostics
4177
+ .map((diagnostic) => formatDiagnostic(options.filename, diagnostic))
4178
+ .join("\n"),
4110
4179
  );
4111
4180
  }
4112
4181
 
@@ -4147,9 +4216,18 @@ function cloudflareWorkspaceRuntimePlugin(): RouterCompatPlugin {
4147
4216
  "@reckona/mreact-compat/event-priority",
4148
4217
  packageFile("react-compat", "@reckona/mreact-compat", "event-priority"),
4149
4218
  ],
4150
- ["@reckona/mreact-compat/flight", packageFile("react-compat", "@reckona/mreact-compat", "flight")],
4151
- ["@reckona/mreact-compat/hooks", packageFile("react-compat", "@reckona/mreact-compat", "hooks-entry")],
4152
- ["@reckona/mreact-compat/internal", packageFile("react-compat", "@reckona/mreact-compat", "internal")],
4219
+ [
4220
+ "@reckona/mreact-compat/flight",
4221
+ packageFile("react-compat", "@reckona/mreact-compat", "flight"),
4222
+ ],
4223
+ [
4224
+ "@reckona/mreact-compat/hooks",
4225
+ packageFile("react-compat", "@reckona/mreact-compat", "hooks-entry"),
4226
+ ],
4227
+ [
4228
+ "@reckona/mreact-compat/internal",
4229
+ packageFile("react-compat", "@reckona/mreact-compat", "internal"),
4230
+ ],
4153
4231
  [
4154
4232
  "@reckona/mreact-compat/jsx-dev-runtime",
4155
4233
  packageFile("react-compat", "@reckona/mreact-compat", "jsx-dev-runtime"),
@@ -4158,9 +4236,15 @@ function cloudflareWorkspaceRuntimePlugin(): RouterCompatPlugin {
4158
4236
  "@reckona/mreact-compat/jsx-runtime",
4159
4237
  packageFile("react-compat", "@reckona/mreact-compat", "jsx-runtime"),
4160
4238
  ],
4161
- ["@reckona/mreact-compat/scheduler", packageFile("react-compat", "@reckona/mreact-compat", "scheduler")],
4239
+ [
4240
+ "@reckona/mreact-compat/scheduler",
4241
+ packageFile("react-compat", "@reckona/mreact-compat", "scheduler"),
4242
+ ],
4162
4243
  ["@reckona/mreact-query", packageFile("query", "@reckona/mreact-query", "index")],
4163
- ["@reckona/mreact-reactive-core", packageFile("reactive-core", "@reckona/mreact-reactive-core", "index")],
4244
+ [
4245
+ "@reckona/mreact-reactive-core",
4246
+ packageFile("reactive-core", "@reckona/mreact-reactive-core", "index"),
4247
+ ],
4164
4248
  [
4165
4249
  "@reckona/mreact-router/adapters/cloudflare",
4166
4250
  packageFile("router", "@reckona/mreact-router", "adapters/cloudflare"),
@@ -4179,10 +4263,13 @@ function cloudflareWorkspaceRuntimePlugin(): RouterCompatPlugin {
4179
4263
  return {
4180
4264
  name: "mreact-cloudflare-workspace-runtime",
4181
4265
  setup(buildApi) {
4182
- buildApi.onResolve({ filter: /^@reckona\/mreact-router\/(?:internal\/)?native-escape$/ }, () => ({
4183
- namespace: "mreact-cloudflare-native-escape",
4184
- path: "native-escape",
4185
- }));
4266
+ buildApi.onResolve(
4267
+ { filter: /^@reckona\/mreact-router\/(?:internal\/)?native-escape$/ },
4268
+ () => ({
4269
+ namespace: "mreact-cloudflare-native-escape",
4270
+ path: "native-escape",
4271
+ }),
4272
+ );
4186
4273
  buildApi.onResolve({ filter: /^@reckona\/mreact-router$/ }, () => ({
4187
4274
  namespace: "mreact-cloudflare-router-index",
4188
4275
  path: "index",
@@ -4192,8 +4279,10 @@ function cloudflareWorkspaceRuntimePlugin(): RouterCompatPlugin {
4192
4279
 
4193
4280
  return path === undefined ? undefined : { path };
4194
4281
  });
4195
- buildApi.onLoad({ filter: /^native-escape$/, namespace: "mreact-cloudflare-native-escape" }, () => ({
4196
- contents: `function escapeHtml(value) {
4282
+ buildApi.onLoad(
4283
+ { filter: /^native-escape$/, namespace: "mreact-cloudflare-native-escape" },
4284
+ () => ({
4285
+ contents: `function escapeHtml(value) {
4197
4286
  return String(value ?? "").replace(/[&<>"']/g, (char) =>
4198
4287
  char === "&" ? "&amp;" : char === "<" ? "&lt;" : char === ">" ? "&gt;" : char === '"' ? "&quot;" : "&#39;"
4199
4288
  );
@@ -4201,8 +4290,9 @@ function cloudflareWorkspaceRuntimePlugin(): RouterCompatPlugin {
4201
4290
  export function escapeHtmlBatch(values) {
4202
4291
  return values.map(escapeHtml);
4203
4292
  }`,
4204
- loader: "js",
4205
- }));
4293
+ loader: "js",
4294
+ }),
4295
+ );
4206
4296
  buildApi.onLoad({ filter: /^index$/, namespace: "mreact-cloudflare-router-index" }, () => ({
4207
4297
  contents: `export { cacheControl, revalidatePath } from ${JSON.stringify(routerCachePath)};
4208
4298
  export { deleteCookie, parseCookieHeader, serializeCookie, setCookie } from ${JSON.stringify(routerCookiesPath)};
@@ -4216,12 +4306,15 @@ export { getServerRuntimeState } from ${JSON.stringify(routerRuntimeStatePath)};
4216
4306
  loader: "js",
4217
4307
  resolveDir: dirname(routerNavigationPath),
4218
4308
  }));
4219
- buildApi.onLoad({ filter: /(?:^|[/\\])packages[/\\]server[/\\](?:src|dist)[/\\]native-flight\.[jt]s$/ }, () => ({
4220
- contents: `export function getNativeFlight() {
4309
+ buildApi.onLoad(
4310
+ { filter: /(?:^|[/\\])packages[/\\]server[/\\](?:src|dist)[/\\]native-flight\.[jt]s$/ },
4311
+ () => ({
4312
+ contents: `export function getNativeFlight() {
4221
4313
  return undefined;
4222
4314
  }`,
4223
- loader: "js",
4224
- }));
4315
+ loader: "js",
4316
+ }),
4317
+ );
4225
4318
  },
4226
4319
  };
4227
4320
  }
@@ -4286,6 +4379,12 @@ function viteManifestFromClientRoutes(routes: ClientRouteManifestEntry[]): Recor
4286
4379
  interface ClientRouteBundleManifest {
4287
4380
  assets: string[];
4288
4381
  routes: ClientRouteManifestEntry[];
4382
+ styles: ClientStyleManifestEntry[];
4383
+ }
4384
+
4385
+ interface ClientStyleManifestEntry {
4386
+ css: string[];
4387
+ file: string;
4289
4388
  }
4290
4389
 
4291
4390
  async function writeClientRouteBundles(options: {
@@ -4322,6 +4421,14 @@ async function writeClientRouteBundles(options: {
4322
4421
  sourceAnalysis: options.sourceAnalysis,
4323
4422
  vitePlugins: options.vitePlugins,
4324
4423
  });
4424
+ const specialCssAssets = await writeSpecialRouteCssAssetBatches({
4425
+ appDir: options.appDir,
4426
+ cacheDir: options.cacheDir,
4427
+ clientDir: options.clientDir,
4428
+ projectRoot: options.projectRoot,
4429
+ sourceAnalysis: options.sourceAnalysis,
4430
+ vitePlugins: options.vitePlugins,
4431
+ });
4325
4432
  const entries: PreparedRouteEntry[] = await Promise.all(
4326
4433
  options.routes.map(async (route) => {
4327
4434
  if (route.kind !== "page") {
@@ -4405,6 +4512,7 @@ async function writeClientRouteBundles(options: {
4405
4512
  return {
4406
4513
  assets: [],
4407
4514
  routes: entries.flatMap((entry) => ("manifest" in entry ? [entry.manifest] : [])),
4515
+ styles: specialCssAssets,
4408
4516
  };
4409
4517
  }
4410
4518
 
@@ -4423,18 +4531,23 @@ async function writeClientRouteBundles(options: {
4423
4531
  });
4424
4532
  } catch (error) {
4425
4533
  const routeContexts = clientEntries
4426
- .map((entry) => `Failed to build client bundle for ${entry.route.path} (${entry.route.file}).`)
4534
+ .map(
4535
+ (entry) => `Failed to build client bundle for ${entry.route.path} (${entry.route.file}).`,
4536
+ )
4427
4537
  .join("\n");
4428
4538
 
4429
- throw new Error(`${routeContexts}\nFailed to build client route bundles.\n${errorMessage(error)}`, {
4430
- cause: error,
4431
- });
4539
+ throw new Error(
4540
+ `${routeContexts}\nFailed to build client route bundles.\n${errorMessage(error)}`,
4541
+ {
4542
+ cause: error,
4543
+ },
4544
+ );
4432
4545
  }
4433
4546
 
4434
4547
  const routeOutputs = new Map(output.routes.map((route) => [route.routePath, route]));
4435
4548
  const mapAssets = new Map(
4436
4549
  output.chunks.flatMap((chunk) =>
4437
- chunk.map === undefined ? [] : [[`${chunk.fileName}.map`, chunk.map] as const]
4550
+ chunk.map === undefined ? [] : [[`${chunk.fileName}.map`, chunk.map] as const],
4438
4551
  ),
4439
4552
  );
4440
4553
 
@@ -4459,9 +4572,7 @@ async function writeClientRouteBundles(options: {
4459
4572
 
4460
4573
  for (const asset of output.assets ?? []) {
4461
4574
  const source =
4462
- typeof asset.source === "string"
4463
- ? asset.source
4464
- : Buffer.from(asset.source).toString("utf8");
4575
+ typeof asset.source === "string" ? asset.source : Buffer.from(asset.source).toString("utf8");
4465
4576
 
4466
4577
  await mkdir(dirname(join(options.clientDir, asset.fileName)), { recursive: true });
4467
4578
  await writeFile(join(options.clientDir, asset.fileName), source);
@@ -4521,9 +4632,65 @@ async function writeClientRouteBundles(options: {
4521
4632
  return {
4522
4633
  assets: Array.from(generatedAssets).sort(),
4523
4634
  routes,
4635
+ styles: specialCssAssets,
4524
4636
  };
4525
4637
  }
4526
4638
 
4639
+ async function writeSpecialRouteCssAssetBatches(options: {
4640
+ appDir: string;
4641
+ cacheDir?: string | undefined;
4642
+ clientDir: string;
4643
+ projectRoot: string;
4644
+ sourceAnalysis: BuildSourceAnalysisScope;
4645
+ vitePlugins?: readonly PluginOption[] | undefined;
4646
+ }): Promise<ClientStyleManifestEntry[]> {
4647
+ const boundaryFiles = await collectSpecialBoundaryFiles(options.appDir);
4648
+ const entries = await mapWithBuildConcurrency(boundaryFiles, async (file) => {
4649
+ const cssFiles = await collectRouteCssFilesFromSources({
4650
+ appDir: options.appDir,
4651
+ pageFile: file,
4652
+ projectRoot: options.projectRoot,
4653
+ readSource: (sourceFile) =>
4654
+ buildSourceAnalysisForFile(options.sourceAnalysis, options.projectRoot, sourceFile)?.source,
4655
+ });
4656
+
4657
+ if (cssFiles.length === 0) {
4658
+ return undefined;
4659
+ }
4660
+
4661
+ const css = await writeRouteCssAssetsForFiles({
4662
+ cacheDir: options.cacheDir,
4663
+ clientDir: options.clientDir,
4664
+ cssFiles,
4665
+ pageFile: file,
4666
+ projectRoot: options.projectRoot,
4667
+ routeIds: [specialBoundaryRouteId(options.appDir, file)],
4668
+ vitePlugins: options.vitePlugins,
4669
+ });
4670
+
4671
+ return css.length === 0
4672
+ ? undefined
4673
+ : ({
4674
+ css,
4675
+ file: relative(options.appDir, file).split(sep).join("/"),
4676
+ } satisfies ClientStyleManifestEntry);
4677
+ });
4678
+
4679
+ return entries
4680
+ .filter((entry): entry is ClientStyleManifestEntry => entry !== undefined)
4681
+ .sort((left, right) => left.file.localeCompare(right.file));
4682
+ }
4683
+
4684
+ function specialBoundaryRouteId(appDir: string, file: string): string {
4685
+ const relativeFile = relative(appDir, file).split(sep);
4686
+ const filename = relativeFile.pop() ?? "boundary.tsx";
4687
+ const boundaryName = filename.replace(/(?:\.mreact)?\.tsx$/u, "");
4688
+ const routeParts = relativeFile.filter((part) => !part.startsWith("(") || !part.endsWith(")"));
4689
+ const path = `/${[...routeParts, boundaryName].join("/")}`;
4690
+
4691
+ return routeIdForPath(path);
4692
+ }
4693
+
4527
4694
  async function writeRouteCssAssetBatches(options: {
4528
4695
  appDir: string;
4529
4696
  cacheDir?: string | undefined;
@@ -4538,12 +4705,16 @@ async function writeRouteCssAssetBatches(options: {
4538
4705
  appDir: options.appDir,
4539
4706
  pageFile: route.file,
4540
4707
  projectRoot: options.projectRoot,
4541
- readSource: (file) => buildSourceAnalysisForFile(options.sourceAnalysis, options.projectRoot, file)?.source,
4708
+ readSource: (file) =>
4709
+ buildSourceAnalysisForFile(options.sourceAnalysis, options.projectRoot, file)?.source,
4542
4710
  });
4543
4711
 
4544
4712
  return { cssFiles, route };
4545
4713
  });
4546
- const groups = new Map<string, { cssFiles: string[]; routeIds: string[]; routeFiles: string[] }>();
4714
+ const groups = new Map<
4715
+ string,
4716
+ { cssFiles: string[]; routeIds: string[]; routeFiles: string[] }
4717
+ >();
4547
4718
 
4548
4719
  for (const { cssFiles, route } of cssInputs) {
4549
4720
  if (cssFiles.length === 0) {
@@ -4557,18 +4728,22 @@ async function writeRouteCssAssetBatches(options: {
4557
4728
  groups.set(key, group);
4558
4729
  }
4559
4730
 
4560
- const writtenGroups = await mapWithBuildConcurrency([...groups.entries()], async ([key, group]) => [
4561
- key,
4562
- await writeRouteCssAssetsForFiles({
4563
- cacheDir: options.cacheDir,
4564
- clientDir: options.clientDir,
4565
- cssFiles: group.cssFiles,
4566
- pageFile: group.routeFiles[0] ?? options.appDir,
4567
- projectRoot: options.projectRoot,
4568
- routeIds: group.routeIds,
4569
- vitePlugins: options.vitePlugins,
4570
- }),
4571
- ] as const);
4731
+ const writtenGroups = await mapWithBuildConcurrency(
4732
+ [...groups.entries()],
4733
+ async ([key, group]) =>
4734
+ [
4735
+ key,
4736
+ await writeRouteCssAssetsForFiles({
4737
+ cacheDir: options.cacheDir,
4738
+ clientDir: options.clientDir,
4739
+ cssFiles: group.cssFiles,
4740
+ pageFile: group.routeFiles[0] ?? options.appDir,
4741
+ projectRoot: options.projectRoot,
4742
+ routeIds: group.routeIds,
4743
+ vitePlugins: options.vitePlugins,
4744
+ }),
4745
+ ] as const,
4746
+ );
4572
4747
  const cssByGroup = new Map(writtenGroups);
4573
4748
  const cssByRoute = new Map<string, string[]>();
4574
4749
 
@@ -4612,15 +4787,14 @@ async function writeRouteCssAssetsForFiles(options: {
4612
4787
  });
4613
4788
  const cssAssets = (output.assets ?? []).filter((asset) => asset.fileName.endsWith(".css"));
4614
4789
  const written: string[] = [];
4615
- const routeStem = options.routeIds.length === 1
4616
- ? (options.routeIds[0] ?? "index")
4617
- : `shared.${hashText(cssFiles.join("\0")).slice(0, 8)}`;
4790
+ const routeStem =
4791
+ options.routeIds.length === 1
4792
+ ? (options.routeIds[0] ?? "index")
4793
+ : `shared.${hashText(cssFiles.join("\0")).slice(0, 8)}`;
4618
4794
 
4619
4795
  for (const [index, asset] of cssAssets.entries()) {
4620
4796
  const source =
4621
- typeof asset.source === "string"
4622
- ? asset.source
4623
- : Buffer.from(asset.source).toString("utf8");
4797
+ typeof asset.source === "string" ? asset.source : Buffer.from(asset.source).toString("utf8");
4624
4798
  const hash = createHash("sha256").update(source).digest("hex").slice(0, 8);
4625
4799
  const cssFile = `assets/routes/${routeStem}${cssAssets.length === 1 ? "" : `.${index}`}.${hash}.css`;
4626
4800
 
@@ -4798,7 +4972,9 @@ function isBarePackageImport(specifier: string): boolean {
4798
4972
 
4799
4973
  async function assertAwsLambdaRuntimeDependencies(outDir: string): Promise<void> {
4800
4974
  try {
4801
- const info = await stat(join(outDir, "node_modules", "@reckona", "mreact-router", "package.json"));
4975
+ const info = await stat(
4976
+ join(outDir, "node_modules", "@reckona", "mreact-router", "package.json"),
4977
+ );
4802
4978
  if (info.isFile()) {
4803
4979
  return;
4804
4980
  }
@@ -4891,7 +5067,9 @@ function assertPackageOutputDoesNotReplaceBuildOutput(options: {
4891
5067
  outDir: string;
4892
5068
  }): void {
4893
5069
  if (resolve(options.fromDir) === resolve(options.outDir)) {
4894
- throw new Error("Package output directory must be different from the mreact build output directory.");
5070
+ throw new Error(
5071
+ "Package output directory must be different from the mreact build output directory.",
5072
+ );
4895
5073
  }
4896
5074
  }
4897
5075
 
@@ -4955,7 +5133,9 @@ async function collectArtifactFiles(
4955
5133
  }
4956
5134
  }
4957
5135
 
4958
- return files.sort((left, right) => right.bytes - left.bytes || left.path.localeCompare(right.path));
5136
+ return files.sort(
5137
+ (left, right) => right.bytes - left.bytes || left.path.localeCompare(right.path),
5138
+ );
4959
5139
  }
4960
5140
 
4961
5141
  function awsLambdaHandlerSource(outDirRelativeToHandler: string): string {
@@ -5000,6 +5180,7 @@ async function validateProductionRoutes(options: {
5000
5180
  cache: options.serverTransformCache,
5001
5181
  code: analysis.routeCode,
5002
5182
  clientBoundaryImports: analysis.clientBoundaryImports,
5183
+ clientBoundaryFallbackImports: analysis.clientBoundaryFallbackImports,
5003
5184
  filename: route.file,
5004
5185
  moduleContextCache: options.clientRouteInferenceCache,
5005
5186
  serverOutput: analysis.streamRoute ? "stream" : "string",
@@ -5039,10 +5220,10 @@ async function collectBuildFiles(
5039
5220
 
5040
5221
  return [{ file, relativeFile }];
5041
5222
  });
5042
- const fileContents = await mapWithBuildConcurrency(sourceFiles, async ({ file, relativeFile }) => [
5043
- relativeFile,
5044
- await readFile(file, "utf8"),
5045
- ] as const);
5223
+ const fileContents = await mapWithBuildConcurrency(
5224
+ sourceFiles,
5225
+ async ({ file, relativeFile }) => [relativeFile, await readFile(file, "utf8")] as const,
5226
+ );
5046
5227
 
5047
5228
  for (const [relativeFile, source] of fileContents) {
5048
5229
  files[relativeFile] = source;