@tanstack/start-plugin-core 1.167.35 → 1.169.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 (187) hide show
  1. package/dist/esm/import-protection/adapterUtils.d.ts +27 -0
  2. package/dist/esm/import-protection/adapterUtils.js +31 -0
  3. package/dist/esm/import-protection/adapterUtils.js.map +1 -0
  4. package/dist/esm/import-protection/analysis.d.ts +36 -0
  5. package/dist/esm/import-protection/analysis.js +407 -0
  6. package/dist/esm/import-protection/analysis.js.map +1 -0
  7. package/dist/esm/{import-protection-plugin → import-protection}/ast.js +1 -1
  8. package/dist/esm/import-protection/ast.js.map +1 -0
  9. package/dist/esm/import-protection/constants.d.ts +11 -0
  10. package/dist/esm/{import-protection-plugin → import-protection}/constants.js +7 -2
  11. package/dist/esm/import-protection/constants.js.map +1 -0
  12. package/dist/esm/{import-protection-plugin → import-protection}/defaults.js +1 -1
  13. package/dist/esm/import-protection/defaults.js.map +1 -0
  14. package/dist/esm/{import-protection-plugin → import-protection}/extensionlessAbsoluteIdResolver.js +2 -2
  15. package/dist/esm/import-protection/extensionlessAbsoluteIdResolver.js.map +1 -0
  16. package/dist/esm/{import-protection-plugin → import-protection}/matchers.js +1 -1
  17. package/dist/esm/import-protection/matchers.js.map +1 -0
  18. package/dist/esm/{import-protection-plugin/rewriteDeniedImports.d.ts → import-protection/rewrite.d.ts} +0 -4
  19. package/dist/esm/import-protection/rewrite.js +121 -0
  20. package/dist/esm/import-protection/rewrite.js.map +1 -0
  21. package/dist/esm/{import-protection-plugin → import-protection}/sourceLocation.d.ts +32 -3
  22. package/dist/esm/{import-protection-plugin → import-protection}/sourceLocation.js +65 -10
  23. package/dist/esm/import-protection/sourceLocation.js.map +1 -0
  24. package/dist/esm/{import-protection-plugin → import-protection}/trace.d.ts +0 -1
  25. package/dist/esm/{import-protection-plugin → import-protection}/trace.js +1 -1
  26. package/dist/esm/import-protection/trace.js.map +1 -0
  27. package/dist/esm/{import-protection-plugin → import-protection}/utils.d.ts +18 -1
  28. package/dist/esm/{import-protection-plugin → import-protection}/utils.js +13 -20
  29. package/dist/esm/import-protection/utils.js.map +1 -0
  30. package/dist/esm/import-protection/virtualModules.d.ts +25 -0
  31. package/dist/esm/{import-protection-plugin → import-protection}/virtualModules.js +5 -117
  32. package/dist/esm/import-protection/virtualModules.js.map +1 -0
  33. package/dist/esm/index.d.ts +1 -5
  34. package/dist/esm/index.js +2 -4
  35. package/dist/esm/post-build.d.ts +9 -0
  36. package/dist/esm/post-build.js +37 -0
  37. package/dist/esm/post-build.js.map +1 -0
  38. package/dist/esm/prerender.d.ts +11 -0
  39. package/dist/esm/prerender.js +159 -0
  40. package/dist/esm/prerender.js.map +1 -0
  41. package/dist/esm/rsbuild/dev-server.d.ts +21 -0
  42. package/dist/esm/rsbuild/dev-server.js +76 -0
  43. package/dist/esm/rsbuild/dev-server.js.map +1 -0
  44. package/dist/esm/rsbuild/import-protection.d.ts +10 -0
  45. package/dist/esm/rsbuild/import-protection.js +775 -0
  46. package/dist/esm/rsbuild/import-protection.js.map +1 -0
  47. package/dist/esm/rsbuild/index.d.ts +4 -0
  48. package/dist/esm/rsbuild/index.js +3 -0
  49. package/dist/esm/rsbuild/normalized-client-build.d.ts +18 -0
  50. package/dist/esm/rsbuild/normalized-client-build.js +207 -0
  51. package/dist/esm/rsbuild/normalized-client-build.js.map +1 -0
  52. package/dist/esm/rsbuild/planning.d.ts +52 -0
  53. package/dist/esm/rsbuild/planning.js +108 -0
  54. package/dist/esm/rsbuild/planning.js.map +1 -0
  55. package/dist/esm/rsbuild/plugin.d.ts +4 -0
  56. package/dist/esm/rsbuild/plugin.js +344 -0
  57. package/dist/esm/rsbuild/plugin.js.map +1 -0
  58. package/dist/esm/rsbuild/post-build.d.ts +6 -0
  59. package/dist/esm/rsbuild/post-build.js +57 -0
  60. package/dist/esm/rsbuild/post-build.js.map +1 -0
  61. package/dist/esm/rsbuild/schema.d.ts +3372 -0
  62. package/dist/esm/rsbuild/schema.js +12 -0
  63. package/dist/esm/rsbuild/schema.js.map +1 -0
  64. package/dist/esm/rsbuild/start-compiler-host.d.ts +20 -0
  65. package/dist/esm/rsbuild/start-compiler-host.js +150 -0
  66. package/dist/esm/rsbuild/start-compiler-host.js.map +1 -0
  67. package/dist/esm/rsbuild/start-router-plugin.d.ts +18 -0
  68. package/dist/esm/rsbuild/start-router-plugin.js +63 -0
  69. package/dist/esm/rsbuild/start-router-plugin.js.map +1 -0
  70. package/dist/esm/rsbuild/swc-rsc.d.ts +14 -0
  71. package/dist/esm/rsbuild/swc-rsc.js +93 -0
  72. package/dist/esm/rsbuild/swc-rsc.js.map +1 -0
  73. package/dist/esm/rsbuild/types.d.ts +17 -0
  74. package/dist/esm/rsbuild/types.js +0 -0
  75. package/dist/esm/rsbuild/virtual-modules.d.ts +53 -0
  76. package/dist/esm/rsbuild/virtual-modules.js +287 -0
  77. package/dist/esm/rsbuild/virtual-modules.js.map +1 -0
  78. package/dist/esm/schema.d.ts +43 -43
  79. package/dist/esm/start-compiler/compiler.d.ts +1 -1
  80. package/dist/esm/start-compiler/compiler.js +80 -9
  81. package/dist/esm/start-compiler/compiler.js.map +1 -1
  82. package/dist/esm/start-compiler/handleCreateServerFn.js +9 -0
  83. package/dist/esm/start-compiler/handleCreateServerFn.js.map +1 -1
  84. package/dist/esm/start-compiler/host.js +5 -1
  85. package/dist/esm/start-compiler/host.js.map +1 -1
  86. package/dist/esm/start-compiler/types.d.ts +1 -0
  87. package/dist/esm/utils.d.ts +1 -0
  88. package/dist/esm/utils.js +10 -1
  89. package/dist/esm/utils.js.map +1 -1
  90. package/dist/esm/{import-protection-plugin → vite/import-protection-plugin}/plugin.js +41 -92
  91. package/dist/esm/vite/import-protection-plugin/plugin.js.map +1 -0
  92. package/dist/esm/{import-protection-plugin → vite/import-protection-plugin}/types.d.ts +5 -5
  93. package/dist/esm/vite/import-protection-plugin/virtualModules.d.ts +8 -0
  94. package/dist/esm/vite/import-protection-plugin/virtualModules.js +49 -0
  95. package/dist/esm/vite/import-protection-plugin/virtualModules.js.map +1 -0
  96. package/dist/esm/vite/index.d.ts +5 -0
  97. package/dist/esm/vite/index.js +4 -0
  98. package/dist/esm/vite/plugin.js +1 -1
  99. package/dist/esm/vite/plugin.js.map +1 -1
  100. package/dist/esm/vite/post-server-build.js +14 -32
  101. package/dist/esm/vite/post-server-build.js.map +1 -1
  102. package/dist/esm/vite/prerender.d.ts +2 -2
  103. package/dist/esm/vite/prerender.js +17 -147
  104. package/dist/esm/vite/prerender.js.map +1 -1
  105. package/dist/esm/vite/schema.d.ts +23 -23
  106. package/dist/esm/vite/start-compiler-plugin/hot-update.d.ts +2 -0
  107. package/dist/esm/vite/start-compiler-plugin/hot-update.js +16 -0
  108. package/dist/esm/vite/start-compiler-plugin/hot-update.js.map +1 -0
  109. package/dist/esm/vite/start-compiler-plugin/module-specifier.js +9 -4
  110. package/dist/esm/vite/start-compiler-plugin/module-specifier.js.map +1 -1
  111. package/dist/esm/vite/start-compiler-plugin/plugin.js +86 -13
  112. package/dist/esm/vite/start-compiler-plugin/plugin.js.map +1 -1
  113. package/package.json +32 -4
  114. package/src/import-protection/INTERNALS.md +266 -0
  115. package/src/import-protection/adapterUtils.ts +94 -0
  116. package/src/import-protection/analysis.ts +853 -0
  117. package/src/{import-protection-plugin → import-protection}/constants.ts +7 -0
  118. package/src/import-protection/rewrite.ts +229 -0
  119. package/src/{import-protection-plugin → import-protection}/sourceLocation.ts +125 -9
  120. package/src/{import-protection-plugin → import-protection}/trace.ts +0 -1
  121. package/src/{import-protection-plugin → import-protection}/utils.ts +36 -21
  122. package/src/{import-protection-plugin → import-protection}/virtualModules.ts +30 -177
  123. package/src/index.ts +1 -8
  124. package/src/post-build.ts +64 -0
  125. package/src/prerender.ts +292 -0
  126. package/src/rsbuild/INTERNALS-import-protection.md +169 -0
  127. package/src/rsbuild/dev-server.ts +129 -0
  128. package/src/rsbuild/import-protection.ts +1599 -0
  129. package/src/rsbuild/index.ts +4 -0
  130. package/src/rsbuild/normalized-client-build.ts +346 -0
  131. package/src/rsbuild/planning.ts +234 -0
  132. package/src/rsbuild/plugin.ts +754 -0
  133. package/src/rsbuild/post-build.ts +96 -0
  134. package/src/rsbuild/schema.ts +31 -0
  135. package/src/rsbuild/start-compiler-host.ts +250 -0
  136. package/src/rsbuild/start-router-plugin.ts +86 -0
  137. package/src/rsbuild/swc-rsc.ts +166 -0
  138. package/src/rsbuild/types.ts +20 -0
  139. package/src/rsbuild/virtual-modules.ts +565 -0
  140. package/src/start-compiler/compiler.ts +153 -19
  141. package/src/start-compiler/handleCreateServerFn.ts +18 -0
  142. package/src/start-compiler/types.ts +1 -0
  143. package/src/utils.ts +14 -0
  144. package/src/vite/import-protection-plugin/INTERNALS.md +187 -0
  145. package/src/{import-protection-plugin → vite/import-protection-plugin}/plugin.ts +73 -158
  146. package/src/{import-protection-plugin → vite/import-protection-plugin}/types.ts +5 -5
  147. package/src/vite/import-protection-plugin/virtualModules.ts +122 -0
  148. package/src/vite/index.ts +8 -0
  149. package/src/vite/plugin.ts +1 -1
  150. package/src/vite/post-server-build.ts +14 -57
  151. package/src/vite/prerender.ts +19 -260
  152. package/src/vite/start-compiler-plugin/hot-update.ts +24 -0
  153. package/src/vite/start-compiler-plugin/module-specifier.ts +15 -5
  154. package/src/vite/start-compiler-plugin/plugin.ts +193 -18
  155. package/dist/esm/import-protection-plugin/ast.js.map +0 -1
  156. package/dist/esm/import-protection-plugin/constants.d.ts +0 -6
  157. package/dist/esm/import-protection-plugin/constants.js.map +0 -1
  158. package/dist/esm/import-protection-plugin/defaults.js.map +0 -1
  159. package/dist/esm/import-protection-plugin/extensionlessAbsoluteIdResolver.js.map +0 -1
  160. package/dist/esm/import-protection-plugin/matchers.js.map +0 -1
  161. package/dist/esm/import-protection-plugin/plugin.js.map +0 -1
  162. package/dist/esm/import-protection-plugin/postCompileUsage.d.ts +0 -13
  163. package/dist/esm/import-protection-plugin/postCompileUsage.js +0 -63
  164. package/dist/esm/import-protection-plugin/postCompileUsage.js.map +0 -1
  165. package/dist/esm/import-protection-plugin/rewriteDeniedImports.js +0 -205
  166. package/dist/esm/import-protection-plugin/rewriteDeniedImports.js.map +0 -1
  167. package/dist/esm/import-protection-plugin/sourceLocation.js.map +0 -1
  168. package/dist/esm/import-protection-plugin/trace.js.map +0 -1
  169. package/dist/esm/import-protection-plugin/utils.js.map +0 -1
  170. package/dist/esm/import-protection-plugin/virtualModules.d.ts +0 -78
  171. package/dist/esm/import-protection-plugin/virtualModules.js.map +0 -1
  172. package/dist/esm/start-compiler/load-module.d.ts +0 -14
  173. package/dist/esm/start-compiler/load-module.js +0 -18
  174. package/dist/esm/start-compiler/load-module.js.map +0 -1
  175. package/src/import-protection-plugin/INTERNALS.md +0 -700
  176. package/src/import-protection-plugin/postCompileUsage.ts +0 -100
  177. package/src/import-protection-plugin/rewriteDeniedImports.ts +0 -379
  178. package/src/start-compiler/load-module.ts +0 -31
  179. /package/dist/esm/{import-protection-plugin → import-protection}/ast.d.ts +0 -0
  180. /package/dist/esm/{import-protection-plugin → import-protection}/defaults.d.ts +0 -0
  181. /package/dist/esm/{import-protection-plugin → import-protection}/extensionlessAbsoluteIdResolver.d.ts +0 -0
  182. /package/dist/esm/{import-protection-plugin → import-protection}/matchers.d.ts +0 -0
  183. /package/dist/esm/{import-protection-plugin → vite/import-protection-plugin}/plugin.d.ts +0 -0
  184. /package/src/{import-protection-plugin → import-protection}/ast.ts +0 -0
  185. /package/src/{import-protection-plugin → import-protection}/defaults.ts +0 -0
  186. /package/src/{import-protection-plugin → import-protection}/extensionlessAbsoluteIdResolver.ts +0 -0
  187. /package/src/{import-protection-plugin → import-protection}/matchers.ts +0 -0
