@react-router/dev 0.0.0-experimental-a2c4d7fad → 0.0.0-experimental-902325fda

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/dist/cli/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * @react-router/dev v0.0.0-experimental-a2c4d7fad
3
+ * @react-router/dev v0.0.0-experimental-902325fda
4
4
  *
5
5
  * Copyright (c) Remix Software Inc.
6
6
  *
@@ -635,25 +635,6 @@ var init_profiler = __esm({
635
635
  }
636
636
  });
637
637
 
638
- // vite/babel.ts
639
- var babel_exports = {};
640
- __export(babel_exports, {
641
- generate: () => generate,
642
- parse: () => import_parser.parse,
643
- t: () => t,
644
- traverse: () => traverse
645
- });
646
- var import_parser, t, traverse, generate;
647
- var init_babel = __esm({
648
- "vite/babel.ts"() {
649
- "use strict";
650
- import_parser = require("@babel/parser");
651
- t = __toESM(require("@babel/types"));
652
- traverse = require("@babel/traverse").default;
653
- generate = require("@babel/generator").default;
654
- }
655
- });
656
-
657
638
  // typegen/paths.ts
658
639
  function getTypesDir(ctx) {
659
640
  return Path2.join(ctx.rootDirectory, ".react-router/types");
@@ -676,8 +657,9 @@ var init_paths = __esm({
676
657
  });
677
658
 
678
659
  // typegen/generate.ts
679
- function generate2(ctx, route) {
660
+ function generate(ctx, route) {
680
661
  const lineage = getRouteLineage(ctx.config.routes, route);
662
+ const urlpath = lineage.map((route2) => route2.path).join("/");
681
663
  const typesPath = getTypesPath(ctx, route);
682
664
  const parents = lineage.slice(0, -1);
683
665
  const parentTypeImports = parents.map((parent, i) => {
@@ -695,15 +677,22 @@ function generate2(ctx, route) {
695
677
  // ${route.file}
696
678
 
697
679
  import type * as T from "react-router/route-module"
698
- import type { Routes } from "react-router/types"
699
680
 
700
681
  ${parentTypeImports}
701
682
 
702
- type RouteId = "${route.id}"
683
+ type Module = typeof import("../${Pathe2.filename(route.file)}.js")
703
684
 
704
- export type Info = Routes[RouteId] & {
685
+ export type Info = {
705
686
  parents: [${parents.map((_, i) => `Parent${i}`).join(", ")}],
706
- id: RouteId
687
+ id: "${route.id}"
688
+ file: "${route.file}"
689
+ path: "${route.path}"
690
+ params: {${formatParamProperties(
691
+ urlpath
692
+ )}} & { [key: string]: string | undefined }
693
+ module: Module
694
+ loaderData: T.CreateLoaderData<Module>
695
+ actionData: T.CreateActionData<Module>
707
696
  }
708
697
 
709
698
  export namespace Route {
@@ -738,6 +727,36 @@ function getRouteLineage(routes2, route) {
738
727
  result.reverse();
739
728
  return result;
740
729
  }
730
+ function formatParamProperties(urlpath) {
731
+ const params = parseParams(urlpath);
732
+ const properties = Object.entries(params).map(([name, values]) => {
733
+ if (values.length === 1) {
734
+ const isOptional = values[0];
735
+ return isOptional ? `"${name}"?: string` : `"${name}": string`;
736
+ }
737
+ const items = values.map(
738
+ (isOptional) => isOptional ? "string | undefined" : "string"
739
+ );
740
+ return `"${name}": [${items.join(", ")}]`;
741
+ });
742
+ return properties.join("; ");
743
+ }
744
+ function parseParams(urlpath) {
745
+ const result = {};
746
+ let segments = urlpath.split("/");
747
+ segments.forEach((segment) => {
748
+ const match = segment.match(/^:([\w-]+)(\?)?/);
749
+ if (!match) return;
750
+ const param = match[1];
751
+ const isOptional = match[2] !== void 0;
752
+ result[param] ??= [];
753
+ result[param].push(isOptional);
754
+ return;
755
+ });
756
+ const hasSplat = segments.at(-1) === "*";
757
+ if (hasSplat) result["*"] = [false];
758
+ return result;
759
+ }
741
760
  var import_dedent, Path3, Pathe2, noExtension;
742
761
  var init_generate = __esm({
743
762
  "typegen/generate.ts"() {
@@ -798,110 +817,33 @@ async function writeAll(ctx) {
798
817
  import_node_fs3.default.rmSync(typegenDir, { recursive: true, force: true });
799
818
  Object.values(ctx.config.routes).forEach((route) => {
800
819
  const typesPath = getTypesPath(ctx, route);
801
- const content = generate2(ctx, route);
820
+ const content = generate(ctx, route);
802
821
  import_node_fs3.default.mkdirSync(Path4.dirname(typesPath), { recursive: true });
803
822
  import_node_fs3.default.writeFileSync(typesPath, content);
804
823
  });
805
- Object.values(ctx.config.routes).map(
806
- (route) => t2.tsPropertySignature(
807
- t2.stringLiteral(route.id),
808
- t2.tsTypeAnnotation(
809
- t2.tsTypeLiteral([
810
- t2.tsPropertySignature(
811
- t2.identifier("parentId"),
812
- t2.tsTypeAnnotation(
813
- route.parentId ? t2.tsLiteralType(t2.stringLiteral(route.parentId)) : t2.tsUndefinedKeyword()
814
- )
815
- ),
816
- t2.tsPropertySignature(
817
- t2.identifier("path"),
818
- t2.tsTypeAnnotation(
819
- route.path ? t2.tsLiteralType(t2.stringLiteral(route.path)) : t2.tsUndefinedKeyword()
820
- )
821
- ),
822
- t2.tsPropertySignature(
823
- t2.identifier("module"),
824
- t2.tsTypeAnnotation(
825
- t2.tsTypeQuery(t2.tsImportType(t2.stringLiteral(route.file)))
826
- )
827
- )
828
- ])
829
- )
830
- )
831
- );
832
- const registerPath = Path4.join(typegenDir, "+register.ts");
833
- import_node_fs3.default.writeFileSync(registerPath, register(ctx));
834
- }
835
- function register(ctx) {
836
- const routes2 = generate(
837
- t2.tsTypeAliasDeclaration(
838
- t2.identifier("Routes"),
839
- null,
840
- t2.tsTypeLiteral(
841
- Object.values(ctx.config.routes).map(
842
- (route) => t2.tsPropertySignature(
843
- t2.stringLiteral(route.id),
844
- t2.tsTypeAnnotation(
845
- t2.tsTypeLiteral([
846
- t2.tsPropertySignature(
847
- t2.identifier("parentId"),
848
- t2.tsTypeAnnotation(
849
- route.parentId ? t2.tsLiteralType(t2.stringLiteral(route.parentId)) : t2.tsUndefinedKeyword()
850
- )
851
- ),
852
- t2.tsPropertySignature(
853
- t2.identifier("path"),
854
- t2.tsTypeAnnotation(
855
- route.path ? t2.tsLiteralType(t2.stringLiteral(route.path)) : t2.tsUndefinedKeyword()
856
- )
857
- ),
858
- t2.tsPropertySignature(
859
- t2.identifier("module"),
860
- t2.tsTypeAnnotation(
861
- t2.tsTypeQuery(
862
- t2.tsImportType(
863
- t2.stringLiteral(compiledModulePath(ctx, route))
864
- )
865
- )
866
- )
867
- )
868
- ])
869
- )
870
- )
871
- )
872
- )
873
- )
874
- ).code;
875
- const registerTypes = import_dedent2.default`
876
- import "react-router/types";
877
-
878
- declare module "react-router/types" {
879
- interface Register {
880
- routes: Routes;
881
- }
882
- }
883
- `;
884
- return [registerTypes, routes2].join("\n\n");
885
- }
886
- function compiledModulePath(ctx, route) {
887
- return "./" + Path4.relative(
888
- ctx.rootDirectory,
889
- Path4.join(ctx.config.appDirectory, route.file)
890
- ).replace(/\.(js|ts)x?$/, ".js");
891
824
  }
892
- var import_node_fs3, import_dedent2, Path4, import_picocolors3, t2;
825
+ var import_node_fs3, Path4, import_picocolors3;
893
826
  var init_typegen = __esm({
894
827
  "typegen/index.ts"() {
895
828
  "use strict";
896
829
  import_node_fs3 = __toESM(require("fs"));
897
- import_dedent2 = __toESM(require("dedent"));
898
830
  Path4 = __toESM(require("pathe"));
899
831
  import_picocolors3 = __toESM(require("picocolors"));
900
832
  init_config();
901
- init_babel();
902
833
  init_generate();
903
834
  init_paths();
904
- ({ t: t2 } = babel_exports);
835
+ }
836
+ });
837
+
838
+ // vite/babel.ts
839
+ var import_parser, t, traverse, generate2;
840
+ var init_babel = __esm({
841
+ "vite/babel.ts"() {
842
+ "use strict";
843
+ import_parser = require("@babel/parser");
844
+ t = __toESM(require("@babel/types"));
845
+ traverse = require("@babel/traverse").default;
846
+ generate2 = require("@babel/generator").default;
905
847
  }
906
848
  });
907
849
 
@@ -1028,11 +970,11 @@ var init_route_chunks = __esm({
1028
970
  });
1029
971
 
1030
972
  // vite/with-props.ts
1031
- var import_dedent3, vmod;
973
+ var import_dedent2, vmod;
1032
974
  var init_with_props = __esm({
1033
975
  "vite/with-props.ts"() {
1034
976
  "use strict";
1035
- import_dedent3 = __toESM(require("dedent"));
977
+ import_dedent2 = __toESM(require("dedent"));
1036
978
  init_babel();
1037
979
  init_virtual_module();
1038
980
  vmod = create("with-props");
@@ -1635,8 +1577,8 @@ var babel = __toESM(require("@babel/core"));
1635
1577
  var import_plugin_syntax_jsx = __toESM(require("@babel/plugin-syntax-jsx"));
1636
1578
  var import_preset_typescript = __toESM(require("@babel/preset-typescript"));
1637
1579
  var import_prettier = __toESM(require("prettier"));
1638
- function transpile(tsx2, options = {}) {
1639
- let mjs = babel.transformSync(tsx2, {
1580
+ function transpile(tsx, options = {}) {
1581
+ let mjs = babel.transformSync(tsx, {
1640
1582
  compact: false,
1641
1583
  cwd: options.cwd,
1642
1584
  filename: options.filename,
@@ -1760,8 +1702,8 @@ async function checkForEntry(rootDirectory, appDirectory, entries2) {
1760
1702
  let entryPath = path9.resolve(appDirectory, entry);
1761
1703
  let exists = await import_fs_extra2.default.pathExists(entryPath);
1762
1704
  if (exists) {
1763
- let relative9 = path9.relative(rootDirectory, entryPath);
1764
- console.error(import_picocolors7.default.red(`Entry file ${relative9} already exists.`));
1705
+ let relative8 = path9.relative(rootDirectory, entryPath);
1706
+ console.error(import_picocolors7.default.red(`Entry file ${relative8} already exists.`));
1765
1707
  return process.exit(1);
1766
1708
  }
1767
1709
  }
package/dist/config.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @react-router/dev v0.0.0-experimental-a2c4d7fad
2
+ * @react-router/dev v0.0.0-experimental-902325fda
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
package/dist/routes.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @react-router/dev v0.0.0-experimental-a2c4d7fad
2
+ * @react-router/dev v0.0.0-experimental-902325fda
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -53,7 +53,7 @@ const enqueueUpdate = debounce(async () => {
53
53
  needsRevalidation,
54
54
  manifest.routes,
55
55
  window.__reactRouterRouteModules,
56
- window.__reactRouterContext.future,
56
+ window.__reactRouterContext.ssr,
57
57
  window.__reactRouterContext.isSpaMode
58
58
  );
59
59
  __reactRouterDataRouter._internalSetRoutes(routes);
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @react-router/dev v0.0.0-experimental-a2c4d7fad
2
+ * @react-router/dev v0.0.0-experimental-902325fda
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
package/dist/vite.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @react-router/dev v0.0.0-experimental-a2c4d7fad
2
+ * @react-router/dev v0.0.0-experimental-902325fda
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -59,7 +59,6 @@ var import_kebabCase = __toESM(require("lodash/kebabCase"));
59
59
 
60
60
  // typegen/index.ts
61
61
  var import_node_fs2 = __toESM(require("fs"));
62
- var import_dedent2 = __toESM(require("dedent"));
63
62
  var Path4 = __toESM(require("pathe"));
64
63
  var import_picocolors2 = __toESM(require("picocolors"));
65
64
 
@@ -633,19 +632,6 @@ function findEntry(dir, basename2, options) {
633
632
  return void 0;
634
633
  }
635
634
 
636
- // vite/babel.ts
637
- var babel_exports = {};
638
- __export(babel_exports, {
639
- generate: () => generate,
640
- parse: () => import_parser.parse,
641
- t: () => t,
642
- traverse: () => traverse
643
- });
644
- var import_parser = require("@babel/parser");
645
- var t = __toESM(require("@babel/types"));
646
- var traverse = require("@babel/traverse").default;
647
- var generate = require("@babel/generator").default;
648
-
649
635
  // typegen/generate.ts
650
636
  var import_dedent = __toESM(require("dedent"));
651
637
  var Path3 = __toESM(require("pathe"));
@@ -667,8 +653,9 @@ function getTypesPath(ctx, route) {
667
653
  }
668
654
 
669
655
  // typegen/generate.ts
670
- function generate2(ctx, route) {
656
+ function generate(ctx, route) {
671
657
  const lineage = getRouteLineage(ctx.config.routes, route);
658
+ const urlpath = lineage.map((route2) => route2.path).join("/");
672
659
  const typesPath = getTypesPath(ctx, route);
673
660
  const parents = lineage.slice(0, -1);
674
661
  const parentTypeImports = parents.map((parent, i) => {
@@ -686,15 +673,22 @@ function generate2(ctx, route) {
686
673
  // ${route.file}
687
674
 
688
675
  import type * as T from "react-router/route-module"
689
- import type { Routes } from "react-router/types"
690
676
 
691
677
  ${parentTypeImports}
692
678
 
693
- type RouteId = "${route.id}"
679
+ type Module = typeof import("../${Pathe2.filename(route.file)}.js")
694
680
 
695
- export type Info = Routes[RouteId] & {
681
+ export type Info = {
696
682
  parents: [${parents.map((_, i) => `Parent${i}`).join(", ")}],
697
- id: RouteId
683
+ id: "${route.id}"
684
+ file: "${route.file}"
685
+ path: "${route.path}"
686
+ params: {${formatParamProperties(
687
+ urlpath
688
+ )}} & { [key: string]: string | undefined }
689
+ module: Module
690
+ loaderData: T.CreateLoaderData<Module>
691
+ actionData: T.CreateActionData<Module>
698
692
  }
699
693
 
700
694
  export namespace Route {
@@ -730,9 +724,38 @@ function getRouteLineage(routes, route) {
730
724
  result.reverse();
731
725
  return result;
732
726
  }
727
+ function formatParamProperties(urlpath) {
728
+ const params = parseParams(urlpath);
729
+ const properties = Object.entries(params).map(([name, values]) => {
730
+ if (values.length === 1) {
731
+ const isOptional = values[0];
732
+ return isOptional ? `"${name}"?: string` : `"${name}": string`;
733
+ }
734
+ const items = values.map(
735
+ (isOptional) => isOptional ? "string | undefined" : "string"
736
+ );
737
+ return `"${name}": [${items.join(", ")}]`;
738
+ });
739
+ return properties.join("; ");
740
+ }
741
+ function parseParams(urlpath) {
742
+ const result = {};
743
+ let segments = urlpath.split("/");
744
+ segments.forEach((segment) => {
745
+ const match = segment.match(/^:([\w-]+)(\?)?/);
746
+ if (!match) return;
747
+ const param = match[1];
748
+ const isOptional = match[2] !== void 0;
749
+ result[param] ??= [];
750
+ result[param].push(isOptional);
751
+ return;
752
+ });
753
+ const hasSplat = segments.at(-1) === "*";
754
+ if (hasSplat) result["*"] = [false];
755
+ return result;
756
+ }
733
757
 
734
758
  // typegen/index.ts
735
- var { t: t2 } = babel_exports;
736
759
  async function watch(rootDirectory, { logger } = {}) {
737
760
  const ctx = await createContext2({ rootDirectory, watch: true });
738
761
  await writeAll(ctx);
@@ -776,98 +799,18 @@ async function writeAll(ctx) {
776
799
  import_node_fs2.default.rmSync(typegenDir, { recursive: true, force: true });
777
800
  Object.values(ctx.config.routes).forEach((route) => {
778
801
  const typesPath = getTypesPath(ctx, route);
779
- const content = generate2(ctx, route);
802
+ const content = generate(ctx, route);
780
803
  import_node_fs2.default.mkdirSync(Path4.dirname(typesPath), { recursive: true });
781
804
  import_node_fs2.default.writeFileSync(typesPath, content);
782
805
  });
783
- Object.values(ctx.config.routes).map(
784
- (route) => t2.tsPropertySignature(
785
- t2.stringLiteral(route.id),
786
- t2.tsTypeAnnotation(
787
- t2.tsTypeLiteral([
788
- t2.tsPropertySignature(
789
- t2.identifier("parentId"),
790
- t2.tsTypeAnnotation(
791
- route.parentId ? t2.tsLiteralType(t2.stringLiteral(route.parentId)) : t2.tsUndefinedKeyword()
792
- )
793
- ),
794
- t2.tsPropertySignature(
795
- t2.identifier("path"),
796
- t2.tsTypeAnnotation(
797
- route.path ? t2.tsLiteralType(t2.stringLiteral(route.path)) : t2.tsUndefinedKeyword()
798
- )
799
- ),
800
- t2.tsPropertySignature(
801
- t2.identifier("module"),
802
- t2.tsTypeAnnotation(
803
- t2.tsTypeQuery(t2.tsImportType(t2.stringLiteral(route.file)))
804
- )
805
- )
806
- ])
807
- )
808
- )
809
- );
810
- const registerPath = Path4.join(typegenDir, "+register.ts");
811
- import_node_fs2.default.writeFileSync(registerPath, register(ctx));
812
- }
813
- function register(ctx) {
814
- const routes = generate(
815
- t2.tsTypeAliasDeclaration(
816
- t2.identifier("Routes"),
817
- null,
818
- t2.tsTypeLiteral(
819
- Object.values(ctx.config.routes).map(
820
- (route) => t2.tsPropertySignature(
821
- t2.stringLiteral(route.id),
822
- t2.tsTypeAnnotation(
823
- t2.tsTypeLiteral([
824
- t2.tsPropertySignature(
825
- t2.identifier("parentId"),
826
- t2.tsTypeAnnotation(
827
- route.parentId ? t2.tsLiteralType(t2.stringLiteral(route.parentId)) : t2.tsUndefinedKeyword()
828
- )
829
- ),
830
- t2.tsPropertySignature(
831
- t2.identifier("path"),
832
- t2.tsTypeAnnotation(
833
- route.path ? t2.tsLiteralType(t2.stringLiteral(route.path)) : t2.tsUndefinedKeyword()
834
- )
835
- ),
836
- t2.tsPropertySignature(
837
- t2.identifier("module"),
838
- t2.tsTypeAnnotation(
839
- t2.tsTypeQuery(
840
- t2.tsImportType(
841
- t2.stringLiteral(compiledModulePath(ctx, route))
842
- )
843
- )
844
- )
845
- )
846
- ])
847
- )
848
- )
849
- )
850
- )
851
- )
852
- ).code;
853
- const registerTypes = import_dedent2.default`
854
- import "react-router/types";
855
-
856
- declare module "react-router/types" {
857
- interface Register {
858
- routes: Routes;
859
- }
860
- }
861
- `;
862
- return [registerTypes, routes].join("\n\n");
863
- }
864
- function compiledModulePath(ctx, route) {
865
- return "./" + Path4.relative(
866
- ctx.rootDirectory,
867
- Path4.join(ctx.config.appDirectory, route.file)
868
- ).replace(/\.(js|ts)x?$/, ".js");
869
806
  }
870
807
 
808
+ // vite/babel.ts
809
+ var import_parser = require("@babel/parser");
810
+ var t = __toESM(require("@babel/types"));
811
+ var traverse = require("@babel/traverse").default;
812
+ var generate2 = require("@babel/generator").default;
813
+
871
814
  // vite/node-adapter.ts
872
815
  var import_node_events = require("events");
873
816
  var import_node_stream = require("stream");
@@ -1654,7 +1597,7 @@ function getChunkedExport(code, exportName, generateOptions = {}, cache, cacheKe
1654
1597
  }
1655
1598
  throw new Error(`Unknown export node type: ${node.type}`);
1656
1599
  }).filter((node) => node !== null);
1657
- return generate(ast, generateOptions);
1600
+ return generate2(ast, generateOptions);
1658
1601
  }
1659
1602
  );
1660
1603
  }
@@ -1770,7 +1713,7 @@ function omitChunkedExports(code, exportNames, generateOptions = {}, cache, cach
1770
1713
  if (ast.program.body.length === 0) {
1771
1714
  return void 0;
1772
1715
  }
1773
- return generate(ast, generateOptions);
1716
+ return generate2(ast, generateOptions);
1774
1717
  }
1775
1718
  );
1776
1719
  }
@@ -1832,7 +1775,7 @@ function getRouteChunkNameFromModuleId(id) {
1832
1775
  }
1833
1776
 
1834
1777
  // vite/with-props.ts
1835
- var import_dedent3 = __toESM(require("dedent"));
1778
+ var import_dedent2 = __toESM(require("dedent"));
1836
1779
  var vmod = create("with-props");
1837
1780
  var NAMED_COMPONENT_EXPORTS = ["HydrateFallback", "ErrorBoundary"];
1838
1781
  var plugin = {
@@ -1843,7 +1786,7 @@ var plugin = {
1843
1786
  },
1844
1787
  async load(id) {
1845
1788
  if (id !== vmod.resolvedId) return;
1846
- return import_dedent3.default`
1789
+ return import_dedent2.default`
1847
1790
  import { createElement as h } from "react";
1848
1791
  import { useActionData, useLoaderData, useMatches, useParams, useRouteError } from "react-router";
1849
1792
 
@@ -1863,6 +1806,8 @@ var plugin = {
1863
1806
  return function Wrapped() {
1864
1807
  const props = {
1865
1808
  params: useParams(),
1809
+ loaderData: useLoaderData(),
1810
+ actionData: useActionData(),
1866
1811
  };
1867
1812
  return h(HydrateFallback, props);
1868
1813
  };
@@ -2223,6 +2168,11 @@ var reactRouterVitePlugin = () => {
2223
2168
  // Otherwise, all routes are imported as usual
2224
2169
  ctx.reactRouterConfig.routes
2225
2170
  );
2171
+ let prerenderPaths = await getPrerenderPaths(
2172
+ ctx.reactRouterConfig.prerender,
2173
+ ctx.reactRouterConfig.ssr,
2174
+ routes
2175
+ );
2226
2176
  return `
2227
2177
  import * as entryServer from ${JSON.stringify(
2228
2178
  resolveFileUrl(ctx, ctx.entryServerFilePath)
@@ -2249,6 +2199,7 @@ var reactRouterVitePlugin = () => {
2249
2199
  export const future = ${JSON.stringify(ctx.reactRouterConfig.future)};
2250
2200
  export const ssr = ${ctx.reactRouterConfig.ssr};
2251
2201
  export const isSpaMode = ${isSpaModeEnabled(ctx.reactRouterConfig)};
2202
+ export const prerender = ${JSON.stringify(prerenderPaths)};
2252
2203
  export const publicPath = ${JSON.stringify(ctx.publicPath)};
2253
2204
  export const entry = { module: entryServer };
2254
2205
  export const routes = {
@@ -2775,7 +2726,7 @@ var reactRouterVitePlugin = () => {
2775
2726
  ].join("\n")
2776
2727
  );
2777
2728
  }
2778
- if (ctx.reactRouterConfig.prerender != null && ctx.reactRouterConfig.prerender !== false) {
2729
+ if (isPrerenderingEnabled(ctx.reactRouterConfig)) {
2779
2730
  await handlePrerender(
2780
2731
  viteConfig,
2781
2732
  ctx.reactRouterConfig,
@@ -3049,12 +3000,15 @@ var reactRouterVitePlugin = () => {
3049
3000
  if (!route) return;
3050
3001
  if (!options?.ssr && isSpaModeEnabled(ctx.reactRouterConfig)) {
3051
3002
  let exportNames = getExportNames(code);
3052
- let serverOnlyExports = exportNames.filter(
3053
- (exp) => SERVER_ONLY_ROUTE_EXPORTS.includes(exp)
3054
- );
3003
+ let serverOnlyExports = exportNames.filter((exp) => {
3004
+ if (route.id === "root" && exp === "loader") {
3005
+ return false;
3006
+ }
3007
+ return SERVER_ONLY_ROUTE_EXPORTS.includes(exp);
3008
+ });
3055
3009
  if (serverOnlyExports.length > 0) {
3056
3010
  let str = serverOnlyExports.map((e) => `\`${e}\``).join(", ");
3057
- let message = `SPA Mode: ${serverOnlyExports.length} invalid route export(s) in \`${route.file}\`: ${str}. See https://remix.run/guides/spa-mode for more information.`;
3011
+ let message = `SPA Mode: ${serverOnlyExports.length} invalid route export(s) in \`${route.file}\`: ${str}. See https://reactrouter.com/how-to/spa for more information.`;
3058
3012
  throw Error(message);
3059
3013
  }
3060
3014
  if (route.id !== "root") {
@@ -3062,7 +3016,7 @@ var reactRouterVitePlugin = () => {
3062
3016
  (exp) => exp === "HydrateFallback"
3063
3017
  );
3064
3018
  if (hasHydrateFallback) {
3065
- let message = `SPA Mode: Invalid \`HydrateFallback\` export found in \`${route.file}\`. \`HydrateFallback\` is only permitted on the root route in SPA Mode. See https://remix.run/guides/spa-mode for more information.`;
3019
+ let message = `SPA Mode: Invalid \`HydrateFallback\` export found in \`${route.file}\`. \`HydrateFallback\` is only permitted on the root route in SPA Mode. See https://reactrouter.com/how-to/spa for more information.`;
3066
3020
  throw Error(message);
3067
3021
  }
3068
3022
  }
@@ -3073,7 +3027,7 @@ var reactRouterVitePlugin = () => {
3073
3027
  removeExports(ast, SERVER_ONLY_ROUTE_EXPORTS);
3074
3028
  }
3075
3029
  transform(ast);
3076
- return generate(ast, {
3030
+ return generate2(ast, {
3077
3031
  sourceMaps: true,
3078
3032
  filename: id,
3079
3033
  sourceFileName: filepath
@@ -3355,8 +3309,11 @@ async function getRouteMetadata(cache, ctx, viteChildCompiler, route, readRouteF
3355
3309
  };
3356
3310
  return info;
3357
3311
  }
3312
+ function isPrerenderingEnabled(reactRouterConfig) {
3313
+ return reactRouterConfig.prerender != null && reactRouterConfig.prerender !== false;
3314
+ }
3358
3315
  function isSpaModeEnabled(reactRouterConfig) {
3359
- return reactRouterConfig.ssr === false && (reactRouterConfig.prerender == null || reactRouterConfig.prerender === false || Array.isArray(reactRouterConfig.prerender) && reactRouterConfig.prerender.length === 1 && reactRouterConfig.prerender[0] === "/");
3316
+ return reactRouterConfig.ssr === false && !isPrerenderingEnabled(reactRouterConfig);
3360
3317
  }
3361
3318
  async function getPrerenderBuildAndHandler(viteConfig, serverBuildDirectory, serverBuildFile) {
3362
3319
  let serverBuildPath = path6.join(serverBuildDirectory, serverBuildFile);
@@ -3368,20 +3325,49 @@ async function getPrerenderBuildAndHandler(viteConfig, serverBuildDirectory, ser
3368
3325
  };
3369
3326
  }
3370
3327
  async function handleSpaMode(viteConfig, reactRouterConfig, serverBuildDirectory, serverBuildFile, clientBuildDirectory) {
3371
- let { handler } = await getPrerenderBuildAndHandler(
3328
+ let { build, handler } = await getPrerenderBuildAndHandler(
3372
3329
  viteConfig,
3373
3330
  serverBuildDirectory,
3374
3331
  serverBuildFile
3375
3332
  );
3376
- let request = new Request(`http://localhost${reactRouterConfig.basename}`);
3333
+ let request = new Request(`http://localhost${reactRouterConfig.basename}`, {
3334
+ headers: {
3335
+ // Enable SPA mode in the server runtime and only render down to the root
3336
+ "X-React-Router-SPA-Mode": "yes"
3337
+ }
3338
+ });
3377
3339
  let response = await handler(request);
3378
3340
  let html = await response.text();
3379
- validatePrerenderedResponse(response, html, "SPA Mode", "/");
3380
- validatePrerenderedHtml(html, "SPA Mode");
3381
- await fse.writeFile(path6.join(clientBuildDirectory, "index.html"), html);
3382
- viteConfig.logger.info(
3383
- "SPA Mode: index.html has been written to your " + import_picocolors3.default.bold(path6.relative(process.cwd(), clientBuildDirectory)) + " directory"
3384
- );
3341
+ let isPrerenderSpaFallback = build.prerender.includes("/");
3342
+ let filename3 = isPrerenderSpaFallback ? "__spa-fallback.html" : "index.html";
3343
+ if (response.status !== 200) {
3344
+ if (isPrerenderSpaFallback) {
3345
+ throw new Error(
3346
+ `Prerender: Received a ${response.status} status code from \`entry.server.tsx\` while prerendering your \`${filename3}\` file.
3347
+ ` + html
3348
+ );
3349
+ } else {
3350
+ throw new Error(
3351
+ `SPA Mode: Received a ${response.status} status code from \`entry.server.tsx\` while prerendering your \`${filename3}\` file.
3352
+ ` + html
3353
+ );
3354
+ }
3355
+ }
3356
+ if (!html.includes("window.__reactRouterContext =") || !html.includes("window.__reactRouterRouteModules =")) {
3357
+ throw new Error(
3358
+ "SPA Mode: Did you forget to include `<Scripts/>` in your root route? Your pre-rendered HTML cannot hydrate without `<Scripts />`."
3359
+ );
3360
+ }
3361
+ await fse.writeFile(path6.join(clientBuildDirectory, filename3), html);
3362
+ let prettyDir = path6.relative(process.cwd(), clientBuildDirectory);
3363
+ let prettyPath = path6.join(prettyDir, filename3);
3364
+ if (build.prerender.length > 0) {
3365
+ viteConfig.logger.info(
3366
+ `Prerender (html): SPA Fallback -> ${import_picocolors3.default.bold(prettyPath)}`
3367
+ );
3368
+ } else {
3369
+ viteConfig.logger.info(`SPA Mode: Generated ${import_picocolors3.default.bold(prettyPath)}`);
3370
+ }
3385
3371
  }
3386
3372
  async function handlePrerender(viteConfig, reactRouterConfig, serverBuildDirectory, serverBuildPath, clientBuildDirectory) {
3387
3373
  let { build, handler } = await getPrerenderBuildAndHandler(
@@ -3390,29 +3376,22 @@ async function handlePrerender(viteConfig, reactRouterConfig, serverBuildDirecto
3390
3376
  serverBuildPath
3391
3377
  );
3392
3378
  let routes = createPrerenderRoutes(build.routes);
3393
- let routesToPrerender;
3394
- if (typeof reactRouterConfig.prerender === "boolean") {
3395
- invariant(reactRouterConfig.prerender, "Expected prerender:true");
3396
- routesToPrerender = determineStaticPrerenderRoutes(
3397
- routes,
3398
- viteConfig,
3399
- true
3400
- );
3401
- } else if (typeof reactRouterConfig.prerender === "function") {
3402
- routesToPrerender = await reactRouterConfig.prerender({
3403
- getStaticPaths: () => determineStaticPrerenderRoutes(routes, viteConfig, false)
3404
- });
3405
- } else {
3406
- routesToPrerender = reactRouterConfig.prerender || ["/"];
3407
- }
3379
+ let prerenderedRoutes = /* @__PURE__ */ new Set();
3408
3380
  let headers = {
3409
3381
  // Header that can be used in the loader to know if you're running at
3410
3382
  // build time or runtime
3411
3383
  "X-React-Router-Prerender": "yes"
3412
3384
  };
3413
- for (let path7 of routesToPrerender) {
3385
+ for (let path7 of build.prerender) {
3414
3386
  let matches = (0, import_react_router2.matchRoutes)(routes, `/${path7}/`.replace(/^\/\/+/, "/"));
3415
- let hasLoaders = matches?.some((m) => m.route.loader);
3387
+ invariant(
3388
+ matches,
3389
+ `Unable to prerender path because it does not match any routes: ${path7}`
3390
+ );
3391
+ matches.forEach((m) => prerenderedRoutes.add(m.route.id));
3392
+ let hasLoaders = matches.some(
3393
+ (m) => build.assets.routes[m.route.id]?.hasLoader
3394
+ );
3416
3395
  let data;
3417
3396
  if (hasLoaders) {
3418
3397
  data = await prerenderData(
@@ -3452,8 +3431,34 @@ async function handlePrerender(viteConfig, reactRouterConfig, serverBuildDirecto
3452
3431
  );
3453
3432
  }
3454
3433
  }
3434
+ if (reactRouterConfig.ssr === false) {
3435
+ let errors = [];
3436
+ for (let [routeId, route] of Object.entries(build.routes)) {
3437
+ let invalidApis = [];
3438
+ if (route) {
3439
+ if (route.module.headers) invalidApis.push("headers");
3440
+ if (route.module.action) invalidApis.push("action");
3441
+ if (invalidApis.length > 0) {
3442
+ errors.push(
3443
+ `Prerender: ${invalidApis.length} invalid route export(s) in \`${route.id}\` when prerendering with \`ssr:false\`: ${invalidApis.join(", ")}. See https://reactrouter.com/how-to/spa for more information.`
3444
+ );
3445
+ }
3446
+ if (route.module.loader && !prerenderedRoutes.has(routeId)) {
3447
+ errors.push(
3448
+ `Prerender: 1 invalid route export in \`${route.id}\` when using \`ssr:false\` with \`prerender\` because the route is never prerendered so the loader will never be called. See https://reactrouter.com/how-to/spa for more information.`
3449
+ );
3450
+ }
3451
+ }
3452
+ }
3453
+ if (errors.length > 0) {
3454
+ viteConfig.logger.error(errors.join("\n"));
3455
+ throw new Error(
3456
+ "Invalid route exports found when prerendering with `ssr:false`"
3457
+ );
3458
+ }
3459
+ }
3455
3460
  }
3456
- function determineStaticPrerenderRoutes(routes, viteConfig, isBooleanUsage = false) {
3461
+ function getStaticPrerenderPaths(routes) {
3457
3462
  let paths = ["/"];
3458
3463
  let paramRoutes = [];
3459
3464
  function recurse(subtree, prefix = "") {
@@ -3473,28 +3478,29 @@ function determineStaticPrerenderRoutes(routes, viteConfig, isBooleanUsage = fal
3473
3478
  }
3474
3479
  }
3475
3480
  recurse(routes);
3476
- if (isBooleanUsage && paramRoutes.length > 0) {
3477
- viteConfig.logger.warn(
3478
- [
3479
- "\u26A0\uFE0F Paths with dynamic/splat params cannot be prerendered when using `prerender: true`.",
3480
- "You may want to use the `prerender()` API to prerender the following paths:",
3481
- ...paramRoutes.map((p) => " - " + p)
3482
- ].join("\n")
3483
- );
3484
- }
3485
- return paths.map((p) => p.replace(/\/\/+/g, "/").replace(/(.+)\/$/, "$1"));
3481
+ return {
3482
+ paths: paths.map((p) => p.replace(/\/\/+/g, "/").replace(/(.+)\/$/, "$1")),
3483
+ paramRoutes
3484
+ };
3486
3485
  }
3487
3486
  async function prerenderData(handler, prerenderPath, clientBuildDirectory, reactRouterConfig, viteConfig, requestInit) {
3488
3487
  let normalizedPath = `${reactRouterConfig.basename}${prerenderPath === "/" ? "/_root.data" : `${prerenderPath.replace(/\/$/, "")}.data`}`.replace(/\/\/+/g, "/");
3489
3488
  let request = new Request(`http://localhost${normalizedPath}`, requestInit);
3490
3489
  let response = await handler(request);
3491
3490
  let data = await response.text();
3492
- validatePrerenderedResponse(response, data, "Prerender", normalizedPath);
3491
+ if (response.status !== 200) {
3492
+ throw new Error(
3493
+ `Prerender (data): Received a ${response.status} status code from \`entry.server.tsx\` while prerendering the \`${path6}\` path.
3494
+ ${normalizedPath}`
3495
+ );
3496
+ }
3493
3497
  let outdir = path6.relative(process.cwd(), clientBuildDirectory);
3494
3498
  let outfile = path6.join(outdir, ...normalizedPath.split("/"));
3495
3499
  await fse.ensureDir(path6.dirname(outfile));
3496
3500
  await fse.outputFile(outfile, data);
3497
- viteConfig.logger.info(`Prerender: Generated ${import_picocolors3.default.bold(outfile)}`);
3501
+ viteConfig.logger.info(
3502
+ `Prerender (data): ${prerenderPath} -> ${import_picocolors3.default.bold(outfile)}`
3503
+ );
3498
3504
  return data;
3499
3505
  }
3500
3506
  async function prerenderRoute(handler, prerenderPath, clientBuildDirectory, reactRouterConfig, viteConfig, requestInit) {
@@ -3505,42 +3511,65 @@ async function prerenderRoute(handler, prerenderPath, clientBuildDirectory, reac
3505
3511
  let request = new Request(`http://localhost${normalizedPath}`, requestInit);
3506
3512
  let response = await handler(request);
3507
3513
  let html = await response.text();
3508
- validatePrerenderedResponse(response, html, "Prerender", normalizedPath);
3509
- if (!reactRouterConfig.ssr) {
3510
- validatePrerenderedHtml(html, "Prerender");
3514
+ if (response.status !== 200) {
3515
+ throw new Error(
3516
+ `Prerender (html): Received a ${response.status} status code from \`entry.server.tsx\` while prerendering the \`${normalizedPath}\` path.
3517
+ ${html}`
3518
+ );
3511
3519
  }
3512
3520
  let outdir = path6.relative(process.cwd(), clientBuildDirectory);
3513
3521
  let outfile = path6.join(outdir, ...normalizedPath.split("/"), "index.html");
3514
3522
  await fse.ensureDir(path6.dirname(outfile));
3515
3523
  await fse.outputFile(outfile, html);
3516
- viteConfig.logger.info(`Prerender: Generated ${import_picocolors3.default.bold(outfile)}`);
3524
+ viteConfig.logger.info(
3525
+ `Prerender (html): ${prerenderPath} -> ${import_picocolors3.default.bold(outfile)}`
3526
+ );
3517
3527
  }
3518
3528
  async function prerenderResourceRoute(handler, prerenderPath, clientBuildDirectory, reactRouterConfig, viteConfig, requestInit) {
3519
3529
  let normalizedPath = `${reactRouterConfig.basename}${prerenderPath}/`.replace(/\/\/+/g, "/").replace(/\/$/g, "");
3520
3530
  let request = new Request(`http://localhost${normalizedPath}`, requestInit);
3521
3531
  let response = await handler(request);
3522
3532
  let text = await response.text();
3523
- validatePrerenderedResponse(response, text, "Prerender", normalizedPath);
3524
- let outdir = path6.relative(process.cwd(), clientBuildDirectory);
3525
- let outfile = path6.join(outdir, ...normalizedPath.split("/"));
3526
- await fse.ensureDir(path6.dirname(outfile));
3527
- await fse.outputFile(outfile, text);
3528
- viteConfig.logger.info(`Prerender: Generated ${import_picocolors3.default.bold(outfile)}`);
3529
- }
3530
- function validatePrerenderedResponse(response, html, prefix, path7) {
3531
3533
  if (response.status !== 200) {
3532
3534
  throw new Error(
3533
- `${prefix}: Received a ${response.status} status code from \`entry.server.tsx\` while prerendering the \`${path7}\` path.
3534
- ${html}`
3535
+ `Prerender (resource): Received a ${response.status} status code from \`entry.server.tsx\` while prerendering the \`${normalizedPath}\` path.
3536
+ ${text}`
3535
3537
  );
3536
3538
  }
3539
+ let outdir = path6.relative(process.cwd(), clientBuildDirectory);
3540
+ let outfile = path6.join(outdir, ...normalizedPath.split("/"));
3541
+ await fse.ensureDir(path6.dirname(outfile));
3542
+ await fse.outputFile(outfile, text);
3543
+ viteConfig.logger.info(
3544
+ `Prerender (resource): ${prerenderPath} -> ${import_picocolors3.default.bold(outfile)}`
3545
+ );
3537
3546
  }
3538
- function validatePrerenderedHtml(html, prefix) {
3539
- if (!html.includes("window.__reactRouterContext =") || !html.includes("window.__reactRouterRouteModules =")) {
3540
- throw new Error(
3541
- `${prefix}: Did you forget to include <Scripts/> in your root route? Your pre-rendered HTML files cannot hydrate without \`<Scripts />\`.`
3542
- );
3547
+ async function getPrerenderPaths(prerender, ssr, routes) {
3548
+ let prerenderPaths = [];
3549
+ if (prerender != null && prerender !== false) {
3550
+ let prerenderRoutes = createPrerenderRoutes(routes);
3551
+ if (prerender === true) {
3552
+ let { paths, paramRoutes } = getStaticPrerenderPaths(prerenderRoutes);
3553
+ if (!ssr && paramRoutes.length > 0) {
3554
+ console.warn(
3555
+ import_picocolors3.default.yellow(
3556
+ [
3557
+ "\u26A0\uFE0F Paths with dynamic/splat params cannot be prerendered when using `prerender: true`. You may want to use the `prerender()` API to prerender the following paths:",
3558
+ ...paramRoutes.map((p) => " - " + p)
3559
+ ].join("\n")
3560
+ )
3561
+ );
3562
+ }
3563
+ prerenderPaths = paths;
3564
+ } else if (typeof prerender === "function") {
3565
+ prerenderPaths = await prerender({
3566
+ getStaticPaths: () => getStaticPrerenderPaths(prerenderRoutes).paths
3567
+ });
3568
+ } else {
3569
+ prerenderPaths = prerender || ["/"];
3570
+ }
3543
3571
  }
3572
+ return prerenderPaths;
3544
3573
  }
3545
3574
  function groupRoutesByParentId2(manifest) {
3546
3575
  let routes = {};
@@ -3558,19 +3587,16 @@ function groupRoutesByParentId2(manifest) {
3558
3587
  function createPrerenderRoutes(manifest, parentId = "", routesByParentId = groupRoutesByParentId2(manifest)) {
3559
3588
  return (routesByParentId[parentId] || []).map((route) => {
3560
3589
  let commonRoute = {
3561
- // Always include root due to default boundaries
3562
- hasErrorBoundary: route.id === "root" || route.module.ErrorBoundary != null,
3563
3590
  id: route.id,
3564
- path: route.path,
3565
- loader: route.module.loader ? () => null : void 0,
3566
- action: void 0,
3567
- handle: route.module.handle
3591
+ path: route.path
3568
3592
  };
3569
- return route.index ? {
3570
- index: true,
3571
- ...commonRoute
3572
- } : {
3573
- caseSensitive: route.caseSensitive,
3593
+ if (route.index) {
3594
+ return {
3595
+ index: true,
3596
+ ...commonRoute
3597
+ };
3598
+ }
3599
+ return {
3574
3600
  children: createPrerenderRoutes(manifest, route.id, routesByParentId),
3575
3601
  ...commonRoute
3576
3602
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-router/dev",
3
- "version": "0.0.0-experimental-a2c4d7fad",
3
+ "version": "0.0.0-experimental-902325fda",
4
4
  "description": "Dev tools and CLI for React Router",
5
5
  "homepage": "https://reactrouter.com",
6
6
  "bugs": {
@@ -88,7 +88,7 @@
88
88
  "set-cookie-parser": "^2.6.0",
89
89
  "valibot": "^0.41.0",
90
90
  "vite-node": "3.0.0-beta.2",
91
- "@react-router/node": "0.0.0-experimental-a2c4d7fad"
91
+ "@react-router/node": "0.0.0-experimental-902325fda"
92
92
  },
93
93
  "devDependencies": {
94
94
  "@types/babel__core": "^7.20.5",
@@ -117,15 +117,15 @@
117
117
  "vite": "^6.0.0",
118
118
  "wireit": "0.14.9",
119
119
  "wrangler": "^3.28.2",
120
- "react-router": "^0.0.0-experimental-a2c4d7fad",
121
- "@react-router/serve": "0.0.0-experimental-a2c4d7fad"
120
+ "@react-router/serve": "0.0.0-experimental-902325fda",
121
+ "react-router": "^0.0.0-experimental-902325fda"
122
122
  },
123
123
  "peerDependencies": {
124
124
  "typescript": "^5.1.0",
125
125
  "vite": "^5.1.0 || ^6.0.0",
126
126
  "wrangler": "^3.28.2",
127
- "@react-router/serve": "^0.0.0-experimental-a2c4d7fad",
128
- "react-router": "^0.0.0-experimental-a2c4d7fad"
127
+ "@react-router/serve": "^0.0.0-experimental-902325fda",
128
+ "react-router": "^0.0.0-experimental-902325fda"
129
129
  },
130
130
  "peerDependenciesMeta": {
131
131
  "@react-router/serve": {