rsbuild-plugin-react-router 0.4.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/README.md +72 -240
  2. package/dist/{451.js → 511.js} +461 -331
  3. package/dist/build-output-transforms.d.ts +32 -7
  4. package/dist/classic-mode.d.ts +56 -0
  5. package/dist/config-imports.d.ts +8 -2
  6. package/dist/constants.d.ts +3 -0
  7. package/dist/dev-background-resources.d.ts +3 -3
  8. package/dist/dev-generation.d.ts +2 -3
  9. package/dist/dev-hmr.d.ts +8 -2
  10. package/dist/dev-runtime-controller.d.ts +6 -1
  11. package/dist/dev-source-maps.d.ts +4 -0
  12. package/dist/effect-runtime.d.ts +17 -5
  13. package/dist/entry-paths.d.ts +16 -0
  14. package/dist/environment-output.d.ts +6 -0
  15. package/dist/export-utils.d.ts +2 -1
  16. package/dist/federation.d.ts +2 -0
  17. package/dist/index.cjs +3675 -2107
  18. package/dist/index.d.ts +3 -1
  19. package/dist/index.js +3614 -2214
  20. package/dist/lazy-compilation-prewarm.d.ts +8 -5
  21. package/dist/manifest.d.ts +16 -4
  22. package/dist/mode-plan.d.ts +82 -0
  23. package/dist/modify-browser-manifest.d.ts +8 -4
  24. package/dist/performance.d.ts +4 -1
  25. package/dist/plugin-utils.d.ts +27 -1
  26. package/dist/prerender-build.d.ts +5 -5
  27. package/dist/prerender.d.ts +1 -5
  28. package/dist/react-router-config.d.ts +11 -3
  29. package/dist/route-artifacts.d.ts +4 -2
  30. package/dist/route-chunks.d.ts +2 -1
  31. package/dist/route-component-transform.d.ts +0 -2
  32. package/dist/route-export-pruning.d.ts +4 -1
  33. package/dist/route-imports.d.ts +18 -0
  34. package/dist/route-module-transform-loader.d.ts +14 -0
  35. package/dist/route-module-transform-loader.js +44 -0
  36. package/dist/route-module-transform-rules.d.ts +14 -0
  37. package/dist/route-transform-tasks.d.ts +6 -0
  38. package/dist/route-watch.d.ts +11 -6
  39. package/dist/rsc-dev-server.d.ts +26 -0
  40. package/dist/rsc-prerender.d.ts +54 -0
  41. package/dist/rsc-route-config.d.ts +6 -0
  42. package/dist/rsc-route-exports.d.ts +15 -0
  43. package/dist/rsc-route-transform-loader.cjs +43 -0
  44. package/dist/rsc-route-transform-loader.d.ts +30 -0
  45. package/dist/rsc-route-transform-loader.js +16 -0
  46. package/dist/rsc-route-transform-registration.d.ts +12 -0
  47. package/dist/rsc-route-transforms.d.ts +21 -0
  48. package/dist/rsc-support.d.ts +23 -0
  49. package/dist/rsc-virtual-modules.d.ts +19 -0
  50. package/dist/server-build-plan.d.ts +2 -1
  51. package/dist/server-build-resolution.d.ts +1 -2
  52. package/dist/server-utils.d.ts +4 -5
  53. package/dist/ssr-asset-relocation.d.ts +98 -0
  54. package/dist/templates/entry.rsc.client.d.ts +1 -0
  55. package/dist/templates/entry.rsc.client.js +61 -0
  56. package/dist/templates/entry.rsc.d.ts +9 -0
  57. package/dist/templates/entry.rsc.js +38 -0
  58. package/dist/templates/entry.rsc.ssr.d.ts +4 -0
  59. package/dist/templates/entry.rsc.ssr.js +24 -0
  60. package/dist/typegen.d.ts +4 -2
  61. package/dist/types.d.ts +34 -6
  62. package/package.json +76 -22
  63. package/src/build-output-transforms.ts +181 -43
  64. package/src/classic-mode.ts +253 -0
  65. package/src/config-imports.ts +153 -6
  66. package/src/constants.ts +6 -2
  67. package/src/dev-background-resources.ts +46 -100
  68. package/src/dev-generation.ts +52 -33
  69. package/src/dev-hmr.ts +112 -80
  70. package/src/dev-runtime-artifacts.ts +11 -12
  71. package/src/dev-runtime-controller.ts +75 -85
  72. package/src/dev-runtime-session.ts +14 -18
  73. package/src/dev-server.ts +2 -0
  74. package/src/dev-source-maps.ts +257 -0
  75. package/src/effect-runtime.ts +105 -57
  76. package/src/entry-paths.ts +80 -0
  77. package/src/environment-output.ts +55 -0
  78. package/src/export-utils.ts +15 -11
  79. package/src/federation.ts +38 -0
  80. package/src/index.ts +666 -519
  81. package/src/lazy-compilation-prewarm.ts +20 -4
  82. package/src/manifest.ts +157 -82
  83. package/src/mode-plan.ts +367 -0
  84. package/src/modify-browser-manifest.ts +130 -124
  85. package/src/performance.ts +11 -2
  86. package/src/plugin-utils.ts +103 -39
  87. package/src/prerender-build.ts +82 -104
  88. package/src/prerender.ts +23 -24
  89. package/src/react-router-config.ts +72 -26
  90. package/src/route-artifacts.ts +96 -66
  91. package/src/route-chunks.ts +167 -51
  92. package/src/route-component-transform.ts +14 -21
  93. package/src/route-export-pruning.ts +4 -3
  94. package/src/route-imports.ts +100 -0
  95. package/src/route-module-transform-loader.ts +149 -0
  96. package/src/route-module-transform-rules.ts +115 -0
  97. package/src/route-transform-tasks.ts +44 -29
  98. package/src/route-watch.ts +166 -188
  99. package/src/rsc-dev-server.ts +112 -0
  100. package/src/rsc-prerender.ts +362 -0
  101. package/src/rsc-route-config.ts +175 -0
  102. package/src/rsc-route-exports.ts +67 -0
  103. package/src/rsc-route-transform-loader.ts +65 -0
  104. package/src/rsc-route-transform-registration.ts +145 -0
  105. package/src/rsc-route-transforms.ts +995 -0
  106. package/src/rsc-runtime.d.ts +143 -0
  107. package/src/rsc-support.ts +116 -0
  108. package/src/rsc-virtual-modules.ts +113 -0
  109. package/src/server-build-plan.ts +14 -3
  110. package/src/server-build-resolution.ts +37 -47
  111. package/src/server-utils.ts +21 -35
  112. package/src/ssr-asset-relocation.ts +183 -0
  113. package/src/ssr-externals.ts +8 -26
  114. package/src/templates/entry.rsc.client.tsx +168 -0
  115. package/src/templates/entry.rsc.ssr.tsx +45 -0
  116. package/src/templates/entry.rsc.tsx +80 -0
  117. package/src/typegen.ts +40 -23
  118. package/src/types.ts +43 -6
  119. package/src/warnings/warn-on-client-source-maps.ts +6 -10
  120. package/dist/parallel-route-transform-protocol.d.ts +0 -25
  121. package/dist/parallel-route-transform-worker.d.ts +0 -1
  122. package/dist/parallel-route-transform-worker.js +0 -38
  123. package/dist/parallel-route-transforms.d.ts +0 -29
  124. package/src/parallel-route-transform-protocol.ts +0 -35
  125. package/src/parallel-route-transform-worker.ts +0 -82
  126. package/src/parallel-route-transforms.ts +0 -458
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rsbuild-plugin-react-router",
3
- "version": "0.4.1",
3
+ "version": "0.6.0",
4
4
  "description": "React Router plugin for Rsbuild",
