@react-router/dev 0.0.0-experimental-cb0fdf114 → 0.0.0-experimental-34996874d

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,112 @@
1
1
  # `@react-router/dev`
2
2
 
3
+ ## v7.14.2
4
+
5
+ ### Patch Changes
6
+
7
+ - Fix typegen for layouts without pages ([[aabf4a1](https://github.com/remix-run/react-router/commit/aabf4a1))
8
+
9
+ Previously, typegen could produce `pages: ;` in `.react-router/types/+routes.ts` when a route corresponded to 0 pages.
10
+ Now, `pages: never;` is correctly generated for those cases.
11
+
12
+ ### Unstable Changes
13
+
14
+ ⚠️ _[Unstable features](https://reactrouter.com/community/api-development-strategy#unstable-flags) are not recommended for production use_
15
+
16
+ - For `unstable_reactRouterRSC` Vite plugin consumers, require `@vitejs/plugin-react` in user Vite config, and more reliably split route modules. ([#14965](https://github.com/remix-run/react-router/pull/14965)) ([[aabf4a1](https://github.com/remix-run/react-router/commit/aabf4a1))
17
+ - ⚠️ This is a breaking change if you have begun using the `unstable_reactRouterRSC` Vite plugin - please install `@vitejs/plugin-react` and add the `react` plugin to your Vite plugins array.
18
+
19
+ - Updated dependencies:
20
+ - [`react-router@7.14.2`](https://github.com/remix-run/react-router/releases/tag/react-router@7.14.2)
21
+ - [`@react-router/node@7.14.2`](https://github.com/remix-run/react-router/releases/tag/@react-router/node@7.14.2)
22
+ - [`@react-router/serve@7.14.2`](https://github.com/remix-run/react-router/releases/tag/@react-router/serve@7.14.2)
23
+
24
+ ## v7.14.1
25
+
26
+ ### Patch Changes
27
+
28
+ - Add TypeScript 6 support to peer dependency ranges
29
+ - Updated dependencies:
30
+ - [`react-router@7.14.1`](https://github.com/remix-run/react-router/releases/tag/react-router@7.14.1)
31
+ - [`@react-router/node@7.14.1`](https://github.com/remix-run/react-router/releases/tag/@react-router/node@7.14.1)
32
+ - [`@react-router/serve@7.14.1`](https://github.com/remix-run/react-router/releases/tag/@react-router/serve@7.14.1)
33
+
34
+ ## 7.14.0
35
+
36
+ ### Minor Changes
37
+
38
+ - Add support for Vite 8 ([#14876](https://github.com/remix-run/react-router/pull/14876))
39
+
40
+ ### Patch Changes
41
+
42
+ - support for prerendering multiple server bundles with v8_viteEnvironmentApi ([#14921](https://github.com/remix-run/react-router/pull/14921))
43
+
44
+ - rsc framework mode prerender / spa mode support ([#14907](https://github.com/remix-run/react-router/pull/14907))
45
+
46
+ - UNSTABLE RSC FRAMEWORK MODE BREAKING CHANGE - Existing route module exports remain unchanged from stable v7 non-RSC mode, but new exports are added for RSC mode. If you want to use RSC features, you will need to update your route modules to export the new annotations. ([#14901](https://github.com/remix-run/react-router/pull/14901))
47
+
48
+ If you are using RSC framework mode currently, you will need to update your route modules to the new conventions. The following route module components have their own mutually exclusive server component counterparts:
49
+
50
+ | Server Component Export | Client Component |
51
+ | ----------------------- | ----------------- |
52
+ | `ServerComponent` | `default` |
53
+ | `ServerErrorBoundary` | `ErrorBoundary` |
54
+ | `ServerLayout` | `Layout` |
55
+ | `ServerHydrateFallback` | `HydrateFallback` |
56
+
57
+ If you were previously exporting a `ServerComponent`, your `ErrorBoundary`, `Layout`, and `HydrateFallback` were also server components. If you want to keep those as server components, you can rename them and prefix them with `Server`. If you were previously importing the implementations of those components from a client module, you can simply inline them.
58
+
59
+ Example:
60
+
61
+ Before
62
+
63
+ ```tsx
64
+ import { ErrorBoundary as ClientErrorBoundary } from "./client";
65
+
66
+ export function ServerComponent() {
67
+ // ...
68
+ }
69
+
70
+ export function ErrorBoundary() {
71
+ return <ClientErrorBoundary />;
72
+ }
73
+
74
+ export function Layout() {
75
+ // ...
76
+ }
77
+
78
+ export function HydrateFallback() {
79
+ // ...
80
+ }
81
+ ```
82
+
83
+ After
84
+
85
+ ```tsx
86
+ export function ServerComponent() {
87
+ // ...
88
+ }
89
+
90
+ export function ErrorBoundary() {
91
+ // previous implementation of ClientErrorBoundary, this is now a client component
92
+ }
93
+
94
+ export function ServerLayout() {
95
+ // rename previous Layout export to ServerLayout to make it a server component
96
+ }
97
+
98
+ export function ServerHydrateFallback() {
99
+ // rename previous HydrateFallback export to ServerHydrateFallback to make it a server component
100
+ }
101
+ ```
102
+
103
+ - update the reveal command to support rsc for `entry.client`, `entry.rsc`, `entry.ssr` ([#14904](https://github.com/remix-run/react-router/pull/14904))
104
+
105
+ - Updated dependencies:
106
+ - `react-router@7.14.0`
107
+ - `@react-router/node@7.14.0`
108
+ - `@react-router/serve@7.14.0`
109
+
3
110
  ## 7.13.2
4
111
 
5
112
  ### 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-cb0fdf114
3
+ * @react-router/dev v0.0.0-experimental-34996874d
4
4
  *
5
5
  * Copyright (c) Remix Software Inc.
6
6
  *
@@ -398,10 +398,10 @@ async function resolveConfig({
398
398
  "The `prerender`/`prerender.paths` config must be a boolean, an array of string paths, or a function returning a boolean or array of string paths."
399
399
  );
400
400
  }
401
- let isValidConcurrencyConfig = typeof prerender2 != "object" || !("unstable_concurrency" in prerender2) || typeof prerender2.unstable_concurrency === "number" && Number.isInteger(prerender2.unstable_concurrency) && prerender2.unstable_concurrency > 0;
401
+ let isValidConcurrencyConfig = typeof prerender2 != "object" || !("concurrency" in prerender2) || typeof prerender2.concurrency === "number" && Number.isInteger(prerender2.concurrency) && prerender2.concurrency > 0;
402
402
  if (!isValidConcurrencyConfig) {
403
403
  return err(
404
- "The `prerender.unstable_concurrency` config must be a positive integer if specified."
404
+ "The `prerender.concurrency` config must be a positive integer if specified."
405
405
  );
406
406
  }
407
407
  }
@@ -503,10 +503,14 @@ async function resolveConfig({
503
503
  'The "future.unstable_viteEnvironmentApi" flag has been stabilized as "future.v8_viteEnvironmentApi"'
504
504
  );
505
505
  }
506
+ if (futureConfig?.unstable_passThroughRequests !== void 0) {
507
+ return err(
508
+ 'The "future.unstable_passThroughRequests" flag has been stabilized as "future.v8_passThroughRequests"'
509
+ );
510
+ }
506
511
  let future = {
507
512
  unstable_optimizeDeps: userAndPresetConfigs.future?.unstable_optimizeDeps ?? false,
508
- unstable_passThroughRequests: userAndPresetConfigs.future?.unstable_passThroughRequests ?? false,
509
- unstable_subResourceIntegrity: userAndPresetConfigs.future?.unstable_subResourceIntegrity ?? false,
513
+ v8_passThroughRequests: userAndPresetConfigs.future?.v8_passThroughRequests ?? false,
510
514
  unstable_trailingSlashAwareDataRequests: userAndPresetConfigs.future?.unstable_trailingSlashAwareDataRequests ?? false,
511
515
  unstable_previewServerPrerendering: userAndPresetConfigs.future?.unstable_previewServerPrerendering ?? false,
512
516
  v8_middleware: userAndPresetConfigs.future?.v8_middleware ?? false,
@@ -514,6 +518,7 @@ async function resolveConfig({
514
518
  v8_viteEnvironmentApi: (userAndPresetConfigs.future?.v8_viteEnvironmentApi || userAndPresetConfigs.future?.unstable_previewServerPrerendering) ?? false
515
519
  };
516
520
  let allowedActionOrigins = userAndPresetConfigs.allowedActionOrigins ?? false;
521
+ let subResourceIntegrity = userAndPresetConfigs.subResourceIntegrity ?? false;
517
522
  let reactRouterConfig = deepFreeze({
518
523
  appDirectory,
519
524
  basename: basename3,
@@ -527,6 +532,7 @@ async function resolveConfig({
527
532
  serverBundles,
528
533
  serverModuleFormat,
529
534
  ssr,
535
+ subResourceIntegrity,
530
536
  allowedActionOrigins,
531
537
  unstable_routeConfig: routeConfig
532
538
  });
@@ -1078,7 +1084,7 @@ function routeFilesType({
1078
1084
  t2.tsPropertySignature(
1079
1085
  t2.identifier("page"),
1080
1086
  t2.tsTypeAnnotation(
1081
- pages ? t2.tsUnionType(
1087
+ pages.size > 0 ? t2.tsUnionType(
1082
1088
  Array.from(pages).map(
1083
1089
  (page) => t2.tsLiteralType(t2.stringLiteral(page))
1084
1090
  )
package/dist/config.d.ts CHANGED
@@ -38,8 +38,7 @@ type ServerBundlesBuildManifest = BaseBuildManifest & {
38
38
  type ServerModuleFormat = "esm" | "cjs";
39
39
  interface FutureConfig {
40
40
  unstable_optimizeDeps: boolean;
41
- unstable_passThroughRequests: boolean;
42
- unstable_subResourceIntegrity: boolean;
41
+ v8_passThroughRequests: boolean;
43
42
  unstable_trailingSlashAwareDataRequests: boolean;
44
43
  /**
45
44
  * Prerender with Vite Preview server
@@ -103,7 +102,7 @@ type ReactRouterConfig = {
103
102
  * An array of URLs to prerender to HTML files at build time. Can also be a
104
103
  * function returning an array to dynamically generate URLs.
105
104
  *
106
- * `unstable_concurrency` defaults to 1, which means "no concurrency" - fully serial execution.
105
+ * `concurrency` defaults to 1, which means "no concurrency" - fully serial execution.
107
106
  * Setting it to a value more than 1 enables concurrent prerendering.
108
107
  * Setting it to a value higher than one can increase the speed of the build,
109
108
  * but may consume more resources, and send more concurrent requests to the
@@ -111,7 +110,7 @@ type ReactRouterConfig = {
111
110
  */
112
111
  prerender?: PrerenderPaths | {
113
112
  paths: PrerenderPaths;
114
- unstable_concurrency?: number;
113
+ concurrency?: number;
115
114
  };
116
115
  /**
117
116
  * An array of React Router plugin config presets to ease integration with
@@ -153,6 +152,11 @@ type ReactRouterConfig = {
153
152
  * SPA without server-rendering. Default's to `true`.
154
153
  */
155
154
  ssr?: boolean;
155
+ /**
156
+ * Enable subresource integrity hashes on asset script tags. Defaults to
157
+ * `false`.
158
+ */
159
+ subResourceIntegrity?: boolean;
156
160
  /**
157
161
  * An array of allowed origin hosts for action submissions to UI routes (does not apply
158
162
  * to resource routes). Supports micromatch glob patterns (`*` to match one segment,
@@ -259,6 +263,10 @@ type ResolvedReactRouterConfig = Readonly<{
259
263
  * SPA without server-rendering. Default's to `true`.
260
264
  */
261
265
  ssr: boolean;
266
+ /**
267
+ * Whether to generate subresource integrity hashes for asset script tags.
268
+ */
269
+ subResourceIntegrity: boolean;
262
270
  /**
263
271
  * The allowed origins for actions / mutations. Does not apply to routes
264
272
  * without a component. micromatch glob patterns are supported.
package/dist/config.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @react-router/dev v0.0.0-experimental-cb0fdf114
2
+ * @react-router/dev v0.0.0-experimental-34996874d
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-cb0fdf114
2
+ * @react-router/dev v0.0.0-experimental-34996874d
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-cb0fdf114
2
+ * @react-router/dev v0.0.0-experimental-34996874d
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -424,10 +424,10 @@ async function resolveConfig({
424
424
  "The `prerender`/`prerender.paths` config must be a boolean, an array of string paths, or a function returning a boolean or array of string paths."
425
425
  );
426
426
  }
427
- let isValidConcurrencyConfig = typeof prerender != "object" || !("unstable_concurrency" in prerender) || typeof prerender.unstable_concurrency === "number" && Number.isInteger(prerender.unstable_concurrency) && prerender.unstable_concurrency > 0;
427
+ let isValidConcurrencyConfig = typeof prerender != "object" || !("concurrency" in prerender) || typeof prerender.concurrency === "number" && Number.isInteger(prerender.concurrency) && prerender.concurrency > 0;
428
428
  if (!isValidConcurrencyConfig) {
429
429
  return err(
430
- "The `prerender.unstable_concurrency` config must be a positive integer if specified."
430
+ "The `prerender.concurrency` config must be a positive integer if specified."
431
431
  );
432
432
  }
433
433
  }
@@ -529,10 +529,14 @@ async function resolveConfig({
529
529
  'The "future.unstable_viteEnvironmentApi" flag has been stabilized as "future.v8_viteEnvironmentApi"'
530
530
  );
531
531
  }
532
+ if (futureConfig?.unstable_passThroughRequests !== void 0) {
533
+ return err(
534
+ 'The "future.unstable_passThroughRequests" flag has been stabilized as "future.v8_passThroughRequests"'
535
+ );
536
+ }
532
537
  let future = {
533
538
  unstable_optimizeDeps: userAndPresetConfigs.future?.unstable_optimizeDeps ?? false,
534
- unstable_passThroughRequests: userAndPresetConfigs.future?.unstable_passThroughRequests ?? false,
535
- unstable_subResourceIntegrity: userAndPresetConfigs.future?.unstable_subResourceIntegrity ?? false,
539
+ v8_passThroughRequests: userAndPresetConfigs.future?.v8_passThroughRequests ?? false,
536
540
  unstable_trailingSlashAwareDataRequests: userAndPresetConfigs.future?.unstable_trailingSlashAwareDataRequests ?? false,
537
541
  unstable_previewServerPrerendering: userAndPresetConfigs.future?.unstable_previewServerPrerendering ?? false,
538
542
  v8_middleware: userAndPresetConfigs.future?.v8_middleware ?? false,
@@ -540,6 +544,7 @@ async function resolveConfig({
540
544
  v8_viteEnvironmentApi: (userAndPresetConfigs.future?.v8_viteEnvironmentApi || userAndPresetConfigs.future?.unstable_previewServerPrerendering) ?? false
541
545
  };
542
546
  let allowedActionOrigins = userAndPresetConfigs.allowedActionOrigins ?? false;
547
+ let subResourceIntegrity = userAndPresetConfigs.subResourceIntegrity ?? false;
543
548
  let reactRouterConfig = deepFreeze({
544
549
  appDirectory,
545
550
  basename,
@@ -553,6 +558,7 @@ async function resolveConfig({
553
558
  serverBundles,
554
559
  serverModuleFormat,
555
560
  ssr,
561
+ subResourceIntegrity,
556
562
  allowedActionOrigins,
557
563
  unstable_routeConfig: routeConfig
558
564
  });
package/dist/vite.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @react-router/dev v0.0.0-experimental-cb0fdf114
2
+ * @react-router/dev v0.0.0-experimental-34996874d
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -461,10 +461,10 @@ async function resolveConfig({
461
461
  "The `prerender`/`prerender.paths` config must be a boolean, an array of string paths, or a function returning a boolean or array of string paths."
462
462
  );
463
463
  }
464
- let isValidConcurrencyConfig = typeof prerender2 != "object" || !("unstable_concurrency" in prerender2) || typeof prerender2.unstable_concurrency === "number" && Number.isInteger(prerender2.unstable_concurrency) && prerender2.unstable_concurrency > 0;
464
+ let isValidConcurrencyConfig = typeof prerender2 != "object" || !("concurrency" in prerender2) || typeof prerender2.concurrency === "number" && Number.isInteger(prerender2.concurrency) && prerender2.concurrency > 0;
465
465
  if (!isValidConcurrencyConfig) {
466
466
  return err(
467
- "The `prerender.unstable_concurrency` config must be a positive integer if specified."
467
+ "The `prerender.concurrency` config must be a positive integer if specified."
468
468
  );
469
469
  }
470
470
  }
@@ -566,10 +566,14 @@ async function resolveConfig({
566
566
  'The "future.unstable_viteEnvironmentApi" flag has been stabilized as "future.v8_viteEnvironmentApi"'
567
567
  );
568
568
  }
569
+ if (futureConfig?.unstable_passThroughRequests !== void 0) {
570
+ return err(
571
+ 'The "future.unstable_passThroughRequests" flag has been stabilized as "future.v8_passThroughRequests"'
572
+ );
573
+ }
569
574
  let future = {
570
575
  unstable_optimizeDeps: userAndPresetConfigs.future?.unstable_optimizeDeps ?? false,
571
- unstable_passThroughRequests: userAndPresetConfigs.future?.unstable_passThroughRequests ?? false,
572
- unstable_subResourceIntegrity: userAndPresetConfigs.future?.unstable_subResourceIntegrity ?? false,
576
+ v8_passThroughRequests: userAndPresetConfigs.future?.v8_passThroughRequests ?? false,
573
577
  unstable_trailingSlashAwareDataRequests: userAndPresetConfigs.future?.unstable_trailingSlashAwareDataRequests ?? false,
574
578
  unstable_previewServerPrerendering: userAndPresetConfigs.future?.unstable_previewServerPrerendering ?? false,
575
579
  v8_middleware: userAndPresetConfigs.future?.v8_middleware ?? false,
@@ -577,6 +581,7 @@ async function resolveConfig({
577
581
  v8_viteEnvironmentApi: (userAndPresetConfigs.future?.v8_viteEnvironmentApi || userAndPresetConfigs.future?.unstable_previewServerPrerendering) ?? false
578
582
  };
579
583
  let allowedActionOrigins = userAndPresetConfigs.allowedActionOrigins ?? false;
584
+ let subResourceIntegrity = userAndPresetConfigs.subResourceIntegrity ?? false;
580
585
  let reactRouterConfig = deepFreeze({
581
586
  appDirectory,
582
587
  basename: basename3,
@@ -590,6 +595,7 @@ async function resolveConfig({
590
595
  serverBundles,
591
596
  serverModuleFormat,
592
597
  ssr,
598
+ subResourceIntegrity,
593
599
  allowedActionOrigins,
594
600
  unstable_routeConfig: routeConfig
595
601
  });
@@ -1096,7 +1102,7 @@ function routeFilesType({
1096
1102
  t2.tsPropertySignature(
1097
1103
  t2.identifier("page"),
1098
1104
  t2.tsTypeAnnotation(
1099
- pages ? t2.tsUnionType(
1105
+ pages.size > 0 ? t2.tsUnionType(
1100
1106
  Array.from(pages).map(
1101
1107
  (page) => t2.tsLiteralType(t2.stringLiteral(page))
1102
1108
  )
@@ -3354,7 +3360,7 @@ var reactRouterVitePlugin = () => {
3354
3360
  )};`
3355
3361
  );
3356
3362
  let sri = void 0;
3357
- if (ctx.reactRouterConfig.future.unstable_subResourceIntegrity) {
3363
+ if (ctx.reactRouterConfig.subResourceIntegrity) {
3358
3364
  sri = await generateSriManifest(ctx);
3359
3365
  }
3360
3366
  let reactRouterServerManifest = {
@@ -4928,8 +4934,8 @@ async function handlePrerender(viteConfig, reactRouterConfig, serverBuildDirecto
4928
4934
  };
4929
4935
  let concurrency = 1;
4930
4936
  let { prerender: prerender2 } = reactRouterConfig;
4931
- if (typeof prerender2 === "object" && "unstable_concurrency" in prerender2) {
4932
- concurrency = prerender2.unstable_concurrency ?? 1;
4937
+ if (typeof prerender2 === "object" && "concurrency" in prerender2) {
4938
+ concurrency = prerender2.concurrency ?? 1;
4933
4939
  }
4934
4940
  const pMap = await import("p-map");
4935
4941
  await pMap.default(build.prerender, prerenderSinglePath, { concurrency });
@@ -5611,8 +5617,8 @@ function assertPrerenderPathsMatchRoutes(config, prerenderPaths) {
5611
5617
  function getPrerenderConcurrencyConfig(reactRouterConfig) {
5612
5618
  let concurrency = 1;
5613
5619
  let { prerender: prerender2 } = reactRouterConfig;
5614
- if (typeof prerender2 === "object" && "unstable_concurrency" in prerender2) {
5615
- concurrency = prerender2.unstable_concurrency ?? 1;
5620
+ if (typeof prerender2 === "object" && "concurrency" in prerender2) {
5621
+ concurrency = prerender2.concurrency ?? 1;
5616
5622
  }
5617
5623
  return concurrency;
5618
5624
  }
@@ -5819,17 +5825,19 @@ import * as ___EnsureClientRouteModuleForHMR_REACT___ from "react";
5819
5825
  export function EnsureClientRouteModuleForHMR___() { return ___EnsureClientRouteModuleForHMR_REACT___.createElement(___EnsureClientRouteModuleForHMR_REACT___.Fragment, null) }
5820
5826
  `;
5821
5827
  function virtualRouteModulesPlugin({
5828
+ enforceSplitRouteModules,
5822
5829
  environments: { client = ["client", "ssr"], server = ["rsc"] } = {},
5823
- isRouteModule,
5830
+ getRouteIdForFile,
5824
5831
  isRootRouteModule,
5825
- transformToJs
5832
+ transformToJs,
5833
+ shouldTransform
5826
5834
  }) {
5827
5835
  let clientEnvironments = new Set(client);
5828
5836
  let serverEnvironments = new Set(server);
5829
5837
  let cache = /* @__PURE__ */ new Map();
5830
- async function createClientRouteEntry(id, code) {
5838
+ async function createClientRouteEntry(id, code, isRootRouteModule2, routeId) {
5831
5839
  let result = "";
5832
- let routeChunks = detectRouteChunks2(cache, id, code);
5840
+ let routeChunks = detectRouteChunks2(cache, id, code, isRootRouteModule2);
5833
5841
  let { staticExports } = await parseRouteExports(code);
5834
5842
  validateRouteModuleExports(staticExports);
5835
5843
  let needsReactImport = false;
@@ -5861,13 +5869,29 @@ function virtualRouteModulesPlugin({
5861
5869
  result = `import * as React from "react";
5862
5870
  ${result}`;
5863
5871
  }
5872
+ if (enforceSplitRouteModules() && !isRootRouteModule2) {
5873
+ let { hasRouteChunkByExportName } = routeChunks;
5874
+ let hasClientAction = staticExports.includes("clientAction");
5875
+ let hasClientLoader = staticExports.includes("clientLoader");
5876
+ let hasClientMiddleware = staticExports.includes("clientMiddleware");
5877
+ let hasHydrateFallback = staticExports.includes("HydrateFallback");
5878
+ validateRouteChunks2({
5879
+ id: routeId,
5880
+ valid: {
5881
+ clientAction: !hasClientAction || hasRouteChunkByExportName.clientAction,
5882
+ clientLoader: !hasClientLoader || hasRouteChunkByExportName.clientLoader,
5883
+ clientMiddleware: !hasClientMiddleware || hasRouteChunkByExportName.clientMiddleware,
5884
+ HydrateFallback: !hasHydrateFallback || hasRouteChunkByExportName.HydrateFallback
5885
+ }
5886
+ });
5887
+ }
5864
5888
  return {
5865
5889
  code: '"use client";\n' + result
5866
5890
  };
5867
5891
  }
5868
- async function createServerRouteEntry(id, code, isRootRouteModule2) {
5892
+ async function createServerRouteEntry(id, code, isRootRouteModule2, routeId) {
5869
5893
  let result = "";
5870
- let routeChunks = detectRouteChunks2(cache, id, code);
5894
+ let routeChunks = detectRouteChunks2(cache, id, code, isRootRouteModule2);
5871
5895
  let { staticExports } = await parseRouteExports(code);
5872
5896
  validateRouteModuleExports(staticExports);
5873
5897
  let needsReactImport = false;
@@ -5912,6 +5936,22 @@ ${result}`;
5912
5936
  result += `export { ErrorBoundary } from "${createId(id, "client-route-module", "shared")}";
5913
5937
  `;
5914
5938
  }
5939
+ if (enforceSplitRouteModules() && !isRootRouteModule2) {
5940
+ let { hasRouteChunkByExportName } = routeChunks;
5941
+ let hasClientAction = staticExports.includes("clientAction");
5942
+ let hasClientLoader = staticExports.includes("clientLoader");
5943
+ let hasClientMiddleware = staticExports.includes("clientMiddleware");
5944
+ let hasHydrateFallback = staticExports.includes("HydrateFallback");
5945
+ validateRouteChunks2({
5946
+ id: routeId,
5947
+ valid: {
5948
+ clientAction: !hasClientAction || hasRouteChunkByExportName.clientAction,
5949
+ clientLoader: !hasClientLoader || hasRouteChunkByExportName.clientLoader,
5950
+ clientMiddleware: !hasClientMiddleware || hasRouteChunkByExportName.clientMiddleware,
5951
+ HydrateFallback: !hasHydrateFallback || hasRouteChunkByExportName.HydrateFallback
5952
+ }
5953
+ });
5954
+ }
5915
5955
  return {
5916
5956
  code: result
5917
5957
  };
@@ -5923,8 +5963,8 @@ ${result}`;
5923
5963
  removeExports(ast, CLIENT_ROUTE_EXPORTS2);
5924
5964
  return generate(ast);
5925
5965
  }
5926
- async function createClientRouteModuleChunk(id, code, chunk, isRootRouteModule2) {
5927
- let routeChunks = detectRouteChunks2(cache, id, code);
5966
+ async function createClientRouteModuleChunk(id, code, chunk, routeId, isRootRouteModule2, isDevMode) {
5967
+ let routeChunks = detectRouteChunks2(cache, id, code, isRootRouteModule2);
5928
5968
  const ast = import_parser.parse(code, {
5929
5969
  sourceType: "module"
5930
5970
  });
@@ -5958,59 +5998,91 @@ import { createElement as __rr_createElement } from "react";
5958
5998
  }
5959
5999
  result += ENSURE_CLIENT_ROUTE_MODULE_CHUNK_FOR_HMR;
5960
6000
  }
5961
- result += `
5962
- if (import.meta.hot) {
6001
+ let hasAction = staticExports.includes("action");
6002
+ let hasLoader = staticExports.includes("loader");
6003
+ let hasComponent = staticExports.includes("default") || staticExports.includes("ServerComponent");
6004
+ let hasErrorBoundary = staticExports.includes("ErrorBoundary") || staticExports.includes("ServerErrorBoundary");
6005
+ if (isDevMode) {
6006
+ result += `export function ReactRouterHMRMeta___() {return null;};
5963
6007
  `;
5964
- result += ` import.meta.hot.accept((mod) => {
5965
- if (typeof __reactRouterDataRouter === "object" && !mod.default) {
5966
- __reactRouterDataRouter.revalidate();
5967
- }
5968
- });
6008
+ result += `Object.assign(ReactRouterHMRMeta___, {
6009
+ hasAction: ${JSON.stringify(hasAction)},
6010
+ hasComponent: ${JSON.stringify(hasComponent)},
6011
+ hasErrorBoundary: ${JSON.stringify(hasErrorBoundary)},
6012
+ hasLoader: ${JSON.stringify(hasLoader)},
6013
+ hasClientLoader: ${JSON.stringify(staticExports.includes("clientLoader"))},
6014
+ });
5969
6015
  `;
5970
- result += `}
6016
+ result += `
6017
+ if (import.meta.hot) {
6018
+ `;
6019
+ result += ` import.meta.hot.accept((mod) => {
6020
+ if (typeof __reactRouterDataRouter === "object") {
6021
+ __reactRouterDataRouter._updateRoutesForHMR(new Map([[${JSON.stringify(routeId)}, {
6022
+ routeModule: mod,
6023
+ ...mod.ReactRouterHMRMeta___,
6024
+ }]]));
6025
+
6026
+ if (${chunk === "shared" ? "!mod.default || " : ""}mod.clientLoader || (
6027
+ mod.ReactRouterHMRMeta___.hasClientLoader || ReactRouterHMRMeta___.hasClientLoader || ReactRouterHMRMeta___.hasLoader
6028
+ )) {
6029
+ __reactRouterDataRouter.revalidate();
6030
+ }
6031
+ }
6032
+ });
6033
+ `;
6034
+ result += `}
5971
6035
  `;
6036
+ }
5972
6037
  return {
5973
6038
  code: result
5974
6039
  };
5975
6040
  }
5976
6041
  return {
5977
6042
  name: "react-router-rsc-virtual-route-modules",
5978
- transform: {
5979
- order: "pre",
5980
- async handler(_code, id) {
5981
- const [filename2, ...rest] = id.split("?");
5982
- if (!isRouteModule(filename2)) {
5983
- return;
5984
- }
5985
- let isClientEnvironment = clientEnvironments.has(this.environment.name);
5986
- let isServerEnvironment = serverEnvironments.has(this.environment.name);
5987
- if (!isClientEnvironment && !isServerEnvironment) {
5988
- return;
5989
- }
5990
- let code = await transformToJs(_code, filename2);
5991
- let searchParams = rest.length > 0 ? new URLSearchParams(rest.join("?")) : null;
5992
- let clientRouteModuleType = searchParams?.get("client-route-module");
5993
- let isServerRouteModule = searchParams?.has("server-route-module");
5994
- if (clientRouteModuleType) {
5995
- return await createClientRouteModuleChunk(
5996
- id,
5997
- code,
5998
- clientRouteModuleType,
5999
- isRootRouteModule(filename2)
6000
- );
6001
- }
6002
- if (isServerRouteModule) {
6003
- return createServerRouteModule(code);
6004
- }
6005
- if (isClientEnvironment) {
6006
- return await createClientRouteEntry(id, code);
6007
- }
6008
- return await createServerRouteEntry(
6043
+ enforce: "pre",
6044
+ async transform(_code, id) {
6045
+ const [filename2, ...rest] = id.split("?");
6046
+ const routeId = getRouteIdForFile(filename2);
6047
+ if (!routeId || shouldTransform && !shouldTransform?.(filename2)) {
6048
+ return;
6049
+ }
6050
+ let isClientEnvironment = clientEnvironments.has(this.environment.name);
6051
+ let isServerEnvironment = serverEnvironments.has(this.environment.name);
6052
+ if (!isClientEnvironment && !isServerEnvironment) {
6053
+ return;
6054
+ }
6055
+ let code = await transformToJs(_code, filename2);
6056
+ let searchParams = rest.length > 0 ? new URLSearchParams(rest.join("?")) : null;
6057
+ let clientRouteModuleType = searchParams?.get("client-route-module");
6058
+ let isServerRouteModule = searchParams?.has("server-route-module");
6059
+ if (clientRouteModuleType) {
6060
+ return await createClientRouteModuleChunk(
6009
6061
  id,
6010
6062
  code,
6011
- isRootRouteModule(filename2)
6063
+ clientRouteModuleType,
6064
+ routeId,
6065
+ isRootRouteModule(filename2),
6066
+ this.environment.mode === "dev"
6012
6067
  );
6013
6068
  }
6069
+ if (isServerRouteModule) {
6070
+ return createServerRouteModule(code);
6071
+ }
6072
+ if (isClientEnvironment) {
6073
+ return await createClientRouteEntry(
6074
+ id,
6075
+ code,
6076
+ isRootRouteModule(filename2),
6077
+ routeId
6078
+ );
6079
+ }
6080
+ return await createServerRouteEntry(
6081
+ id,
6082
+ code,
6083
+ isRootRouteModule(filename2),
6084
+ routeId
6085
+ );
6014
6086
  }
6015
6087
  };
6016
6088
  }
@@ -6100,7 +6172,7 @@ function validateRouteModuleExports(toValidate) {
6100
6172
  );
6101
6173
  }
6102
6174
  }
6103
- function detectRouteChunks2(cache, id, code) {
6175
+ function detectRouteChunks2(cache, id, code, isRootRouteModule) {
6104
6176
  function noRouteChunks() {
6105
6177
  return {
6106
6178
  chunkedExports: [],
@@ -6113,6 +6185,9 @@ function detectRouteChunks2(cache, id, code) {
6113
6185
  }
6114
6186
  };
6115
6187
  }
6188
+ if (isRootRouteModule) {
6189
+ return noRouteChunks();
6190
+ }
6116
6191
  if (!Array.from(CLIENT_MODULE_CHUNKS).some(
6117
6192
  (exportName) => code.includes(exportName)
6118
6193
  )) {
@@ -6121,6 +6196,23 @@ function detectRouteChunks2(cache, id, code) {
6121
6196
  let [filename2] = id.split("?");
6122
6197
  return detectRouteChunks(code, cache, filename2);
6123
6198
  }
6199
+ function validateRouteChunks2({
6200
+ id,
6201
+ valid
6202
+ }) {
6203
+ let invalidChunks = Object.entries(valid).filter(([_, isValid]) => !isValid).map(([chunkName]) => chunkName);
6204
+ if (invalidChunks.length === 0) {
6205
+ return;
6206
+ }
6207
+ let plural = invalidChunks.length > 1;
6208
+ throw new Error(
6209
+ [
6210
+ `Error splitting route module: ${id}`,
6211
+ invalidChunks.map((name) => `- ${name}`).join("\n"),
6212
+ `${plural ? "These exports" : "This export"} could not be split into ${plural ? "their own chunks" : "its own chunk"} because ${plural ? "they share" : "it shares"} code with other exports. You should extract any shared code into its own module and then import it within the route module.`
6213
+ ].join("\n\n")
6214
+ );
6215
+ }
6124
6216
 
6125
6217
  // vite/rsc/plugin.ts
6126
6218
  var redirectStatusCodes2 = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
@@ -6145,19 +6237,56 @@ function reactRouterRSCVitePlugin() {
6145
6237
  newConfig.routes.root.file
6146
6238
  );
6147
6239
  }
6148
- function isRouteModule(id) {
6149
- if (routeIdByFile?.has(id)) {
6150
- return true;
6151
- }
6152
- return false;
6153
- }
6154
6240
  function isRootRouteModule(id) {
6155
6241
  return import_pathe6.default.normalize(id) === import_pathe6.default.normalize(rootRouteFile);
6156
6242
  }
6243
+ function getRouteIdForFile(file) {
6244
+ let normalizedFile = import_pathe6.default.normalize(file);
6245
+ let directMatch = routeIdByFile?.get(normalizedFile);
6246
+ if (directMatch) {
6247
+ return directMatch;
6248
+ }
6249
+ return Array.from(routeIdByFile ?? []).find(
6250
+ ([routeFile]) => import_pathe6.default.normalize(routeFile).endsWith(normalizedFile)
6251
+ )?.[1];
6252
+ }
6253
+ function isMdxRouteModule(filename2) {
6254
+ let extension = import_pathe6.default.extname(filename2).toLowerCase();
6255
+ return extension === ".md" || extension === ".mdx";
6256
+ }
6257
+ function getTransformLanguage(filename2) {
6258
+ let extension = import_pathe6.default.extname(filename2).toLowerCase();
6259
+ switch (extension) {
6260
+ case ".ts":
6261
+ case ".cts":
6262
+ case ".mts":
6263
+ return "ts";
6264
+ case ".tsx":
6265
+ return "tsx";
6266
+ case ".js":
6267
+ case ".cjs":
6268
+ case ".mjs":
6269
+ case ".jsx":
6270
+ case ".md":
6271
+ case ".mdx":
6272
+ return "jsx";
6273
+ default:
6274
+ return void 0;
6275
+ }
6276
+ }
6157
6277
  async function transformToJs(code, filename2) {
6158
6278
  await preloadVite();
6159
6279
  let vite2 = getVite();
6160
- return (await vite2.transformWithEsbuild(code, filename2, {
6280
+ let lang = getTransformLanguage(filename2);
6281
+ return ("transformWithOxc" in vite2 && typeof vite2.transformWithOxc === "function" ? await vite2.transformWithOxc(code, filename2, {
6282
+ lang,
6283
+ jsx: {
6284
+ runtime: "automatic",
6285
+ development: viteCommand !== "build",
6286
+ target: "esnext"
6287
+ }
6288
+ }) : await vite2.transformWithEsbuild(code, filename2, {
6289
+ loader: lang,
6161
6290
  target: "esnext",
6162
6291
  format: "esm",
6163
6292
  jsx: "automatic",
@@ -6189,12 +6318,10 @@ function reactRouterRSCVitePlugin() {
6189
6318
  if (userConfig.serverBundles) errors.push("serverBundles");
6190
6319
  if (userConfig.future?.v8_middleware === false)
6191
6320
  errors.push("future.v8_middleware: false");
6192
- if (userConfig.future?.v8_splitRouteModules)
6193
- errors.push("future.v8_splitRouteModules");
6194
6321
  if (userConfig.future?.v8_viteEnvironmentApi === false)
6195
6322
  errors.push("future.v8_viteEnvironmentApi: false");
6196
- if (userConfig.future?.unstable_subResourceIntegrity)
6197
- errors.push("future.unstable_subResourceIntegrity");
6323
+ if (userConfig.subResourceIntegrity)
6324
+ errors.push("subResourceIntegrity");
6198
6325
  if (errors.length) {
6199
6326
  return `RSC Framework Mode does not currently support the following React Router config:
6200
6327
  ${errors.map((x) => ` - ${x}`).join("\n")}
@@ -6500,9 +6627,10 @@ ${errors.map((x) => ` - ${x}`).join("\n")}
6500
6627
  client: ["client", "ssr"],
6501
6628
  server: ["rsc"]
6502
6629
  },
6503
- isRouteModule,
6630
+ getRouteIdForFile,
6504
6631
  isRootRouteModule,
6505
- transformToJs
6632
+ transformToJs,
6633
+ enforceSplitRouteModules: () => config.future.v8_splitRouteModules === "enforce"
6506
6634
  }),
6507
6635
  {
6508
6636
  name: "react-router/rsc/virtual-basename",
@@ -6548,126 +6676,18 @@ ${errors.map((x) => ` - ${x}`).join("\n")}
6548
6676
  `if (import.meta.hot) {
6549
6677
  import.meta.hot.accept();
6550
6678
  import.meta.hot.on('rsc:update', () => {
6551
- __reactRouterDataRouter.revalidate()
6679
+ // Defer revalidation to the next animation frame so React Fast Refresh
6680
+ // can apply pending client component updates first. Without this delay,
6681
+ // the RSC payload (showing updated text) can arrive and be reconciled
6682
+ // against a DOM that still has the old text, causing a hydration mismatch.
6683
+ requestAnimationFrame(() => {
6684
+ __reactRouterDataRouter.revalidate()
6685
+ });
6552
6686
  })
6553
6687
  }`
6554
6688
  ].join("\n") : "";
6555
6689
  }
6556
6690
  },
6557
- // {
6558
- // name: "react-router/rsc/hmr/runtime",
6559
- // enforce: "pre",
6560
- // resolveId(id) {
6561
- // if (id === virtual.hmrRuntime.id) return virtual.hmrRuntime.resolvedId;
6562
- // },
6563
- // async load(id) {
6564
- // if (id !== virtual.hmrRuntime.resolvedId) return;
6565
- // const reactRefreshDir = path.dirname(
6566
- // require.resolve("react-refresh/package.json"),
6567
- // );
6568
- // const reactRefreshRuntimePath = join(
6569
- // reactRefreshDir,
6570
- // "cjs/react-refresh-runtime.development.js",
6571
- // );
6572
- // return [
6573
- // "const exports = {}",
6574
- // await readFile(reactRefreshRuntimePath, "utf8"),
6575
- // await readFile(
6576
- // require.resolve("./static/rsc-refresh-utils.mjs"),
6577
- // "utf8",
6578
- // ),
6579
- // "export default exports",
6580
- // ].join("\n");
6581
- // },
6582
- // },
6583
- // {
6584
- // name: "react-router/rsc/hmr/react-refresh",
6585
- // async transform(code, id, options) {
6586
- // if (viteCommand !== "serve") return;
6587
- // if (id.includes("/node_modules/")) return;
6588
- // const filepath = id.split("?")[0];
6589
- // const extensionsRE = /\.(jsx?|tsx?|mdx?)$/;
6590
- // if (!extensionsRE.test(filepath)) return;
6591
- // const devRuntime = "react/jsx-dev-runtime";
6592
- // const ssr = options?.ssr === true;
6593
- // const isJSX = filepath.endsWith("x");
6594
- // const useFastRefresh = !ssr && (isJSX || code.includes(devRuntime));
6595
- // if (!useFastRefresh) return;
6596
- // // if (isVirtualClientRouteModuleId(id)) {
6597
- // // const routeId = routeIdByFile?.get(filepath);
6598
- // // return { code: addRefreshWrapper({ routeId, code, id }) };
6599
- // // }
6600
- // const result = await babel.transformAsync(code, {
6601
- // babelrc: false,
6602
- // configFile: false,
6603
- // filename: id,
6604
- // sourceFileName: filepath,
6605
- // parserOpts: {
6606
- // sourceType: "module",
6607
- // allowAwaitOutsideFunction: true,
6608
- // },
6609
- // plugins: [[require("react-refresh/babel"), { skipEnvCheck: true }]],
6610
- // sourceMaps: true,
6611
- // });
6612
- // if (result === null) return;
6613
- // code = result.code!;
6614
- // const refreshContentRE = /\$Refresh(?:Reg|Sig)\$\(/;
6615
- // if (refreshContentRE.test(code)) {
6616
- // code = addRefreshWrapper({ code, id });
6617
- // }
6618
- // return { code, map: result.map };
6619
- // },
6620
- // },
6621
- // {
6622
- // name: "react-router/rsc/hmr/updates",
6623
- // async hotUpdate(this, { server, file, modules }) {
6624
- // if (this.environment.name !== "rsc") return;
6625
- // const clientModules =
6626
- // server.environments.client.moduleGraph.getModulesByFile(file);
6627
- // const vite = await import("vite");
6628
- // const isServerOnlyChange =
6629
- // !clientModules ||
6630
- // clientModules.size === 0 ||
6631
- // // Handle CSS injected from server-first routes (with ?direct query
6632
- // // string) since the client graph has a reference to the CSS
6633
- // (vite.isCSSRequest(file) &&
6634
- // Array.from(clientModules).some((mod) =>
6635
- // mod.id?.includes("?direct"),
6636
- // ));
6637
- // for (const mod of getModulesWithImporters(modules)) {
6638
- // if (!mod.file) continue;
6639
- // const normalizedPath = path.normalize(mod.file);
6640
- // const routeId = routeIdByFile?.get(normalizedPath);
6641
- // if (routeId !== undefined) {
6642
- // const routeSource = await readFile(normalizedPath, "utf8");
6643
- // const virtualRouteModuleCode = (
6644
- // await server.environments.rsc.pluginContainer.transform(
6645
- // routeSource,
6646
- // `${normalizedPath}?route-module`,
6647
- // )
6648
- // ).code;
6649
- // const { staticExports } = parseRouteExports(virtualRouteModuleCode);
6650
- // const hasAction = staticExports.includes("action");
6651
- // const hasComponent = staticExports.includes("default");
6652
- // const hasErrorBoundary = staticExports.includes("ErrorBoundary");
6653
- // const hasLoader = staticExports.includes("loader");
6654
- // server.hot.send({
6655
- // type: "custom",
6656
- // event: "react-router:hmr",
6657
- // data: {
6658
- // routeId,
6659
- // isServerOnlyChange,
6660
- // hasAction,
6661
- // hasComponent,
6662
- // hasErrorBoundary,
6663
- // hasLoader,
6664
- // },
6665
- // });
6666
- // }
6667
- // }
6668
- // return modules;
6669
- // },
6670
- // },
6671
6691
  {
6672
6692
  name: "react-router/rsc/virtual-react-router-serve-config",
6673
6693
  resolveId(id) {
@@ -6791,7 +6811,6 @@ var virtual2 = {
6791
6811
  routeConfig: create("unstable_rsc/routes"),
6792
6812
  routeDiscovery: create("unstable_rsc/route-discovery"),
6793
6813
  injectHmrRuntime: create("unstable_rsc/inject-hmr-runtime"),
6794
- // hmrRuntime: create("unstable_rsc/runtime"),
6795
6814
  basename: create("unstable_rsc/basename"),
6796
6815
  reactRouterServeConfig: create("unstable_rsc/react-router-serve-config")
6797
6816
  };
@@ -6812,8 +6831,8 @@ var getClientBuildDirectory2 = (reactRouterConfig) => import_pathe6.default.join
6812
6831
  function getPrerenderConcurrencyConfig2(reactRouterConfig) {
6813
6832
  let concurrency = 1;
6814
6833
  let { prerender: prerender2 } = reactRouterConfig;
6815
- if (typeof prerender2 === "object" && "unstable_concurrency" in prerender2) {
6816
- concurrency = prerender2.unstable_concurrency ?? 1;
6834
+ if (typeof prerender2 === "object" && "concurrency" in prerender2) {
6835
+ concurrency = prerender2.concurrency ?? 1;
6817
6836
  }
6818
6837
  return concurrency;
6819
6838
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-router/dev",
3
- "version": "0.0.0-experimental-cb0fdf114",
3
+ "version": "0.0.0-experimental-34996874d",
4
4
  "description": "Dev tools and CLI for React Router",
5
5
  "homepage": "https://reactrouter.com",
6
6
  "bugs": {
@@ -92,7 +92,7 @@
92
92
  "tinyglobby": "^0.2.14",
93
93
  "valibot": "^1.2.0",
94
94
  "vite-node": "^3.2.2",
95
- "@react-router/node": "0.0.0-experimental-cb0fdf114"
95
+ "@react-router/node": "0.0.0-experimental-34996874d"
96
96
  },
97
97
  "devDependencies": {
98
98
  "@types/babel__core": "^7.20.5",
@@ -116,17 +116,17 @@
116
116
  "vite": "^6.3.0",
117
117
  "wireit": "0.14.9",
118
118
  "wrangler": "^4.23.0",
119
- "@react-router/serve": "0.0.0-experimental-cb0fdf114",
120
- "react-router": "^0.0.0-experimental-cb0fdf114"
119
+ "@react-router/serve": "0.0.0-experimental-34996874d",
120
+ "react-router": "^0.0.0-experimental-34996874d"
121
121
  },
122
122
  "peerDependencies": {
123
123
  "@vitejs/plugin-rsc": "~0.5.21",
124
124
  "react-server-dom-webpack": "^19.2.3",
125
- "typescript": "^5.1.0",
125
+ "typescript": "^5.1.0 || ^6.0.0",
126
126
  "vite": "^5.1.0 || ^6.0.0 || ^7.0.0 || ^8.0.0",
127
127
  "wrangler": "^3.28.2 || ^4.0.0",
128
- "@react-router/serve": "^0.0.0-experimental-cb0fdf114",
129
- "react-router": "^0.0.0-experimental-cb0fdf114"
128
+ "@react-router/serve": "^0.0.0-experimental-34996874d",
129
+ "react-router": "^0.0.0-experimental-34996874d"
130
130
  },
131
131
  "peerDependenciesMeta": {
132
132
  "@vitejs/plugin-rsc": {