@@ -0,0 +1,754 @@
1
+ import { existsSync, readdirSync, realpathSync, statSync } from 'node:fs'
2
+ import { dirname, join, resolve } from 'node:path'
3
+ import { fileURLToPath } from 'node:url'
4
+ import { joinURL } from 'ufo'
5
+ import {
6
+ applyResolvedBaseAndOutput,
7
+ applyResolvedRouterBasepath,
8
+ createStartConfigContext,
9
+ } from '../config-context'
10
+ import { normalizePath } from '../utils'
11
+ import { createServerFnBasePath, normalizePublicBase } from '../planning'
12
+ import { parseStartConfig } from './schema'
13
+ import {
14
+ RSBUILD_ENVIRONMENT_NAMES,
15
+ RSBUILD_RSC_LAYERS,
16
+ createRsbuildEnvironmentPlan,
17
+ createRsbuildResolvedEntryAliases,
18
+ resolveRsbuildOutputDirectory,
19
+ } from './planning'
20
+ import { registerStartCompilerTransforms } from './start-compiler-host'
21
+ import { registerImportProtection } from './import-protection'
22
+ import {
23
+ START_MANIFEST_PLACEHOLDER,
24
+ registerVirtualModules,
25
+ } from './virtual-modules'
26
+ import { createServerSetup } from './dev-server'
27
+ import { registerClientBuildCapture } from './normalized-client-build'
28
+ import { registerRouterPlugins } from './start-router-plugin'
29
+ import { postBuildWithRsbuild } from './post-build'
30
+ import { enableSwcReactServerComponents } from './swc-rsc'
31
+ import type { ServerFn } from '../start-compiler/types'
32
+ import type { TanStackStartRsbuildPluginCoreOptions } from './types'
33
+ import type {
34
+ ModifyRspackConfigFn,
35
+ RsbuildDevServer,
36
+ RsbuildPlugin,
37
+ RsbuildPluginAPI,
38
+ Rspack,
39
+ rspack as rspackNamespaceType,
40
+ } from '@rsbuild/core'
41
+ import type { TanStackStartRsbuildInputConfig } from './schema'
42
+
43
+ // Detect whether this plugin source is running from inside the TanStack
44
+ // Router monorepo (packages/start-plugin-core/src/rsbuild/plugin.ts). When
45
+ // installed from npm in a user app, the path structure is different and
46
+ // this evaluates to false. Used to gate dev-only workarounds that only
47
+ // matter when workspace package dists are symlinked into node_modules.
48
+ const currentDir = dirname(fileURLToPath(import.meta.url))
49
+ const isInsideRouterMonoRepo = (() => {
50
+ // src layout: <repo>/packages/start-plugin-core/src/rsbuild → 4 levels up
51
+ // dist layout (CJS/ESM): <repo>/packages/start-plugin-core/dist/<fmt>/rsbuild
52
+ // → also 4 levels up
53
+ const candidate = resolve(currentDir, '../../../../')
54
+ return candidate.endsWith('/packages') || candidate.endsWith('\\packages')
55
+ })()
56
+
57
+ type RspackNamespace = typeof rspackNamespaceType
58
+ type RscPluginPair = ReturnType<
59
+ NonNullable<RspackNamespace['experiments']['rsc']>['createPlugins']
60
+ >
61
+ type RspackConfig = Parameters<ModifyRspackConfigFn>[0]
62
+ type RspackCompiler = Rspack.Compiler
63
+ type RspackCompilationExtended = Rspack.Compilation
64
+
65
+ export function tanStackStartRsbuild(
66
+ corePluginOpts: TanStackStartRsbuildPluginCoreOptions,
67
+ startPluginOpts: TanStackStartRsbuildInputConfig = {},
68
+ ): RsbuildPlugin {
69
+ const rscOpts = corePluginOpts.rsc
70
+ const rscEnabled = Boolean(rscOpts)
71
+
72
+ const configContext = createStartConfigContext({
73
+ corePluginOpts,
74
+ startPluginOpts,
75
+ parseConfig: parseStartConfig,
76
+ })
77
+ const { getConfig, resolvedStartConfig } = configContext
78
+ const serverFnProviderEnv = corePluginOpts.providerEnvironmentName
79
+ const ssrIsProvider = corePluginOpts.ssrIsProvider
80
+
81
+ // RSC plugin instances — created lazily when rspack namespace is available
82
+ let rscPlugins: RscPluginPair | undefined
83
+
84
+ // Reference to the dev server for RSC HMR socket writes
85
+ let devServerRef: Pick<RsbuildDevServer, 'sockWrite'> | null = null
86
+ const serverFnsById: Record<string, ServerFn> = {}
87
+ let updateServerFnResolver: (() => void) | undefined
88
+
89
+ return {
90
+ name: 'tanstack-start-rsbuild',
91
+ setup(api: RsbuildPluginAPI) {
92
+ // ---------------------------------------------------------------
93
+ // 1. modifyRsbuildConfig — resolve config, set up environments
94
+ // ---------------------------------------------------------------
95
+ api.modifyRsbuildConfig((rsbuildConfig, { mergeRsbuildConfig }) => {
96
+ const root =
97
+ typeof rsbuildConfig.root === 'string'
98
+ ? rsbuildConfig.root
99
+ : process.cwd()
100
+
101
+ const serverBase = rsbuildConfig.server?.base
102
+ const assetPrefix = rsbuildConfig.output?.assetPrefix
103
+ const publicBase = normalizePublicBase(
104
+ typeof serverBase === 'string'
105
+ ? serverBase
106
+ : typeof assetPrefix === 'string' && assetPrefix !== 'auto'
107
+ ? assetPrefix
108
+ : undefined,
109
+ )
110
+ const rootDistPath = rsbuildConfig.output?.distPath
111
+ const clientDistPath =
112
+ rsbuildConfig.environments?.[RSBUILD_ENVIRONMENT_NAMES.client]?.output
113
+ ?.distPath
114
+ const serverDistPath =
115
+ rsbuildConfig.environments?.[RSBUILD_ENVIRONMENT_NAMES.server]?.output
116
+ ?.distPath
117
+
118
+ applyResolvedBaseAndOutput({
119
+ resolvedStartConfig,
120
+ root,
121
+ publicBase,
122
+ clientOutputDirectory: resolveRsbuildOutputDirectory({
123
+ distPath: clientDistPath,
124
+ rootDistPath,
125
+ fallback: 'dist/client',
126
+ subdirectory: 'client',
127
+ }),
128
+ serverOutputDirectory: resolveRsbuildOutputDirectory({
129
+ distPath: serverDistPath,
130
+ rootDistPath,
131
+ fallback: 'dist/server',
132
+ subdirectory: 'server',
133
+ }),
134
+ })
135
+
136
+ const { startConfig } = getConfig()
137
+ const routerBasepath = applyResolvedRouterBasepath({
138
+ resolvedStartConfig,
139
+ startConfig,
140
+ })
141
+
142
+ const resolvedEntryPlan = configContext.resolveEntries()
143
+ const isDev = api.context.action === 'dev'
144
+
145
+ const entryAliases = createRsbuildResolvedEntryAliases({
146
+ entryPaths: resolvedEntryPlan.entryPaths,
147
+ })
148
+
149
+ const environmentPlan = createRsbuildEnvironmentPlan({
150
+ root,
151
+ entryAliases,
152
+ clientOutputDirectory: resolvedStartConfig.outputDirectories.client,
153
+ serverOutputDirectory: resolvedStartConfig.outputDirectories.server,
154
+ publicBase: resolvedStartConfig.basePaths.publicBase,
155
+ serverFnProviderEnv,
156
+ environmentOverrides: corePluginOpts.rsbuild?.environments,
157
+ rsc: rscOpts,
158
+ dev: isDev,
159
+ })
160
+ const serverFnBase = createServerFnBasePath({
161
+ routerBasepath,
162
+ serverFnBase: startConfig.serverFns.base,
163
+ })
164
+
165
+ return mergeRsbuildConfig(rsbuildConfig, {
166
+ source: {
167
+ define: {
168
+ 'process.env.TSS_SERVER_FN_BASE': JSON.stringify(serverFnBase),
169
+ 'import.meta.env.TSS_SERVER_FN_BASE':
170
+ JSON.stringify(serverFnBase),
171
+ 'process.env.TSS_ROUTER_BASEPATH': JSON.stringify(routerBasepath),
172
+ 'import.meta.env.TSS_ROUTER_BASEPATH':
173
+ JSON.stringify(routerBasepath),
174
+ 'process.env.TSS_DEV_SERVER': JSON.stringify(
175
+ isDev ? 'true' : 'false',
176
+ ),
177
+ 'import.meta.env.TSS_DEV_SERVER': JSON.stringify(
178
+ isDev ? 'true' : 'false',
179
+ ),
180
+ // Rsbuild dev already injects emitted CSS asset hrefs, so keep
181
+ // Start's synthetic `/@tanstack-start/styles.css` path disabled.
182
+ 'process.env.TSS_DEV_SSR_STYLES_ENABLED': JSON.stringify('false'),
183
+ 'import.meta.env.TSS_DEV_SSR_STYLES_ENABLED':
184
+ JSON.stringify('false'),
185
+ 'process.env.TSS_DEV_SSR_STYLES_BASEPATH': JSON.stringify(
186
+ resolvedStartConfig.basePaths.publicBase,
187
+ ),
188
+ 'import.meta.env.TSS_DEV_SSR_STYLES_BASEPATH': JSON.stringify(
189
+ resolvedStartConfig.basePaths.publicBase,
190
+ ),
191
+ },
192
+ },
193
+ server: {
194
+ // SSR apps render every route on the server — disable HTML
195
+ // fallback so rsbuild doesn't intercept /_serverFn/ URLs.
196
+ htmlFallback: false,
197
+ // server.setup returned callback runs after built-in middleware
198
+ // but BEFORE fallback middleware — the ideal slot for SSR.
199
+ ...(isDev &&
200
+ startPluginOpts.rsbuild?.installDevServerMiddleware !== false
201
+ ? {
202
+ setup: createServerSetup({
203
+ serverFnBasePath: serverFnBase,
204
+ }),
205
+ }
206
+ : {}),
207
+ },
208
+ ...(isDev
209
+ ? {
210
+ dev: {
211
+ lazyCompilation: false,
212
+ ...(rscEnabled ? { liveReload: false } : {}),
213
+ },
214
+ }
215
+ : {}),
216
+ environments: environmentPlan.environments,
217
+ resolve: {
218
+ alias: environmentPlan.alias,
219
+ },
220
+ })
221
+ })
222
+
223
+ // ---------------------------------------------------------------
224
+ // 2. StartCompiler transforms — server fns, isomorphic fns, etc.
225
+ // ---------------------------------------------------------------
226
+ registerStartCompilerTransforms(api, {
227
+ framework: corePluginOpts.framework,
228
+ // modifyRsbuildConfig copies rsbuildConfig.root into resolvedStartConfig.root,
229
+ // so defer this read until transform time instead of falling back to
230
+ // process.cwd() during plugin setup.
231
+ root: () => resolvedStartConfig.root || process.cwd(),
232
+ providerEnvName: serverFnProviderEnv,
233
+ generateFunctionId: startPluginOpts.serverFns?.generateFunctionId,
234
+ serverFnsById,
235
+ onServerFnsByIdChange: () => {
236
+ updateServerFnResolver?.()
237
+ },
238
+ })
239
+
240
+ registerImportProtection(api, {
241
+ getConfig,
242
+ framework: corePluginOpts.framework,
243
+ environments: [
244
+ { name: RSBUILD_ENVIRONMENT_NAMES.client, type: 'client' },
245
+ { name: RSBUILD_ENVIRONMENT_NAMES.server, type: 'server' },
246
+ ...(serverFnProviderEnv !== RSBUILD_ENVIRONMENT_NAMES.server &&
247
+ !rscEnabled
248
+ ? [{ name: serverFnProviderEnv, type: 'server' as const }]
249
+ : []),
250
+ ],
251
+ })
252
+
253
+ // ---------------------------------------------------------------
254
+ // 3. Virtual modules — manifest, server fn resolver, adapters,
255
+ // RSC runtime, RSC HMR
256
+ // ---------------------------------------------------------------
257
+ const virtualModuleState = registerVirtualModules(api, {
258
+ root: resolvedStartConfig.root || process.cwd(),
259
+ getConfig,
260
+ serverFnsById,
261
+ providerEnvName: serverFnProviderEnv,
262
+ ssrIsProvider,
263
+ serializationAdapters: corePluginOpts.serializationAdapters,
264
+ getDevClientEntryUrl: (publicBase: string) =>
265
+ joinURL(publicBase, 'static/js/index.js'),
266
+ rscEnabled,
267
+ })
268
+ updateServerFnResolver = virtualModuleState.updateServerFnResolver
269
+
270
+ // ---------------------------------------------------------------
271
+ // 4. Client build stats capture via processAssets
272
+ // ---------------------------------------------------------------
273
+ const { getClientBuild } = registerClientBuildCapture(api)
274
+
275
+ // ---------------------------------------------------------------
276
+ // 4b. Server manifest module generation (build only)
277
+ // For ordinary multi-environment builds, Rsbuild can compile the
278
+ // server environment after the client environment finishes. Generate
279
+ // the final manifest as module source in that phase instead of
280
+ // patching emitted server assets afterwards.
281
+ // ---------------------------------------------------------------
282
+ if (api.context.action !== 'dev') {
283
+ const normalizedManifestPath = normalizePath(
284
+ virtualModuleState.manifestPath,
285
+ )
286
+ const matchesManifestPath = (id: string) =>
287
+ normalizePath(id) === normalizedManifestPath
288
+
289
+ api.transform(
290
+ {
291
+ test: (id: string) => matchesManifestPath(id),
292
+ environments: [RSBUILD_ENVIRONMENT_NAMES.server],
293
+ },
294
+ ({ code }) => {
295
+ const clientBuild = getClientBuild()
296
+
297
+ if (clientBuild) {
298
+ return virtualModuleState.generateManifestContent(clientBuild)
299
+ }
300
+
301
+ if (!rscEnabled) {
302
+ throw new Error(
303
+ 'TanStack Start could not generate the rsbuild server manifest before the client build completed',
304
+ )
305
+ }
306
+
307
+ // RSC builds cannot express the required client -> server ordering
308
+ // through MultiCompiler dependencies, so keep the placeholder for
309
+ // the RSC-only asset-patching fallback below.
310
+ return code
311
+ },
312
+ )
313
+ }
314
+
315
+ // ---------------------------------------------------------------
316
+ // 5. Router plugin wiring (generator + code splitter)
317
+ // ---------------------------------------------------------------
318
+ registerRouterPlugins(api, {
319
+ getConfig,
320
+ corePluginOpts,
321
+ startPluginOpts,
322
+ })
323
+
324
+ // ---------------------------------------------------------------
325
+ // 6. Dev SSR middleware — registered via server.setup in
326
+ // modifyRsbuildConfig above (returned callback runs after
327
+ // built-ins but before fallback middleware)
328
+ // ---------------------------------------------------------------
329
+
330
+ // ---------------------------------------------------------------
331
+ // 6b. Dev watcher: ignore workspace package `dist/**` directories.
332
+ //
333
+ // In a real user app, `@tanstack/react-router` and friends live
334
+ // inside `node_modules/` and are ignored by Rspack's default
335
+ // watcher. In this monorepo, pnpm symlinks them to
336
+ // `packages/*/dist` (realpath outside node_modules), so the
337
+ // watcher follows them and treats their dist files as live
338
+ // sources. If anything rewrites those files during dev, Rspack
339
+ // sees transient half-written modules and fails to resolve
340
+ // relative imports between them.
341
+ //
342
+ // Only apply this in monorepo development. In user apps this
343
+ // is a no-op — their `node_modules/@tanstack/*/dist/**` is
344
+ // already ignored by Rspack's default watchOptions.
345
+ // ---------------------------------------------------------------
346
+ if (isInsideRouterMonoRepo && api.context.action === 'dev') {
347
+ api.modifyRspackConfig((config) => {
348
+ const workspaceDistRealpaths = resolveWorkspacePackageDistRealpaths()
349
+ if (workspaceDistRealpaths.length === 0) return
350
+
351
+ const workspaceDistIgnored = new RegExp(
352
+ workspaceDistRealpaths
353
+ .map((path) => `^${escapeRegExp(path)}(?:[\\\\/]|$)`)
354
+ .join('|'),
355
+ )
356
+ const ignored = config.watchOptions?.ignored
357
+
358
+ config.watchOptions = {
359
+ ...(config.watchOptions ?? {}),
360
+ ignored:
361
+ ignored == null
362
+ ? new RegExp(
363
+ `${defaultRspackWatchIgnored.source}|${workspaceDistIgnored.source}`,
364
+ )
365
+ : typeof ignored === 'string'
366
+ ? [ignored, ...workspaceDistRealpaths]
367
+ : Array.isArray(ignored)
368
+ ? [...ignored, ...workspaceDistRealpaths]
369
+ : new RegExp(
370
+ `${ignored.source}|${workspaceDistIgnored.source}`,
371
+ ),
372
+ }
373
+ })
374
+ }
375
+
376
+ // ---------------------------------------------------------------
377
+ // 7. RSC: rspack layer rules + native RSC plugins
378
+ // When RSC is enabled, we add:
379
+ // - issuerLayer rule for react-server condition propagation
380
+ // - SWC reactServerComponents: true
381
+ // - rspack ServerPlugin (server env) / ClientPlugin (client env)
382
+ // The Coordinator inside createPlugins() handles compilation
383
+ // ordering (server→client→server-actions) automatically.
384
+ // ---------------------------------------------------------------
385
+ if (rscEnabled) {
386
+ api.modifyRspackConfig((config, utils) => {
387
+ const envName = utils.environment.name
388
+ const isServerEnv = envName === RSBUILD_ENVIRONMENT_NAMES.server
389
+ const isClientEnv = envName === RSBUILD_ENVIRONMENT_NAMES.client
390
+
391
+ // Create RSC plugin pair lazily (once per build)
392
+ if (!rscPlugins) {
393
+ rscPlugins = utils.rspack.experiments.rsc.createPlugins()
394
+ }
395
+
396
+ if (isServerEnv) {
397
+ // --- issuerLayer rule: modules imported from RSC layer
398
+ // get react-server resolve condition ---
399
+ const moduleRules = (config.module.rules ??= [])
400
+ const root = resolvedStartConfig.root || process.cwd()
401
+
402
+ // Split server-fn provider modules are the actual RSC execution
403
+ // boundary in Start's layered model. They must compile in the
404
+ // RSC layer so React and react-server-dom-rspack resolve their
405
+ // react-server exports without forcing the whole SSR graph into
406
+ // react-server conditions.
407
+ moduleRules.push({
408
+ resourceQuery: /(?:^|[?&])tss-serverfn-split(?:&|$)/,
409
+ layer: RSBUILD_RSC_LAYERS.rsc,
410
+ resolve: {
411
+ conditionNames: ['react-server', '...'],
412
+ },
413
+ })
414
+
415
+ // All modules imported from the RSC layer inherit
416
+ // the react-server condition (transitive propagation), except
417
+ // route split virtual modules. Those remain ordinary SSR/client
418
+ // route code; only `?tsr-shared=1` modules may be shared with the
419
+ // provider subtree.
420
+ moduleRules.push({
421
+ issuerLayer: RSBUILD_RSC_LAYERS.rsc,
422
+ resourceQuery: {
423
+ not: [/(?:^|[?&])tsr-split(?:=|&|$)/],
424
+ },
425
+ resolve: {
426
+ conditionNames: ['react-server', '...'],
427
+ },
428
+ })
429
+
430
+ // The RSC ServerPlugin injects imports like
431
+ // `react-server-dom-rspack/server` into transformed modules.
432
+ // Some modules in the server graph resolve from real package paths
433
+ // outside the app root, so relying on the default relative
434
+ // `node_modules` lookup is not enough. Seed resolve.modules with the
435
+ // app root explicitly, without package-manager-specific heuristics.
436
+ seedResolveModules(config, [`${root}/node_modules`, 'node_modules'])
437
+
438
+ // Add ServerPlugin with HMR callback
439
+ config.plugins.push(
440
+ new rscPlugins.ServerPlugin({
441
+ clientEntryName: 'index',
442
+ runtimeEntryName: 'index',
443
+ injectSsrModulesToEntries: ['index'],
444
+ onServerComponentChanges: () => {
445
+ // Send rsc:update to connected clients for HMR
446
+ devServerRef?.sockWrite('custom', {
447
+ event: 'rsc:update',
448
+ })
449
+ },
450
+ }),
451
+ )
452
+
453
+ config.plugins.push({
454
+ apply(compiler: RspackCompiler) {
455
+ compiler.hooks.finishMake.tapPromise(
456
+ {
457
+ name: 'TanStackStartRscServerFnResolverRebuild',
458
+ stage: -10,
459
+ },
460
+ async (compilation: RspackCompilationExtended) => {
461
+ if (Object.keys(serverFnsById).length === 0) {
462
+ return
463
+ }
464
+
465
+ const resolverContent =
466
+ virtualModuleState.generateCurrentResolverContent(true)
467
+ virtualModuleState.tryUpdateServerFnResolver(
468
+ resolverContent,
469
+ )
470
+
471
+ await rebuildModulesContaining(
472
+ compilation,
473
+ virtualModuleState.serverFnResolverPath,
474
+ )
475
+ },
476
+ )
477
+ },
478
+ })
479
+
480
+ if (api.context.action !== 'dev') {
481
+ config.plugins.push({
482
+ apply(compiler: RspackCompiler) {
483
+ compiler.hooks.finishMake.tapPromise(
484
+ {
485
+ name: 'TanStackStartRscManifestRebuild',
486
+ // The native RSC ServerPlugin completes the client-entry
487
+ // handoff during its finishMake hook. Rebuild the manifest
488
+ // after that point so the transform hook can emit the final
489
+ // manifest source instead of the placeholder.
490
+ stage: 10,
491
+ },
492
+ async (compilation: RspackCompilationExtended) => {
493
+ const clientBuild = getClientBuild()
494
+
495
+ if (!clientBuild) {
496
+ return
497
+ }
498
+
499
+ virtualModuleState.updateManifest(clientBuild)
500
+
501
+ await rebuildModulesContaining(
502
+ compilation,
503
+ virtualModuleState.manifestPath,
504
+ )
505
+ },
506
+ )
507
+ },
508
+ })
509
+ }
510
+ }
511
+
512
+ if (isClientEnv) {
513
+ // Add ClientPlugin — the Coordinator links it to the
514
+ // ServerPlugin's compilation state
515
+ config.plugins.push(new rscPlugins.ClientPlugin())
516
+ }
517
+
518
+ // --- SWC reactServerComponents ---
519
+ // Enable RSC directive detection where the native RSC plugins need it.
520
+ // In the server build, scope it to the actual RSC provider subtree so
521
+ // ordinary route-split modules (e.g. ?tsr-split=component) stay out of
522
+ // RSC validation unless they are really imported by a provider module.
523
+ if (isServerEnv) {
524
+ enableSwcReactServerComponents(config, 'rsc-subtree')
525
+ } else if (isClientEnv) {
526
+ enableSwcReactServerComponents(config, 'all')
527
+ }
528
+ })
529
+
530
+ // Capture dev server reference for RSC HMR socket writes
531
+ if (api.context.action === 'dev') {
532
+ api.onBeforeStartDevServer(({ server }) => {
533
+ devServerRef = server
534
+ })
535
+ }
536
+ }
537
+
538
+ // ---------------------------------------------------------------
539
+ // 8. Build ordering — client must complete before server starts
540
+ // so that the manifest virtual module has real client build stats.
541
+ // Uses rspack MultiCompiler.setDependencies() under the hood.
542
+ //
543
+ // IMPORTANT: When RSC is enabled we must NOT set dependencies.
544
+ // The RSC Coordinator already orchestrates server↔client
545
+ // compilation ordering by interleaving phases within compiler
546
+ // hooks. Adding setDependencies(server, [client]) on top of
547
+ // the Coordinator creates a deadlock: MultiCompiler blocks
548
+ // the server compiler until client is `done`, but the
549
+ // Coordinator blocks the client's `make` hook until the
550
+ // server's entries phase completes — neither can start.
551
+ // ---------------------------------------------------------------
552
+ if (!rscEnabled) {
553
+ api.onAfterCreateCompiler(({ compiler }) => {
554
+ // MultiCompiler has a `compilers` array; single compiler does not
555
+ if ('compilers' in compiler) {
556
+ const serverCompiler = compiler.compilers.find(
557
+ (c) => c.name === RSBUILD_ENVIRONMENT_NAMES.server,
558
+ )
559
+ if (serverCompiler) {
560
+ compiler.setDependencies(serverCompiler, [
561
+ RSBUILD_ENVIRONMENT_NAMES.client,
562
+ ])
563
+ }
564
+ }
565
+ })
566
+ }
567
+
568
+ // ---------------------------------------------------------------
569
+ // 8b. Manifest asset replacement fallback (RSC build only)
570
+ // Rsbuild's native RSC coordinator interleaves server and client
571
+ // compilers, so the server manifest module can compile before Start's
572
+ // normalized client build stats exist. Keep final replacement in the
573
+ // server asset pipeline, after the client processAssets capture has a
574
+ // chance to run.
575
+ // ---------------------------------------------------------------
576
+ if (api.context.action !== 'dev' && rscEnabled) {
577
+ const manifestPlaceholderLiteral = JSON.stringify(
578
+ START_MANIFEST_PLACEHOLDER,
579
+ )
580
+ api.modifyRspackConfig((config, utils) => {
581
+ if (utils.environment.name !== RSBUILD_ENVIRONMENT_NAMES.server)
582
+ return
583
+
584
+ config.plugins.push({
585
+ apply(compiler: RspackCompiler) {
586
+ compiler.hooks.compilation.tap(
587
+ 'TanStackStartManifestReplace',
588
+ (compilation: RspackCompilationExtended) => {
589
+ compilation.hooks.processAssets.tap(
590
+ {
591
+ name: 'TanStackStartManifestReplace',
592
+ stage:
593
+ utils.rspack.Compilation
594
+ .PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE,
595
+ },
596
+ () => {
597
+ const assetsWithPlaceholder = compilation
598
+ .getAssets()
599
+ .flatMap((asset) => {
600
+ if (!asset.name.endsWith('.js')) return []
601
+
602
+ const sourceStr = String(asset.source.source())
603
+ return sourceStr.includes(manifestPlaceholderLiteral)
604
+ ? [{ asset, sourceStr }]
605
+ : []
606
+ })
607
+
608
+ if (assetsWithPlaceholder.length === 0) return
609
+
610
+ const clientBuild = getClientBuild()
611
+ if (!clientBuild) {
612
+ throw new Error(
613
+ 'TanStack Start could not replace the rsbuild RSC server manifest placeholder because the client build was unavailable',
614
+ )
615
+ }
616
+
617
+ const manifestValueLiteral =
618
+ virtualModuleState.generateManifestValueLiteral(
619
+ clientBuild,
620
+ )
621
+
622
+ for (const {
623
+ asset,
624
+ sourceStr,
625
+ } of assetsWithPlaceholder) {
626
+ compilation.updateAsset(
627
+ asset.name,
628
+ new utils.rspack.sources.RawSource(
629
+ sourceStr.replace(
630
+ manifestPlaceholderLiteral,
631
+ manifestValueLiteral,
632
+ ),
633
+ ),
634
+ )
635
+ }
636
+ },
637
+ )
638
+ },
639
+ )
640
+ },
641
+ })
642
+ })
643
+ }
644
+
645
+ // ---------------------------------------------------------------
646
+ // 9. After client env compiles — refresh resolver + manifest
647
+ // ---------------------------------------------------------------
648
+ api.onAfterEnvironmentCompile(({ environment }) => {
649
+ if (environment.name !== RSBUILD_ENVIRONMENT_NAMES.client) return
650
+
651
+ virtualModuleState.updateServerFnResolver()
652
+
653
+ const clientBuild = getClientBuild()
654
+ if (clientBuild) {
655
+ virtualModuleState.updateManifest(clientBuild)
656
+ }
657
+ })
658
+
659
+ if (api.context.action === 'build') {
660
+ api.onAfterBuild(async () => {
661
+ const { startConfig } = getConfig()
662
+
663
+ await postBuildWithRsbuild({
664
+ startConfig,
665
+ clientOutputDirectory: resolvedStartConfig.outputDirectories.client,
666
+ serverOutputDirectory: resolvedStartConfig.outputDirectories.server,
667
+ })
668
+ })
669
+ }
670
+ },
671
+ }
672
+ }
673
+
674
+ function escapeRegExp(value: string): string {
675
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
676
+ }
677
+
678
+ const defaultRspackWatchIgnored = /[\\/](?:\.git|node_modules)[\\/]/
679
+
680
+ function seedResolveModules(
681
+ config: RspackConfig,
682
+ entries: Array<string>,
683
+ ): void {
684
+ const resolveModules = (config.resolve.modules ??= [])
685
+
686
+ for (const entry of entries) {
687
+ if (!resolveModules.includes(entry)) {
688
+ resolveModules.push(entry)
689
+ }
690
+ }
691
+ }
692
+
693
+ function rebuildModulesContaining(
694
+ compilation: RspackCompilationExtended,
695
+ identifierFragment: string,
696
+ ): Promise<void> {
697
+ const modulesToRebuild = Array.from(compilation.modules).filter((mod) =>
698
+ mod.identifier().includes(identifierFragment),
699
+ )
700
+
701
+ if (modulesToRebuild.length === 0) {
702
+ return Promise.resolve()
703
+ }
704
+
705
+ return Promise.all(
706
+ modulesToRebuild.map(
707
+ (mod) =>
708
+ new Promise<void>((resolve, reject) => {
709
+ compilation.rebuildModule(mod, (err: Error | null) => {
710
+ if (err) reject(err)
711
+ else resolve()
712
+ })
713
+ }),
714
+ ),
715
+ ).then(() => undefined)
716
+ }
717
+
718
+ /**
719
+ * Return the realpath of every packages/<name>/dist directory in the
720
+ * TanStack Router monorepo. Only meaningful when called from inside the
721
+ * monorepo — in user apps, callers should guard with
722
+ * `isInsideRouterMonoRepo` before invoking this.
723
+ */
724
+ function resolveWorkspacePackageDistRealpaths(): Array<string> {
725
+ // currentDir points at either <repo>/packages/start-plugin-core/src/rsbuild
726
+ // or <repo>/packages/start-plugin-core/dist/<fmt>/rsbuild. Four levels up
727
+ // lands on <repo>/packages in both layouts.
728
+ const packagesDir = resolve(currentDir, '../../../../')
729
+ if (!existsSync(packagesDir)) return []
730
+
731
+ let entries: Array<string>
732
+ try {
733
+ entries = readdirSync(packagesDir)
734
+ } catch {
735
+ return []
736
+ }
737
+
738
+ const dists: Array<string> = []
739
+ for (const entry of entries) {
740
+ const distPath = join(packagesDir, entry, 'dist')
741
+ try {
742
+ if (!statSync(distPath).isDirectory()) continue
743
+ } catch {
744
+ continue
745
+ }
746
+ try {
747
+ dists.push(realpathSync(distPath))
748
+ } catch {
749
+ dists.push(distPath)
750
+ }
751
+ }
752
+
753
+ return dists
754
+ }