5
5
  "repository": {
6
6
  "type": "git",
@@ -37,6 +37,18 @@
37
37
  "types": "./dist/templates/entry.server.d.ts",
38
38
  "import": "./dist/templates/entry.server.js",
39
39
  "require": "./dist/templates/entry.server.cjs"
40
+ },
41
+ "./templates/entry.rsc": {
42
+ "types": "./dist/templates/entry.rsc.d.ts",
43
+ "import": "./dist/templates/entry.rsc.js"
44
+ },
45
+ "./templates/entry.rsc.client": {
46
+ "types": "./dist/templates/entry.rsc.client.d.ts",
47
+ "import": "./dist/templates/entry.rsc.client.js"
48
+ },
49
+ "./templates/entry.rsc.ssr": {
50
+ "types": "./dist/templates/entry.rsc.ssr.d.ts",
51
+ "import": "./dist/templates/entry.rsc.ssr.js"
40
52
  }
41
53
  },
42
54
  "main": "./dist/index.cjs",
@@ -49,8 +61,9 @@
49
61
  "ignoreMissingDts": true
50
62
  },
51
63
  "dependencies": {
52
- "@react-router/node": "^7.13.0",
53
- "@remix-run/node-fetch-server": "^0.13.0",
64
+ "@jridgewell/remapping": "^2.3.5",
65
+ "@react-router/node": "^7.13.0 || ^8.0.0",
66
+ "@remix-run/node-fetch-server": "^0.13.3",
54
67
  "@rspack/plugin-react-refresh": "^2.0.2",
55
68
  "execa": "^9.6.1",
56
69
  "fs-extra": "11.3.3",
@@ -65,39 +78,78 @@
65
78
  },
