@react-router/dev 7.5.3 → 7.6.0-pre.0

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/config.d.ts CHANGED
@@ -102,6 +102,22 @@ type ReactRouterConfig = {
102
102
  * other platforms and tools.
103
103
  */
104
104
  presets?: Array<Preset>;
105
+ /**
106
+ * Control the "Lazy Route Discovery" behavior
107
+ *
108
+ * - `routeDiscovery.mode`: By default, this resolves to `lazy` which will
109
+ * lazily discover routes as the user navigates around your application.
110
+ * You can set this to `initial` to opt-out of this behavior and load all
111
+ * routes with the initial HTML document load.
112
+ * - `routeDiscovery.manifestPath`: The path to serve the manifest file from.
113
+ * Only applies to `mode: "lazy"` and defaults to `/__manifest`.
114
+ */
115
+ routeDiscovery?: {
116
+ mode: "lazy";
117
+ manifestPath?: string;
118
+ } | {
119
+ mode: "initial";
120
+ };
105
121
  /**
106
122
  * The file name of the server build output. This file
107
123
  * should end in a `.js` extension and should be deployed to your server.
@@ -148,6 +164,17 @@ type ResolvedReactRouterConfig = Readonly<{
148
164
  * function returning an array to dynamically generate URLs.
149
165
  */
150
166
  prerender: ReactRouterConfig["prerender"];
167
+ /**
168
+ * Control the "Lazy Route Discovery" behavior
169
+ *
170
+ * - `routeDiscovery.mode`: By default, this resolves to `lazy` which will
171
+ * lazily discover routes as the user navigates around your application.
172
+ * You can set this to `initial` to opt-out of this behavior and load all
173
+ * routes with the initial HTML document load.
174
+ * - `routeDiscovery.manifestPath`: The path to serve the manifest file from.
175
+ * Only applies to `mode: "lazy"` and defaults to `/__manifest`.
176
+ */
177
+ routeDiscovery: ReactRouterConfig["routeDiscovery"];
151
178
  /**
152
179
  * An object of all available routes, keyed by route id.
153
180
  */
package/dist/config.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @react-router/dev v7.5.3
2
+ * @react-router/dev v7.6.0-pre.0
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 v7.5.3
2
+ * @react-router/dev v7.6.0-pre.0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @react-router/dev v7.5.3
2
+ * @react-router/dev v7.6.0-pre.0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -113,7 +113,9 @@ function fromNodeRequest(nodeReq, nodeRes) {
113
113
  }
114
114
  async function toNodeRequest(res, nodeRes) {
115
115
  nodeRes.statusCode = res.status;
116
- nodeRes.statusMessage = res.statusText;
116
+ if (!nodeRes.req || nodeRes.req.httpVersionMajor < 2) {
117
+ nodeRes.statusMessage = res.statusText;
118
+ }
117
119
  let cookiesStrings = [];
118
120
  for (let [name, value] of res.headers) {
119
121
  if (name === "set-cookie") {
@@ -195,13 +197,15 @@ var ssrExternals = isReactRouterRepo() ? [
195
197
  // vite/vite-node.ts
196
198
  async function createContext({
197
199
  root,
198
- mode
200
+ mode,
201
+ customLogger
199
202
  }) {
200
203
  await preloadVite();
201
204
  const vite2 = getVite();
202
205
  const devServer = await vite2.createServer({
203
206
  root,
204
207
  mode,
208
+ customLogger,
205
209
  server: {
206
210
  preTransformRequests: false,
207
211
  hmr: false,
@@ -213,6 +217,15 @@ async function createContext({
213
217
  optimizeDeps: {
214
218
  noDiscovery: true
215
219
  },
220
+ css: {
221
+ // This empty PostCSS config object prevents the PostCSS config file from
222
+ // being loaded. We don't need it in a React Router config context, and
223
+ // there's also an issue in Vite 5 when using a .ts PostCSS config file in
224
+ // an ESM project: https://github.com/vitejs/vite/issues/15869. Consumers
225
+ // can work around this in their own Vite config file, but they can't
226
+ // configure this internal usage of vite-node.
227
+ postcss: {}
228
+ },
216
229
  configFile: false,
217
230
  envFile: false,
218
231
  plugins: []
@@ -290,7 +303,7 @@ function validateRouteConfig({
290
303
  `Route config in "${routeConfigFile}" is invalid.`,
291
304
  root ? `${root}` : [],
292
305
  nested ? Object.entries(nested).map(
293
- ([path4, message]) => `Path: routes.${path4}
306
+ ([path3, message]) => `Path: routes.${path3}
294
307
  ${message}`
295
308
  ) : []
296
309
  ].flat().join("\n\n")
@@ -383,7 +396,8 @@ function err(error) {
383
396
  async function resolveConfig({
384
397
  root,
385
398
  viteNodeContext,
386
- reactRouterConfigFile
399
+ reactRouterConfigFile,
400
+ skipRoutes
387
401
  }) {
388
402
  let reactRouterUserConfig = {};
389
403
  if (reactRouterConfigFile) {
@@ -432,12 +446,17 @@ async function resolveConfig({
432
446
  serverModuleFormat: "esm",
433
447
  ssr: true
434
448
  };
449
+ let userAndPresetConfigs = mergeReactRouterConfig(
450
+ ...presets,
451
+ reactRouterUserConfig
452
+ );
435
453
  let {
436
454
  appDirectory: userAppDirectory,
437
455
  basename,
438
456
  buildDirectory: userBuildDirectory,
439
457
  buildEnd,
440
458
  prerender,
459
+ routeDiscovery: userRouteDiscovery,
441
460
  serverBuildFile,
442
461
  serverBundles,
443
462
  serverModuleFormat,
@@ -445,7 +464,7 @@ async function resolveConfig({
445
464
  } = {
446
465
  ...defaults,
447
466
  // Default values should be completely overridden by user/preset config, not merged
448
- ...mergeReactRouterConfig(...presets, reactRouterUserConfig)
467
+ ...userAndPresetConfigs
449
468
  };
450
469
  if (!ssr && serverBundles) {
451
470
  serverBundles = void 0;
@@ -456,6 +475,32 @@ async function resolveConfig({
456
475
  "The `prerender` config must be a boolean, an array of string paths, or a function returning a boolean or array of string paths"
457
476
  );
458
477
  }
478
+ let routeDiscovery;
479
+ if (userRouteDiscovery == null) {
480
+ if (ssr) {
481
+ routeDiscovery = {
482
+ mode: "lazy",
483
+ manifestPath: "/__manifest"
484
+ };
485
+ } else {
486
+ routeDiscovery = { mode: "initial" };
487
+ }
488
+ } else if (userRouteDiscovery.mode === "initial") {
489
+ routeDiscovery = userRouteDiscovery;
490
+ } else if (userRouteDiscovery.mode === "lazy") {
491
+ if (!ssr) {
492
+ return err(
493
+ 'The `routeDiscovery.mode` config cannot be set to "lazy" when setting `ssr:false`'
494
+ );
495
+ }
496
+ let { manifestPath } = userRouteDiscovery;
497
+ if (manifestPath != null && !manifestPath.startsWith("/")) {
498
+ return err(
499
+ 'The `routeDiscovery.manifestPath` config must be a root-relative pathname beginning with a slash (i.e., "/__manifest")'
500
+ );
501
+ }
502
+ routeDiscovery = userRouteDiscovery;
503
+ }
459
504
  let appDirectory = import_pathe3.default.resolve(root, userAppDirectory || "app");
460
505
  let buildDirectory = import_pathe3.default.resolve(root, userBuildDirectory);
461
506
  let rootRouteFile = findEntry(appDirectory, "root");
@@ -468,45 +513,50 @@ async function resolveConfig({
468
513
  `Could not find a root route module in the app directory as "${rootRouteDisplayPath}"`
469
514
  );
470
515
  }
471
- let routes = {
472
- root: { path: "", id: "root", file: rootRouteFile }
473
- };
474
- let routeConfigFile = findEntry(appDirectory, "routes");
475
- try {
476
- if (!routeConfigFile) {
477
- let routeConfigDisplayPath = import_pathe3.default.relative(
478
- root,
479
- import_pathe3.default.join(appDirectory, "routes.ts")
480
- );
481
- return err(`Route config file not found at "${routeConfigDisplayPath}".`);
482
- }
483
- setAppDirectory(appDirectory);
484
- let routeConfigExport = (await viteNodeContext.runner.executeFile(
485
- import_pathe3.default.join(appDirectory, routeConfigFile)
486
- )).default;
487
- let routeConfig = await routeConfigExport;
488
- let result = validateRouteConfig({
489
- routeConfigFile,
490
- routeConfig
491
- });
492
- if (!result.valid) {
493
- return err(result.message);
494
- }
516
+ let routes = {};
517
+ if (!skipRoutes) {
495
518
  routes = {
496
- ...routes,
497
- ...configRoutesToRouteManifest(appDirectory, routeConfig)
519
+ root: { path: "", id: "root", file: rootRouteFile }
498
520
  };
499
- } catch (error) {
500
- return err(
501
- [
502
- import_picocolors.default.red(`Route config in "${routeConfigFile}" is invalid.`),
503
- "",
504
- error.loc?.file && error.loc?.column && error.frame ? [
505
- import_pathe3.default.relative(appDirectory, error.loc.file) + ":" + error.loc.line + ":" + error.loc.column,
506
- error.frame.trim?.()
507
- ] : error.stack
508
- ].flat().join("\n")
509
- );
521
+ let routeConfigFile = findEntry(appDirectory, "routes");
522
+ try {
523
+ if (!routeConfigFile) {
524
+ let routeConfigDisplayPath = import_pathe3.default.relative(
525
+ root,
526
+ import_pathe3.default.join(appDirectory, "routes.ts")
527
+ );
528
+ return err(
529
+ `Route config file not found at "${routeConfigDisplayPath}".`
530
+ );
531
+ }
532
+ setAppDirectory(appDirectory);
533
+ let routeConfigExport = (await viteNodeContext.runner.executeFile(
534
+ import_pathe3.default.join(appDirectory, routeConfigFile)
535
+ )).default;
536
+ let routeConfig = await routeConfigExport;
537
+ let result = validateRouteConfig({
538
+ routeConfigFile,
539
+ routeConfig
540
+ });
541
+ if (!result.valid) {
542
+ return err(result.message);
543
+ }
544
+ routes = {
545
+ ...routes,
546
+ ...configRoutesToRouteManifest(appDirectory, routeConfig)
547
+ };
548
+ } catch (error) {
549
+ return err(
550
+ [
551
+ import_picocolors.default.red(`Route config in "${routeConfigFile}" is invalid.`),
552
+ "",
553
+ error.loc?.file && error.loc?.column && error.frame ? [
554
+ import_pathe3.default.relative(appDirectory, error.loc.file) + ":" + error.loc.line + ":" + error.loc.column,
555
+ error.frame.trim?.()
556
+ ] : error.stack
557
+ ].flat().join("\n")
558
+ );
559
+ }
510
560
  }
511
561
  let future = {
512
562
  unstable_middleware: reactRouterUserConfig.future?.unstable_middleware ?? false,
@@ -523,6 +573,7 @@ async function resolveConfig({
523
573
  future,
524
574
  prerender,
525
575
  routes,
576
+ routeDiscovery,
526
577
  serverBuildFile,
527
578
  serverBundles,
528
579
  serverModuleFormat,
@@ -535,24 +586,35 @@ async function resolveConfig({
535
586
  }
536
587
  async function createConfigLoader({
537
588
  rootDirectory: root,
538
- watch
589
+ watch,
590
+ mode,
591
+ skipRoutes
539
592
  }) {
540
- root = root ?? process.env.REACT_ROUTER_ROOT ?? process.cwd();
593
+ root = import_pathe3.default.normalize(root ?? process.env.REACT_ROUTER_ROOT ?? process.cwd());
594
+ let vite2 = await import("vite");
541
595
  let viteNodeContext = await createContext({
542
596
  root,
543
- mode: watch ? "development" : "production"
544
- });
545
- let reactRouterConfigFile = findEntry(root, "react-router.config", {
546
- absolute: true
597
+ mode,
598
+ // Filter out any info level logs from vite-node
599
+ customLogger: vite2.createLogger("warn", {
600
+ prefix: "[react-router]"
601
+ })
547
602
  });
548
- let getConfig = () => resolveConfig({ root, viteNodeContext, reactRouterConfigFile });
603
+ let reactRouterConfigFile;
604
+ let updateReactRouterConfigFile = () => {
605
+ reactRouterConfigFile = findEntry(root, "react-router.config", {
606
+ absolute: true
607
+ });
608
+ };
609
+ updateReactRouterConfigFile();
610
+ let getConfig = () => resolveConfig({ root, viteNodeContext, reactRouterConfigFile, skipRoutes });
549
611
  let appDirectory;
550
612
  let initialConfigResult = await getConfig();
551
613
  if (!initialConfigResult.ok) {
552
614
  throw new Error(initialConfigResult.error);
553
615
  }
554
- appDirectory = initialConfigResult.value.appDirectory;
555
- let lastConfig = initialConfigResult.value;
616
+ appDirectory = import_pathe3.default.normalize(initialConfigResult.value.appDirectory);
617
+ let currentConfig = initialConfigResult.value;
556
618
  let fsWatcher;
557
619
  let changeHandlers = [];
558
620
  return {
@@ -565,41 +627,71 @@ async function createConfigLoader({
565
627
  }
566
628
  changeHandlers.push(handler);
567
629
  if (!fsWatcher) {
568
- fsWatcher = import_chokidar.default.watch(
569
- [
570
- ...reactRouterConfigFile ? [reactRouterConfigFile] : [],
571
- appDirectory
572
- ],
573
- { ignoreInitial: true }
574
- );
630
+ fsWatcher = import_chokidar.default.watch([root, appDirectory], {
631
+ ignoreInitial: true,
632
+ ignored: (path3) => {
633
+ let dirname = import_pathe3.default.dirname(path3);
634
+ return !dirname.startsWith(appDirectory) && // Ensure we're only watching files outside of the app directory
635
+ // that are at the root level, not nested in subdirectories
636
+ path3 !== root && // Watch the root directory itself
637
+ dirname !== root;
638
+ }
639
+ });
575
640
  fsWatcher.on("all", async (...args) => {
576
641
  let [event, rawFilepath] = args;
577
642
  let filepath = import_pathe3.default.normalize(rawFilepath);
578
- let appFileAddedOrRemoved = appDirectory && (event === "add" || event === "unlink") && filepath.startsWith(import_pathe3.default.normalize(appDirectory));
579
- let configCodeUpdated = Boolean(
643
+ let fileAddedOrRemoved = event === "add" || event === "unlink";
644
+ let appFileAddedOrRemoved = fileAddedOrRemoved && filepath.startsWith(import_pathe3.default.normalize(appDirectory));
645
+ let rootRelativeFilepath = import_pathe3.default.relative(root, filepath);
646
+ let configFileAddedOrRemoved = fileAddedOrRemoved && isEntryFile("react-router.config", rootRelativeFilepath);
647
+ if (configFileAddedOrRemoved) {
648
+ updateReactRouterConfigFile();
649
+ }
650
+ let moduleGraphChanged = configFileAddedOrRemoved || Boolean(
580
651
  viteNodeContext.devServer?.moduleGraph.getModuleById(filepath)
581
652
  );
582
- if (configCodeUpdated || appFileAddedOrRemoved) {
583
- viteNodeContext.devServer?.moduleGraph.invalidateAll();
584
- viteNodeContext.runner?.moduleCache.clear();
653
+ if (!moduleGraphChanged && !appFileAddedOrRemoved) {
654
+ return;
585
655
  }
586
- if (appFileAddedOrRemoved || configCodeUpdated) {
587
- let result = await getConfig();
588
- let configChanged = result.ok && !(0, import_isEqual.default)(lastConfig, result.value);
589
- let routeConfigChanged = result.ok && !(0, import_isEqual.default)(lastConfig?.routes, result.value.routes);
590
- for (let handler2 of changeHandlers) {
591
- handler2({
592
- result,
593
- configCodeUpdated,
594
- configChanged,
595
- routeConfigChanged,
596
- path: filepath,
597
- event
598
- });
599
- }
600
- if (result.ok) {
601
- lastConfig = result.value;
602
- }
656
+ viteNodeContext.devServer?.moduleGraph.invalidateAll();
657
+ viteNodeContext.runner?.moduleCache.clear();
658
+ let result = await getConfig();
659
+ let prevAppDirectory = appDirectory;
660
+ appDirectory = import_pathe3.default.normalize(
661
+ (result.value ?? currentConfig).appDirectory
662
+ );
663
+ if (appDirectory !== prevAppDirectory) {
664
+ fsWatcher.unwatch(prevAppDirectory);
665
+ fsWatcher.add(appDirectory);
666
+ }
667
+ let configCodeChanged = configFileAddedOrRemoved || reactRouterConfigFile !== void 0 && isEntryFileDependency(
668
+ viteNodeContext.devServer.moduleGraph,
669
+ reactRouterConfigFile,
670
+ filepath
671
+ );
672
+ let routeConfigFile = !skipRoutes ? findEntry(appDirectory, "routes", {
673
+ absolute: true
674
+ }) : void 0;
675
+ let routeConfigCodeChanged = routeConfigFile !== void 0 && isEntryFileDependency(
676
+ viteNodeContext.devServer.moduleGraph,
677
+ routeConfigFile,
678
+ filepath
679
+ );
680
+ let configChanged = result.ok && !(0, import_isEqual.default)(omitRoutes(currentConfig), omitRoutes(result.value));
681
+ let routeConfigChanged = result.ok && !(0, import_isEqual.default)(currentConfig?.routes, result.value.routes);
682
+ for (let handler2 of changeHandlers) {
683
+ handler2({
684
+ result,
685
+ configCodeChanged,
686
+ routeConfigCodeChanged,
687
+ configChanged,
688
+ routeConfigChanged,
689
+ path: filepath,
690
+ event
691
+ });
692
+ }
693
+ if (result.ok) {
694
+ currentConfig = result.value;
603
695
  }
604
696
  });
605
697
  }
@@ -616,24 +708,74 @@ async function createConfigLoader({
616
708
  }
617
709
  };
618
710
  }
619
- async function loadConfig({ rootDirectory }) {
711
+ async function loadConfig({
712
+ rootDirectory,
713
+ mode,
714
+ skipRoutes
715
+ }) {
620
716
  let configLoader = await createConfigLoader({
621
717
  rootDirectory,
718
+ mode,
719
+ skipRoutes,
622
720
  watch: false
623
721
  });
624
722
  let config = await configLoader.getConfig();
625
723
  await configLoader.close();
626
724
  return config;
627
725
  }
726
+ function omitRoutes(config) {
727
+ return {
728
+ ...config,
729
+ routes: {}
730
+ };
731
+ }
628
732
  var entryExts = [".js", ".jsx", ".ts", ".tsx"];
733
+ function isEntryFile(entryBasename, filename) {
734
+ return entryExts.some((ext) => filename === `${entryBasename}${ext}`);
735
+ }
629
736
  function findEntry(dir, basename, options) {
630
- for (let ext of entryExts) {
631
- let file = import_pathe3.default.resolve(dir, basename + ext);
632
- if (import_node_fs.default.existsSync(file)) {
633
- return options?.absolute ?? false ? file : import_pathe3.default.relative(dir, file);
737
+ let currentDir = import_pathe3.default.resolve(dir);
738
+ let { root } = import_pathe3.default.parse(currentDir);
739
+ while (true) {
740
+ for (let ext of options?.extensions ?? entryExts) {
741
+ let file = import_pathe3.default.resolve(currentDir, basename + ext);
742
+ if (import_node_fs.default.existsSync(file)) {
743
+ return options?.absolute ?? false ? file : import_pathe3.default.relative(dir, file);
744
+ }
745
+ }
746
+ if (!options?.walkParents) {
747
+ return void 0;
748
+ }
749
+ let parentDir = import_pathe3.default.dirname(currentDir);
750
+ if (currentDir === root || parentDir === currentDir) {
751
+ return void 0;
752
+ }
753
+ currentDir = parentDir;
754
+ }
755
+ }
756
+ function isEntryFileDependency(moduleGraph, entryFilepath, filepath, visited = /* @__PURE__ */ new Set()) {
757
+ entryFilepath = import_pathe3.default.normalize(entryFilepath);
758
+ filepath = import_pathe3.default.normalize(filepath);
759
+ if (visited.has(filepath)) {
760
+ return false;
761
+ }
762
+ visited.add(filepath);
763
+ if (filepath === entryFilepath) {
764
+ return true;
765
+ }
766
+ let mod = moduleGraph.getModuleById(filepath);
767
+ if (!mod) {
768
+ return false;
769
+ }
770
+ for (let importer of mod.importers) {
771
+ if (!importer.id) {
772
+ continue;
773
+ }
774
+ if (importer.id === entryFilepath || isEntryFileDependency(moduleGraph, entryFilepath, importer.id, visited)) {
775
+ return true;
634
776
  }
635
777
  }
636
- return void 0;
778
+ return false;
637
779
  }
638
780
 
639
781
  // vite/cloudflare-dev-proxy.ts
@@ -652,14 +794,15 @@ var cloudflareDevProxyVitePlugin = (options = {}) => {
652
794
  let future;
653
795
  return {
654
796
  name: PLUGIN_NAME,
655
- config: async (config) => {
797
+ config: async (config, configEnv) => {
656
798
  await preloadVite();
657
799
  const vite2 = getVite();
658
800
  const serverConditions = [
659
801
  ...vite2.defaultServerConditions ?? []
660
802
  ];
661
803
  let configResult = await loadConfig({
662
- rootDirectory: config.root ?? process.cwd()
804
+ rootDirectory: config.root ?? process.cwd(),
805
+ mode: configEnv.mode
663
806
  });
664
807
  if (!configResult.ok) {
665
808
  throw new Error(configResult.error);