@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,565 @@
1
+ import { VIRTUAL_MODULES } from '@tanstack/start-server-core'
2
+ import { generateSerializationAdaptersModule } from '../serialization-adapters-module'
3
+ import { generateServerFnResolverModule } from '../start-compiler/server-fn-resolver-module'
4
+ import { buildStartManifest } from '../start-manifest-plugin/manifestBuilder'
5
+ import { RSBUILD_ENVIRONMENT_NAMES } from './planning'
6
+ import type {
7
+ RsbuildPluginAPI,
8
+ rspack as rspackNamespaceType,
9
+ } from '@rsbuild/core'
10
+ import type {
11
+ GetConfigFn,
12
+ NormalizedClientBuild,
13
+ SerializationAdapterConfig,
14
+ } from '../types'
15
+ import type { ServerFn } from '../start-compiler/types'
16
+
17
+ type RspackNamespace = typeof rspackNamespaceType
18
+ type RspackVirtualModulesPlugin = InstanceType<
19
+ RspackNamespace['experiments']['VirtualModulesPlugin']
20
+ >
21
+
22
+ // Virtual module IDs for RSC (must match what react-start-rsc imports)
23
+ const RSC_RUNTIME_VIRTUAL_ID = 'virtual:tanstack-rsc-runtime'
24
+ const RSC_HMR_VIRTUAL_ID = 'virtual:tanstack-rsc-hmr'
25
+ const RSC_BROWSER_DECODE_VIRTUAL_ID = 'virtual:tanstack-rsc-browser-decode'
26
+ const RSC_SSR_DECODE_VIRTUAL_ID = 'virtual:tanstack-rsc-ssr-decode'
27
+ export const START_MANIFEST_PLACEHOLDER = '__TSS_START_MANIFEST_PLACEHOLDER__'
28
+ const DEV_START_MANIFEST_GLOBAL = '__TSS_DEV_START_MANIFEST__'
29
+ /**
30
+ * VirtualModulesPlugin resolves module paths relative to compiler.context.
31
+ * Prefix them under the app root so they are unique and watcher-friendly.
32
+ */
33
+ function virtualPath(root: string, moduleId: string): string {
34
+ // VirtualModulesPlugin resolves paths relative to compiler.context (root).
35
+ // Use a recognizable prefix to avoid collisions with real files.
36
+ const sanitized = moduleId.replace(/[:#]/g, '_')
37
+ return `${root}/node_modules/.virtual/${sanitized}.js`
38
+ }
39
+
40
+ export interface VirtualModuleState {
41
+ /** Call to update manifest content after client build completes */
42
+ updateManifest: (clientBuild: NormalizedClientBuild) => void
43
+ /** Call to update server fn resolver content after compilation discovers fns */
44
+ updateServerFnResolver: () => void
45
+ /** Try to write explicit resolver content now; queues if env not ready */
46
+ tryUpdateServerFnResolver: (content: string) => void
47
+ /** Get the virtual path for a given module ID */
48
+ getVirtualPath: (moduleId: string) => string
49
+ /** Generate resolver module content from current serverFnsById state.
50
+ * When forProvider=true, generates without isClientReferenced checks (RSC layer). */
51
+ generateCurrentResolverContent: (forProvider?: boolean) => string
52
+ /** The absolute virtual path of the server fn resolver module */
53
+ serverFnResolverPath: string
54
+ /** The absolute virtual path of the manifest module */
55
+ manifestPath: string
56
+ /** Generate manifest module content from a given client build */
57
+ generateManifestContent: (clientBuild: NormalizedClientBuild) => string
58
+ /** Generate the serialized manifest value literal for asset patching */
59
+ generateManifestValueLiteral: (clientBuild: NormalizedClientBuild) => string
60
+ /** VirtualModulesPlugin instances keyed by environment name */
61
+ vmPlugins: Record<string, RspackVirtualModulesPlugin>
62
+ }
63
+
64
+ // ---------------------------------------------------------------------------
65
+ // Manifest module codegen
66
+ // ---------------------------------------------------------------------------
67
+
68
+ function generateManifestModuleDev(devClientEntryUrl: string): string {
69
+ return `const fallbackManifest = {
70
+ routes: {},
71
+ clientEntry: '${devClientEntryUrl}',
72
+ }
73
+ export const tsrStartManifest = () => globalThis[${JSON.stringify(DEV_START_MANIFEST_GLOBAL)}] ?? fallbackManifest`
74
+ }
75
+
76
+ function buildStartManifestData(
77
+ clientBuild: NormalizedClientBuild,
78
+ publicBase: string,
79
+ ) {
80
+ const routeTreeRoutes = globalThis.TSS_ROUTES_MANIFEST
81
+ return buildStartManifest({
82
+ clientBuild,
83
+ routeTreeRoutes,
84
+ basePath: publicBase,
85
+ })
86
+ }
87
+
88
+ function serializeStartManifestData(
89
+ clientBuild: NormalizedClientBuild,
90
+ publicBase: string,
91
+ ): string {
92
+ return JSON.stringify(buildStartManifestData(clientBuild, publicBase))
93
+ }
94
+
95
+ function generateManifestModuleBuild(
96
+ clientBuild: NormalizedClientBuild | undefined,
97
+ publicBase: string,
98
+ _devClientEntryUrl: string,
99
+ ): string {
100
+ if (!clientBuild) {
101
+ return `const tsrStartManifestData = ${JSON.stringify(START_MANIFEST_PLACEHOLDER)}
102
+ export const tsrStartManifest = () => tsrStartManifestData`
103
+ }
104
+
105
+ return `export const tsrStartManifest = () => (${serializeStartManifestData(clientBuild, publicBase)})`
106
+ }
107
+
108
+ // ---------------------------------------------------------------------------
109
+ // Injected head scripts codegen
110
+ // ---------------------------------------------------------------------------
111
+
112
+ function generateInjectedHeadScripts(scripts?: string): string {
113
+ return `export const injectedHeadScripts = ${scripts ? JSON.stringify(scripts) : 'undefined'}`
114
+ }
115
+
116
+ // ---------------------------------------------------------------------------
117
+ // RSC virtual module codegen
118
+ // ---------------------------------------------------------------------------
119
+
120
+ /**
121
+ * Generate virtual:tanstack-rsc-runtime content.
122
+ * In the RSC layer this re-exports from react-server-dom-rspack/server.
123
+ * In other layers it provides stubs that throw.
124
+ */
125
+ function generateRscRuntimeModule(isRscLayer: boolean): string {
126
+ if (isRscLayer) {
127
+ // Re-export the RSC runtime functions from react-server-dom-rspack/server.
128
+ // NOTE: `createFromReadableStream` is a CLIENT-side API — it's NOT exported
129
+ // by react-server-dom-rspack/server. The RSC layer never needs it (RSC
130
+ // renders TO streams, doesn't decode FROM them). We provide a stub that
131
+ // throws so the export surface stays consistent across bundlers.
132
+ return `export { renderToReadableStream, createTemporaryReferenceSet, decodeReply, decodeAction, decodeFormState } from 'react-server-dom-rspack/server'
133
+ export function createFromReadableStream() { throw new Error('createFromReadableStream is not available in RSC environment (use SSR or browser decode instead)'); }
134
+ // loadServerAction is provided by the RSC entry, not react-server-dom-rspack
135
+ import { getServerFnById } from '#tanstack-start-server-fn-resolver'
136
+ export const loadServerAction = async (id) => getServerFnById(id, { origin: 'server' })`
137
+ }
138
+
139
+ // In other layers, provide stubs that throw
140
+ return `
141
+ export function renderToReadableStream() { throw new Error('renderToReadableStream can only be used in RSC environment'); }
142
+ export function createFromReadableStream() { throw new Error('createFromReadableStream can only be used in RSC environment'); }
143
+ export function createTemporaryReferenceSet() { throw new Error('createTemporaryReferenceSet can only be used in RSC environment'); }
144
+ export function decodeReply() { throw new Error('decodeReply can only be used in RSC environment'); }
145
+ export function loadServerAction() { throw new Error('loadServerAction can only be used in RSC environment'); }
146
+ export function decodeAction() { throw new Error('decodeAction can only be used in RSC environment'); }
147
+ export function decodeFormState() { throw new Error('decodeFormState can only be used in RSC environment'); }
148
+ `
149
+ }
150
+
151
+ /**
152
+ * Generate virtual:tanstack-rsc-hmr content.
153
+ * In the client env during dev, listens for rsc:update WebSocket events
154
+ * and invalidates the router. In all other contexts, exports nothing.
155
+ */
156
+ function generateRscHmrModule(isClientEnv: boolean, isDev: boolean): string {
157
+ if (!isClientEnv || !isDev) {
158
+ return 'export function setupRscHmr() {}'
159
+ }
160
+
161
+ // Rsbuild dev server delivers custom WebSocket events through
162
+ // import.meta.webpackHot.on(event, cb). The server-side
163
+ // sockWrite('custom', { event: 'rsc:update' }) maps directly to that.
164
+ return `
165
+ // RSC HMR listener for rsbuild dev server
166
+ // Listens for 'rsc:update' custom events sent via sockWrite
167
+ export function setupRscHmr() {
168
+ let __invalidateQueued = false
169
+
170
+ function __queueInvalidate() {
171
+ if (__invalidateQueued) return
172
+ __invalidateQueued = true
173
+ queueMicrotask(async () => {
174
+ __invalidateQueued = false
175
+ try {
176
+ const router = window.__TSR_ROUTER__
177
+ if (!router) {
178
+ console.warn('[rsc:hmr] No router found on window.__TSR_ROUTER__')
179
+ return
180
+ }
181
+ await router.invalidate()
182
+ } catch (e) {
183
+ console.warn('[rsc:hmr] Failed to invalidate router:', e)
184
+ }
185
+ })
186
+ }
187
+
188
+ if (import.meta.webpackHot) {
189
+ import.meta.webpackHot.on('rsc:update', () => {
190
+ __queueInvalidate()
191
+ })
192
+ }
193
+ }
194
+ `
195
+ }
196
+
197
+ // ---------------------------------------------------------------------------
198
+ // Main registration
199
+ // ---------------------------------------------------------------------------
200
+
201
+ export interface RegisterVirtualModulesOptions {
202
+ root: string
203
+ getConfig: GetConfigFn
204
+ serverFnsById: Record<string, ServerFn>
205
+ providerEnvName: string
206
+ ssrIsProvider: boolean
207
+ serializationAdapters: Array<SerializationAdapterConfig> | undefined
208
+ /**
209
+ * Get the URL at which the rsbuild dev server serves the client entry JS.
210
+ * Called lazily inside modifyRspackConfig when getConfig() is available.
211
+ * Example return: '/static/js/index.js'
212
+ */
213
+ getDevClientEntryUrl: (publicBase: string) => string
214
+ /** Whether RSC virtual modules should be registered. */
215
+ rscEnabled?: boolean | undefined
216
+ }
217
+
218
+ /**
219
+ * Registers virtual modules for the rsbuild adapter using VirtualModulesPlugin.
220
+ *
221
+ * Creates one VirtualModulesPlugin per environment and registers them via
222
+ * `modifyBundlerChain`. Provides update functions to refresh content dynamically.
223
+ */
224
+ export function registerVirtualModules(
225
+ api: RsbuildPluginAPI,
226
+ opts: RegisterVirtualModulesOptions,
227
+ ): VirtualModuleState {
228
+ const isDev = api.context.action === 'dev'
229
+ const root = opts.root
230
+
231
+ // Virtual module paths keyed by module ID
232
+ const paths = {
233
+ manifest: virtualPath(root, VIRTUAL_MODULES.startManifest),
234
+ injectedHeadScripts: virtualPath(root, VIRTUAL_MODULES.injectedHeadScripts),
235
+ serverFnResolver: virtualPath(root, VIRTUAL_MODULES.serverFnResolver),
236
+ pluginAdapters: virtualPath(root, VIRTUAL_MODULES.pluginAdapters),
237
+ }
238
+
239
+ // RSC virtual module paths (only defined when RSC is enabled)
240
+ const rscPaths = opts.rscEnabled
241
+ ? {
242
+ rscRuntime: virtualPath(root, RSC_RUNTIME_VIRTUAL_ID),
243
+ rscHmr: virtualPath(root, RSC_HMR_VIRTUAL_ID),
244
+ rscBrowserDecode: virtualPath(root, RSC_BROWSER_DECODE_VIRTUAL_ID),
245
+ rscSsrDecode: virtualPath(root, RSC_SSR_DECODE_VIRTUAL_ID),
246
+ }
247
+ : null
248
+
249
+ // Track VirtualModulesPlugin instances per environment for dynamic updates
250
+ const vmPlugins: Record<string, RspackVirtualModulesPlugin> = {}
251
+ const readyVmPlugins: Record<string, boolean> = {}
252
+ const pendingWrites = new Map<string, Map<string, string>>()
253
+
254
+ let clientBuild: NormalizedClientBuild | undefined
255
+ const lastResolverContentByEnvironment: Record<string, string | undefined> =
256
+ {}
257
+ const hasSeparateProviderEnvironment =
258
+ !opts.rscEnabled &&
259
+ opts.providerEnvName !== RSBUILD_ENVIRONMENT_NAMES.server
260
+
261
+ function isProviderEnvironment(environmentName: string): boolean {
262
+ return environmentName === opts.providerEnvName
263
+ }
264
+
265
+ function needsServerFnResolver(environmentName: string): boolean {
266
+ return (
267
+ environmentName === RSBUILD_ENVIRONMENT_NAMES.server ||
268
+ (hasSeparateProviderEnvironment && isProviderEnvironment(environmentName))
269
+ )
270
+ }
271
+
272
+ function generateResolverContent(environmentName: string): string {
273
+ return generateServerFnResolverModule({
274
+ serverFnsById: opts.serverFnsById,
275
+ includeClientReferencedCheck: !isProviderEnvironment(environmentName),
276
+ useStaticImports: Boolean(opts.rscEnabled && isDev),
277
+ })
278
+ }
279
+
280
+ function writeResolverContent(environmentName: string, content: string) {
281
+ if (
282
+ !isDev ||
283
+ content !== lastResolverContentByEnvironment[environmentName]
284
+ ) {
285
+ lastResolverContentByEnvironment[environmentName] = content
286
+ tryWriteModule(environmentName, paths.serverFnResolver, content)
287
+ }
288
+ }
289
+
290
+ function queuePendingWrite(
291
+ environmentName: string,
292
+ filePath: string,
293
+ content: string,
294
+ ) {
295
+ let writes = pendingWrites.get(environmentName)
296
+ if (!writes) {
297
+ writes = new Map()
298
+ pendingWrites.set(environmentName, writes)
299
+ }
300
+ writes.set(filePath, content)
301
+ }
302
+
303
+ function tryWriteModule(
304
+ environmentName: string,
305
+ filePath: string,
306
+ content: string,
307
+ ) {
308
+ const vmPlugin = vmPlugins[environmentName]
309
+ if (!vmPlugin || !readyVmPlugins[environmentName]) {
310
+ queuePendingWrite(environmentName, filePath, content)
311
+ return false
312
+ }
313
+
314
+ vmPlugin.writeModule(filePath, content)
315
+ return true
316
+ }
317
+
318
+ function flushPendingWrites(environmentName: string) {
319
+ if (!readyVmPlugins[environmentName]) {
320
+ return
321
+ }
322
+
323
+ const writes = pendingWrites.get(environmentName)
324
+ if (!writes?.size) {
325
+ return
326
+ }
327
+
328
+ for (const [filePath, content] of writes) {
329
+ if (!tryWriteModule(environmentName, filePath, content)) {
330
+ return
331
+ }
332
+ writes.delete(filePath)
333
+ }
334
+
335
+ if (writes.size === 0) {
336
+ pendingWrites.delete(environmentName)
337
+ }
338
+ }
339
+
340
+ // NOTE: getConfig() is deferred — it must NOT be called until modifyRsbuildConfig
341
+ // has resolved (which sets resolvedStartConfig.root). All access to resolvedStartConfig
342
+ // happens inside modifyRspackConfig (which runs after modifyRsbuildConfig) or
343
+ // inside update callbacks (which run even later during/after compilation).
344
+
345
+ // Generate initial content for each virtual module per environment
346
+ function getInitialContent(environmentName: string): Record<string, string> {
347
+ // Safe to call getConfig() here — this runs inside modifyRspackConfig
348
+ const { resolvedStartConfig } = opts.getConfig()
349
+ const isServerEnv = environmentName === RSBUILD_ENVIRONMENT_NAMES.server
350
+ const isClientEnv = environmentName === RSBUILD_ENVIRONMENT_NAMES.client
351
+ const content: Record<string, string> = {}
352
+
353
+ // Manifest — only meaningful for server env
354
+ if (isServerEnv) {
355
+ const devClientEntryUrl = opts.getDevClientEntryUrl(
356
+ resolvedStartConfig.basePaths.publicBase,
357
+ )
358
+ content[paths.manifest] = isDev
359
+ ? generateManifestModuleDev(devClientEntryUrl)
360
+ : generateManifestModuleBuild(
361
+ clientBuild,
362
+ resolvedStartConfig.basePaths.publicBase,
363
+ devClientEntryUrl,
364
+ )
365
+ } else {
366
+ content[paths.manifest] = 'export default {}'
367
+ }
368
+
369
+ // Injected head scripts — only server
370
+ content[paths.injectedHeadScripts] = generateInjectedHeadScripts()
371
+
372
+ // Server fn resolver — SSR and provider environments
373
+ if (needsServerFnResolver(environmentName)) {
374
+ content[paths.serverFnResolver] = generateResolverContent(environmentName)
375
+ } else {
376
+ // Client doesn't need the resolver but needs a valid module
377
+ content[paths.serverFnResolver] = 'export {}'
378
+ }
379
+
380
+ // Plugin adapters — both environments get environment-specific content
381
+ content[paths.pluginAdapters] = generateSerializationAdaptersModule({
382
+ adapters: opts.serializationAdapters,
383
+ runtime:
384
+ environmentName === RSBUILD_ENVIRONMENT_NAMES.client
385
+ ? 'client'
386
+ : 'server',
387
+ })
388
+
389
+ // --- RSC virtual modules ---
390
+ if (rscPaths) {
391
+ // virtual:tanstack-rsc-runtime
392
+ // In the server env, this provides the RSC runtime for the RSC layer.
393
+ // The virtual module content is the same regardless of layer since
394
+ // rspack layers handle module isolation. The RSC entry imports this
395
+ // and the react-server condition on the RSC layer resolves
396
+ // react-server-dom-rspack/server correctly.
397
+ if (isServerEnv) {
398
+ // Server env gets the real RSC runtime (used by RSC layer)
399
+ content[rscPaths.rscRuntime] = generateRscRuntimeModule(true)
400
+ } else {
401
+ // Client env gets stubs
402
+ content[rscPaths.rscRuntime] = generateRscRuntimeModule(false)
403
+ }
404
+
405
+ // virtual:tanstack-rsc-hmr
406
+ content[rscPaths.rscHmr] = generateRscHmrModule(isClientEnv, isDev)
407
+ content[rscPaths.rscBrowserDecode] = isClientEnv
408
+ ? `export * from '@tanstack/react-start/rsbuild/browser-decode'`
409
+ : `export function createFromReadableStream() { throw new Error('RSC browser decode is only available in the client environment') }
410
+ export function createFromFetch() { throw new Error('RSC browser decode is only available in the client environment') }`
411
+ content[rscPaths.rscSsrDecode] = isServerEnv
412
+ ? `export * from '@tanstack/react-start/rsbuild/ssr-decode'`
413
+ : `export function setOnClientReference() {}
414
+ export function createFromReadableStream() { throw new Error('RSC SSR decode is only available in the server environment') }`
415
+ }
416
+
417
+ return content
418
+ }
419
+
420
+ // Build a map from virtual module IDs to their virtual file paths.
421
+ // Scheme-like IDs are rewritten with NormalModuleReplacementPlugin because
422
+ // rspack validates request schemes before normal alias resolution.
423
+ const aliasMap: Record<string, string> = {
424
+ [VIRTUAL_MODULES.startManifest]: paths.manifest,
425
+ [VIRTUAL_MODULES.injectedHeadScripts]: paths.injectedHeadScripts,
426
+ [VIRTUAL_MODULES.serverFnResolver]: paths.serverFnResolver,
427
+ [VIRTUAL_MODULES.pluginAdapters]: paths.pluginAdapters,
428
+ }
429
+
430
+ // Add RSC virtual module aliases
431
+ if (rscPaths) {
432
+ aliasMap[RSC_RUNTIME_VIRTUAL_ID] = rscPaths.rscRuntime
433
+ aliasMap[RSC_HMR_VIRTUAL_ID] = rscPaths.rscHmr
434
+ aliasMap[RSC_BROWSER_DECODE_VIRTUAL_ID] = rscPaths.rscBrowserDecode
435
+ aliasMap[RSC_SSR_DECODE_VIRTUAL_ID] = rscPaths.rscSsrDecode
436
+ }
437
+
438
+ // Register VirtualModulesPlugin per environment via modifyRspackConfig
439
+ api.modifyRspackConfig((config, utils) => {
440
+ const envName = utils.environment.name
441
+ const initialContent = getInitialContent(envName)
442
+
443
+ // Create VirtualModulesPlugin instance
444
+ const VMP = utils.rspack.experiments.VirtualModulesPlugin
445
+ const vmPlugin = new VMP(initialContent)
446
+ vmPlugins[envName] = vmPlugin
447
+ readyVmPlugins[envName] = false
448
+ config.plugins.push(vmPlugin)
449
+ config.plugins.push({
450
+ apply(compiler: {
451
+ hooks: {
452
+ thisCompilation: { tap: (name: string, handler: () => void) => void }
453
+ }
454
+ }) {
455
+ compiler.hooks.thisCompilation.tap(
456
+ 'TanStackStartFlushPendingVirtualModules',
457
+ () => {
458
+ readyVmPlugins[envName] = true
459
+ flushPendingWrites(envName)
460
+ },
461
+ )
462
+ },
463
+ })
464
+
465
+ // Rewrite scheme-like IDs to the VirtualModulesPlugin-backed file paths.
466
+ for (const [moduleId, virtualFilePath] of Object.entries(aliasMap)) {
467
+ const escaped = moduleId.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
468
+ const NMR = utils.rspack.NormalModuleReplacementPlugin
469
+ config.plugins.push(new NMR(new RegExp(`^${escaped}$`), virtualFilePath))
470
+ }
471
+
472
+ const resolve = config.resolve
473
+ const resolveAlias = (resolve.alias ??= {}) as Record<string, string>
474
+ resolveAlias[VIRTUAL_MODULES.serverFnResolver] = paths.serverFnResolver
475
+ resolveAlias[VIRTUAL_MODULES.pluginAdapters] = paths.pluginAdapters
476
+
477
+ // Add RSC-specific resolve aliases
478
+ if (rscPaths) {
479
+ resolveAlias[RSC_RUNTIME_VIRTUAL_ID] = rscPaths.rscRuntime
480
+ resolveAlias[RSC_HMR_VIRTUAL_ID] = rscPaths.rscHmr
481
+ resolveAlias[RSC_BROWSER_DECODE_VIRTUAL_ID] = rscPaths.rscBrowserDecode
482
+ resolveAlias[RSC_SSR_DECODE_VIRTUAL_ID] = rscPaths.rscSsrDecode
483
+ }
484
+ })
485
+
486
+ return {
487
+ serverFnResolverPath: paths.serverFnResolver,
488
+ manifestPath: paths.manifest,
489
+ vmPlugins,
490
+
491
+ generateCurrentResolverContent(forProvider?: boolean): string {
492
+ return generateResolverContent(
493
+ forProvider ? opts.providerEnvName : RSBUILD_ENVIRONMENT_NAMES.server,
494
+ )
495
+ },
496
+
497
+ generateManifestContent(newClientBuild: NormalizedClientBuild): string {
498
+ const { resolvedStartConfig } = opts.getConfig()
499
+ const devClientEntryUrl = opts.getDevClientEntryUrl(
500
+ resolvedStartConfig.basePaths.publicBase,
501
+ )
502
+ return generateManifestModuleBuild(
503
+ newClientBuild,
504
+ resolvedStartConfig.basePaths.publicBase,
505
+ devClientEntryUrl,
506
+ )
507
+ },
508
+
509
+ generateManifestValueLiteral(
510
+ newClientBuild: NormalizedClientBuild,
511
+ ): string {
512
+ const { resolvedStartConfig } = opts.getConfig()
513
+ return serializeStartManifestData(
514
+ newClientBuild,
515
+ resolvedStartConfig.basePaths.publicBase,
516
+ )
517
+ },
518
+
519
+ updateManifest(newClientBuild: NormalizedClientBuild) {
520
+ clientBuild = newClientBuild
521
+ // Safe to call getConfig() here — runs after client build
522
+ const { resolvedStartConfig } = opts.getConfig()
523
+ if (isDev) {
524
+ ;(
525
+ globalThis as typeof globalThis & {
526
+ [DEV_START_MANIFEST_GLOBAL]?: ReturnType<typeof buildStartManifest>
527
+ }
528
+ )[DEV_START_MANIFEST_GLOBAL] = buildStartManifestData(
529
+ clientBuild,
530
+ resolvedStartConfig.basePaths.publicBase,
531
+ )
532
+ }
533
+ },
534
+
535
+ updateServerFnResolver() {
536
+ for (const environmentName of new Set([
537
+ RSBUILD_ENVIRONMENT_NAMES.server,
538
+ ...(hasSeparateProviderEnvironment ? [opts.providerEnvName] : []),
539
+ ])) {
540
+ if (!needsServerFnResolver(environmentName)) {
541
+ continue
542
+ }
543
+
544
+ writeResolverContent(
545
+ environmentName,
546
+ generateResolverContent(environmentName),
547
+ )
548
+ }
549
+ },
550
+
551
+ tryUpdateServerFnResolver(content: string) {
552
+ lastResolverContentByEnvironment[RSBUILD_ENVIRONMENT_NAMES.server] =
553
+ content
554
+ tryWriteModule(
555
+ RSBUILD_ENVIRONMENT_NAMES.server,
556
+ paths.serverFnResolver,
557
+ content,
558
+ )
559
+ },
560
+
561
+ getVirtualPath(moduleId: string): string {
562
+ return virtualPath(root, moduleId)
563
+ },
564
+ }
565
+ }