66
79
  "devDependencies": {
67
80
  "@changesets/cli": "^2.29.8",
81
+ "@codspeed/vitest-plugin": "^5.7.1",
82
+ "@jridgewell/trace-mapping": "^0.3.31",
83
+ "@playwright/test": "^1.61.1",
68
84
  "@react-router/dev": "^8.0.1",
69
- "@rsbuild/core": "2.1.0",
85
+ "@react-router/express": "^8.0.1",
86
+ "@react-router/fs-routes": "^8.0.1",
87
+ "@react-router/remix-routes-option-adapter": "^8.0.1",
88
+ "@react-router/serve": "^8.0.1",
89
+ "@rsbuild/core": "2.2.0",
90
+ "@rsbuild/plugin-less": "1.6.4",
91
+ "@rsbuild/plugin-mdx": "^1.1.3",
70
92
  "@rsbuild/plugin-react": "2.1.0",
71
- "@rslib/core": "^0.22.1",
72
- "@rspack/core": "2.1.0",
73
- "@rstest/core": "^0.8.1",
74
- "@rstest/coverage-istanbul": "^0.2.0",
93
+ "@rsbuild/plugin-sass": "1.5.3",
94
+ "@rsbuild/plugin-svgr": "2.0.4",
95
+ "@rsbuild/plugin-tailwindcss": "2.0.3",
96
+ "@rslib/core": "^0.23.2",
97
+ "@rspack/core": "2.2.0",
98
+ "@rstest/core": "^0.11.4",
99
+ "@rstest/coverage-istanbul": "^0.11.4",
75
100
  "@swc/helpers": "^0.5.23",
76
101
  "@types/fs-extra": "11.0.4",
77
102
  "@types/jsesc": "^3.0.3",
78
103
  "@types/node": "^25.0.10",
79
104
  "@types/react": "^19.2.10",
80
105
  "@types/react-dom": "^19.2.3",
81
- "effect": "^3.21.4",
106
+ "@types/webpack-env": "^1.18.8",
107
+ "@vanilla-extract/css": "^1.20.1",
108
+ "cheerio": "^1.2.0",
109
+ "cross-spawn": "^7.0.6",
110
+ "dedent": "^1.7.2",
111
+ "effect": "^3.22.0",
82
112
  "es-module-lexer": "1.7.0",
113
+ "express": "^4.22.2",
114
+ "fast-glob": "^3.3.3",
115
+ "get-port": "7.1.0",
83
116
  "kill-port": "^2.0.1",
84
117
  "pkg-pr-new": "^0.0.75",
85
118
  "playwright": "^1.61.1",
86
119
  "prettier": "3.8.1",
87
120
  "react": "^19.2.4",
88
121
  "react-dom": "^19.2.4",
89
- "react-router": "^7.13.0",
90
- "react-router-dom": "^7.13.0",
122
+ "react-router": "^7.18.0",
123
+ "react-router-dom": "^7.18.0",
124
+ "react-server-dom-rspack": "0.1.0",
125
+ "rsbuild-plugin-rsc": "^0.1.1",
126
+ "semver": "^7.8.5",
127
+ "shelljs": "^0.10.0",
128
+ "strip-ansi": "^7.2.0",
129
+ "strip-indent": "^4.1.1",
130
+ "tailwindcss": "4.3.2",
131
+ "type-fest": "^5.7.0",
91
132
  "typescript": "^5.9.3",
133
+ "vitest": "^4.1.10",
134
+ "wait-on": "^9.0.10",
92
135
  "@rsbuild/config": "1.0.1"
93
136
  },
