@react-router/dev 0.0.0-experimental-8e9d8ef63 → 0.0.0-experimental-8677247c0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,91 @@
1
1
  # `@react-router/dev`
2
2
 
3
+ ## 7.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Generate a "SPA fallback" HTML file for scenarios where applications are prerendering the `/` route with `ssr:false` ([#12948](https://github.com/remix-run/react-router/pull/12948))
8
+
9
+ - If you specify `ssr:false` without a `prerender` config, this is considered "SPA Mode" and the generated `index.html` file will only render down to the root route and will be able to hydrate for any valid application path
10
+ - If you specify `ssr:false` with a `prerender` config but _do not_ include the `/` path (i.e., `prerender: ['/blog/post']`), then we still generate a "SPA Mode" `index.html` file that can hydrate for any path in the application
11
+ - However, previously if you specified `ssr:false` and included the `/` path in your `prerender` config, we would prerender the `/` route into `index.html` as a non-SPA page
12
+ - The generated HTML would include the root index route which prevented hydration for any other paths
13
+ - With this change, we now generate a "SPA Mode" file in `__spa-fallback.html` that will allow you to hydrate for any non-prerendered paths
14
+ - You can serve this file from your static file server for any paths that would otherwise 404 if you only want to pre-render _some_ routes in your `ssr:false` app and serve the others as a SPA
15
+ - `npx sirv-cli build/client --single __spa-fallback.html`
16
+
17
+ - Allow a `loader` in the root route in SPA mode because it can be called/server-rendered at build time ([#12948](https://github.com/remix-run/react-router/pull/12948))
18
+
19
+ - `Route.HydrateFallbackProps` now also receives `loaderData`
20
+ - This will be defined so long as the `HydrateFallback` is rendering while _children_ routes are loading
21
+ - This will be `undefined` if the `HydrateFallback` is rendering because the route has it's own hydrating `clientLoader`
22
+ - In SPA mode, this will allow you to render loader root data into the SPA `index.html`
23
+
24
+ - New type-safe `href` utility that guarantees links point to actual paths in your app ([#13012](https://github.com/remix-run/react-router/pull/13012))
25
+
26
+ ```tsx
27
+ import { href } from "react-router";
28
+
29
+ export default function Component() {
30
+ const link = href("/blog/:slug", { slug: "my-first-post" });
31
+ return (
32
+ <main>
33
+ <Link to={href("/products/:id", { id: "asdf" })} />
34
+ <NavLink to={href("/:lang?/about", { lang: "en" })} />
35
+ </main>
36
+ );
37
+ }
38
+ ```
39
+
40
+ ### Patch Changes
41
+
42
+ - Handle custom `envDir` in Vite config ([#12969](https://github.com/remix-run/react-router/pull/12969))
43
+
44
+ - Fix typegen for repeated params ([#13012](https://github.com/remix-run/react-router/pull/13012))
45
+
46
+ In React Router, path parameters are keyed by their name.
47
+ So for a path pattern like `/a/:id/b/:id?/c/:id`, the last `:id` will set the value for `id` in `useParams` and the `params` prop.
48
+ For example, `/a/1/b/2/c/3` will result in the value `{ id: 3 }` at runtime.
49
+
50
+ Previously, generated types for params incorrectly modeled repeated params with an array.
51
+ So `/a/1/b/2/c/3` generated a type like `{ id: [1,2,3] }`.
52
+
53
+ To be consistent with runtime behavior, the generated types now correctly model the "last one wins" semantics of path parameters.
54
+ So `/a/1/b/2/c/3` now generates a type like `{ id: 3 }`.
55
+
56
+ - Fix CLI parsing to allow argumentless `npx react-router` usage ([#12925](https://github.com/remix-run/react-router/pull/12925))
57
+
58
+ - Fix `ArgError: unknown or unexpected option: --version` when running `react-router --version` ([#13012](https://github.com/remix-run/react-router/pull/13012))
59
+
60
+ - Skip action-only resource routes when using `prerender:true` ([#13004](https://github.com/remix-run/react-router/pull/13004))
61
+
62
+ - Enhance invalid export detection when using `ssr:false` ([#12948](https://github.com/remix-run/react-router/pull/12948))
63
+
64
+ - `headers`/`action` are prohibited in all routes with `ssr:false` because there will be no runtime server on which to run them
65
+ - `loader` functions are more nuanced and depend on whether a given route is prerendered
66
+ - When using `ssr:false` without a `prerender` config, only the `root` route can have a `loader`
67
+ - This is "SPA mode" which generates a single `index.html` file with the root route `HydrateFallback` so it is capable of hydrating for any path in your application - therefore we can only call a root route `loader` at build time
68
+ - When using `ssr:false` with a `prerender` config, you can export a `loader` from routes matched by one of the `prerender` paths because those routes will be server rendered at build time
69
+ - Exporting a `loader` from a route that is never matched by a `prerender` path will throw a build time error because there will be no runtime server to ever run the loader
70
+
71
+ - Limit prerendered resource route `.data` files to only the target route ([#13004](https://github.com/remix-run/react-router/pull/13004))
72
+
73
+ - Add unstable support for splitting route modules in framework mode via `future.unstable_splitRouteModules` ([#11871](https://github.com/remix-run/react-router/pull/11871))
74
+
75
+ - Fix prerendering of binary files ([#13039](https://github.com/remix-run/react-router/pull/13039))
76
+
77
+ - Add `future.unstable_viteEnvironmentApi` flag to enable experimental Vite Environment API support ([#12936](https://github.com/remix-run/react-router/pull/12936))
78
+
79
+ - Disable Lazy Route Discovery for all `ssr:false` apps and not just "SPA Mode" because there is no runtime server to serve the search-param-configured `__manifest` requests ([#12894](https://github.com/remix-run/react-router/pull/12894))
80
+
81
+ - We previously only disabled this for "SPA Mode" which is `ssr:false` and no `prerender` config but we realized it should apply to all `ssr:false` apps, including those prerendering multiple pages
82
+ - In those `prerender` scenarios we would prerender the `/__manifest` file assuming the static file server would serve it but that makes some unneccesary assumptions about the static file server behaviors
83
+
84
+ - Updated dependencies:
85
+ - `react-router@7.2.0`
86
+ - `@react-router/node@7.2.0`
87
+ - `@react-router/serve@7.2.0`
88
+
3
89
  ## 7.1.5
4
90
 
5
91
  ### Patch Changes
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-8e9d8ef63
3
+ * @react-router/dev v0.0.0-experimental-8677247c0
4
4
  *
5
5
  * Copyright (c) Remix Software Inc.
6
6
  *
@@ -944,7 +944,6 @@ var init_styles = __esm({
944
944
  path6 = __toESM(require("path"));
945
945
  import_react_router = require("react-router");
946
946
  init_resolve_file_url();
947
- init_vite();
948
947
  cssFileRegExp = /\.(css|less|sass|scss|styl|stylus|pcss|postcss|sss)(?:$|\?)/;
949
948
  cssModulesRegExp = new RegExp(`\\.module${cssFileRegExp.source}`);
950
949
  }
@@ -1361,11 +1360,28 @@ async function getEnvironmentOptionsResolvers(ctx, buildManifest, viteCommand) {
1361
1360
  build: {
1362
1361
  outDir: getServerBuildDirectory(ctx),
1363
1362
  rollupOptions: {
1364
- input: viteUserConfig.build?.rollupOptions?.input ?? virtual.serverBuild.id
1363
+ input: (ctx.reactRouterConfig.future.unstable_viteEnvironmentApi ? viteUserConfig.environments?.ssr?.build?.rollupOptions?.input : viteUserConfig.build?.rollupOptions?.input) ?? virtual.serverBuild.id
1365
1364
  }
1366
- }
1365
+ },
1366
+ optimizeDeps: ctx.reactRouterConfig.future.unstable_viteEnvironmentApi && viteUserConfig.environments?.ssr?.optimizeDeps?.noDiscovery === false ? {
1367
+ entries: [
1368
+ vite2.normalizePath(ctx.entryServerFilePath),
1369
+ ...Object.values(ctx.reactRouterConfig.routes).map(
1370
+ (route) => resolveRelativeRouteFilePath(route, ctx.reactRouterConfig)
1371
+ )
1372
+ ],
1373
+ include: [
1374
+ "react",
1375
+ "react/jsx-dev-runtime",
1376
+ "react-dom/server",
1377
+ "react-router"
1378
+ ]
1379
+ } : void 0
1367
1380
  });
1368
1381
  }
1382
+ if (ctx.reactRouterConfig.future.unstable_viteEnvironmentApi && viteCommand === "serve") {
1383
+ environmentOptionsResolvers[CSS_DEV_HELPER_ENVIRONMENT_NAME] = () => ({});
1384
+ }
1369
1385
  return environmentOptionsResolvers;
1370
1386
  }
1371
1387
  function resolveEnvironmentsOptions(environmentResolvers, resolverOptions) {
@@ -1380,7 +1396,7 @@ function resolveEnvironmentsOptions(environmentResolvers, resolverOptions) {
1380
1396
  function isNonNullable(x) {
1381
1397
  return x != null;
1382
1398
  }
1383
- var import_node_crypto, path7, url, fse, babel2, import_react_router2, import_es_module_lexer, import_pick3, import_jsesc, import_picocolors4, import_kebabCase, BUILD_CLIENT_ROUTE_QUERY_STRING, SSR_BUNDLE_PREFIX, virtualHmrRuntime, virtualInjectHmrRuntime, virtual, getServerBuildDirectory, getClientBuildDirectory, defaultEntriesDir, defaultEntries, REACT_REFRESH_HEADER;
1399
+ var import_node_crypto, path7, url, fse, babel2, import_react_router2, import_es_module_lexer, import_pick3, import_jsesc, import_picocolors4, import_kebabCase, BUILD_CLIENT_ROUTE_QUERY_STRING, SSR_BUNDLE_PREFIX, CSS_DEV_HELPER_ENVIRONMENT_NAME, virtualHmrRuntime, virtualInjectHmrRuntime, resolveRelativeRouteFilePath, virtual, getServerBuildDirectory, getClientBuildDirectory, defaultEntriesDir, defaultEntries, REACT_REFRESH_HEADER;
1384
1400
  var init_plugin = __esm({
1385
1401
  "vite/plugin.ts"() {
1386
1402
  "use strict";
@@ -1410,8 +1426,15 @@ var init_plugin = __esm({
1410
1426
  init_with_props();
1411
1427
  BUILD_CLIENT_ROUTE_QUERY_STRING = "?__react-router-build-client-route";
1412
1428
  SSR_BUNDLE_PREFIX = "ssrBundle_";
1429
+ CSS_DEV_HELPER_ENVIRONMENT_NAME = "__react_router_css_dev_helper__";
1413
1430
  virtualHmrRuntime = create("hmr-runtime");
1414
1431
  virtualInjectHmrRuntime = create("inject-hmr-runtime");
1432
+ resolveRelativeRouteFilePath = (route, reactRouterConfig) => {
1433
+ let vite2 = getVite();
1434
+ let file = route.file;
1435
+ let fullPath = path7.resolve(reactRouterConfig.appDirectory, file);
1436
+ return vite2.normalizePath(fullPath);
1437
+ };
1415
1438
  virtual = {
1416
1439
  serverBuild: create("server-build"),
1417
1440
  serverManifest: create("server-manifest"),
@@ -2058,7 +2081,7 @@ async function run2(argv = process.argv.slice(2)) {
2058
2081
  return;
2059
2082
  }
2060
2083
  if (flags.version) {
2061
- let version = require("../package.json").version;
2084
+ let version = require("../../package.json").version;
2062
2085
  console.log(version);
2063
2086
  return;
2064
2087
  }
package/dist/config.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @react-router/dev v0.0.0-experimental-8e9d8ef63
2
+ * @react-router/dev v0.0.0-experimental-8677247c0
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-8e9d8ef63
2
+ * @react-router/dev v0.0.0-experimental-8677247c0
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-8e9d8ef63
2
+ * @react-router/dev v0.0.0-experimental-8677247c0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -678,11 +678,14 @@ var cloudflareDevProxyVitePlugin = (options = {}) => {
678
678
  }
679
679
  },
680
680
  configureServer: async (viteDevServer) => {
681
- let { getPlatformProxy } = await importWrangler();
682
- let { dispose, ...cloudflare } = await getPlatformProxy(
683
- restOptions
684
- );
685
- let context = { cloudflare };
681
+ let context;
682
+ let getContext = async () => {
683
+ let { getPlatformProxy } = await importWrangler();
684
+ let { dispose, ...cloudflare } = await getPlatformProxy(
685
+ restOptions
686
+ );
687
+ return { cloudflare };
688
+ };
686
689
  return () => {
687
690
  if (!viteDevServer.config.server.middlewareMode) {
688
691
  viteDevServer.middlewares.use(async (nodeReq, nodeRes, next) => {
@@ -692,6 +695,7 @@ var cloudflareDevProxyVitePlugin = (options = {}) => {
692
695
  );
693
696
  let handler = (0, import_react_router.createRequestHandler)(build, "development");
694
697
  let req = fromNodeRequest(nodeReq, nodeRes);
698
+ context ??= await getContext();
695
699
  let loadContext = getLoadContext ? await getLoadContext({ request: req, context }) : context;
696
700
  let res = await handler(req, loadContext);
697
701
  await toNodeRequest(res, nodeRes);
package/dist/vite.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @react-router/dev v0.0.0-experimental-8e9d8ef63
2
+ * @react-router/dev v0.0.0-experimental-8677247c0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -963,15 +963,12 @@ var isCssUrlWithoutSideEffects = (url2) => {
963
963
  }
964
964
  return false;
965
965
  };
966
- var injectQuery = (url2, query) => url2.includes("?") ? url2.replace("?", `?${query}&`) : `${url2}?${query}`;
967
966
  var getStylesForFiles = async ({
968
967
  viteDevServer,
969
968
  rootDirectory,
970
- cssModulesManifest,
969
+ loadCssContents,
971
970
  files
972
971
  }) => {
973
- let vite2 = getVite();
974
- let viteMajor = parseInt(vite2.version.split(".")[0], 10);
975
972
  let styles = {};
976
973
  let deps = /* @__PURE__ */ new Set();
977
974
  try {
@@ -1000,21 +997,9 @@ var getStylesForFiles = async ({
1000
997
  for (let dep of deps) {
1001
998
  if (dep.file && isCssFile(dep.file) && !isCssUrlWithoutSideEffects(dep.url)) {
1002
999
  try {
1003
- let css = isCssModulesFile(dep.file) ? cssModulesManifest[dep.file] : (await viteDevServer.ssrLoadModule(
1004
- // We need the ?inline query in Vite v6 when loading CSS in SSR
1005
- // since it does not expose the default export for CSS in a
1006
- // server environment. This is to align with non-SSR
1007
- // environments. For backwards compatibility with v5 we keep
1008
- // using the URL without ?inline query because the HMR code was
1009
- // relying on the implicit SSR-client module graph relationship.
1010
- viteMajor >= 6 ? injectQuery(dep.url, "inline") : dep.url
1011
- )).default;
1012
- if (css === void 0) {
1013
- throw new Error();
1014
- }
1015
- styles[dep.url] = css;
1000
+ styles[dep.url] = await loadCssContents(viteDevServer, dep);
1016
1001
  } catch {
1017
- console.warn(`Could not load ${dep.file}`);
1002
+ console.warn(`Failed to load CSS for ${dep.file}`);
1018
1003
  }
1019
1004
  }
1020
1005
  }
@@ -1073,7 +1058,7 @@ var getStylesForUrl = async ({
1073
1058
  rootDirectory,
1074
1059
  reactRouterConfig,
1075
1060
  entryClientFilePath,
1076
- cssModulesManifest,
1061
+ loadCssContents,
1077
1062
  build,
1078
1063
  url: url2
1079
1064
  }) => {
@@ -1088,7 +1073,7 @@ var getStylesForUrl = async ({
1088
1073
  let styles = await getStylesForFiles({
1089
1074
  viteDevServer,
1090
1075
  rootDirectory,
1091
- cssModulesManifest,
1076
+ loadCssContents,
1092
1077
  files: [
1093
1078
  // Always include the client entry file when crawling the module graph for CSS
1094
1079
  path5.relative(rootDirectory, entryClientFilePath),
@@ -1857,6 +1842,8 @@ var plugin = {
1857
1842
  return function Wrapped() {
1858
1843
  const props = {
1859
1844
  params: useParams(),
1845
+ loaderData: useLoaderData(),
1846
+ actionData: useActionData(),
1860
1847
  };
1861
1848
  return h(HydrateFallback, props);
1862
1849
  };
@@ -1965,6 +1952,7 @@ var CLIENT_ROUTE_EXPORTS = [
1965
1952
  ];
1966
1953
  var BUILD_CLIENT_ROUTE_QUERY_STRING = "?__react-router-build-client-route";
1967
1954
  var SSR_BUNDLE_PREFIX = "ssrBundle_";
1955
+ var CSS_DEV_HELPER_ENVIRONMENT_NAME = "__react_router_css_dev_helper__";
1968
1956
  function isSeverBundleEnvironmentName(name) {
1969
1957
  return name.startsWith(SSR_BUNDLE_PREFIX);
1970
1958
  }
@@ -2156,6 +2144,7 @@ var getServerBuildDirectory = (ctx, { serverBundleId } = {}) => path6.join(
2156
2144
  ...serverBundleId ? [serverBundleId] : []
2157
2145
  );
2158
2146
  var getClientBuildDirectory = (reactRouterConfig) => path6.join(reactRouterConfig.buildDirectory, "client");
2147
+ var injectQuery = (url2, query) => url2.includes("?") ? url2.replace("?", `?${query}&`) : `${url2}?${query}`;
2159
2148
  var defaultEntriesDir = path6.resolve(
2160
2149
  path6.dirname(require.resolve("@react-router/dev/package.json")),
2161
2150
  "dist",
@@ -2232,6 +2221,11 @@ var reactRouterVitePlugin = () => {
2232
2221
  // Otherwise, all routes are imported as usual
2233
2222
  ctx.reactRouterConfig.routes
2234
2223
  );
2224
+ let prerenderPaths = await getPrerenderPaths(
2225
+ ctx.reactRouterConfig.prerender,
2226
+ ctx.reactRouterConfig.ssr,
2227
+ routes
2228
+ );
2235
2229
  return `
2236
2230
  import * as entryServer from ${JSON.stringify(
2237
2231
  resolveFileUrl(ctx, ctx.entryServerFilePath)
@@ -2258,6 +2252,7 @@ var reactRouterVitePlugin = () => {
2258
2252
  export const future = ${JSON.stringify(ctx.reactRouterConfig.future)};
2259
2253
  export const ssr = ${ctx.reactRouterConfig.ssr};
2260
2254
  export const isSpaMode = ${isSpaModeEnabled(ctx.reactRouterConfig)};
2255
+ export const prerender = ${JSON.stringify(prerenderPaths)};
2261
2256
  export const publicPath = ${JSON.stringify(ctx.publicPath)};
2262
2257
  export const entry = { module: entryServer };
2263
2258
  export const routes = {
@@ -2406,6 +2401,7 @@ var reactRouterVitePlugin = () => {
2406
2401
  reactRouterServerManifest
2407
2402
  };
2408
2403
  };
2404
+ let currentReactRouterManifestForDev = null;
2409
2405
  let getReactRouterManifestForDev = async () => {
2410
2406
  let routes = {};
2411
2407
  let routeManifestExports = await getRouteManifestModuleExports(
@@ -2462,7 +2458,7 @@ var reactRouterVitePlugin = () => {
2462
2458
  imports: []
2463
2459
  };
2464
2460
  }
2465
- return {
2461
+ let reactRouterManifestForDev = {
2466
2462
  version: String(Math.random()),
2467
2463
  url: combineURLs(ctx.publicPath, virtual.browserManifest.url),
2468
2464
  hmr: {
@@ -2477,6 +2473,42 @@ var reactRouterVitePlugin = () => {
2477
2473
  },
2478
2474
  routes
2479
2475
  };
2476
+ currentReactRouterManifestForDev = reactRouterManifestForDev;
2477
+ return reactRouterManifestForDev;
2478
+ };
2479
+ const loadCssContents = async (viteDevServer, dep) => {
2480
+ invariant(
2481
+ viteCommand === "serve",
2482
+ "loadCssContents is only available in dev mode"
2483
+ );
2484
+ if (dep.file && isCssModulesFile(dep.file)) {
2485
+ return cssModulesManifest[dep.file];
2486
+ }
2487
+ const vite2 = getVite();
2488
+ const viteMajor = parseInt(vite2.version.split(".")[0], 10);
2489
+ const url2 = viteMajor >= 6 ? (
2490
+ // We need the ?inline query in Vite v6 when loading CSS in SSR
2491
+ // since it does not expose the default export for CSS in a
2492
+ // server environment. This is to align with non-SSR
2493
+ // environments. For backwards compatibility with v5 we keep
2494
+ // using the URL without ?inline query because the HMR code was
2495
+ // relying on the implicit SSR-client module graph relationship.
2496
+ injectQuery(dep.url, "inline")
2497
+ ) : dep.url;
2498
+ let cssMod;
2499
+ if (ctx.reactRouterConfig.future.unstable_viteEnvironmentApi) {
2500
+ const cssDevHelperEnvironment = viteDevServer.environments[CSS_DEV_HELPER_ENVIRONMENT_NAME];
2501
+ invariant(cssDevHelperEnvironment, "Missing CSS dev helper environment");
2502
+ invariant(vite2.isRunnableDevEnvironment(cssDevHelperEnvironment));
2503
+ cssMod = await cssDevHelperEnvironment.runner.import(url2);
2504
+ } else {
2505
+ cssMod = await viteDevServer.ssrLoadModule(url2);
2506
+ }
2507
+ invariant(
2508
+ typeof cssMod === "object" && cssMod !== null && "default" in cssMod && typeof cssMod.default === "string",
2509
+ `Failed to load CSS for ${dep.file ?? dep.url}`
2510
+ );
2511
+ return cssMod.default;
2480
2512
  };
2481
2513
  return [
2482
2514
  {
@@ -2655,6 +2687,8 @@ var reactRouterVitePlugin = () => {
2655
2687
  }
2656
2688
  viteChildCompiler = await vite2.createServer({
2657
2689
  ...viteUserConfig,
2690
+ // Ensure child compiler cannot overwrite the default cache directory
2691
+ cacheDir: "node_modules/.vite-child-compiler",
2658
2692
  mode: viteConfig.mode,
2659
2693
  server: {
2660
2694
  watch: viteConfig.command === "build" ? null : void 0,
@@ -2666,7 +2700,23 @@ var reactRouterVitePlugin = () => {
2666
2700
  plugins: [
2667
2701
  ...(childCompilerConfigFile.config.plugins ?? []).flat().filter(
2668
2702
  (plugin2) => typeof plugin2 === "object" && plugin2 !== null && "name" in plugin2 && plugin2.name !== "react-router" && plugin2.name !== "react-router:route-exports" && plugin2.name !== "react-router:hmr-updates"
2669
- )
2703
+ ),
2704
+ {
2705
+ name: "react-router:override-optimize-deps",
2706
+ config(userConfig) {
2707
+ if (ctx.reactRouterConfig.future.unstable_viteEnvironmentApi && userConfig.environments) {
2708
+ for (const environmentName of Object.keys(
2709
+ userConfig.environments
2710
+ )) {
2711
+ userConfig.environments[environmentName].optimizeDeps = {
2712
+ noDiscovery: true
2713
+ };
2714
+ }
2715
+ } else {
2716
+ userConfig.optimizeDeps = { noDiscovery: true };
2717
+ }
2718
+ }
2719
+ }
2670
2720
  ]
2671
2721
  });
2672
2722
  await viteChildCompiler.pluginContainer.buildStart({});
@@ -2703,7 +2753,7 @@ var reactRouterVitePlugin = () => {
2703
2753
  entryClientFilePath: ctx.entryClientFilePath,
2704
2754
  reactRouterConfig: ctx.reactRouterConfig,
2705
2755
  viteDevServer,
2706
- cssModulesManifest,
2756
+ loadCssContents,
2707
2757
  build,
2708
2758
  url: url2
2709
2759
  });
@@ -2818,7 +2868,7 @@ var reactRouterVitePlugin = () => {
2818
2868
  ].join("\n")
2819
2869
  );
2820
2870
  }
2821
- if (ctx.reactRouterConfig.prerender != null && ctx.reactRouterConfig.prerender !== false) {
2871
+ if (isPrerenderingEnabled(ctx.reactRouterConfig)) {
2822
2872
  await handlePrerender(
2823
2873
  viteConfig,
2824
2874
  ctx.reactRouterConfig,
@@ -2992,6 +3042,15 @@ var reactRouterVitePlugin = () => {
2992
3042
  let reactRouterManifest = viteCommand === "build" ? (await generateReactRouterManifestsForBuild({
2993
3043
  routeIds
2994
3044
  })).reactRouterServerManifest : await getReactRouterManifestForDev();
3045
+ if (!ctx.reactRouterConfig.ssr) {
3046
+ invariant(viteConfig);
3047
+ validateSsrFalsePrerenderExports(
3048
+ viteConfig,
3049
+ ctx,
3050
+ reactRouterManifest,
3051
+ viteChildCompiler
3052
+ );
3053
+ }
2995
3054
  return `export default ${(0, import_jsesc.default)(reactRouterManifest, {
2996
3055
  es6: true
2997
3056
  })};`;
@@ -3092,12 +3151,15 @@ var reactRouterVitePlugin = () => {
3092
3151
  if (!route) return;
3093
3152
  if (!options?.ssr && isSpaModeEnabled(ctx.reactRouterConfig)) {
3094
3153
  let exportNames = getExportNames(code);
3095
- let serverOnlyExports = exportNames.filter(
3096
- (exp) => SERVER_ONLY_ROUTE_EXPORTS.includes(exp)
3097
- );
3154
+ let serverOnlyExports = exportNames.filter((exp) => {
3155
+ if (route.id === "root" && exp === "loader") {
3156
+ return false;
3157
+ }
3158
+ return SERVER_ONLY_ROUTE_EXPORTS.includes(exp);
3159
+ });
3098
3160
  if (serverOnlyExports.length > 0) {
3099
3161
  let str = serverOnlyExports.map((e) => `\`${e}\``).join(", ");
3100
- let message = `SPA Mode: ${serverOnlyExports.length} invalid route export(s) in \`${route.file}\`: ${str}. See https://remix.run/guides/spa-mode for more information.`;
3162
+ let message = `SPA Mode: ${serverOnlyExports.length} invalid route export(s) in \`${route.file}\`: ${str}. See https://reactrouter.com/how-to/spa for more information.`;
3101
3163
  throw Error(message);
3102
3164
  }
3103
3165
  if (route.id !== "root") {
@@ -3105,7 +3167,7 @@ var reactRouterVitePlugin = () => {
3105
3167
  (exp) => exp === "HydrateFallback"
3106
3168
  );
3107
3169
  if (hasHydrateFallback) {
3108
- 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.`;
3170
+ 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.`;
3109
3171
  throw Error(message);
3110
3172
  }
3111
3173
  }
@@ -3211,8 +3273,7 @@ var reactRouterVitePlugin = () => {
3211
3273
  let route = getRoute(ctx.reactRouterConfig, file);
3212
3274
  let hmrEventData = { route: null };
3213
3275
  if (route) {
3214
- let serverManifest = (await server.ssrLoadModule(virtual.serverManifest.id)).default;
3215
- let oldRouteMetadata = serverManifest.routes[route.id];
3276
+ let oldRouteMetadata = currentReactRouterManifestForDev?.routes[route.id];
3216
3277
  let newRouteMetadata = await getRouteMetadata(
3217
3278
  cache,
3218
3279
  ctx,
@@ -3398,8 +3459,11 @@ async function getRouteMetadata(cache, ctx, viteChildCompiler, route, readRouteF
3398
3459
  };
3399
3460
  return info;
3400
3461
  }
3462
+ function isPrerenderingEnabled(reactRouterConfig) {
3463
+ return reactRouterConfig.prerender != null && reactRouterConfig.prerender !== false;
3464
+ }
3401
3465
  function isSpaModeEnabled(reactRouterConfig) {
3402
- return reactRouterConfig.ssr === false && (reactRouterConfig.prerender == null || reactRouterConfig.prerender === false || Array.isArray(reactRouterConfig.prerender) && reactRouterConfig.prerender.length === 1 && reactRouterConfig.prerender[0] === "/");
3466
+ return reactRouterConfig.ssr === false && !isPrerenderingEnabled(reactRouterConfig);
3403
3467
  }
3404
3468
  async function getPrerenderBuildAndHandler(viteConfig, serverBuildDirectory, serverBuildFile) {
3405
3469
  let serverBuildPath = path6.join(serverBuildDirectory, serverBuildFile);
@@ -3411,20 +3475,49 @@ async function getPrerenderBuildAndHandler(viteConfig, serverBuildDirectory, ser
3411
3475
  };
3412
3476
  }
3413
3477
  async function handleSpaMode(viteConfig, reactRouterConfig, serverBuildDirectory, serverBuildFile, clientBuildDirectory) {
3414
- let { handler } = await getPrerenderBuildAndHandler(
3478
+ let { build, handler } = await getPrerenderBuildAndHandler(
3415
3479
  viteConfig,
3416
3480
  serverBuildDirectory,
3417
3481
  serverBuildFile
3418
3482
  );
3419
- let request = new Request(`http://localhost${reactRouterConfig.basename}`);
3483
+ let request = new Request(`http://localhost${reactRouterConfig.basename}`, {
3484
+ headers: {
3485
+ // Enable SPA mode in the server runtime and only render down to the root
3486
+ "X-React-Router-SPA-Mode": "yes"
3487
+ }
3488
+ });
3420
3489
  let response = await handler(request);
3421
3490
  let html = await response.text();
3422
- validatePrerenderedResponse(response, html, "SPA Mode", "/");
3423
- validatePrerenderedHtml(html, "SPA Mode");
3424
- await fse.writeFile(path6.join(clientBuildDirectory, "index.html"), html);
3425
- viteConfig.logger.info(
3426
- "SPA Mode: index.html has been written to your " + import_picocolors3.default.bold(path6.relative(process.cwd(), clientBuildDirectory)) + " directory"
3427
- );
3491
+ let isPrerenderSpaFallback = build.prerender.includes("/");
3492
+ let filename3 = isPrerenderSpaFallback ? "__spa-fallback.html" : "index.html";
3493
+ if (response.status !== 200) {
3494
+ if (isPrerenderSpaFallback) {
3495
+ throw new Error(
3496
+ `Prerender: Received a ${response.status} status code from \`entry.server.tsx\` while prerendering your \`${filename3}\` file.
3497
+ ` + html
3498
+ );
3499
+ } else {
3500
+ throw new Error(
3501
+ `SPA Mode: Received a ${response.status} status code from \`entry.server.tsx\` while prerendering your \`${filename3}\` file.
3502
+ ` + html
3503
+ );
3504
+ }
3505
+ }
3506
+ if (!html.includes("window.__reactRouterContext =") || !html.includes("window.__reactRouterRouteModules =")) {
3507
+ throw new Error(
3508
+ "SPA Mode: Did you forget to include `<Scripts/>` in your root route? Your pre-rendered HTML cannot hydrate without `<Scripts />`."
3509
+ );
3510
+ }
3511
+ await fse.writeFile(path6.join(clientBuildDirectory, filename3), html);
3512
+ let prettyDir = path6.relative(process.cwd(), clientBuildDirectory);
3513
+ let prettyPath = path6.join(prettyDir, filename3);
3514
+ if (build.prerender.length > 0) {
3515
+ viteConfig.logger.info(
3516
+ `Prerender (html): SPA Fallback -> ${import_picocolors3.default.bold(prettyPath)}`
3517
+ );
3518
+ } else {
3519
+ viteConfig.logger.info(`SPA Mode: Generated ${import_picocolors3.default.bold(prettyPath)}`);
3520
+ }
3428
3521
  }
3429
3522
  async function handlePrerender(viteConfig, reactRouterConfig, serverBuildDirectory, serverBuildPath, clientBuildDirectory) {
3430
3523
  let { build, handler } = await getPrerenderBuildAndHandler(
@@ -3433,53 +3526,62 @@ async function handlePrerender(viteConfig, reactRouterConfig, serverBuildDirecto
3433
3526
  serverBuildPath
3434
3527
  );
3435
3528
  let routes = createPrerenderRoutes(build.routes);
3436
- let routesToPrerender;
3437
- if (typeof reactRouterConfig.prerender === "boolean") {
3438
- invariant(reactRouterConfig.prerender, "Expected prerender:true");
3439
- routesToPrerender = determineStaticPrerenderRoutes(
3440
- routes,
3441
- viteConfig,
3442
- true
3443
- );
3444
- } else if (typeof reactRouterConfig.prerender === "function") {
3445
- routesToPrerender = await reactRouterConfig.prerender({
3446
- getStaticPaths: () => determineStaticPrerenderRoutes(routes, viteConfig, false)
3447
- });
3448
- } else {
3449
- routesToPrerender = reactRouterConfig.prerender || ["/"];
3450
- }
3451
3529
  let headers = {
3452
3530
  // Header that can be used in the loader to know if you're running at
3453
3531
  // build time or runtime
3454
3532
  "X-React-Router-Prerender": "yes"
3455
3533
  };
3456
- for (let path7 of routesToPrerender) {
3534
+ for (let path7 of build.prerender) {
3457
3535
  let matches = (0, import_react_router2.matchRoutes)(routes, `/${path7}/`.replace(/^\/\/+/, "/"));
3458
- let hasLoaders = matches?.some((m) => m.route.loader);
3459
- let data;
3460
- if (hasLoaders) {
3461
- data = await prerenderData(
3462
- handler,
3463
- path7,
3464
- clientBuildDirectory,
3465
- reactRouterConfig,
3466
- viteConfig,
3467
- { headers }
3468
- );
3469
- }
3536
+ invariant(
3537
+ matches,
3538
+ `Unable to prerender path because it does not match any routes: ${path7}`
3539
+ );
3470
3540
  let leafRoute = matches ? matches[matches.length - 1].route : null;
3471
3541
  let manifestRoute = leafRoute ? build.routes[leafRoute.id]?.module : null;
3472
- let isResourceRoute = manifestRoute && !manifestRoute.default && !manifestRoute.ErrorBoundary && manifestRoute.loader;
3542
+ let isResourceRoute = manifestRoute && !manifestRoute.default && !manifestRoute.ErrorBoundary;
3473
3543
  if (isResourceRoute) {
3474
- await prerenderResourceRoute(
3475
- handler,
3476
- path7,
3477
- clientBuildDirectory,
3478
- reactRouterConfig,
3479
- viteConfig,
3480
- { headers }
3481
- );
3544
+ invariant(leafRoute);
3545
+ invariant(manifestRoute);
3546
+ if (manifestRoute.loader) {
3547
+ await prerenderData(
3548
+ handler,
3549
+ path7,
3550
+ [leafRoute.id],
3551
+ clientBuildDirectory,
3552
+ reactRouterConfig,
3553
+ viteConfig,
3554
+ { headers }
3555
+ );
3556
+ await prerenderResourceRoute(
3557
+ handler,
3558
+ path7,
3559
+ clientBuildDirectory,
3560
+ reactRouterConfig,
3561
+ viteConfig,
3562
+ { headers }
3563
+ );
3564
+ } else {
3565
+ viteConfig.logger.warn(
3566
+ `\u26A0\uFE0F Skipping prerendering for resource route without a loader: ${leafRoute?.id}`
3567
+ );
3568
+ }
3482
3569
  } else {
3570
+ let hasLoaders = matches.some(
3571
+ (m) => build.assets.routes[m.route.id]?.hasLoader
3572
+ );
3573
+ let data;
3574
+ if (!isResourceRoute && hasLoaders) {
3575
+ data = await prerenderData(
3576
+ handler,
3577
+ path7,
3578
+ null,
3579
+ clientBuildDirectory,
3580
+ reactRouterConfig,
3581
+ viteConfig,
3582
+ { headers }
3583
+ );
3584
+ }
3483
3585
  await prerenderRoute(
3484
3586
  handler,
3485
3587
  path7,
@@ -3496,7 +3598,7 @@ async function handlePrerender(viteConfig, reactRouterConfig, serverBuildDirecto
3496
3598
  }
3497
3599
  }
3498
3600
  }
3499
- function determineStaticPrerenderRoutes(routes, viteConfig, isBooleanUsage = false) {
3601
+ function getStaticPrerenderPaths(routes) {
3500
3602
  let paths = ["/"];
3501
3603
  let paramRoutes = [];
3502
3604
  function recurse(subtree, prefix = "") {
@@ -3516,28 +3618,33 @@ function determineStaticPrerenderRoutes(routes, viteConfig, isBooleanUsage = fal
3516
3618
  }
3517
3619
  }
3518
3620
  recurse(routes);
3519
- if (isBooleanUsage && paramRoutes.length > 0) {
3520
- viteConfig.logger.warn(
3521
- [
3522
- "\u26A0\uFE0F Paths with dynamic/splat params cannot be prerendered when using `prerender: true`.",
3523
- "You may want to use the `prerender()` API to prerender the following paths:",
3524
- ...paramRoutes.map((p) => " - " + p)
3525
- ].join("\n")
3526
- );
3527
- }
3528
- return paths.map((p) => p.replace(/\/\/+/g, "/").replace(/(.+)\/$/, "$1"));
3621
+ return {
3622
+ paths: paths.map((p) => p.replace(/\/\/+/g, "/").replace(/(.+)\/$/, "$1")),
3623
+ paramRoutes
3624
+ };
3529
3625
  }
3530
- async function prerenderData(handler, prerenderPath, clientBuildDirectory, reactRouterConfig, viteConfig, requestInit) {
3626
+ async function prerenderData(handler, prerenderPath, onlyRoutes, clientBuildDirectory, reactRouterConfig, viteConfig, requestInit) {
3531
3627
  let normalizedPath = `${reactRouterConfig.basename}${prerenderPath === "/" ? "/_root.data" : `${prerenderPath.replace(/\/$/, "")}.data`}`.replace(/\/\/+/g, "/");
3532
- let request = new Request(`http://localhost${normalizedPath}`, requestInit);
3628
+ let url2 = new URL(`http://localhost${normalizedPath}`);
3629
+ if (onlyRoutes?.length) {
3630
+ url2.searchParams.set("_routes", onlyRoutes.join(","));
3631
+ }
3632
+ let request = new Request(url2, requestInit);
3533
3633
  let response = await handler(request);
3534
3634
  let data = await response.text();
3535
- validatePrerenderedResponse(response, data, "Prerender", normalizedPath);
3635
+ if (response.status !== 200) {
3636
+ throw new Error(
3637
+ `Prerender (data): Received a ${response.status} status code from \`entry.server.tsx\` while prerendering the \`${path6}\` path.
3638
+ ${normalizedPath}`
3639
+ );
3640
+ }
3536
3641
  let outdir = path6.relative(process.cwd(), clientBuildDirectory);
3537
3642
  let outfile = path6.join(outdir, ...normalizedPath.split("/"));
3538
3643
  await fse.ensureDir(path6.dirname(outfile));
3539
3644
  await fse.outputFile(outfile, data);
3540
- viteConfig.logger.info(`Prerender: Generated ${import_picocolors3.default.bold(outfile)}`);
3645
+ viteConfig.logger.info(
3646
+ `Prerender (data): ${prerenderPath} -> ${import_picocolors3.default.bold(outfile)}`
3647
+ );
3541
3648
  return data;
3542
3649
  }
3543
3650
  async function prerenderRoute(handler, prerenderPath, clientBuildDirectory, reactRouterConfig, viteConfig, requestInit) {
@@ -3548,42 +3655,65 @@ async function prerenderRoute(handler, prerenderPath, clientBuildDirectory, reac
3548
3655
  let request = new Request(`http://localhost${normalizedPath}`, requestInit);
3549
3656
  let response = await handler(request);
3550
3657
  let html = await response.text();
3551
- validatePrerenderedResponse(response, html, "Prerender", normalizedPath);
3552
- if (!reactRouterConfig.ssr) {
3553
- validatePrerenderedHtml(html, "Prerender");
3658
+ if (response.status !== 200) {
3659
+ throw new Error(
3660
+ `Prerender (html): Received a ${response.status} status code from \`entry.server.tsx\` while prerendering the \`${normalizedPath}\` path.
3661
+ ${html}`
3662
+ );
3554
3663
  }
3555
3664
  let outdir = path6.relative(process.cwd(), clientBuildDirectory);
3556
3665
  let outfile = path6.join(outdir, ...normalizedPath.split("/"), "index.html");
3557
3666
  await fse.ensureDir(path6.dirname(outfile));
3558
3667
  await fse.outputFile(outfile, html);
3559
- viteConfig.logger.info(`Prerender: Generated ${import_picocolors3.default.bold(outfile)}`);
3668
+ viteConfig.logger.info(
3669
+ `Prerender (html): ${prerenderPath} -> ${import_picocolors3.default.bold(outfile)}`
3670
+ );
3560
3671
  }
3561
3672
  async function prerenderResourceRoute(handler, prerenderPath, clientBuildDirectory, reactRouterConfig, viteConfig, requestInit) {
3562
3673
  let normalizedPath = `${reactRouterConfig.basename}${prerenderPath}/`.replace(/\/\/+/g, "/").replace(/\/$/g, "");
3563
3674
  let request = new Request(`http://localhost${normalizedPath}`, requestInit);
3564
3675
  let response = await handler(request);
3565
- let text = await response.text();
3566
- validatePrerenderedResponse(response, text, "Prerender", normalizedPath);
3567
- let outdir = path6.relative(process.cwd(), clientBuildDirectory);
3568
- let outfile = path6.join(outdir, ...normalizedPath.split("/"));
3569
- await fse.ensureDir(path6.dirname(outfile));
3570
- await fse.outputFile(outfile, text);
3571
- viteConfig.logger.info(`Prerender: Generated ${import_picocolors3.default.bold(outfile)}`);
3572
- }
3573
- function validatePrerenderedResponse(response, html, prefix, path7) {
3676
+ let content = Buffer.from(await response.arrayBuffer());
3574
3677
  if (response.status !== 200) {
3575
3678
  throw new Error(
3576
- `${prefix}: Received a ${response.status} status code from \`entry.server.tsx\` while prerendering the \`${path7}\` path.
3577
- ${html}`
3679
+ `Prerender (resource): Received a ${response.status} status code from \`entry.server.tsx\` while prerendering the \`${normalizedPath}\` path.
3680
+ ${content.toString("utf8")}`
3578
3681
  );
3579
3682
  }
3683
+ let outdir = path6.relative(process.cwd(), clientBuildDirectory);
3684
+ let outfile = path6.join(outdir, ...normalizedPath.split("/"));
3685
+ await fse.ensureDir(path6.dirname(outfile));
3686
+ await fse.outputFile(outfile, content);
3687
+ viteConfig.logger.info(
3688
+ `Prerender (resource): ${prerenderPath} -> ${import_picocolors3.default.bold(outfile)}`
3689
+ );
3580
3690
  }
3581
- function validatePrerenderedHtml(html, prefix) {
3582
- if (!html.includes("window.__reactRouterContext =") || !html.includes("window.__reactRouterRouteModules =")) {
3583
- throw new Error(
3584
- `${prefix}: Did you forget to include <Scripts/> in your root route? Your pre-rendered HTML files cannot hydrate without \`<Scripts />\`.`
3585
- );
3691
+ async function getPrerenderPaths(prerender, ssr, routes, logWarning = false) {
3692
+ let prerenderPaths = [];
3693
+ if (prerender != null && prerender !== false) {
3694
+ let prerenderRoutes = createPrerenderRoutes(routes);
3695
+ if (prerender === true) {
3696
+ let { paths, paramRoutes } = getStaticPrerenderPaths(prerenderRoutes);
3697
+ if (logWarning && !ssr && paramRoutes.length > 0) {
3698
+ console.warn(
3699
+ import_picocolors3.default.yellow(
3700
+ [
3701
+ "\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:",
3702
+ ...paramRoutes.map((p) => " - " + p)
3703
+ ].join("\n")
3704
+ )
3705
+ );
3706
+ }
3707
+ prerenderPaths = paths;
3708
+ } else if (typeof prerender === "function") {
3709
+ prerenderPaths = await prerender({
3710
+ getStaticPaths: () => getStaticPrerenderPaths(prerenderRoutes).paths
3711
+ });
3712
+ } else {
3713
+ prerenderPaths = prerender || ["/"];
3714
+ }
3586
3715
  }
3716
+ return prerenderPaths;
3587
3717
  }
3588
3718
  function groupRoutesByParentId2(manifest) {
3589
3719
  let routes = {};
@@ -3601,24 +3731,84 @@ function groupRoutesByParentId2(manifest) {
3601
3731
  function createPrerenderRoutes(manifest, parentId = "", routesByParentId = groupRoutesByParentId2(manifest)) {
3602
3732
  return (routesByParentId[parentId] || []).map((route) => {
3603
3733
  let commonRoute = {
3604
- // Always include root due to default boundaries
3605
- hasErrorBoundary: route.id === "root" || route.module.ErrorBoundary != null,
3606
3734
  id: route.id,
3607
- path: route.path,
3608
- loader: route.module.loader ? () => null : void 0,
3609
- action: void 0,
3610
- handle: route.module.handle
3735
+ path: route.path
3611
3736
  };
3612
- return route.index ? {
3613
- index: true,
3614
- ...commonRoute
3615
- } : {
3616
- caseSensitive: route.caseSensitive,
3737
+ if (route.index) {
3738
+ return {
3739
+ index: true,
3740
+ ...commonRoute
3741
+ };
3742
+ }
3743
+ return {
3617
3744
  children: createPrerenderRoutes(manifest, route.id, routesByParentId),
3618
3745
  ...commonRoute
3619
3746
  };
3620
3747
  });
3621
3748
  }
3749
+ async function validateSsrFalsePrerenderExports(viteConfig, ctx, manifest, viteChildCompiler) {
3750
+ let prerenderPaths = await getPrerenderPaths(
3751
+ ctx.reactRouterConfig.prerender,
3752
+ ctx.reactRouterConfig.ssr,
3753
+ manifest.routes,
3754
+ true
3755
+ );
3756
+ if (prerenderPaths.length === 0) {
3757
+ return;
3758
+ }
3759
+ let prerenderRoutes = createPrerenderRoutes(manifest.routes);
3760
+ let prerenderedRoutes = /* @__PURE__ */ new Set();
3761
+ for (let path7 of prerenderPaths) {
3762
+ let matches = (0, import_react_router2.matchRoutes)(
3763
+ prerenderRoutes,
3764
+ `/${path7}/`.replace(/^\/\/+/, "/")
3765
+ );
3766
+ invariant(
3767
+ matches,
3768
+ `Unable to prerender path because it does not match any routes: ${path7}`
3769
+ );
3770
+ matches.forEach((m) => prerenderedRoutes.add(m.route.id));
3771
+ }
3772
+ let errors = [];
3773
+ let routeExports = await getRouteManifestModuleExports(
3774
+ viteChildCompiler,
3775
+ ctx
3776
+ );
3777
+ for (let [routeId, route] of Object.entries(manifest.routes)) {
3778
+ let invalidApis = [];
3779
+ invariant(route, "Expected a route object in validateSsrFalseExports");
3780
+ let exports2 = routeExports[route.id];
3781
+ if (exports2.includes("headers")) invalidApis.push("headers");
3782
+ if (exports2.includes("action")) invalidApis.push("action");
3783
+ if (invalidApis.length > 0) {
3784
+ errors.push(
3785
+ `Prerender: ${invalidApis.length} invalid route export(s) in \`${route.id}\` when pre-rendering with \`ssr:false\`: ${invalidApis.map((a) => `\`${a}\``).join(", ")}. See https://reactrouter.com/how-to/pre-rendering#invalid-exports for more information.`
3786
+ );
3787
+ }
3788
+ if (!prerenderedRoutes.has(routeId)) {
3789
+ if (exports2.includes("loader")) {
3790
+ errors.push(
3791
+ `Prerender: 1 invalid route export in \`${route.id}\` when pre-rendering with \`ssr:false\`: \`loader\`. See https://reactrouter.com/how-to/pre-rendering#invalid-exports for more information.`
3792
+ );
3793
+ }
3794
+ let parentRoute = route.parentId ? manifest.routes[route.parentId] : null;
3795
+ while (parentRoute && parentRoute.id !== "root") {
3796
+ if (parentRoute.hasLoader && !parentRoute.hasClientLoader) {
3797
+ errors.push(
3798
+ `Prerender: 1 invalid route export in \`${parentRoute.id}\` when pre-rendering with \`ssr:false\`: \`loader\`. See https://reactrouter.com/how-to/pre-rendering#invalid-exports for more information.`
3799
+ );
3800
+ }
3801
+ parentRoute = parentRoute.parentId && parentRoute.parentId !== "root" ? manifest.routes[parentRoute.parentId] : null;
3802
+ }
3803
+ }
3804
+ }
3805
+ if (errors.length > 0) {
3806
+ viteConfig.logger.error(import_picocolors3.default.red(errors.join("\n")));
3807
+ throw new Error(
3808
+ "Invalid route exports found when prerendering with `ssr:false`"
3809
+ );
3810
+ }
3811
+ }
3622
3812
  function getAddressableRoutes(routes) {
3623
3813
  let nonAddressableIds = /* @__PURE__ */ new Set();
3624
3814
  for (let id in routes) {
@@ -3961,11 +4151,28 @@ async function getEnvironmentOptionsResolvers(ctx, buildManifest, viteCommand) {
3961
4151
  build: {
3962
4152
  outDir: getServerBuildDirectory(ctx),
3963
4153
  rollupOptions: {
3964
- input: viteUserConfig.build?.rollupOptions?.input ?? virtual.serverBuild.id
4154
+ input: (ctx.reactRouterConfig.future.unstable_viteEnvironmentApi ? viteUserConfig.environments?.ssr?.build?.rollupOptions?.input : viteUserConfig.build?.rollupOptions?.input) ?? virtual.serverBuild.id
3965
4155
  }
3966
- }
4156
+ },
4157
+ optimizeDeps: ctx.reactRouterConfig.future.unstable_viteEnvironmentApi && viteUserConfig.environments?.ssr?.optimizeDeps?.noDiscovery === false ? {
4158
+ entries: [
4159
+ vite2.normalizePath(ctx.entryServerFilePath),
4160
+ ...Object.values(ctx.reactRouterConfig.routes).map(
4161
+ (route) => resolveRelativeRouteFilePath(route, ctx.reactRouterConfig)
4162
+ )
4163
+ ],
4164
+ include: [
4165
+ "react",
4166
+ "react/jsx-dev-runtime",
4167
+ "react-dom/server",
4168
+ "react-router"
4169
+ ]
4170
+ } : void 0
3967
4171
  });
3968
4172
  }
4173
+ if (ctx.reactRouterConfig.future.unstable_viteEnvironmentApi && viteCommand === "serve") {
4174
+ environmentOptionsResolvers[CSS_DEV_HELPER_ENVIRONMENT_NAME] = () => ({});
4175
+ }
3969
4176
  return environmentOptionsResolvers;
3970
4177
  }
3971
4178
  function resolveEnvironmentsOptions(environmentResolvers, resolverOptions) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-router/dev",
3
- "version": "0.0.0-experimental-8e9d8ef63",
3
+ "version": "0.0.0-experimental-8677247c0",
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-8e9d8ef63"
91
+ "@react-router/node": "0.0.0-experimental-8677247c0"
92
92
  },
93
93
  "devDependencies": {
94
94
  "@types/babel__core": "^7.20.5",
@@ -106,7 +106,7 @@
106
106
  "@types/prettier": "^2.7.3",
107
107
  "@types/set-cookie-parser": "^2.4.1",
108
108
  "dotenv": "^16.0.0",
109
- "esbuild-register": "^3.3.2",
109
+ "esbuild-register": "^3.6.0",
110
110
  "execa": "5.1.1",
111
111
  "express": "^4.19.2",
112
112
  "fast-glob": "3.2.11",
@@ -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-8e9d8ef63",
121
- "@react-router/serve": "0.0.0-experimental-8e9d8ef63"
120
+ "@react-router/serve": "0.0.0-experimental-8677247c0",
121
+ "react-router": "^0.0.0-experimental-8677247c0"
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-8e9d8ef63",
128
- "react-router": "^0.0.0-experimental-8e9d8ef63"
127
+ "@react-router/serve": "^0.0.0-experimental-8677247c0",
128
+ "react-router": "^0.0.0-experimental-8677247c0"
129
129
  },
130
130
  "peerDependenciesMeta": {
131
131
  "@react-router/serve": {