@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,4 @@
1
+ export { RSBUILD_ENVIRONMENT_NAMES } from './planning'
2
+ export type { TanStackStartRsbuildPluginCoreOptions } from './types'
3
+ export type { TanStackStartRsbuildInputConfig } from './schema'
4
+ export { tanStackStartRsbuild } from './plugin'
@@ -0,0 +1,346 @@
1
+ import { tsrSplit } from '@tanstack/router-plugin'
2
+ import { RSBUILD_ENVIRONMENT_NAMES } from './planning'
3
+ import type { RsbuildPluginAPI, Rspack } from '@rsbuild/core'
4
+ import type { NormalizedClientBuild, NormalizedClientChunk } from '../types'
5
+
6
+ type ProcessAssetsContext = Parameters<
7
+ Parameters<RsbuildPluginAPI['processAssets']>[1]
8
+ >[0]
9
+ type RspackCompilation = Rspack.Compilation
10
+ type RspackCompilationChunk = Rspack.Chunk
11
+ type RspackModule = Rspack.Module
12
+
13
+ /**
14
+ * Extract route file paths from rspack module identifiers.
15
+ *
16
+ * In rspack, module identifiers contain query params similar to Vite's moduleIds.
17
+ * We look for the `tsr-split` query to identify route-split chunks.
18
+ */
19
+ function getRouteFilePathsFromModules(
20
+ modules: Array<RspackModule>,
21
+ ): Array<string> {
22
+ let routeFilePaths: Array<string> | undefined
23
+ let seen: Set<string> | undefined
24
+
25
+ for (const mod of modules) {
26
+ const identifier = mod.identifier()
27
+
28
+ // rspack module identifiers include loader prefixes separated by '!'.
29
+ // The actual file path (with query string) is after the last '!'.
30
+ // Example: "builtin:swc-loader??ruleSet[...]!.../transform.js??...!.../rsc-basic.tsx?tsr-split=component"
31
+ const lastBangIndex = identifier.lastIndexOf('!')
32
+ const resourcePart =
33
+ lastBangIndex >= 0 ? identifier.slice(lastBangIndex + 1) : identifier
34
+
35
+ const queryIndex = resourcePart.indexOf('?')
36
+ if (queryIndex < 0) continue
37
+
38
+ const query = resourcePart.slice(queryIndex + 1)
39
+ if (!query.includes(tsrSplit)) continue
40
+ if (!new URLSearchParams(query).has(tsrSplit)) continue
41
+
42
+ const nameForCondition = mod.nameForCondition()
43
+ const routeFilePath = nameForCondition ?? resourcePart.slice(0, queryIndex)
44
+
45
+ if (seen?.has(routeFilePath)) continue
46
+
47
+ if (!routeFilePaths || !seen) {
48
+ routeFilePaths = []
49
+ seen = new Set()
50
+ }
51
+
52
+ routeFilePaths.push(routeFilePath)
53
+ seen.add(routeFilePath)
54
+ }
55
+
56
+ return routeFilePaths ?? []
57
+ }
58
+
59
+ /**
60
+ * Returns true for Rspack/webpack HMR runtime chunks that should never be
61
+ * surfaced to the Start manifest. These files are emitted on every rebuild
62
+ * (e.g. `index.<hash>.hot-update.mjs`) and must not be treated as the entry
63
+ * chunk, route preloads, or sibling imports.
64
+ */
65
+ function isHotUpdateAsset(file: string): boolean {
66
+ return file.includes('.hot-update.')
67
+ }
68
+
69
+ /**
70
+ * True for any JS/MJS asset that should be included in the manifest.
71
+ * Excludes HMR runtime patches.
72
+ */
73
+ function isManifestJsAsset(file: string): boolean {
74
+ if (!file.endsWith('.js') && !file.endsWith('.mjs')) return false
75
+ return !isHotUpdateAsset(file)
76
+ }
77
+
78
+ /**
79
+ * Get all JS file names from a chunk.
80
+ */
81
+ function getChunkJsFiles(chunk: RspackCompilationChunk): Array<string> {
82
+ const jsFiles: Array<string> = []
83
+ for (const file of chunk.files) {
84
+ if (isManifestJsAsset(file)) {
85
+ jsFiles.push(file)
86
+ }
87
+ }
88
+ return jsFiles
89
+ }
90
+
91
+ /**
92
+ * Compute dynamicImports for a chunk by traversing its chunk groups'
93
+ * childrenIterable (async/dynamic import edges).
94
+ *
95
+ * In rspack, a chunk belongs to one or more ChunkGroups. Each ChunkGroup
96
+ * has childrenIterable — child ChunkGroups representing dynamic import()
97
+ * points. The JS files from those child groups' chunks are the
98
+ * dynamicImports (analogous to Rollup's OutputChunk.dynamicImports).
99
+ */
100
+ function computeDynamicImports(chunk: RspackCompilationChunk): Array<string> {
101
+ const dynamicImportFiles: Array<string> = []
102
+ const seen = new Set<string>()
103
+
104
+ for (const group of chunk.groupsIterable) {
105
+ for (const childGroup of group.childrenIterable) {
106
+ for (const childChunk of childGroup.chunks) {
107
+ for (const file of childChunk.files) {
108
+ if (isManifestJsAsset(file) && !seen.has(file)) {
109
+ seen.add(file)
110
+ dynamicImportFiles.push(file)
111
+ }
112
+ }
113
+ }
114
+ }
115
+ }
116
+
117
+ return dynamicImportFiles
118
+ }
119
+
120
+ /**
121
+ * Compute static imports (sibling chunks) for an async chunk.
122
+ *
123
+ * In rspack/webpack, an async chunk's ChunkGroup contains ALL chunks needed to
124
+ * satisfy that dynamic import — the async chunk itself plus any shared/vendor
125
+ * chunks it statically imports. This is analogous to Rollup's
126
+ * `OutputChunk.imports` for async chunks.
127
+ *
128
+ * We collect JS files from all sibling chunks in the group (excluding the
129
+ * current chunk's own file) to populate the `imports` field.
130
+ */
131
+ function computeAsyncChunkImports(
132
+ chunk: RspackCompilationChunk,
133
+ currentFile: string,
134
+ ): Array<string> {
135
+ const imports: Array<string> = []
136
+ const seen = new Set<string>()
137
+ seen.add(currentFile)
138
+
139
+ for (const group of chunk.groupsIterable) {
140
+ for (const siblingChunk of group.chunks) {
141
+ for (const file of siblingChunk.files) {
142
+ if (isManifestJsAsset(file) && !seen.has(file)) {
143
+ seen.add(file)
144
+ imports.push(file)
145
+ }
146
+ }
147
+ }
148
+ }
149
+
150
+ return imports
151
+ }
152
+
153
+ /**
154
+ * Normalize an rspack compilation into a NormalizedClientBuild.
155
+ *
156
+ * Iterates ALL chunks in the compilation (initial + async), not just
157
+ * entrypoint chunks, to ensure route-split async chunks are included.
158
+ */
159
+ export function normalizeRspackClientBuild(
160
+ compilation: RspackCompilation,
161
+ ): NormalizedClientBuild {
162
+ const chunksByFileName = new Map<string, NormalizedClientChunk>()
163
+ const chunkFileNamesByRouteFilePath = new Map<string, Array<string>>()
164
+ const cssFilesBySourcePath = new Map<string, Array<string>>()
165
+ let entryChunkFileName: string | undefined
166
+
167
+ // Collect all initial JS file names from the main entry for computing
168
+ // the entry chunk's `imports` (vendor/shared sibling chunks).
169
+ const entrypoint = compilation.entrypoints.get('index')
170
+ const initialJsFileNames: Array<string> = []
171
+ const entryChunkSet = new Set<RspackCompilationChunk>()
172
+ if (entrypoint) {
173
+ for (const chunk of entrypoint.chunks) {
174
+ entryChunkSet.add(chunk)
175
+ for (const file of chunk.files) {
176
+ if (isManifestJsAsset(file)) {
177
+ initialJsFileNames.push(file)
178
+ }
179
+ }
180
+ }
181
+ }
182
+
183
+ // Iterate ALL chunks (initial + async) to capture route-split chunks
184
+ for (const chunk of compilation.chunks) {
185
+ const modules = compilation.chunkGraph.getChunkModules(chunk)
186
+ const routeFilePaths = getRouteFilePathsFromModules(modules)
187
+ const cssFiles: Array<string> = []
188
+ const seenCssFiles = new Set<string>()
189
+
190
+ for (const auxFile of chunk.auxiliaryFiles) {
191
+ if (auxFile.endsWith('.css') && !seenCssFiles.has(auxFile)) {
192
+ seenCssFiles.add(auxFile)
193
+ cssFiles.push(auxFile)
194
+ }
195
+ }
196
+
197
+ for (const mainFile of chunk.files) {
198
+ if (mainFile.endsWith('.css') && !seenCssFiles.has(mainFile)) {
199
+ seenCssFiles.add(mainFile)
200
+ cssFiles.push(mainFile)
201
+ }
202
+ }
203
+
204
+ if (cssFiles.length > 0) {
205
+ for (const mod of modules) {
206
+ const sourcePath = mod.nameForCondition()
207
+ if (!sourcePath) continue
208
+
209
+ const existing = cssFilesBySourcePath.get(sourcePath)
210
+ cssFilesBySourcePath.set(
211
+ sourcePath,
212
+ existing ? appendUniqueStrings(existing, cssFiles) : cssFiles.slice(),
213
+ )
214
+ }
215
+ }
216
+
217
+ // The entry chunk is the one named 'index' in the 'index' entrypoint
218
+ const isEntryChunk = chunk.name === 'index' && entryChunkSet.has(chunk)
219
+
220
+ const jsFiles = getChunkJsFiles(chunk)
221
+ if (jsFiles.length === 0) continue
222
+
223
+ // Compute dynamicImports from chunk group children
224
+ const dynamicImports = computeDynamicImports(chunk)
225
+
226
+ for (const file of jsFiles) {
227
+ // For the entry chunk, `imports` contains all sibling initial chunks
228
+ // (vendor/shared). For async chunks, `imports` contains all sibling
229
+ // chunks from the ChunkGroup (shared dependencies the browser must
230
+ // load alongside this chunk). This mirrors Rollup's
231
+ // OutputChunk.imports which lists statically imported chunks.
232
+ const imports = isEntryChunk
233
+ ? initialJsFileNames.filter((f) => f !== file)
234
+ : computeAsyncChunkImports(chunk, file)
235
+
236
+ const normalizedChunk: NormalizedClientChunk = {
237
+ fileName: file,
238
+ isEntry: isEntryChunk,
239
+ imports,
240
+ dynamicImports,
241
+ css: [],
242
+ routeFilePaths,
243
+ }
244
+
245
+ chunksByFileName.set(file, normalizedChunk)
246
+
247
+ if (isEntryChunk && !entryChunkFileName) {
248
+ entryChunkFileName = file
249
+ }
250
+
251
+ for (const routeFilePath of routeFilePaths) {
252
+ let chunkFileNames = chunkFileNamesByRouteFilePath.get(routeFilePath)
253
+ if (!chunkFileNames) {
254
+ chunkFileNames = []
255
+ chunkFileNamesByRouteFilePath.set(routeFilePath, chunkFileNames)
256
+ }
257
+ chunkFileNames.push(file)
258
+ }
259
+ }
260
+
261
+ for (const cssFile of cssFiles) {
262
+ for (const file of jsFiles) {
263
+ const existing = chunksByFileName.get(file)
264
+ if (existing && !existing.css.includes(cssFile)) {
265
+ existing.css.push(cssFile)
266
+ }
267
+ }
268
+ }
269
+ }
270
+
271
+ if (!entryChunkFileName) {
272
+ throw new Error('No entry file found in rspack client build')
273
+ }
274
+
275
+ // In RSC mode, CSS from server components is associated with the 'rsc'
276
+ // client entry chunk (not the main 'index' entry). The manifest builder
277
+ // merges the entry chunk's CSS into __root__, so by appending RSC CSS
278
+ // to the entry chunk, those stylesheets get loaded on all pages.
279
+ // CSS may appear in either `files` or `auxiliaryFiles` depending on
280
+ // rspack's CSS extraction strategy.
281
+ const rscEntrypoint = compilation.entrypoints.get('rsc')
282
+
283
+ if (rscEntrypoint && entryChunkFileName) {
284
+ const mainEntryChunk = chunksByFileName.get(entryChunkFileName)
285
+ if (mainEntryChunk) {
286
+ for (const rscChunk of rscEntrypoint.chunks) {
287
+ const allFiles = [...rscChunk.files, ...rscChunk.auxiliaryFiles]
288
+ for (const file of allFiles) {
289
+ if (file.endsWith('.css') && !mainEntryChunk.css.includes(file)) {
290
+ mainEntryChunk.css.push(file)
291
+ }
292
+ }
293
+ }
294
+ }
295
+ }
296
+
297
+ return {
298
+ entryChunkFileName,
299
+ chunksByFileName,
300
+ chunkFileNamesByRouteFilePath,
301
+ cssFilesBySourcePath,
302
+ }
303
+ }
304
+
305
+ function appendUniqueStrings(
306
+ target: Array<string>,
307
+ source: Array<string>,
308
+ ): Array<string> {
309
+ const seen = new Set(target)
310
+ let result: Array<string> | undefined
311
+
312
+ for (const value of source) {
313
+ if (seen.has(value)) continue
314
+ seen.add(value)
315
+ if (!result) {
316
+ result = target.slice()
317
+ }
318
+ result.push(value)
319
+ }
320
+
321
+ return result ?? target
322
+ }
323
+
324
+ /**
325
+ * Registers a processAssets hook to capture the client build stats
326
+ * after compilation. Returns a getter for the captured build.
327
+ */
328
+ export function registerClientBuildCapture(api: RsbuildPluginAPI): {
329
+ getClientBuild: () => NormalizedClientBuild | undefined
330
+ } {
331
+ let clientBuild: NormalizedClientBuild | undefined
332
+
333
+ api.processAssets(
334
+ {
335
+ stage: 'report',
336
+ environments: [RSBUILD_ENVIRONMENT_NAMES.client],
337
+ },
338
+ (context: ProcessAssetsContext) => {
339
+ clientBuild = normalizeRspackClientBuild(context.compilation)
340
+ },
341
+ )
342
+
343
+ return {
344
+ getClientBuild: () => clientBuild,
345
+ }
346
+ }
@@ -0,0 +1,234 @@
1
+ import { createRequire } from 'node:module'
2
+ import { join } from 'pathe'
3
+ import { mergeRsbuildConfig } from '@rsbuild/core'
4
+ import { ENTRY_POINTS } from '../constants'
5
+ import type { EnvironmentConfig } from '@rsbuild/core'
6
+ import type { ResolvedStartEntryPlan } from '../planning'
7
+ import type { RsbuildEnvironmentOverrides } from './types'
8
+
9
+ const require = createRequire(import.meta.url)
10
+
11
+ export const RSBUILD_ENVIRONMENT_NAMES = {
12
+ client: 'client',
13
+ server: 'ssr',
14
+ } as const
15
+
16
+ /**
17
+ * Rspack layer names for the rsbuild RSC layered model.
18
+ * These match the canonical names from `rspack.experiments.rsc.Layers`.
19
+ */
20
+ export const RSBUILD_RSC_LAYERS = {
21
+ /** React Server Components layer — uses `react-server` resolve condition */
22
+ rsc: 'react-server-components',
23
+ /** Server-Side Rendering layer — standard Node resolve */
24
+ ssr: 'server-side-rendering',
25
+ } as const
26
+
27
+ export type RsbuildEnvironmentName =
28
+ (typeof RSBUILD_ENVIRONMENT_NAMES)[keyof typeof RSBUILD_ENVIRONMENT_NAMES]
29
+
30
+ type RsbuildDistPath = NonNullable<EnvironmentConfig['output']>['distPath']
31
+
32
+ export interface RsbuildResolvedEntryAliases {
33
+ client: string
34
+ server: string
35
+ start: string
36
+ router: string
37
+ alias: Record<(typeof ENTRY_POINTS)[keyof typeof ENTRY_POINTS], string>
38
+ }
39
+
40
+ export function createRsbuildResolvedEntryAliases(opts: {
41
+ entryPaths: ResolvedStartEntryPlan['entryPaths']
42
+ }): RsbuildResolvedEntryAliases {
43
+ const client = normalizeEntryPath(opts.entryPaths.client)
44
+ const server = normalizeEntryPath(opts.entryPaths.server)
45
+ const start = normalizeEntryPath(opts.entryPaths.start)
46
+ const router = normalizeEntryPath(opts.entryPaths.router)
47
+
48
+ return {
49
+ client,
50
+ server,
51
+ start,
52
+ router,
53
+ alias: {
54
+ [ENTRY_POINTS.client]: client,
55
+ [ENTRY_POINTS.server]: server,
56
+ [ENTRY_POINTS.start]: start,
57
+ [ENTRY_POINTS.router]: router,
58
+ },
59
+ }
60
+ }
61
+
62
+ export interface RsbuildEnvironmentPlanResult {
63
+ environments: Record<string, EnvironmentConfig>
64
+ alias: Record<string, string>
65
+ }
66
+
67
+ export function createRsbuildEnvironmentPlan(opts: {
68
+ root: string
69
+ entryAliases: RsbuildResolvedEntryAliases
70
+ clientOutputDirectory: string
71
+ serverOutputDirectory: string
72
+ publicBase: string
73
+ serverFnProviderEnv: string
74
+ environmentOverrides?: RsbuildEnvironmentOverrides
75
+ rsc?: boolean | undefined
76
+ dev?: boolean | undefined
77
+ }): RsbuildEnvironmentPlanResult {
78
+ const alias = {
79
+ ...opts.entryAliases.alias,
80
+ ...(opts.rsc
81
+ ? {
82
+ 'react-server-dom-rspack/server$': resolveFromRoot(
83
+ 'react-server-dom-rspack/server.node',
84
+ opts.root,
85
+ ),
86
+ }
87
+ : {}),
88
+ }
89
+ const environmentOverrides = opts.environmentOverrides ?? {}
90
+
91
+ return {
92
+ environments: {
93
+ [RSBUILD_ENVIRONMENT_NAMES.client]: mergeRsbuildConfig(
94
+ {
95
+ source: {
96
+ entry: {
97
+ index: {
98
+ import: opts.entryAliases.client,
99
+ html: false,
100
+ },
101
+ },
102
+ },
103
+ output: {
104
+ target: 'web',
105
+ module: true,
106
+ distPath: {
107
+ root: opts.clientOutputDirectory,
108
+ },
109
+ assetPrefix: opts.publicBase,
110
+ },
111
+ resolve: {
112
+ alias,
113
+ },
114
+ // Only split async chunks (route code-splitting). Keep all initial
115
+ // vendor/shared code inlined in the entry chunk so the SSR HTML only
116
+ // needs the single client entry bootstrap.
117
+ performance: {
118
+ chunkSplit: {
119
+ strategy: 'custom',
120
+ override: {
121
+ chunks: 'async',
122
+ },
123
+ },
124
+ },
125
+ },
126
+ environmentOverrides.all,
127
+ environmentOverrides.client,
128
+ ),
129
+ [RSBUILD_ENVIRONMENT_NAMES.server]: mergeRsbuildConfig(
130
+ {
131
+ source: {
132
+ entry: {
133
+ index: {
134
+ import: opts.entryAliases.server,
135
+ html: false,
136
+ ...(opts.rsc ? { layer: RSBUILD_RSC_LAYERS.ssr } : {}),
137
+ },
138
+ },
139
+ },
140
+ output: {
141
+ target: 'node',
142
+ // Rsbuild's dev `loadBundle()` path evaluates ESM via vm.SourceTextModule,
143
+ // which requires `--experimental-vm-modules`. Emit CJS for the dev
144
+ // server bundle so SSR works without extra Node flags.
145
+ ...(opts.dev ? { module: false } : {}),
146
+ distPath: {
147
+ root: opts.serverOutputDirectory,
148
+ },
149
+ },
150
+ resolve: {
151
+ alias,
152
+ },
153
+ ...(opts.rsc
154
+ ? {
155
+ splitChunks: {
156
+ preset: 'single-vendor',
157
+ },
158
+ }
159
+ : {}),
160
+ },
161
+ environmentOverrides.all,
162
+ environmentOverrides.server,
163
+ ),
164
+ // When provider is a separate environment (not layered RSC),
165
+ // create a third environment. With the layered RSC setup this branch
166
+ // is not taken because provider maps to the same `ssr` environment.
167
+ ...(opts.serverFnProviderEnv !== RSBUILD_ENVIRONMENT_NAMES.server &&
168
+ !opts.rsc
169
+ ? {
170
+ [opts.serverFnProviderEnv]: mergeRsbuildConfig(
171
+ {
172
+ source: {
173
+ entry: {
174
+ index: {
175
+ import: opts.entryAliases.server,
176
+ html: false,
177
+ },
178
+ },
179
+ },
180
+ output: {
181
+ target: 'node',
182
+ ...(opts.dev ? { module: false } : {}),
183
+ distPath: {
184
+ root: `${opts.serverOutputDirectory}/${opts.serverFnProviderEnv}`,
185
+ },
186
+ },
187
+ resolve: {
188
+ alias,
189
+ },
190
+ },
191
+ environmentOverrides.all,
192
+ environmentOverrides.provider,
193
+ ),
194
+ }
195
+ : {}),
196
+ },
197
+ alias,
198
+ }
199
+ }
200
+
201
+ export function resolveRsbuildOutputDirectory(opts: {
202
+ distPath: RsbuildDistPath | undefined
203
+ rootDistPath: RsbuildDistPath | undefined
204
+ fallback: string
205
+ subdirectory: string
206
+ }): string {
207
+ if (typeof opts.distPath === 'string') {
208
+ return opts.distPath
209
+ }
210
+
211
+ if (typeof opts.distPath?.root === 'string') {
212
+ return opts.distPath.root
213
+ }
214
+
215
+ if (typeof opts.rootDistPath === 'string') {
216
+ return join(opts.rootDistPath, opts.subdirectory)
217
+ }
218
+
219
+ if (typeof opts.rootDistPath?.root === 'string') {
220
+ return join(opts.rootDistPath.root, opts.subdirectory)
221
+ }
222
+
223
+ return opts.fallback
224
+ }
225
+
226
+ function normalizeEntryPath(path: string) {
227
+ return path.replaceAll('\\', '/')
228
+ }
229
+
230
+ function resolveFromRoot(specifier: string, root: string): string {
231
+ return require.resolve(specifier, {
232
+ paths: [root],
233
+ })
234
+ }