@react-router/dev 0.0.0-experimental-66d5af831 → 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-66d5af831
3
+ * @react-router/dev v0.0.0-experimental-8677247c0
4
4
  *
5
5
  * Copyright (c) Remix Software Inc.
6
6
  *
@@ -389,7 +389,6 @@ async function resolveConfig({
389
389
  );
390
390
  }
391
391
  let future = {
392
- turboV3: reactRouterUserConfig.future?.turboV3 ?? false,
393
392
  unstable_optimizeDeps: reactRouterUserConfig.future?.unstable_optimizeDeps ?? false,
394
393
  unstable_splitRouteModules: reactRouterUserConfig.future?.unstable_splitRouteModules ?? false,
395
394
  unstable_viteEnvironmentApi: reactRouterUserConfig.future?.unstable_viteEnvironmentApi ?? false
@@ -945,7 +944,6 @@ var init_styles = __esm({
945
944
  path6 = __toESM(require("path"));
946
945
  import_react_router = require("react-router");
947
946
  init_resolve_file_url();
948
- init_vite();
949
947
  cssFileRegExp = /\.(css|less|sass|scss|styl|stylus|pcss|postcss|sss)(?:$|\?)/;
950
948
  cssModulesRegExp = new RegExp(`\\.module${cssFileRegExp.source}`);
951
949
  }
@@ -1364,9 +1362,26 @@ async function getEnvironmentOptionsResolvers(ctx, buildManifest, viteCommand) {
1364
1362
  rollupOptions: {
1365
1363
  input: (ctx.reactRouterConfig.future.unstable_viteEnvironmentApi ? viteUserConfig.environments?.ssr?.build?.rollupOptions?.input : viteUserConfig.build?.rollupOptions?.input) ?? virtual.serverBuild.id
1366
1364
  }
1367
- }
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
1368
1380
  });
1369
1381
  }
1382
+ if (ctx.reactRouterConfig.future.unstable_viteEnvironmentApi && viteCommand === "serve") {
1383
+ environmentOptionsResolvers[CSS_DEV_HELPER_ENVIRONMENT_NAME] = () => ({});
1384
+ }
1370
1385
  return environmentOptionsResolvers;
1371
1386
  }
