@tanstack/start-plugin-core 1.167.34 → 1.168.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 (197) 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 +1 -1
  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 +12 -19
  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 +4 -0
  34. package/dist/esm/index.js +3 -1
  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/normalized-client-build.d.ts +18 -0
  48. package/dist/esm/rsbuild/normalized-client-build.js +207 -0
  49. package/dist/esm/rsbuild/normalized-client-build.js.map +1 -0
  50. package/dist/esm/rsbuild/planning.d.ts +52 -0
  51. package/dist/esm/rsbuild/planning.js +108 -0
  52. package/dist/esm/rsbuild/planning.js.map +1 -0
  53. package/dist/esm/rsbuild/plugin.d.ts +4 -0
  54. package/dist/esm/rsbuild/plugin.js +344 -0
  55. package/dist/esm/rsbuild/plugin.js.map +1 -0
  56. package/dist/esm/rsbuild/post-build.d.ts +6 -0
  57. package/dist/esm/rsbuild/post-build.js +57 -0
  58. package/dist/esm/rsbuild/post-build.js.map +1 -0
  59. package/dist/esm/rsbuild/schema.d.ts +3372 -0
  60. package/dist/esm/rsbuild/schema.js +12 -0
  61. package/dist/esm/rsbuild/schema.js.map +1 -0
  62. package/dist/esm/rsbuild/start-compiler-host.d.ts +20 -0
  63. package/dist/esm/rsbuild/start-compiler-host.js +150 -0
  64. package/dist/esm/rsbuild/start-compiler-host.js.map +1 -0
  65. package/dist/esm/rsbuild/start-router-plugin.d.ts +18 -0
  66. package/dist/esm/rsbuild/start-router-plugin.js +63 -0
  67. package/dist/esm/rsbuild/start-router-plugin.js.map +1 -0
  68. package/dist/esm/rsbuild/swc-rsc.d.ts +14 -0
  69. package/dist/esm/rsbuild/swc-rsc.js +93 -0
  70. package/dist/esm/rsbuild/swc-rsc.js.map +1 -0
  71. package/dist/esm/rsbuild/types.d.ts +17 -0
  72. package/dist/esm/rsbuild/types.js +0 -0
  73. package/dist/esm/rsbuild/virtual-modules.d.ts +53 -0
  74. package/dist/esm/rsbuild/virtual-modules.js +287 -0
  75. package/dist/esm/rsbuild/virtual-modules.js.map +1 -0
  76. package/dist/esm/schema.d.ts +43 -43
  77. package/dist/esm/schema.js +1 -1
  78. package/dist/esm/start-compiler/compiler.d.ts +1 -1
  79. package/dist/esm/start-compiler/compiler.js +80 -9
  80. package/dist/esm/start-compiler/compiler.js.map +1 -1
  81. package/dist/esm/start-compiler/handleCreateServerFn.js +9 -0
  82. package/dist/esm/start-compiler/handleCreateServerFn.js.map +1 -1
  83. package/dist/esm/start-compiler/host.js +5 -1
  84. package/dist/esm/start-compiler/host.js.map +1 -1
  85. package/dist/esm/start-compiler/types.d.ts +1 -0
  86. package/dist/esm/start-manifest-plugin/manifestBuilder.d.ts +3 -6
  87. package/dist/esm/start-manifest-plugin/manifestBuilder.js +34 -81
  88. package/dist/esm/start-manifest-plugin/manifestBuilder.js.map +1 -1
  89. package/dist/esm/utils.d.ts +1 -0
  90. package/dist/esm/utils.js +4 -1
  91. package/dist/esm/utils.js.map +1 -1
  92. package/dist/esm/{import-protection-plugin → vite/import-protection-plugin}/plugin.js +41 -92
  93. package/dist/esm/vite/import-protection-plugin/plugin.js.map +1 -0
  94. package/dist/esm/{import-protection-plugin → vite/import-protection-plugin}/types.d.ts +5 -5
  95. package/dist/esm/vite/import-protection-plugin/virtualModules.d.ts +8 -0
  96. package/dist/esm/vite/import-protection-plugin/virtualModules.js +49 -0
  97. package/dist/esm/vite/import-protection-plugin/virtualModules.js.map +1 -0
  98. package/dist/esm/vite/plugin.js +4 -12
  99. package/dist/esm/vite/plugin.js.map +1 -1
  100. package/dist/esm/vite/plugins.d.ts +1 -5
  101. package/dist/esm/vite/plugins.js +2 -17
  102. package/dist/esm/vite/plugins.js.map +1 -1
  103. package/dist/esm/vite/post-server-build.js +14 -32
  104. package/dist/esm/vite/post-server-build.js.map +1 -1
  105. package/dist/esm/vite/prerender.d.ts +2 -2
  106. package/dist/esm/vite/prerender.js +17 -147
  107. package/dist/esm/vite/prerender.js.map +1 -1
  108. package/dist/esm/vite/schema.d.ts +23 -23
  109. package/dist/esm/vite/start-compiler-plugin/hot-update.d.ts +2 -0
  110. package/dist/esm/vite/start-compiler-plugin/hot-update.js +16 -0
  111. package/dist/esm/vite/start-compiler-plugin/hot-update.js.map +1 -0
  112. package/dist/esm/vite/start-compiler-plugin/module-specifier.js +9 -4
  113. package/dist/esm/vite/start-compiler-plugin/module-specifier.js.map +1 -1
  114. package/dist/esm/vite/start-compiler-plugin/plugin.js +86 -13
  115. package/dist/esm/vite/start-compiler-plugin/plugin.js.map +1 -1
  116. package/dist/esm/vite/start-manifest-plugin/normalized-client-build.js +2 -2
  117. package/dist/esm/vite/start-manifest-plugin/normalized-client-build.js.map +1 -1
  118. package/dist/esm/vite/start-manifest-plugin/plugin.d.ts +1 -2
  119. package/dist/esm/vite/start-manifest-plugin/plugin.js +48 -14
  120. package/dist/esm/vite/start-manifest-plugin/plugin.js.map +1 -1
  121. package/package.json +17 -4
  122. package/src/import-protection/INTERNALS.md +266 -0
  123. package/src/import-protection/adapterUtils.ts +94 -0
  124. package/src/import-protection/analysis.ts +853 -0
  125. package/src/{import-protection-plugin → import-protection}/constants.ts +7 -0
  126. package/src/import-protection/rewrite.ts +229 -0
  127. package/src/{import-protection-plugin → import-protection}/sourceLocation.ts +125 -9
  128. package/src/{import-protection-plugin → import-protection}/trace.ts +0 -1
  129. package/src/{import-protection-plugin → import-protection}/utils.ts +35 -20
  130. package/src/{import-protection-plugin → import-protection}/virtualModules.ts +30 -177
  131. package/src/index.ts +5 -0
  132. package/src/post-build.ts +64 -0
  133. package/src/prerender.ts +292 -0
  134. package/src/rsbuild/INTERNALS-import-protection.md +169 -0
  135. package/src/rsbuild/dev-server.ts +129 -0
  136. package/src/rsbuild/import-protection.ts +1600 -0
  137. package/src/rsbuild/normalized-client-build.ts +346 -0
  138. package/src/rsbuild/planning.ts +234 -0
  139. package/src/rsbuild/plugin.ts +754 -0
  140. package/src/rsbuild/post-build.ts +96 -0
  141. package/src/rsbuild/schema.ts +31 -0
  142. package/src/rsbuild/start-compiler-host.ts +250 -0
  143. package/src/rsbuild/start-router-plugin.ts +86 -0
  144. package/src/rsbuild/swc-rsc.ts +166 -0
  145. package/src/rsbuild/types.ts +20 -0
  146. package/src/rsbuild/virtual-modules.ts +565 -0
  147. package/src/start-compiler/compiler.ts +153 -19
  148. package/src/start-compiler/handleCreateServerFn.ts +18 -0
  149. package/src/start-compiler/types.ts +1 -0
  150. package/src/start-manifest-plugin/manifestBuilder.ts +53 -116
  151. package/src/utils.ts +4 -0
  152. package/src/vite/import-protection-plugin/INTERNALS.md +187 -0
  153. package/src/{import-protection-plugin → vite/import-protection-plugin}/plugin.ts +73 -158
  154. package/src/{import-protection-plugin → vite/import-protection-plugin}/types.ts +5 -5
  155. package/src/vite/import-protection-plugin/virtualModules.ts +122 -0
  156. package/src/vite/plugin.ts +1 -18
  157. package/src/vite/plugins.ts +2 -33
  158. package/src/vite/post-server-build.ts +14 -57
  159. package/src/vite/prerender.ts +19 -260
  160. package/src/vite/start-compiler-plugin/hot-update.ts +24 -0
  161. package/src/vite/start-compiler-plugin/module-specifier.ts +15 -5
  162. package/src/vite/start-compiler-plugin/plugin.ts +193 -18
  163. package/src/vite/start-manifest-plugin/normalized-client-build.ts +15 -16
  164. package/src/vite/start-manifest-plugin/plugin.ts +121 -38
  165. package/dist/esm/import-protection-plugin/ast.js.map +0 -1
  166. package/dist/esm/import-protection-plugin/constants.d.ts +0 -6
  167. package/dist/esm/import-protection-plugin/constants.js.map +0 -1
  168. package/dist/esm/import-protection-plugin/defaults.js.map +0 -1
  169. package/dist/esm/import-protection-plugin/extensionlessAbsoluteIdResolver.js.map +0 -1
  170. package/dist/esm/import-protection-plugin/matchers.js.map +0 -1
  171. package/dist/esm/import-protection-plugin/plugin.js.map +0 -1
  172. package/dist/esm/import-protection-plugin/postCompileUsage.d.ts +0 -13
  173. package/dist/esm/import-protection-plugin/postCompileUsage.js +0 -63
  174. package/dist/esm/import-protection-plugin/postCompileUsage.js.map +0 -1
  175. package/dist/esm/import-protection-plugin/rewriteDeniedImports.js +0 -205
  176. package/dist/esm/import-protection-plugin/rewriteDeniedImports.js.map +0 -1
  177. package/dist/esm/import-protection-plugin/sourceLocation.js.map +0 -1
  178. package/dist/esm/import-protection-plugin/trace.js.map +0 -1
  179. package/dist/esm/import-protection-plugin/utils.js.map +0 -1
  180. package/dist/esm/import-protection-plugin/virtualModules.d.ts +0 -78
  181. package/dist/esm/import-protection-plugin/virtualModules.js.map +0 -1
  182. package/dist/esm/start-compiler/load-module.d.ts +0 -14
  183. package/dist/esm/start-compiler/load-module.js +0 -18
  184. package/dist/esm/start-compiler/load-module.js.map +0 -1
  185. package/src/import-protection-plugin/INTERNALS.md +0 -700
  186. package/src/import-protection-plugin/postCompileUsage.ts +0 -100
  187. package/src/import-protection-plugin/rewriteDeniedImports.ts +0 -379
  188. package/src/start-compiler/load-module.ts +0 -31
  189. /package/dist/esm/{import-protection-plugin → import-protection}/ast.d.ts +0 -0
  190. /package/dist/esm/{import-protection-plugin → import-protection}/defaults.d.ts +0 -0
  191. /package/dist/esm/{import-protection-plugin → import-protection}/extensionlessAbsoluteIdResolver.d.ts +0 -0
  192. /package/dist/esm/{import-protection-plugin → import-protection}/matchers.d.ts +0 -0
  193. /package/dist/esm/{import-protection-plugin → vite/import-protection-plugin}/plugin.d.ts +0 -0
  194. /package/src/{import-protection-plugin → import-protection}/ast.ts +0 -0
  195. /package/src/{import-protection-plugin → import-protection}/defaults.ts +0 -0
  196. /package/src/{import-protection-plugin → import-protection}/extensionlessAbsoluteIdResolver.ts +0 -0
  197. /package/src/{import-protection-plugin → import-protection}/matchers.ts +0 -0