94
137
  "peerDependencies": {
95
138
  "@react-router/dev": "^7.13.0 || ^8.0.0",
96
- "@rsbuild/core": "^2.0.0"
139
+ "@rsbuild/core": "^2.2.0",
140
+ "react-router": "^7.13.0 || ^8.0.0",
141
+ "react-server-dom-rspack": "0.1.0",
142
+ "rsbuild-plugin-rsc": "^0.1.1"
97
143
  },
98
144
  "peerDependenciesMeta": {
99
145
  "@react-router/dev": {
100
146
  "optional": true
147
+ },
148
+ "react-server-dom-rspack": {
149
+ "optional": true
150
+ },
151
+ "rsbuild-plugin-rsc": {
152
+ "optional": true
101
153
  }
102
154
  },
103
155
  "publishConfig": {
@@ -107,22 +159,24 @@
107
159
  },
108
160
  "scripts": {
109
161
  "build": "rslib build",
110
- "bench": "node scripts/bench-builds.mts",
111
- "bench:ci-report": "node scripts/report-benchmark-ci.mts",
112
- "bench:compare": "node scripts/compare-benchmarks.mts",
113
- "bench:smoke": "node scripts/bench-builds.mts --profile smoke --iterations 1 --warmup 0 --format both --out .benchmark/results/smoke",
114
- "bench:baseline": "node scripts/bench-builds.mts --profile default --iterations 5 --warmup 1 --clean build --format both --out .benchmark/results/baseline",
115
- "bench:full": "node scripts/bench-builds.mts --profile full --iterations 5 --warmup 1 --clean build --format both --out .benchmark/results/full",
116
- "bench:large": "node scripts/bench-builds.mts --profile large --iterations 1 --warmup 0 --clean cold --format both --out .benchmark/results/large",
117
- "bench:synthetic-app": "node scripts/bench-synthetic-app.mjs",
118
- "e2e": "pnpm build && pnpm test:package-interop && pnpm --filter './examples/{default-template,spa-mode,prerender,custom-node-server,cloudflare,client-only}' test:e2e",
162
+ "bench": "pnpm build && node benchmarks/run.mts",
163
+ "bench:smoke": "pnpm build && node benchmarks/run.mts --iterations 1 --warmup 0",
164
+ "bench:codspeed": "pnpm build && vitest bench --run --config vitest.benchmark.config.mts -t build-256-ssr",
165
+ "e2e": "pnpm build && pnpm e2e:no-build",
166
+ "e2e:no-build": "pnpm test:package-interop && pnpm --filter './examples/{default-template,spa-mode,prerender,custom-node-server,cloudflare,client-only,react-router-8,rsc-mode}' test:e2e",
119
167
  "dev": "rslib build --watch",
120
- "test": "rstest run",
168
+ "test": "pnpm typecheck && rstest run",
169
+ "typecheck": "tsc --noEmit && tsc -p tsconfig.tests.json && pnpm --filter integration typecheck && pnpm --filter integration-rsbuild-template typecheck && pnpm --filter integration-rsc-framework typecheck && pnpm --filter integration-rsc-preview typecheck",
121
170
  "test:watch": "rstest watch",
122
171
  "test:coverage": "rstest run --coverage",
123
172
  "test:core": "rstest run -c ./rstest.config.ts",
124
173
  "test:core:watch": "rstest watch -c ./rstest.config.ts",
125
174
  "test:package-interop": "node scripts/test-package-interop.mts",
175
+ "test:react-router-framework": "pnpm build && playwright test --config tests/react-router-framework/integration/playwright.config.ts",
176
+ "test:react-router-framework:failfast": "pnpm build && pnpm test:react-router-framework:failfast:no-build",
177
+ "test:react-router-framework:failfast:no-build": "RR_FRAMEWORK_MAX_WORKERS=2 playwright test --config tests/react-router-framework/integration/playwright.config.ts tests/react-router-framework/integration/hmr-hdr-test.ts --workers=2 --max-failures=1 --retries=0 --reporter=line && RR_FRAMEWORK_MAX_WORKERS=2 playwright test --config tests/react-router-framework/integration/playwright.config.ts tests/react-router-framework/integration/hmr-hdr-rsc-test.ts --workers=2 --max-failures=1 --retries=0 --reporter=line && RR_FRAMEWORK_MAX_WORKERS=4 playwright test --config tests/react-router-framework/integration/playwright.config.ts --grep-invert 'HMR & HDR' --workers=4 --max-failures=1 --retries=0 --reporter=line",
178
+ "test:react-router-framework:smoke": "pnpm build && playwright test --config tests/react-router-framework/integration/playwright.config.ts use-route-test.ts loader-test.ts",
179
+ "check:react-router-framework-upstream": "node scripts/check-react-router-framework-upstream.mjs",
126
180
  "format": "prettier --write \"src/**/*.{js,jsx,ts,tsx}\"",
127
181
  "format:check": "prettier --check \"src/**/*.{js,jsx,ts,tsx}\"",
128
182
  "changeset": "changeset",
@@ -3,19 +3,77 @@ import jsesc from 'jsesc';
3
3
  import { relative } from 'pathe';
4
4
  import { PLUGIN_NAME } from './constants.js';
5
5
  import {
6
+ createReactRouterManifestOptions,
6
7
  getReactRouterManifestForDev,
8
+ type ReactRouterManifestForDev as ReactRouterManifest,
9
+ type RouteChunkManifestOptions,
10
+ type RouteModuleAnalysisProvider,
7
11
  type ReactRouterManifestStats,
8
12
  } from './manifest.js';
9
- import type { RouteTransformExecutor } from './parallel-route-transforms.js';
10
13
  import type { ReactRouterPerformanceProfiler } from './performance.js';
14
+ import {
15
+ validateSpaModeRouteExports,
16
+ type RouteTransformRunner,
17
+ } from './route-transform-tasks.js';
11
18
  import { createBundlerRouteExportResolver } from './route-export-resolution.js';
12
- import type { RouteChunkConfig } from './route-chunks.js';
19
+ import {
20
+ getRouteChunkNameFromModuleId,
21
+ type RouteChunkConfig,
22
+ } from './route-chunks.js';
13
23
  import type { PluginOptions, Route } from './types.js';
14
24
  import { isSourceMapEnabled } from './warnings/warn-on-client-source-maps.js';
25
+ import {
26
+ analyzeRouteModuleCode,
27
+ type RouteModuleAnalysis,
28
+ } from './export-utils.js';
29
+ import {
30
+ relocateServerAssetsToClient,
31
+ type RelocatableAssetCompilation,
32
+ } from './ssr-asset-relocation.js';
15
33
 
16
- type ReactRouterManifest = Awaited<
17
- ReturnType<typeof getReactRouterManifestForDev>
18
- >;
34
+ /**
35
+ * Register the node-compilation hook that relocates server-only static assets
36
+ * (`?url` imports, `.css?url` files, and other `asset/resource` outputs
37
+ * referenced only by loaders or `.server` modules) into the client build. The
38
+ * loader/`links()` export returns the asset URL to the client, which fetches it
39
+ * from `build/client` at runtime, so the file must exist there even though only
40
+ * the node compilation referenced it. The assets are also stripped from the
41
+ * server build to avoid shipping duplicate static files, mirroring upstream
42
+ * React Router's Vite plugin. This runs for every node compilation, so it also
43
+ * covers `serverBundles` (multiple node outputs) and dev mode (where
44
+ * `writeToDisk` is enabled).
45
+ *
46
+ * Registered by both the classic build-output transforms and the RSC branch so
47
+ * `.css?url`/`?url` assets referenced from `links()` resolve in RSC framework
48
+ * mode too.
49
+ */
50
+ export const registerSsrAssetRelocation = ({
51
+ api,
52
+ outputClientPath,
53
+ performanceProfiler,
54
+ }: {
55
+ api: RsbuildPluginAPI;
56
+ outputClientPath: string;
57
+ performanceProfiler: ReactRouterPerformanceProfiler;
58
+ }): void => {
59
+ const relocatedDestinations = new Map<string, string>();
60
+ api.processAssets(
61
+ { stage: 'report', targets: ['node'] },
62
+ async ({ compilation }) => {
63
+ await performanceProfiler.record(
64
+ 'node',
65
+ 'assets:relocate-ssr-only',
66
+ 'ssr-only-assets',
67
+ () =>
68
+ relocateServerAssetsToClient({
69
+ compilation: compilation as unknown as RelocatableAssetCompilation,
70
+ outputClientPath,
71
+ relocatedDestinations,
72
+ })
73
+ );
74
+ }
75
+ );
76
+ };
19
77
 
20
78
  type RegisterBuildOutputTransformsOptions = {
21
79
  api: RsbuildPluginAPI;
@@ -30,16 +88,23 @@ type RegisterBuildOutputTransformsOptions = {
30
88
  getClientStats: () => ReactRouterManifestStats | undefined;
31
89
  appDirectory: string;
32
90
  getAssetPrefix: () => string;
33
- routeChunkOptions: Parameters<typeof getReactRouterManifestForDev>[5];
34
- routeTransformExecutor: RouteTransformExecutor;
91
+ routeChunkOptions: RouteChunkManifestOptions | undefined;
92
+ routeModuleAnalysis?: RouteModuleAnalysisProvider;
93
+ routeTransformRunner: RouteTransformRunner;
35
94
  routeByFilePath: Map<string, Route>;
36
95
  routeChunkConfig: RouteChunkConfig;
37
96
  isBuild: boolean;
38
97
  splitRouteModules: boolean;
98
+ useRouteModuleTransformApi: boolean;
39
99
  ssr: boolean;
40
100
  isSpaMode: boolean;
41
101
  rootRoutePath: string;
102
+ outputClientPath: string;
42
103
  isDevHmrEnabled?: () => boolean;
104
+ onRouteModuleAnalysis?: (
105
+ resourcePath: string,
106
+ analysis: RouteModuleAnalysis
107
+ ) => void;
43
108
  };
44
109
 
45
110
  export const registerBuildOutputTransforms = ({
@@ -54,23 +119,41 @@ export const registerBuildOutputTransforms = ({
54
119
  appDirectory,
55
120
  getAssetPrefix,
56
121
  routeChunkOptions,
57
- routeTransformExecutor,
122
+ routeModuleAnalysis,
123
+ routeTransformRunner,
58
124
  routeByFilePath,
59
125
  routeChunkConfig,
60
126
  isBuild,
61
127
  splitRouteModules,
128
+ useRouteModuleTransformApi,
62
129
  ssr,
63
130
  isSpaMode,
64
131
  rootRoutePath,
132
+ outputClientPath,
65
133
  isDevHmrEnabled = () => false,
134
+ onRouteModuleAnalysis,
66
135
  }: RegisterBuildOutputTransformsOptions): void => {
67
- const transformRouteModule = async (args: Parameters<TransformHandler>[0]) =>
68
- performanceProfiler.record(
136
+ const rememberRouteModuleAnalysis = (
137
+ args: Parameters<TransformHandler>[0]
138
+ ): void => {
139
+ if (!routeByFilePath.has(args.resourcePath)) {
140
+ return;
141
+ }
142
+ onRouteModuleAnalysis?.(
143
+ args.resourcePath,
144
+ analyzeRouteModuleCode(args.code)
145
+ );
146
+ };
147
+
148
+ const transformRouteModule = async (
149
+ args: Parameters<TransformHandler>[0]
150
+ ) => {
151
+ return performanceProfiler.record(
69
152
  args.environment?.name,
70
153
  'route:module',
71
154
  args.resource,
72
155
  async () =>
73
- routeTransformExecutor.run({
156
+ routeTransformRunner({
74
157
  kind: 'routeModule',
75
158
  code: args.code,
76
159
  resource: args.resource,
@@ -86,6 +169,7 @@ export const registerBuildOutputTransforms = ({
86
169
  devHmr: isDevHmrEnabled(),
87
170
  })
88
171
  );
172
+ };
89
173
 
90
174
  api.processAssets(
91
175
  { stage: 'additional', targets: ['node'] },
@@ -103,6 +187,8 @@ export const registerBuildOutputTransforms = ({
103
187
  }
104
188
  );
105
189
 
190
+ registerSsrAssetRelocation({ api, outputClientPath, performanceProfiler });
191
+
106
192
  api.transform(
107
193
  {
108
194
  test: /virtual\/react-router\/(browser|server)-manifest/,
@@ -135,7 +221,10 @@ export const registerBuildOutputTransforms = ({
135
221
  getClientStats(),
136
222
  appDirectory,
137
223
  getAssetPrefix(),
138
- routeChunkOptions
224
+ createReactRouterManifestOptions({
225
+ routeChunks: routeChunkOptions,
226
+ routeModuleAnalysis,
227
+ })
139
228
  ));
140
229
  return {
141
230
  code: `export default ${jsesc(manifest, { es6: true })};`,
@@ -147,14 +236,16 @@ export const registerBuildOutputTransforms = ({
147
236
  api.transform(
148
237
  {
149
238
  resourceQuery: /__react-router-build-client-route/,
239
+ order: 'post',
150
240
  },
151
- async args =>
152
- performanceProfiler.record(
241
+ async args => {
242
+ rememberRouteModuleAnalysis(args);
243
+ return performanceProfiler.record(
153
244
  args.environment?.name,
154
245
  'route:client-entry',
155
246
  args.resource,
156
247
  async () =>
157
- routeTransformExecutor.run({
248
+ routeTransformRunner({
158
249
  kind: 'routeClientEntry',
159
250
  code: args.code,
160
251
  resourcePath: args.resourcePath,
@@ -164,29 +255,67 @@ export const registerBuildOutputTransforms = ({
164
255
  routeId: routeByFilePath.get(args.resourcePath)?.id,
165
256
  devHmr: isDevHmrEnabled(),
166
257
  })
167
- )
258
+ );
259
+ }
168
260
  );
169
261
 
170
262
  api.transform(
171
263
  {
172
264
  resourceQuery: /route-chunk=/,
173
265
  environments: ['web'],
266
+ order: 'post',
174
267
  },
175
- async args =>
176
- performanceProfiler.record(
268
+ async args => {
269
+ return performanceProfiler.record(
177
270
  args.environment?.name,
178
271
  'route:chunk',
179
272
  args.resource,
180
- async () =>
181
- routeTransformExecutor.run({
273
+ async () => {
274
+ const routeChunkName = getRouteChunkNameFromModuleId(args.resource);
275
+ if (isBuild && isSpaMode && routeChunkName === 'main') {
276
+ validateSpaModeRouteExports({
277
+ exportNames: analyzeRouteModuleCode(args.code, args.resourcePath)
278
+ .exports,
279
+ resourcePath: args.resourcePath,
280
+ rootRoutePath,
281
+ });
282
+ }
283
+
284
+ const routeChunkArtifact = await routeTransformRunner({
182
285
  kind: 'routeChunk',
183
286
  code: args.code,
184
287
  resource: args.resource,
185
288
  resourcePath: args.resourcePath,
186
289
  isBuild,
187
290
  routeChunkConfig,
188
- })
189
- )
291
+ });
292
+
293
+ // Invariant with the transformRouteModule registration below: in
294
+ // split-chunk production builds, web route modules are transformed
295
+ // HERE (on the main chunk) and the shared registration is scoped to
296
+ // ['node']. If either gate changes, web modules get transformed
297
+ // twice or not at all.
298
+ if (!isBuild || routeChunkName !== 'main') {
299
+ return routeChunkArtifact;
300
+ }
301
+
302
+ return routeTransformRunner({
303
+ kind: 'routeModule',
304
+ code: routeChunkArtifact.code,
305
+ resource: args.resource,
306
+ resourcePath: args.resourcePath,
307
+ environmentName: 'web',
308
+ sourceMaps: isSourceMapEnabled(
309
+ args.environment.config.output.sourceMap
310
+ ),
311
+ ssr,
312
+ isBuild,
313
+ isSpaMode,
314
+ rootRoutePath,
315
+ });
316
+ }
317
+ );
318
+ }
190
319
  );
191
320
 
192
321
  if (isBuild && splitRouteModules) {
@@ -197,20 +326,22 @@ export const registerBuildOutputTransforms = ({
197
326
  not: /__react-router-build-client-route|react-router-route|route-chunk=/,
198
327
  },
199
328
  environments: ['web'],
329
+ order: 'post',
200
330
  },
201
- async args =>
202
- performanceProfiler.record(
331
+ async args => {
332
+ return performanceProfiler.record(
203
333
  args.environment?.name,
204
334
  'route:split-exports',
205
335
  args.resource,
206
336
  async () =>
207
- routeTransformExecutor.run({
337
+ routeTransformRunner({
208
338
  kind: 'splitRouteExports',
209
339
  code: args.code,
210
340
  resourcePath: args.resourcePath,
211
341
  routeChunkConfig,
212
342
  })
213
- )
343
+ );
344
+ }
214
345
  );
215
346
  }
216
347
 
@@ -244,7 +375,7 @@ export const registerBuildOutputTransforms = ({
244
375
  'module:client-only-stub',
245
376
  args.resource,
246
377
  async () => {
247
- return routeTransformExecutor.run({
378
+ return routeTransformRunner({
248
379
  kind: 'clientOnlyStub',
249
380
  code: args.code,
250
381
  resourcePath: args.resourcePath,
@@ -257,22 +388,29 @@ export const registerBuildOutputTransforms = ({
257
388
  )
258
389
  );
259
390
 
260
- api.transform(
261
- {
262
- resourceQuery: /\?react-router-route/,
263
- order: 'post',
264
- },
265
- transformRouteModule
266
- );
391
+ if (useRouteModuleTransformApi) {
392
+ api.transform(
393
+ {
394
+ resourceQuery: /\?react-router-route/,
395
+ order: 'post',
396
+ },
397
+ transformRouteModule
398
+ );
267
399
 
268
- api.transform(
269
- {
270
- test: path => routeByFilePath.has(path),
271
- resourceQuery: {
272
- not: /__react-router-build-client-route|react-router-route|route-chunk=/,
400
+ api.transform(
401
+ {
402
+ test: path => routeByFilePath.has(path),
403
+ resourceQuery: {
404
+ not: /__react-router-build-client-route|react-router-route|route-chunk=/,
405
+ },
406
+ // Invariant with the route-chunk= handler above: when split-chunk
407
+ // production builds transform web modules on the main chunk, this
408
+ // registration must stay scoped to ['node'] so web modules are not
409
+ // transformed twice.
410
+ environments: isBuild && splitRouteModules ? ['node'] : undefined,
411
+ order: 'post',
273
412
  },
274
- order: 'post',
275
- },
276
- transformRouteModule
277
- );
413
+ transformRouteModule
414
+ );
415
+ }
278
416
  };