1372
1387
  function resolveEnvironmentsOptions(environmentResolvers, resolverOptions) {
@@ -1381,7 +1396,7 @@ function resolveEnvironmentsOptions(environmentResolvers, resolverOptions) {
1381
1396
  function isNonNullable(x) {
1382
1397
  return x != null;
1383
1398
  }
1384
- 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;
1385
1400
  var init_plugin = __esm({
1386
1401
  "vite/plugin.ts"() {
1387
1402
  "use strict";
@@ -1411,8 +1426,15 @@ var init_plugin = __esm({
1411
1426
  init_with_props();
1412
1427
  BUILD_CLIENT_ROUTE_QUERY_STRING = "?__react-router-build-client-route";
1413
1428
  SSR_BUNDLE_PREFIX = "ssrBundle_";
1429
+ CSS_DEV_HELPER_ENVIRONMENT_NAME = "__react_router_css_dev_helper__";
1414
1430
  virtualHmrRuntime = create("hmr-runtime");
1415
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
+ };
1416
1438
  virtual = {
1417
1439
  serverBuild: create("server-build"),
1418
1440
  serverManifest: create("server-manifest"),
package/dist/config.d.ts CHANGED
@@ -37,7 +37,6 @@ type ServerBundlesBuildManifest = BaseBuildManifest & {
37
37
  };
38
38
  type ServerModuleFormat = "esm" | "cjs";
39
39
  interface FutureConfig {
40
- turboV3: boolean;
41
40
  unstable_optimizeDeps: boolean;
42
41
  /**
43
42
  * Automatically split route modules into multiple chunks when possible.
package/dist/config.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @react-router/dev v0.0.0-experimental-66d5af831
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-66d5af831
2
+ * @react-router/dev v0.0.0-experimental-8677247c0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @react-router/dev v0.0.0-experimental-66d5af831
2
+ * @react-router/dev v0.0.0-experimental-8677247c0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -477,7 +477,6 @@ async function resolveConfig({
477
477
  );
478
478
  }
479
479
  let future = {
480
- turboV3: reactRouterUserConfig.future?.turboV3 ?? false,
481
480
  unstable_optimizeDeps: reactRouterUserConfig.future?.unstable_optimizeDeps ?? false,
482
481
  unstable_splitRouteModules: reactRouterUserConfig.future?.unstable_splitRouteModules ?? false,
483
482
  unstable_viteEnvironmentApi: reactRouterUserConfig.future?.unstable_viteEnvironmentApi ?? false
package/dist/vite.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @react-router/dev v0.0.0-experimental-66d5af831
2
+ * @react-router/dev v0.0.0-experimental-8677247c0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -453,7 +453,6 @@ async function resolveConfig({
453
453
  );
454
454
  }
455
455
  let future = {
456
- turboV3: reactRouterUserConfig.future?.turboV3 ?? false,
457
456
  unstable_optimizeDeps: reactRouterUserConfig.future?.unstable_optimizeDeps ?? false,
458
457
  unstable_splitRouteModules: reactRouterUserConfig.future?.unstable_splitRouteModules ?? false,
459
458
  unstable_viteEnvironmentApi: reactRouterUserConfig.future?.unstable_viteEnvironmentApi ?? false
@@ -964,15 +963,12 @@ var isCssUrlWithoutSideEffects = (url2) => {
964
963
  }
965
964
  return false;
966
965
  };
967
- var injectQuery = (url2, query) => url2.includes("?") ? url2.replace("?", `?${query}&`) : `${url2}?${query}`;
968
966
  var getStylesForFiles = async ({
969
967
  viteDevServer,
970
968
  rootDirectory,
971
- cssModulesManifest,
969
+ loadCssContents,
972
970
  files
973
971
  }) => {
974
- let vite2 = getVite();
975
- let viteMajor = parseInt(vite2.version.split(".")[0], 10);
976
972
  let styles = {};
977
973
  let deps = /* @__PURE__ */ new Set();
978
974
  try {
@@ -1001,21 +997,9 @@ var getStylesForFiles = async ({
1001
997
  for (let dep of deps) {
1002
998
  if (dep.file && isCssFile(dep.file) && !isCssUrlWithoutSideEffects(dep.url)) {
1003
999
  try {
1004
- let css = isCssModulesFile(dep.file) ? cssModulesManifest[dep.file] : (await viteDevServer.ssrLoadModule(
1005
- // We need the ?inline query in Vite v6 when loading CSS in SSR
1006
- // since it does not expose the default export for CSS in a
1007
- // server environment. This is to align with non-SSR
1008
- // environments. For backwards compatibility with v5 we keep
1009
- // using the URL without ?inline query because the HMR code was
1010
- // relying on the implicit SSR-client module graph relationship.
1011
- viteMajor >= 6 ? injectQuery(dep.url, "inline") : dep.url
1012
- )).default;
1013
- if (css === void 0) {
1014
- throw new Error();
1015
- }
1016
- styles[dep.url] = css;
1000
+ styles[dep.url] = await loadCssContents(viteDevServer, dep);
1017
1001
  } catch {
1018
- console.warn(`Could not load ${dep.file}`);
1002
+ console.warn(`Failed to load CSS for ${dep.file}`);
1019
1003
  }
1020
1004
  }
1021
1005
  }
@@ -1074,7 +1058,7 @@ var getStylesForUrl = async ({
1074
1058
  rootDirectory,
1075
1059
  reactRouterConfig,
1076
1060
  entryClientFilePath,
1077
- cssModulesManifest,
1061
+ loadCssContents,
1078
1062
  build,
1079
1063
  url: url2
1080
1064
  }) => {
@@ -1089,7 +1073,7 @@ var getStylesForUrl = async ({
1089
1073
  let styles = await getStylesForFiles({
1090
1074
  viteDevServer,
1091
1075
  rootDirectory,
1092
- cssModulesManifest,
1076
+ loadCssContents,
1093
1077
  files: [
1094
1078
  // Always include the client entry file when crawling the module graph for CSS
1095
1079
  path5.relative(rootDirectory, entryClientFilePath),
@@ -1968,6 +1952,7 @@ var CLIENT_ROUTE_EXPORTS = [
1968
1952
  ];
1969
1953
  var BUILD_CLIENT_ROUTE_QUERY_STRING = "?__react-router-build-client-route";
1970
1954
  var SSR_BUNDLE_PREFIX = "ssrBundle_";
1955
+ var CSS_DEV_HELPER_ENVIRONMENT_NAME = "__react_router_css_dev_helper__";
1971
1956
  function isSeverBundleEnvironmentName(name) {
1972
1957
  return name.startsWith(SSR_BUNDLE_PREFIX);
1973
1958
  }
@@ -2159,6 +2144,7 @@ var getServerBuildDirectory = (ctx, { serverBundleId } = {}) => path6.join(
2159
2144
  ...serverBundleId ? [serverBundleId] : []
2160
2145
  );
2161
2146
  var getClientBuildDirectory = (reactRouterConfig) => path6.join(reactRouterConfig.buildDirectory, "client");
2147
+ var injectQuery = (url2, query) => url2.includes("?") ? url2.replace("?", `?${query}&`) : `${url2}?${query}`;
2162
2148
  var defaultEntriesDir = path6.resolve(
2163
2149
  path6.dirname(require.resolve("@react-router/dev/package.json")),
2164
2150
  "dist",
@@ -2415,6 +2401,7 @@ var reactRouterVitePlugin = () => {
2415
2401
  reactRouterServerManifest
2416
2402
  };
2417
2403
  };
2404
+ let currentReactRouterManifestForDev = null;
2418
2405
  let getReactRouterManifestForDev = async () => {
2419
2406
  let routes = {};
2420
2407
  let routeManifestExports = await getRouteManifestModuleExports(
@@ -2471,7 +2458,7 @@ var reactRouterVitePlugin = () => {
2471
2458
  imports: []
2472
2459
  };
2473
2460
  }
2474
- return {
2461
+ let reactRouterManifestForDev = {
2475
2462
  version: String(Math.random()),
2476
2463
  url: combineURLs(ctx.publicPath, virtual.browserManifest.url),
2477
2464
  hmr: {
@@ -2486,6 +2473,42 @@ var reactRouterVitePlugin = () => {
2486
2473
  },
2487
2474
  routes
2488
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;
2489
2512
  };
2490
2513
  return [
2491
2514
  {
@@ -2664,6 +2687,8 @@ var reactRouterVitePlugin = () => {
2664
2687
  }
2665
2688
  viteChildCompiler = await vite2.createServer({
2666
2689
  ...viteUserConfig,
2690
+ // Ensure child compiler cannot overwrite the default cache directory
2691
+ cacheDir: "node_modules/.vite-child-compiler",
2667
2692
  mode: viteConfig.mode,
2668
2693
  server: {
2669
2694
  watch: viteConfig.command === "build" ? null : void 0,
@@ -2675,7 +2700,23 @@ var reactRouterVitePlugin = () => {
2675
2700
  plugins: [
2676
2701
  ...(childCompilerConfigFile.config.plugins ?? []).flat().filter(
2677
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"
2678
- )
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
+ }
2679
2720
  ]
2680
2721
  });
2681
2722
  await viteChildCompiler.pluginContainer.buildStart({});
@@ -2712,7 +2753,7 @@ var reactRouterVitePlugin = () => {
2712
2753
  entryClientFilePath: ctx.entryClientFilePath,
2713
2754
  reactRouterConfig: ctx.reactRouterConfig,
2714
2755
  viteDevServer,
2715
- cssModulesManifest,
2756
+ loadCssContents,
2716
2757
  build,
2717
2758
  url: url2
2718
2759
  });
@@ -3232,8 +3273,7 @@ var reactRouterVitePlugin = () => {
3232
3273
  let route = getRoute(ctx.reactRouterConfig, file);
3233
3274
  let hmrEventData = { route: null };
3234
3275
  if (route) {
3235
- let serverManifest = (await server.ssrLoadModule(virtual.serverManifest.id)).default;
3236
- let oldRouteMetadata = serverManifest.routes[route.id];
3276
+ let oldRouteMetadata = currentReactRouterManifestForDev?.routes[route.id];
3237
3277
  let newRouteMetadata = await getRouteMetadata(
3238
3278
  cache,
3239
3279
  ctx,
@@ -3633,17 +3673,17 @@ async function prerenderResourceRoute(handler, prerenderPath, clientBuildDirecto
3633
3673
  let normalizedPath = `${reactRouterConfig.basename}${prerenderPath}/`.replace(/\/\/+/g, "/").replace(/\/$/g, "");
3634
3674
  let request = new Request(`http://localhost${normalizedPath}`, requestInit);
3635
3675
  let response = await handler(request);
3636
- let text = await response.text();
3676
+ let content = Buffer.from(await response.arrayBuffer());
3637
3677
  if (response.status !== 200) {
3638
3678
  throw new Error(
3639
3679
  `Prerender (resource): Received a ${response.status} status code from \`entry.server.tsx\` while prerendering the \`${normalizedPath}\` path.
3640
- ${text}`
3680
+ ${content.toString("utf8")}`
3641
3681
  );
3642
3682
  }
3643
3683
  let outdir = path6.relative(process.cwd(), clientBuildDirectory);
3644
3684
  let outfile = path6.join(outdir, ...normalizedPath.split("/"));
3645
3685
  await fse.ensureDir(path6.dirname(outfile));
3646
- await fse.outputFile(outfile, text);
3686
+ await fse.outputFile(outfile, content);
3647
3687
  viteConfig.logger.info(
3648
3688
  `Prerender (resource): ${prerenderPath} -> ${import_picocolors3.default.bold(outfile)}`
3649
3689
  );
@@ -3742,13 +3782,24 @@ async function validateSsrFalsePrerenderExports(viteConfig, ctx, manifest, viteC
3742
3782
  if (exports2.includes("action")) invalidApis.push("action");
3743
3783
  if (invalidApis.length > 0) {
3744
3784
  errors.push(
3745
- `Prerender: ${invalidApis.length} invalid route export(s) in \`${route.id}\` when prerendering with \`ssr:false\`: ${invalidApis.join(", ")}. See https://reactrouter.com/how-to/pre-rendering for more information.`
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.`
3746
3786
  );
3747
3787
  }
3748
- if (exports2.includes("loader") && !prerenderedRoutes.has(routeId)) {
3749
- errors.push(
3750
- `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/pre-rendering for more information.`
3751
- );
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
+ }
3752
3803
  }
3753
3804
  }
3754
3805
  if (errors.length > 0) {
@@ -4102,9 +4153,26 @@ async function getEnvironmentOptionsResolvers(ctx, buildManifest, viteCommand) {
4102
4153
  rollupOptions: {
4103
4154
  input: (ctx.reactRouterConfig.future.unstable_viteEnvironmentApi ? viteUserConfig.environments?.ssr?.build?.rollupOptions?.input : viteUserConfig.build?.rollupOptions?.input) ?? virtual.serverBuild.id
4104
4155
  }
4105
- }
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
4106
4171
  });
4107
4172
  }
4173
+ if (ctx.reactRouterConfig.future.unstable_viteEnvironmentApi && viteCommand === "serve") {
4174
+ environmentOptionsResolvers[CSS_DEV_HELPER_ENVIRONMENT_NAME] = () => ({});
4175
+ }
4108
4176
  return environmentOptionsResolvers;
4109
4177
  }
4110
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-66d5af831",
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-66d5af831"
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-66d5af831",
121
- "@react-router/serve": "0.0.0-experimental-66d5af831"
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-66d5af831",
128
- "react-router": "^0.0.0-experimental-66d5af831"
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": {