@@ -1,4 +1,3 @@
1
- import assert from 'node:assert'
2
1
  import { VIRTUAL_MODULES } from '@tanstack/start-server-core'
3
2
  import { resolve as resolvePath } from 'pathe'
4
3
  import {
@@ -12,7 +11,6 @@ import {
12
11
  createStartCompiler,
13
12
  mergeServerFnsById,
14
13
  } from '../../start-compiler/host'
15
- import { loadModuleForViteCompiler } from '../../start-compiler/load-module'
16
14
  import { generateServerFnResolverModule } from '../../start-compiler/server-fn-resolver-module'
17
15
  import { cleanId } from '../../start-compiler/utils'
18
16
  import { createVirtualModule } from '../createVirtualModule'
@@ -21,17 +19,116 @@ import {
21
19
  createViteDevServerFnModuleSpecifierEncoder,
22
20
  decodeViteDevServerModuleSpecifier,
23
21
  } from './module-specifier'
22
+ import { mergeHotUpdateModules } from './hot-update'
24
23
  import type { CompileStartFrameworkOptions } from '../../types'
25
24
  import type {
26
25
  GenerateFunctionIdFnOptional,
27
26
  ServerFn,
28
27
  } from '../../start-compiler/types'
29
- import type { PluginOption } from 'vite'
28
+ import type { EnvironmentModuleNode, PluginOption } from 'vite'
30
29
 
31
30
  // Re-export from shared constants for backwards compatibility
32
31
  export { SERVER_FN_LOOKUP }
33
32
 
34
33
  const validateServerFnIdVirtualModule = `virtual:tanstack-start-validate-server-fn-id`
34
+ const TSS_SERVERFN_SPLIT_PARAM = 'tss-serverfn-split'
35
+
36
+ type ModuleInvalidationEnvironment = {
37
+ moduleGraph: {
38
+ getModulesByFile: (file: string) => Set<EnvironmentModuleNode> | undefined
39
+ invalidateModule: (
40
+ mod: EnvironmentModuleNode,
41
+ seen?: Set<EnvironmentModuleNode>,
42
+ ) => void
43
+ }
44
+ }
45
+
46
+ function invalidateMatchingFileModules(
47
+ environment: ModuleInvalidationEnvironment,
48
+ ids: Iterable<string>,
49
+ shouldInvalidate: (mod: EnvironmentModuleNode) => boolean,
50
+ ) {
51
+ const seen = new Set<EnvironmentModuleNode>()
52
+ const invalidatedModules: Array<EnvironmentModuleNode> = []
53
+
54
+ for (const id of ids) {
55
+ const fileModules = environment.moduleGraph.getModulesByFile(cleanId(id))
56
+
57
+ if (!fileModules) {
58
+ continue
59
+ }
60
+
61
+ for (const fileModule of fileModules) {
62
+ if (!shouldInvalidate(fileModule)) {
63
+ continue
64
+ }
65
+
66
+ environment.moduleGraph.invalidateModule(fileModule, seen)
67
+ invalidatedModules.push(fileModule)
68
+ }
69
+ }
70
+
71
+ return invalidatedModules
72
+ }
73
+
74
+ function invalidateServerFnProviderModules(
75
+ environment: {
76
+ moduleGraph: {
77
+ getModulesByFile: (file: string) => Set<EnvironmentModuleNode> | undefined
78
+ invalidateModule: (
79
+ mod: EnvironmentModuleNode,
80
+ seen?: Set<EnvironmentModuleNode>,
81
+ ) => void
82
+ }
83
+ },
84
+ ids: Iterable<string>,
85
+ ) {
86
+ return invalidateMatchingFileModules(
87
+ environment,
88
+ ids,
89
+ (fileModule) => fileModule.id?.includes(TSS_SERVERFN_SPLIT_PARAM) ?? false,
90
+ )
91
+ }
92
+
93
+ function invalidateServerFnLookupModules(
94
+ environment: ModuleInvalidationEnvironment,
95
+ ids: Iterable<string>,
96
+ ) {
97
+ invalidateMatchingFileModules(
98
+ environment,
99
+ ids,
100
+ (fileModule) => fileModule.id?.includes(SERVER_FN_LOOKUP) ?? false,
101
+ )
102
+ }
103
+
104
+ function getServerFnProviderIds(ids: Iterable<string>) {
105
+ const providerIds = new Set<string>()
106
+
107
+ for (const id of ids) {
108
+ const cleanedId = cleanId(id)
109
+ providerIds.add(`${cleanedId}?${TSS_SERVERFN_SPLIT_PARAM}`)
110
+ }
111
+
112
+ return providerIds
113
+ }
114
+
115
+ function invalidateModuleNodes(
116
+ environment: {
117
+ moduleGraph: {
118
+ invalidateModule: (
119
+ mod: EnvironmentModuleNode,
120
+ seen?: Set<EnvironmentModuleNode>,
121
+ ) => void
122
+ }
123
+ },
124
+ modules: Iterable<EnvironmentModuleNode>,
125
+ ) {
126
+ const seen = new Set<EnvironmentModuleNode>()
127
+
128
+ for (const mod of modules) {
129
+ environment.moduleGraph.invalidateModule(mod, seen)
130
+ }
131
+ }
35
132
 
36
133
  function getDevServerFnValidatorModule(): string {
37
134
  return `
@@ -148,16 +245,23 @@ export function startCompilerPlugin(
148
245
  ? createViteDevServerFnModuleSpecifierEncoder(root)
149
246
  : undefined,
150
247
  loadModule: async (id: string) => {
151
- await loadModuleForViteCompiler({
152
- compiler: compiler!,
153
- mode: this.environment.mode,
154
- fetchModule:
155
- this.environment.mode === 'dev'
156
- ? this.environment.fetchModule.bind(this.environment)
157
- : undefined,
158
- loadModule: this.load.bind(this),
159
- id,
160
- })
248
+ if (mode === 'build') {
249
+ const loaded = await this.load({ id })
250
+ const code = loaded.code ?? ''
251
+
252
+ compiler!.ingestModule({ code, id })
253
+ return
254
+ }
255
+
256
+ if (this.environment.mode !== 'dev') {
257
+ this.error(
258
+ `could not load module ${id}: unknown environment mode ${this.environment.mode}`,
259
+ )
260
+ }
261
+
262
+ await this.environment.transformRequest(
263
+ `${id}?${SERVER_FN_LOOKUP}`,
264
+ )
161
265
  },
162
266
 
163
267
  resolveId: async (source: string, importer?: string) => {
@@ -190,19 +294,85 @@ export function startCompilerPlugin(
190
294
 
191
295
  hotUpdate(ctx) {
192
296
  const compiler = compilers.get(this.environment.name)
297
+ const idsToInvalidate = new Set<string>()
298
+ const transitiveCompilerImportersToInvalidate = new Set<string>()
299
+ const importerModulesToInvalidate = new Set<EnvironmentModuleNode>()
193
300
 
194
301
  ctx.modules.forEach((m) => {
195
302
  if (m.id) {
303
+ idsToInvalidate.add(m.id)
196
304
  const deleted = compiler?.invalidateModule(m.id)
305
+
306
+ if (deleted) {
307
+ transitiveCompilerImportersToInvalidate.add(cleanId(m.id))
308
+ }
309
+
197
310
  if (deleted) {
198
311
  m.importers.forEach((importer) => {
199
312
  if (importer.id) {
200
- compiler?.invalidateModule(importer.id)
313
+ idsToInvalidate.add(importer.id)
314
+ importerModulesToInvalidate.add(importer)
315
+ transitiveCompilerImportersToInvalidate.add(
316
+ cleanId(importer.id),
317
+ )
201
318
  }
202
319
  })
203
320
  }
204
321
  }
205
322
  })
323
+
324
+ const finishHotUpdate = async () => {
325
+ if (environment.type === 'server' && compiler) {
326
+ const pendingImporters = [
327
+ ...transitiveCompilerImportersToInvalidate,
328
+ ]
329
+ const seenImporters = new Set(pendingImporters)
330
+
331
+ while (pendingImporters.length > 0) {
332
+ const importerId = pendingImporters.pop()!
333
+ const nestedImporters =
334
+ await compiler.getTransitiveImporters(importerId)
335
+
336
+ for (const nestedImporterId of nestedImporters) {
337
+ if (seenImporters.has(nestedImporterId)) {
338
+ continue
339
+ }
340
+
341
+ seenImporters.add(nestedImporterId)
342
+ pendingImporters.push(nestedImporterId)
343
+ }
344
+ }
345
+
346
+ for (const importerId of seenImporters) {
347
+ idsToInvalidate.add(importerId)
348
+ compiler.invalidateModule(importerId)
349
+ }
350
+ }
351
+
352
+ invalidateModuleNodes(this.environment, importerModulesToInvalidate)
353
+ invalidateServerFnLookupModules(this.environment, idsToInvalidate)
354
+
355
+ if (environment.type !== 'server') {
356
+ return
357
+ }
358
+
359
+ invalidateModuleNodes(this.environment, ctx.modules)
360
+
361
+ const providerIdsToInvalidate =
362
+ getServerFnProviderIds(idsToInvalidate)
363
+ for (const providerId of providerIdsToInvalidate) {
364
+ compiler?.invalidateModule(providerId)
365
+ }
366
+
367
+ const providerModules = invalidateServerFnProviderModules(
368
+ this.environment,
369
+ [...idsToInvalidate, ...providerIdsToInvalidate],
370
+ )
371
+
372
+ return mergeHotUpdateModules(ctx.modules, providerModules)
373
+ }
374
+
375
+ return finishHotUpdate()
206
376
  },
207
377
  }
208
378
  }
@@ -267,10 +437,15 @@ export function startCompilerPlugin(
267
437
  // Trigger transform of the source file in this environment,
268
438
  // which will compile createServerFn calls and populate
269
439
  // serverFnsById as a side effect.
270
- // This plugin only runs in dev (apply: 'serve'), so mode
271
- // must be 'dev' — assert to narrow to DevEnvironment.
272
- assert(this.environment.mode === 'dev')
273
- await this.environment.fetchModule(absPath)
440
+ if (this.environment.mode !== 'dev') {
441
+ this.error(
442
+ `could not validate server function ID ${fnId}: unknown environment mode ${this.environment.mode}`,
443
+ )
444
+ }
445
+
446
+ await this.environment.transformRequest(
447
+ `${absPath}?${SERVER_FN_LOOKUP}`,
448
+ )
274
449
 
275
450
  // Re-check after lazy compilation
276
451
  if (serverFnsById[fnId]) {
@@ -42,6 +42,8 @@ export function normalizeViteClientBuild(
42
42
  const cssFilesBySourcePath = new Map<string, Array<string>>()
43
43
 
44
44
  for (const chunk of chunksByFileName.values()) {
45
+ const bundleEntry = clientBundle[chunk.fileName] as Rollup.OutputChunk
46
+
45
47
  if (chunk.isEntry) {
46
48
  if (entryChunkFileName) {
47
49
  throw new Error(
@@ -60,22 +62,19 @@ export function normalizeViteClientBuild(
60
62
  chunkFileNames.push(chunk.fileName)
61
63
  }
62
64
 
63
- const bundleEntry = clientBundle[chunk.fileName]
64
- if (bundleEntry?.type === 'chunk') {
65
- for (const moduleId of bundleEntry.moduleIds) {
66
- const queryIndex = moduleId.indexOf('?')
67
- const sourcePath =
68
- queryIndex >= 0 ? moduleId.slice(0, queryIndex) : moduleId
69
- if (!sourcePath) continue
70
-
71
- const existing = cssFilesBySourcePath.get(sourcePath)
72
- cssFilesBySourcePath.set(
73
- sourcePath,
74
- existing
75
- ? Array.from(new Set([...existing, ...chunk.css]))
76
- : chunk.css.slice(),
77
- )
78
- }
65
+ for (const moduleId of bundleEntry.moduleIds) {
66
+ const queryIndex = moduleId.indexOf('?')
67
+ const sourcePath =
68
+ queryIndex >= 0 ? moduleId.slice(0, queryIndex) : moduleId
69
+ if (!sourcePath) continue
70
+
71
+ const existing = cssFilesBySourcePath.get(sourcePath)
72
+ cssFilesBySourcePath.set(
73
+ sourcePath,
74
+ existing
75
+ ? Array.from(new Set([...existing, ...chunk.css]))
76
+ : chunk.css.slice(),
77
+ )
79
78
  }
80
79
  }
81
80
 
@@ -1,53 +1,136 @@
1
1
  import { joinURL } from 'ufo'
2
2
  import { VIRTUAL_MODULES } from '@tanstack/start-server-core'
3
+ import { rootRouteId } from '@tanstack/router-core'
3
4
  import { ENTRY_POINTS, START_ENVIRONMENT_NAMES } from '../../constants'
4
5
  import {
5
6
  buildStartManifest,
7
+ createManifestAssetResolvers,
8
+ normalizeViteClientBuild,
6
9
  serializeStartManifest,
7
10
  } from '../../start-manifest-plugin/manifestBuilder'
8
11
  import { createVirtualModule } from '../createVirtualModule'
9
12
  import type { GetConfigFn, NormalizedClientBuild } from '../../types'
10
- import type { PluginOption } from 'vite'
13
+ import type { PluginOption, Rollup } from 'vite'
11
14
 
12
15
  export function startManifestPlugin(opts: {
13
- getClientBuild: () => NormalizedClientBuild | undefined
14
16
  getConfig: GetConfigFn
15
17
  }): PluginOption {
16
- return createVirtualModule({
17
- name: 'tanstack-start:start-manifest-plugin',
18
- moduleId: VIRTUAL_MODULES.startManifest,
19
- enforce: 'pre',
20
- load() {
21
- const { resolvedStartConfig } = opts.getConfig()
22
- if (this.environment.name !== START_ENVIRONMENT_NAMES.server) {
23
- return 'export default {}'
24
- }
25
-
26
- if (this.environment.config.command === 'serve') {
27
- return `export const tsrStartManifest = () => ({
28
- routes: {},
29
- clientEntry: '${joinURL(resolvedStartConfig.basePaths.publicBase, '@id', ENTRY_POINTS.client)}',
30
- })`
31
- }
32
-
33
- const routeTreeRoutes = globalThis.TSS_ROUTES_MANIFEST
34
- const clientBuild = opts.getClientBuild()
35
- // TODO this needs further discussion with vite-rsc, this is a temporary workaround
36
- // If the client bundle isn't available yet (e.g., during RSC scan builds),
37
- // return a dummy manifest. The real manifest will be generated in the actual build.
38
- if (!clientBuild) {
39
- return `export const tsrStartManifest = () => ({
40
- routes: {},
41
- clientEntry: '${joinURL(resolvedStartConfig.basePaths.publicBase, '@id', ENTRY_POINTS.client)}',
42
- })`
43
- }
44
- const startManifest = buildStartManifest({
45
- clientBuild,
46
- routeTreeRoutes,
47
- basePath: resolvedStartConfig.basePaths.publicBase,
48
- })
49
-
50
- return `export const tsrStartManifest = () => (${serializeStartManifest(startManifest)})`
18
+ let clientBuild: NormalizedClientBuild | undefined
19
+ let cssCodeSplitDisabledFileName: string | undefined
20
+
21
+ return [
22
+ {
23
+ name: 'tanstack-start:start-manifest-capture-client-build',
24
+ applyToEnvironment(environment) {
25
+ return environment.name === START_ENVIRONMENT_NAMES.client
26
+ },
27
+ enforce: 'post',
28
+ generateBundle(_options, bundle) {
29
+ if (this.environment.name !== START_ENVIRONMENT_NAMES.client) {
30
+ throw new Error(
31
+ `Unexpected environment for client build capture: ${this.environment.name}`,
32
+ )
33
+ }
34
+
35
+ clientBuild = normalizeViteClientBuild(bundle)
36
+ cssCodeSplitDisabledFileName = getAssetFileNameByName(
37
+ bundle,
38
+ 'style.css',
39
+ )
40
+ },
51
41
  },
52
- })
42
+ createVirtualModule({
43
+ name: 'tanstack-start:start-manifest-plugin',
44
+ moduleId: VIRTUAL_MODULES.startManifest,
45
+ enforce: 'pre',
46
+ load() {
47
+ const { resolvedStartConfig } = opts.getConfig()
48
+ const clientEntry = joinURL(
49
+ resolvedStartConfig.basePaths.publicBase,
50
+ '@id',
51
+ ENTRY_POINTS.client,
52
+ )
53
+
54
+ if (this.environment.name !== START_ENVIRONMENT_NAMES.server) {
55
+ return getEmptyStartManifestModule(clientEntry)
56
+ }
57
+
58
+ if (this.environment.config.command === 'serve') {
59
+ return getEmptyStartManifestModule(clientEntry)
60
+ }
61
+
62
+ const routeTreeRoutes = globalThis.TSS_ROUTES_MANIFEST
63
+ // TODO this needs further discussion with vite-rsc, this is a temporary workaround
64
+ // If the client bundle isn't available yet (e.g., during RSC scan builds),
65
+ // return a dummy manifest. The real manifest will be generated in the actual build.
66
+ if (!clientBuild) {
67
+ return getEmptyStartManifestModule(clientEntry)
68
+ }
69
+ const startManifest = buildStartManifest({
70
+ clientBuild,
71
+ routeTreeRoutes,
72
+ basePath: resolvedStartConfig.basePaths.publicBase,
73
+ additionalRouteAssets: getViteAdditionalRouteAssets({
74
+ cssCodeSplitDisabledFileName,
75
+ basePath: resolvedStartConfig.basePaths.publicBase,
76
+ cssCodeSplit: this.environment.config.build.cssCodeSplit,
77
+ }),
78
+ })
79
+
80
+ return `export const tsrStartManifest = () => (${serializeStartManifest(startManifest)})`
81
+ },
82
+ }),
83
+ ]
84
+ }
85
+
86
+ function getViteAdditionalRouteAssets(options: {
87
+ cssCodeSplitDisabledFileName: string | undefined
88
+ basePath: string
89
+ cssCodeSplit: boolean | undefined
90
+ }) {
91
+ if (options.cssCodeSplit !== false) {
92
+ return undefined
93
+ }
94
+
95
+ if (!options.cssCodeSplitDisabledFileName) {
96
+ throw new Error(
97
+ "TanStack Start could not find Vite's generated `style.css` manifest entry while `build.cssCodeSplit` is disabled",
98
+ )
99
+ }
100
+
101
+ const { getStylesheetAsset } = createManifestAssetResolvers(options.basePath)
102
+
103
+ return {
104
+ [rootRouteId]: [getStylesheetAsset(options.cssCodeSplitDisabledFileName)],
105
+ }
106
+ }
107
+
108
+ function getAssetFileNameByName(
109
+ bundle: Rollup.OutputBundle,
110
+ assetName: string,
111
+ ) {
112
+ for (const fileName in bundle) {
113
+ const bundleEntry = bundle[fileName]!
114
+
115
+ if (bundleEntry.type !== 'asset') {
116
+ continue
117
+ }
118
+
119
+ if (bundleEntry.name === assetName) {
120
+ return fileName
121
+ }
122
+
123
+ if ('names' in bundleEntry && bundleEntry.names.includes(assetName)) {
124
+ return fileName
125
+ }
126
+ }
127
+
128
+ return undefined
129
+ }
130
+
131
+ function getEmptyStartManifestModule(clientEntry: string) {
132
+ return `export const tsrStartManifest = () => ({
133
+ routes: {},
134
+ clientEntry: '${clientEntry}',
135
+ })`
53
136
  }
@@ -1 +0,0 @@
1
- {"version":3,"file":"ast.js","names":[],"sources":["../../../src/import-protection-plugin/ast.ts"],"sourcesContent":["import { parseAst } from '@tanstack/router-utils'\n\nexport type ParsedAst = ReturnType<typeof parseAst>\n\nexport function parseImportProtectionAst(code: string): ParsedAst {\n return parseAst({ code })\n}\n"],"mappings":";;AAIA,SAAgB,yBAAyB,MAAyB;AAChE,QAAO,SAAS,EAAE,MAAM,CAAC"}
@@ -1,6 +0,0 @@
1
- export declare const SERVER_FN_LOOKUP_QUERY = "?server-fn-module-lookup";
2
- export declare const IMPORT_PROTECTION_DEBUG: boolean;
3
- export declare const IMPORT_PROTECTION_DEBUG_FILTER: string | undefined;
4
- export declare const KNOWN_SOURCE_EXTENSIONS: Set<string>;
5
- /** Vite's browser-visible prefix for virtual modules (replaces `\0`). */
6
- export declare const VITE_BROWSER_VIRTUAL_PREFIX = "/@id/__x00__";
@@ -1 +0,0 @@
1
- {"version":3,"file":"constants.js","names":[],"sources":["../../../src/import-protection-plugin/constants.ts"],"sourcesContent":["import { SERVER_FN_LOOKUP } from '../constants'\n\nexport const SERVER_FN_LOOKUP_QUERY = `?${SERVER_FN_LOOKUP}`\n\nexport const IMPORT_PROTECTION_DEBUG =\n process.env.TSR_IMPORT_PROTECTION_DEBUG === '1' ||\n process.env.TSR_IMPORT_PROTECTION_DEBUG === 'true'\n\nexport const IMPORT_PROTECTION_DEBUG_FILTER =\n process.env.TSR_IMPORT_PROTECTION_DEBUG_FILTER\n\nexport const KNOWN_SOURCE_EXTENSIONS = new Set([\n '.ts',\n '.tsx',\n '.mts',\n '.cts',\n '.js',\n '.jsx',\n '.mjs',\n '.cjs',\n '.json',\n])\n\n/** Vite's browser-visible prefix for virtual modules (replaces `\\0`). */\nexport const VITE_BROWSER_VIRTUAL_PREFIX = '/@id/__x00__'\n"],"mappings":";;AAEA,IAAa,yBAAyB,IAAI;AAE1C,IAAa,0BACX,QAAQ,IAAI,gCAAgC,OAC5C,QAAQ,IAAI,gCAAgC;AAE9C,IAAa,iCACX,QAAQ,IAAI;AAEd,IAAa,0BAA0B,IAAI,IAAI;CAC7C;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;;AAGF,IAAa,8BAA8B"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"defaults.js","names":[],"sources":["../../../src/import-protection-plugin/defaults.ts"],"sourcesContent":["import type { ImportProtectionEnvRules } from '../schema'\nimport type { Pattern } from './utils'\n\nexport interface DefaultImportProtectionRules {\n client: Required<ImportProtectionEnvRules>\n server: Required<ImportProtectionEnvRules>\n}\n\nconst frameworks = ['react', 'solid', 'vue'] as const\n\n/**\n * Returns the default import protection rules.\n *\n * All three framework variants are always included so that, e.g., a React\n * project also denies `@tanstack/solid-start/server` imports.\n */\nexport function getDefaultImportProtectionRules(): DefaultImportProtectionRules {\n const clientSpecifiers: Array<Pattern> = frameworks.map(\n (fw) => `@tanstack/${fw}-start/server`,\n )\n\n return {\n client: {\n specifiers: clientSpecifiers,\n files: ['**/*.server.*'],\n excludeFiles: ['**/node_modules/**'],\n },\n server: {\n specifiers: [],\n files: ['**/*.client.*'],\n excludeFiles: ['**/node_modules/**'],\n },\n }\n}\n\n/**\n * Marker module specifiers that restrict a file to a specific environment.\n */\nexport function getMarkerSpecifiers(): {\n serverOnly: Array<string>\n clientOnly: Array<string>\n} {\n return {\n serverOnly: frameworks.map((fw) => `@tanstack/${fw}-start/server-only`),\n clientOnly: frameworks.map((fw) => `@tanstack/${fw}-start/client-only`),\n }\n}\n"],"mappings":";AAQA,IAAM,aAAa;CAAC;CAAS;CAAS;CAAM;;;;;;;AAQ5C,SAAgB,kCAAgE;AAK9E,QAAO;EACL,QAAQ;GACN,YANqC,WAAW,KACjD,OAAO,aAAa,GAAG,eACzB;GAKG,OAAO,CAAC,gBAAgB;GACxB,cAAc,CAAC,qBAAqB;GACrC;EACD,QAAQ;GACN,YAAY,EAAE;GACd,OAAO,CAAC,gBAAgB;GACxB,cAAc,CAAC,qBAAqB;GACrC;EACF;;;;;AAMH,SAAgB,sBAGd;AACA,QAAO;EACL,YAAY,WAAW,KAAK,OAAO,aAAa,GAAG,oBAAoB;EACvE,YAAY,WAAW,KAAK,OAAO,aAAa,GAAG,oBAAoB;EACxE"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"extensionlessAbsoluteIdResolver.js","names":[],"sources":["../../../src/import-protection-plugin/extensionlessAbsoluteIdResolver.ts"],"sourcesContent":["import { basename, dirname, extname, isAbsolute } from 'node:path'\nimport { resolveModulePath } from 'exsolve'\n\nimport { KNOWN_SOURCE_EXTENSIONS } from './constants'\nimport { normalizeFilePath } from './utils'\n\nconst FILE_RESOLUTION_EXTENSIONS = [...KNOWN_SOURCE_EXTENSIONS]\n\ntype DepKey = `file:${string}` | `dir:${string}`\n\n/**\n * Canonicalize extensionless absolute IDs like `/src/foo.server` to the\n * physical file when possible.\n *\n * Keeps a small cache plus a reverse index so we can invalidate on HMR\n * updates without clearing the whole map.\n */\nexport class ExtensionlessAbsoluteIdResolver {\n private entries = new Map<string, { value: string; deps: Set<DepKey> }>()\n private keysByDep = new Map<DepKey, Set<string>>()\n\n clear(): void {\n this.entries.clear()\n this.keysByDep.clear()\n }\n\n /**\n * Invalidate any cached entries that might be affected by changes to `id`.\n * We invalidate both the file and its containing directory.\n */\n invalidateByFile(id: string): void {\n const file = normalizeFilePath(id)\n this.invalidateDep(`file:${file}`)\n if (isAbsolute(file)) {\n this.invalidateDep(`dir:${dirname(file)}`)\n }\n }\n\n resolve(id: string): string {\n const key = normalizeFilePath(id)\n const cached = this.entries.get(key)\n if (cached) return cached.value\n\n let result = key\n let resolvedPhysical: string | undefined\n\n if (isAbsolute(key)) {\n const ext = extname(key)\n if (!FILE_RESOLUTION_EXTENSIONS.includes(ext)) {\n const resolved = resolveModulePath(`./${basename(key)}`, {\n from: dirname(key),\n extensions: FILE_RESOLUTION_EXTENSIONS,\n try: true,\n })\n if (resolved) {\n resolvedPhysical = resolved\n result = normalizeFilePath(resolved)\n }\n }\n }\n\n const resolvedFile = resolvedPhysical\n ? normalizeFilePath(resolvedPhysical)\n : undefined\n\n const deps = this.buildDepsForKey(key, resolvedFile)\n this.entries.set(key, { value: result, deps })\n this.indexDeps(key, deps)\n return result\n }\n\n private invalidateDep(dep: DepKey): void {\n const keys = this.keysByDep.get(dep)\n if (!keys) return\n\n // Copy because deleting keys mutates indexes.\n for (const key of Array.from(keys)) {\n this.deleteKey(key)\n }\n }\n\n private buildDepsForKey(key: string, resolvedFile: string | undefined) {\n const deps = new Set<DepKey>()\n deps.add(`file:${key}`)\n\n if (isAbsolute(key)) {\n deps.add(`dir:${dirname(key)}`)\n }\n if (resolvedFile) {\n deps.add(`file:${resolvedFile}`)\n }\n\n return deps\n }\n\n private indexDeps(key: string, deps: Set<DepKey>): void {\n for (const dep of deps) {\n let keys = this.keysByDep.get(dep)\n if (!keys) {\n keys = new Set<string>()\n this.keysByDep.set(dep, keys)\n }\n keys.add(key)\n }\n }\n\n private deleteKey(key: string): void {\n const entry = this.entries.get(key)\n this.entries.delete(key)\n if (!entry) return\n\n for (const dep of entry.deps) {\n const keys = this.keysByDep.get(dep)\n if (!keys) continue\n keys.delete(key)\n if (keys.size === 0) {\n this.keysByDep.delete(dep)\n }\n }\n }\n}\n"],"mappings":";;;;;AAMA,IAAM,6BAA6B,CAAC,GAAG,wBAAwB;;;;;;;;AAW/D,IAAa,kCAAb,MAA6C;CAC3C,0BAAkB,IAAI,KAAmD;CACzE,4BAAoB,IAAI,KAA0B;CAElD,QAAc;AACZ,OAAK,QAAQ,OAAO;AACpB,OAAK,UAAU,OAAO;;;;;;CAOxB,iBAAiB,IAAkB;EACjC,MAAM,OAAO,kBAAkB,GAAG;AAClC,OAAK,cAAc,QAAQ,OAAO;AAClC,MAAI,WAAW,KAAK,CAClB,MAAK,cAAc,OAAO,QAAQ,KAAK,GAAG;;CAI9C,QAAQ,IAAoB;EAC1B,MAAM,MAAM,kBAAkB,GAAG;EACjC,MAAM,SAAS,KAAK,QAAQ,IAAI,IAAI;AACpC,MAAI,OAAQ,QAAO,OAAO;EAE1B,IAAI,SAAS;EACb,IAAI;AAEJ,MAAI,WAAW,IAAI,EAAE;GACnB,MAAM,MAAM,QAAQ,IAAI;AACxB,OAAI,CAAC,2BAA2B,SAAS,IAAI,EAAE;IAC7C,MAAM,WAAW,kBAAkB,KAAK,SAAS,IAAI,IAAI;KACvD,MAAM,QAAQ,IAAI;KAClB,YAAY;KACZ,KAAK;KACN,CAAC;AACF,QAAI,UAAU;AACZ,wBAAmB;AACnB,cAAS,kBAAkB,SAAS;;;;EAK1C,MAAM,eAAe,mBACjB,kBAAkB,iBAAiB,GACnC,KAAA;EAEJ,MAAM,OAAO,KAAK,gBAAgB,KAAK,aAAa;AACpD,OAAK,QAAQ,IAAI,KAAK;GAAE,OAAO;GAAQ;GAAM,CAAC;AAC9C,OAAK,UAAU,KAAK,KAAK;AACzB,SAAO;;CAGT,cAAsB,KAAmB;EACvC,MAAM,OAAO,KAAK,UAAU,IAAI,IAAI;AACpC,MAAI,CAAC,KAAM;AAGX,OAAK,MAAM,OAAO,MAAM,KAAK,KAAK,CAChC,MAAK,UAAU,IAAI;;CAIvB,gBAAwB,KAAa,cAAkC;EACrE,MAAM,uBAAO,IAAI,KAAa;AAC9B,OAAK,IAAI,QAAQ,MAAM;AAEvB,MAAI,WAAW,IAAI,CACjB,MAAK,IAAI,OAAO,QAAQ,IAAI,GAAG;AAEjC,MAAI,aACF,MAAK,IAAI,QAAQ,eAAe;AAGlC,SAAO;;CAGT,UAAkB,KAAa,MAAyB;AACtD,OAAK,MAAM,OAAO,MAAM;GACtB,IAAI,OAAO,KAAK,UAAU,IAAI,IAAI;AAClC,OAAI,CAAC,MAAM;AACT,2BAAO,IAAI,KAAa;AACxB,SAAK,UAAU,IAAI,KAAK,KAAK;;AAE/B,QAAK,IAAI,IAAI;;;CAIjB,UAAkB,KAAmB;EACnC,MAAM,QAAQ,KAAK,QAAQ,IAAI,IAAI;AACnC,OAAK,QAAQ,OAAO,IAAI;AACxB,MAAI,CAAC,MAAO;AAEZ,OAAK,MAAM,OAAO,MAAM,MAAM;GAC5B,MAAM,OAAO,KAAK,UAAU,IAAI,IAAI;AACpC,OAAI,CAAC,KAAM;AACX,QAAK,OAAO,IAAI;AAChB,OAAI,KAAK,SAAS,EAChB,MAAK,UAAU,OAAO,IAAI"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"matchers.js","names":[],"sources":["../../../src/import-protection-plugin/matchers.ts"],"sourcesContent":["import picomatch from 'picomatch'\n\nimport type { Pattern } from './utils'\n\nexport interface CompiledMatcher {\n pattern: Pattern\n test: (value: string) => boolean\n}\n\n/**\n * Compile a Pattern (string glob or RegExp) into a fast test function.\n * String patterns use picomatch for full glob support (**, *, ?, braces, etc.).\n * RegExp patterns are used as-is.\n */\nexport function compileMatcher(pattern: Pattern): CompiledMatcher {\n if (pattern instanceof RegExp) {\n // RegExp with `g` or `y` flags are stateful because `.test()` mutates\n // `lastIndex`. Reset it to keep matcher evaluation deterministic.\n return {\n pattern,\n test: (value: string) => {\n pattern.lastIndex = 0\n return pattern.test(value)\n },\n }\n }\n\n const isMatch = picomatch(pattern, { dot: true })\n return { pattern, test: isMatch }\n}\n\nexport function compileMatchers(\n patterns: Array<Pattern>,\n): Array<CompiledMatcher> {\n return patterns.map(compileMatcher)\n}\n\nexport function matchesAny(\n value: string,\n matchers: Array<CompiledMatcher>,\n): CompiledMatcher | undefined {\n for (const matcher of matchers) {\n if (matcher.test(value)) {\n return matcher\n }\n }\n return undefined\n}\n"],"mappings":";;;;;;;AAcA,SAAgB,eAAe,SAAmC;AAChE,KAAI,mBAAmB,OAGrB,QAAO;EACL;EACA,OAAO,UAAkB;AACvB,WAAQ,YAAY;AACpB,UAAO,QAAQ,KAAK,MAAM;;EAE7B;AAIH,QAAO;EAAE;EAAS,MADF,UAAU,SAAS,EAAE,KAAK,MAAM,CAAC;EAChB;;AAGnC,SAAgB,gBACd,UACwB;AACxB,QAAO,SAAS,IAAI,eAAe;;AAGrC,SAAgB,WACd,OACA,UAC6B;AAC7B,MAAK,MAAM,WAAW,SACpB,KAAI,QAAQ,KAAK,MAAM,CACrB,QAAO"}