zyzz 0.0.27 → 0.0.29

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 (320) hide show
  1. package/binding/wasi/wasi-worker.mjs +186 -0
  2. package/binding/wasi/zyzz.wasi.cjs +2302 -0
  3. package/binding/wasi/zyzz.wasm32-wasi.wasm +0 -0
  4. package/binding/zyzz.linux-x64-gnu.node +0 -0
  5. package/dist/babel/index.d.ts.map +1 -1
  6. package/dist/babel/index.js +2 -4
  7. package/dist/babel/index.js.map +1 -1
  8. package/dist/babel/internal/Compilation.d.ts +0 -1
  9. package/dist/babel/internal/Compilation.d.ts.map +1 -1
  10. package/dist/compiler/Graph.d.ts +14 -11
  11. package/dist/compiler/Graph.d.ts.map +1 -1
  12. package/dist/compiler/Graph.js +57 -1442
  13. package/dist/compiler/Graph.js.map +1 -1
  14. package/dist/compiler/Native.d.ts +7 -9
  15. package/dist/compiler/Native.d.ts.map +1 -1
  16. package/dist/compiler/Native.js +12 -716
  17. package/dist/compiler/Native.js.map +1 -1
  18. package/dist/compiler/Source.d.ts +122 -24
  19. package/dist/compiler/Source.d.ts.map +1 -1
  20. package/dist/compiler/Source.js +10 -1113
  21. package/dist/compiler/Source.js.map +1 -1
  22. package/dist/compiler/Transform.d.ts +4 -3
  23. package/dist/compiler/Transform.d.ts.map +1 -1
  24. package/dist/compiler/Transform.js +13 -958
  25. package/dist/compiler/Transform.js.map +1 -1
  26. package/dist/compiler/internal/AtRules.d.ts +5 -1
  27. package/dist/compiler/internal/AtRules.d.ts.map +1 -1
  28. package/dist/compiler/internal/AtRules.js +12 -106
  29. package/dist/compiler/internal/AtRules.js.map +1 -1
  30. package/dist/compiler/internal/Context.d.ts +11 -0
  31. package/dist/compiler/internal/Context.d.ts.map +1 -0
  32. package/dist/compiler/internal/Context.js +11 -0
  33. package/dist/compiler/internal/Context.js.map +1 -0
  34. package/dist/compiler/internal/Results.d.ts +148 -0
  35. package/dist/compiler/internal/Results.d.ts.map +1 -0
  36. package/dist/compiler/internal/Results.js +259 -0
  37. package/dist/compiler/internal/Results.js.map +1 -0
  38. package/dist/compiler/internal/Syntax.d.ts +0 -2
  39. package/dist/compiler/internal/Syntax.d.ts.map +1 -1
  40. package/dist/compiler/internal/Syntax.js +0 -2
  41. package/dist/compiler/internal/Syntax.js.map +1 -1
  42. package/dist/compiler/internal/Wire.d.ts +17 -0
  43. package/dist/compiler/internal/Wire.d.ts.map +1 -0
  44. package/dist/compiler/internal/Wire.js +795 -0
  45. package/dist/compiler/internal/Wire.js.map +1 -0
  46. package/dist/internal/Addon.d.ts +28 -0
  47. package/dist/internal/Addon.d.ts.map +1 -0
  48. package/dist/internal/Addon.js +87 -0
  49. package/dist/internal/Addon.js.map +1 -0
  50. package/dist/internal/Authoring.d.ts.map +1 -1
  51. package/dist/internal/Authoring.js +37 -7
  52. package/dist/internal/Authoring.js.map +1 -1
  53. package/dist/internal/Configuration.d.ts +6 -1
  54. package/dist/internal/Configuration.d.ts.map +1 -1
  55. package/dist/internal/Configuration.js.map +1 -1
  56. package/dist/internal/Literal.d.ts +5 -2
  57. package/dist/internal/Literal.d.ts.map +1 -1
  58. package/dist/internal/Literal.js +7 -2
  59. package/dist/internal/Literal.js.map +1 -1
  60. package/dist/internal/Theme.d.ts +4 -0
  61. package/dist/internal/Theme.d.ts.map +1 -1
  62. package/dist/internal/Theme.js.map +1 -1
  63. package/dist/internal/Token.d.ts +2 -1
  64. package/dist/internal/Token.d.ts.map +1 -1
  65. package/dist/internal/Token.js +2 -2
  66. package/dist/internal/Token.js.map +1 -1
  67. package/dist/internal/Vlq.d.ts +14 -0
  68. package/dist/internal/Vlq.d.ts.map +1 -0
  69. package/dist/internal/Vlq.js +47 -0
  70. package/dist/internal/Vlq.js.map +1 -0
  71. package/dist/internal/Wasm.d.ts +22 -0
  72. package/dist/internal/Wasm.d.ts.map +1 -0
  73. package/dist/internal/Wasm.js +83 -0
  74. package/dist/internal/Wasm.js.map +1 -0
  75. package/dist/metro/Graph.d.ts +0 -1
  76. package/dist/metro/Graph.d.ts.map +1 -1
  77. package/dist/metro/transformer.d.ts.map +1 -1
  78. package/dist/metro/transformer.js +0 -1
  79. package/dist/metro/transformer.js.map +1 -1
  80. package/dist/next/internal/Project.d.ts.map +1 -1
  81. package/dist/next/internal/Project.js +2 -5
  82. package/dist/next/internal/Project.js.map +1 -1
  83. package/dist/node/Host.d.ts.map +1 -1
  84. package/dist/node/Host.js +0 -2
  85. package/dist/node/Host.js.map +1 -1
  86. package/dist/node/internal/Contracts.d.ts.map +1 -1
  87. package/dist/node/internal/Contracts.js +3 -7
  88. package/dist/node/internal/Contracts.js.map +1 -1
  89. package/dist/node/internal/Snapshot.d.ts +0 -1
  90. package/dist/node/internal/Snapshot.d.ts.map +1 -1
  91. package/dist/node/internal/Snapshot.js +0 -6
  92. package/dist/node/internal/Snapshot.js.map +1 -1
  93. package/dist/runtime/Composition.d.ts +2 -3
  94. package/dist/runtime/Composition.d.ts.map +1 -1
  95. package/dist/runtime/Composition.js +51 -30
  96. package/dist/runtime/Composition.js.map +1 -1
  97. package/dist/runtime/Recipe.d.ts.map +1 -1
  98. package/dist/runtime/Recipe.js +16 -12
  99. package/dist/runtime/Recipe.js.map +1 -1
  100. package/dist/styleFunction.d.ts +11 -2
  101. package/dist/styleFunction.d.ts.map +1 -1
  102. package/dist/styleFunction.js +3 -1
  103. package/dist/styleFunction.js.map +1 -1
  104. package/dist/unplugin/index.d.ts.map +1 -1
  105. package/dist/unplugin/index.js +3 -6
  106. package/dist/unplugin/index.js.map +1 -1
  107. package/dist/vite/index.d.ts +7 -0
  108. package/dist/vite/index.d.ts.map +1 -1
  109. package/dist/vite/index.js +862 -216
  110. package/dist/vite/index.js.map +1 -1
  111. package/dist/web/Css.d.ts +8 -3
  112. package/dist/web/Css.d.ts.map +1 -1
  113. package/dist/web/Css.js +5 -721
  114. package/dist/web/Css.js.map +1 -1
  115. package/dist/web/internal/Block.d.ts +0 -2
  116. package/dist/web/internal/Block.d.ts.map +1 -1
  117. package/dist/web/internal/Block.js +1 -12
  118. package/dist/web/internal/Block.js.map +1 -1
  119. package/dist/web/internal/ClassName.d.ts +23 -18
  120. package/dist/web/internal/ClassName.d.ts.map +1 -1
  121. package/dist/web/internal/ClassName.js +26 -55
  122. package/dist/web/internal/ClassName.js.map +1 -1
  123. package/dist/web/internal/Contributions.d.ts +3 -5
  124. package/dist/web/internal/Contributions.d.ts.map +1 -1
  125. package/dist/web/internal/Contributions.js +0 -45
  126. package/dist/web/internal/Contributions.js.map +1 -1
  127. package/dist/web/internal/Engine.browser.d.ts +8 -0
  128. package/dist/web/internal/Engine.browser.d.ts.map +1 -0
  129. package/dist/web/internal/Engine.browser.js +42 -0
  130. package/dist/web/internal/Engine.browser.js.map +1 -0
  131. package/dist/web/internal/Engine.d.ts +9 -0
  132. package/dist/web/internal/Engine.d.ts.map +1 -0
  133. package/dist/web/internal/Engine.js +14 -0
  134. package/dist/web/internal/Engine.js.map +1 -0
  135. package/dist/web/internal/Themes.d.ts +1 -9
  136. package/dist/web/internal/Themes.d.ts.map +1 -1
  137. package/dist/web/internal/Themes.js +3 -221
  138. package/dist/web/internal/Themes.js.map +1 -1
  139. package/dist/web/internal/Wasm.generated.d.ts +4 -0
  140. package/dist/web/internal/Wasm.generated.d.ts.map +1 -0
  141. package/dist/web/internal/Wasm.generated.js +4 -0
  142. package/dist/web/internal/Wasm.generated.js.map +1 -0
  143. package/dist/web/internal/Wire.d.ts +19 -0
  144. package/dist/web/internal/Wire.d.ts.map +1 -0
  145. package/dist/web/internal/Wire.js +387 -0
  146. package/dist/web/internal/Wire.js.map +1 -0
  147. package/package.json +20 -1
  148. package/src/babel/index.ts +2 -5
  149. package/src/babel/internal/Compilation.ts +0 -1
  150. package/src/compiler/Graph.ts +89 -1930
  151. package/src/compiler/Native.ts +18 -871
  152. package/src/compiler/Source.ts +144 -1579
  153. package/src/compiler/Transform.ts +16 -1312
  154. package/src/compiler/internal/AtRules.ts +27 -115
  155. package/src/compiler/internal/Context.ts +11 -0
  156. package/src/compiler/internal/Results.ts +406 -0
  157. package/src/compiler/internal/Syntax.ts +0 -3
  158. package/src/compiler/internal/Wire.ts +1045 -0
  159. package/src/internal/Addon.ts +118 -0
  160. package/src/internal/Authoring.ts +53 -18
  161. package/src/internal/Configuration.ts +38 -1
  162. package/src/internal/Literal.ts +7 -2
  163. package/src/internal/Theme.ts +15 -0
  164. package/src/internal/Token.ts +2 -2
  165. package/src/internal/Vlq.ts +59 -0
  166. package/src/internal/Wasm.ts +111 -0
  167. package/src/metro/transformer.ts +0 -1
  168. package/src/next/internal/Project.ts +2 -9
  169. package/src/node/Host.ts +0 -2
  170. package/src/node/internal/Contracts.ts +3 -7
  171. package/src/node/internal/Snapshot.ts +0 -8
  172. package/src/runtime/Composition.ts +48 -25
  173. package/src/runtime/Recipe.ts +21 -14
  174. package/src/styleFunction.ts +31 -2
  175. package/src/unplugin/index.ts +6 -6
  176. package/src/vite/index.ts +1157 -241
  177. package/src/web/Css.ts +12 -908
  178. package/src/web/internal/Block.ts +0 -14
  179. package/src/web/internal/ClassName.ts +52 -85
  180. package/src/web/internal/Contributions.ts +3 -59
  181. package/src/web/internal/Engine.browser.ts +51 -0
  182. package/src/web/internal/Engine.ts +17 -0
  183. package/src/web/internal/Themes.ts +3 -279
  184. package/src/web/internal/Wasm.generated.ts +3 -0
  185. package/src/web/internal/Wire.ts +466 -0
  186. package/src/web/internal/wasm.d.ts +9 -0
  187. package/dist/compiler/internal/Applications.d.ts +0 -55
  188. package/dist/compiler/internal/Applications.d.ts.map +0 -1
  189. package/dist/compiler/internal/Applications.js +0 -352
  190. package/dist/compiler/internal/Applications.js.map +0 -1
  191. package/dist/compiler/internal/ColorProfiles.d.ts +0 -3
  192. package/dist/compiler/internal/ColorProfiles.d.ts.map +0 -1
  193. package/dist/compiler/internal/ColorProfiles.js +0 -84
  194. package/dist/compiler/internal/ColorProfiles.js.map +0 -1
  195. package/dist/compiler/internal/CompositionBindings.d.ts +0 -16
  196. package/dist/compiler/internal/CompositionBindings.d.ts.map +0 -1
  197. package/dist/compiler/internal/CompositionBindings.js +0 -89
  198. package/dist/compiler/internal/CompositionBindings.js.map +0 -1
  199. package/dist/compiler/internal/Compositions.d.ts +0 -37
  200. package/dist/compiler/internal/Compositions.d.ts.map +0 -1
  201. package/dist/compiler/internal/Compositions.js +0 -494
  202. package/dist/compiler/internal/Compositions.js.map +0 -1
  203. package/dist/compiler/internal/Configurations.d.ts +0 -26
  204. package/dist/compiler/internal/Configurations.d.ts.map +0 -1
  205. package/dist/compiler/internal/Configurations.js +0 -259
  206. package/dist/compiler/internal/Configurations.js.map +0 -1
  207. package/dist/compiler/internal/Contract.d.ts +0 -30
  208. package/dist/compiler/internal/Contract.d.ts.map +0 -1
  209. package/dist/compiler/internal/Contract.js +0 -753
  210. package/dist/compiler/internal/Contract.js.map +0 -1
  211. package/dist/compiler/internal/Contributions.d.ts +0 -54
  212. package/dist/compiler/internal/Contributions.d.ts.map +0 -1
  213. package/dist/compiler/internal/Contributions.js +0 -1151
  214. package/dist/compiler/internal/Contributions.js.map +0 -1
  215. package/dist/compiler/internal/Descriptors.d.ts +0 -15
  216. package/dist/compiler/internal/Descriptors.d.ts.map +0 -1
  217. package/dist/compiler/internal/Descriptors.js +0 -192
  218. package/dist/compiler/internal/Descriptors.js.map +0 -1
  219. package/dist/compiler/internal/Dynamic.d.ts +0 -14
  220. package/dist/compiler/internal/Dynamic.d.ts.map +0 -1
  221. package/dist/compiler/internal/Dynamic.js +0 -175
  222. package/dist/compiler/internal/Dynamic.js.map +0 -1
  223. package/dist/compiler/internal/Expression.d.ts +0 -19
  224. package/dist/compiler/internal/Expression.d.ts.map +0 -1
  225. package/dist/compiler/internal/Expression.js +0 -164
  226. package/dist/compiler/internal/Expression.js.map +0 -1
  227. package/dist/compiler/internal/Functions.d.ts +0 -5
  228. package/dist/compiler/internal/Functions.d.ts.map +0 -1
  229. package/dist/compiler/internal/Functions.js +0 -191
  230. package/dist/compiler/internal/Functions.js.map +0 -1
  231. package/dist/compiler/internal/Keyframes.d.ts +0 -5
  232. package/dist/compiler/internal/Keyframes.d.ts.map +0 -1
  233. package/dist/compiler/internal/Keyframes.js +0 -51
  234. package/dist/compiler/internal/Keyframes.js.map +0 -1
  235. package/dist/compiler/internal/Names.d.ts +0 -5
  236. package/dist/compiler/internal/Names.d.ts.map +0 -1
  237. package/dist/compiler/internal/Names.js +0 -37
  238. package/dist/compiler/internal/Names.js.map +0 -1
  239. package/dist/compiler/internal/NativeBindings.d.ts +0 -72
  240. package/dist/compiler/internal/NativeBindings.d.ts.map +0 -1
  241. package/dist/compiler/internal/NativeBindings.js +0 -171
  242. package/dist/compiler/internal/NativeBindings.js.map +0 -1
  243. package/dist/compiler/internal/NativeMedia.d.ts +0 -14
  244. package/dist/compiler/internal/NativeMedia.d.ts.map +0 -1
  245. package/dist/compiler/internal/NativeMedia.js +0 -287
  246. package/dist/compiler/internal/NativeMedia.js.map +0 -1
  247. package/dist/compiler/internal/NativeVars.d.ts +0 -28
  248. package/dist/compiler/internal/NativeVars.d.ts.map +0 -1
  249. package/dist/compiler/internal/NativeVars.js +0 -160
  250. package/dist/compiler/internal/NativeVars.js.map +0 -1
  251. package/dist/compiler/internal/PackedStyles.d.ts +0 -37
  252. package/dist/compiler/internal/PackedStyles.d.ts.map +0 -1
  253. package/dist/compiler/internal/PackedStyles.js +0 -344
  254. package/dist/compiler/internal/PackedStyles.js.map +0 -1
  255. package/dist/compiler/internal/Properties.d.ts +0 -5
  256. package/dist/compiler/internal/Properties.d.ts.map +0 -1
  257. package/dist/compiler/internal/Properties.js +0 -82
  258. package/dist/compiler/internal/Properties.js.map +0 -1
  259. package/dist/compiler/internal/RecipeConditions.d.ts +0 -41
  260. package/dist/compiler/internal/RecipeConditions.d.ts.map +0 -1
  261. package/dist/compiler/internal/RecipeConditions.js +0 -82
  262. package/dist/compiler/internal/RecipeConditions.js.map +0 -1
  263. package/dist/compiler/internal/RecipePayloads.d.ts +0 -31
  264. package/dist/compiler/internal/RecipePayloads.d.ts.map +0 -1
  265. package/dist/compiler/internal/RecipePayloads.js +0 -99
  266. package/dist/compiler/internal/RecipePayloads.js.map +0 -1
  267. package/dist/compiler/internal/Recipes.d.ts +0 -32
  268. package/dist/compiler/internal/Recipes.d.ts.map +0 -1
  269. package/dist/compiler/internal/Recipes.js +0 -339
  270. package/dist/compiler/internal/Recipes.js.map +0 -1
  271. package/dist/compiler/internal/Selectors.d.ts +0 -12
  272. package/dist/compiler/internal/Selectors.d.ts.map +0 -1
  273. package/dist/compiler/internal/Selectors.js +0 -268
  274. package/dist/compiler/internal/Selectors.js.map +0 -1
  275. package/dist/compiler/internal/Static.d.ts +0 -39
  276. package/dist/compiler/internal/Static.d.ts.map +0 -1
  277. package/dist/compiler/internal/Static.js +0 -534
  278. package/dist/compiler/internal/Static.js.map +0 -1
  279. package/dist/compiler/internal/Themes.d.ts +0 -190
  280. package/dist/compiler/internal/Themes.d.ts.map +0 -1
  281. package/dist/compiler/internal/Themes.js +0 -1577
  282. package/dist/compiler/internal/Themes.js.map +0 -1
  283. package/dist/compiler/internal/Vars.d.ts +0 -33
  284. package/dist/compiler/internal/Vars.d.ts.map +0 -1
  285. package/dist/compiler/internal/Vars.js +0 -387
  286. package/dist/compiler/internal/Vars.js.map +0 -1
  287. package/dist/compiler/internal/ViewTransitions.d.ts +0 -3
  288. package/dist/compiler/internal/ViewTransitions.d.ts.map +0 -1
  289. package/dist/compiler/internal/ViewTransitions.js +0 -68
  290. package/dist/compiler/internal/ViewTransitions.js.map +0 -1
  291. package/dist/internal/Cascade.d.ts +0 -74
  292. package/dist/internal/Cascade.d.ts.map +0 -1
  293. package/dist/internal/Cascade.js +0 -210
  294. package/dist/internal/Cascade.js.map +0 -1
  295. package/src/compiler/internal/Applications.ts +0 -554
  296. package/src/compiler/internal/ColorProfiles.ts +0 -93
  297. package/src/compiler/internal/CompositionBindings.ts +0 -120
  298. package/src/compiler/internal/Compositions.ts +0 -678
  299. package/src/compiler/internal/Configurations.ts +0 -349
  300. package/src/compiler/internal/Contract.ts +0 -1028
  301. package/src/compiler/internal/Contributions.ts +0 -1561
  302. package/src/compiler/internal/Descriptors.ts +0 -242
  303. package/src/compiler/internal/Dynamic.ts +0 -275
  304. package/src/compiler/internal/Expression.ts +0 -218
  305. package/src/compiler/internal/Functions.ts +0 -206
  306. package/src/compiler/internal/Keyframes.ts +0 -62
  307. package/src/compiler/internal/Names.ts +0 -48
  308. package/src/compiler/internal/NativeBindings.ts +0 -235
  309. package/src/compiler/internal/NativeMedia.ts +0 -342
  310. package/src/compiler/internal/NativeVars.ts +0 -231
  311. package/src/compiler/internal/PackedStyles.ts +0 -473
  312. package/src/compiler/internal/Properties.ts +0 -121
  313. package/src/compiler/internal/RecipeConditions.ts +0 -139
  314. package/src/compiler/internal/RecipePayloads.ts +0 -132
  315. package/src/compiler/internal/Recipes.ts +0 -531
  316. package/src/compiler/internal/Selectors.ts +0 -339
  317. package/src/compiler/internal/Static.ts +0 -784
  318. package/src/compiler/internal/Themes.ts +0 -2398
  319. package/src/compiler/internal/Vars.ts +0 -516
  320. package/src/compiler/internal/ViewTransitions.ts +0 -82
@@ -5,18 +5,20 @@
5
5
  import * as Namespaces from '../compiler/internal/Namespaces.js';
6
6
  import * as AtRules from '../compiler/internal/AtRules.js';
7
7
  import * as Catalogs from '../compiler/internal/Catalogs.js';
8
+ import * as Results from '../compiler/internal/Results.js';
8
9
  import * as Contracts from '../node/internal/Contracts.js';
9
10
  import * as Mapping from '@jridgewell/gen-mapping';
10
11
  import * as Lightning from 'lightningcss';
11
12
  import * as Crypto from 'node:crypto';
13
+ import { existsSync } from 'node:fs';
12
14
  import * as Fs from 'node:fs/promises';
13
15
  import * as Path from 'node:path';
14
16
  import * as Reset from '../node/Reset.js';
15
- import { isBuiltin } from 'node:module';
17
+ import * as Vlq from '../internal/Vlq.js';
18
+ import { createRequire, isBuiltin } from 'node:module';
16
19
  import * as Parser from 'oxc-parser';
17
20
  import * as Walker from 'oxc-walker';
18
21
  import * as Snapshot from '../node/internal/Snapshot.js';
19
- import * as Syntax from '../compiler/internal/Syntax.js';
20
22
  import * as Scope from '../compiler/internal/Scope.js';
21
23
  import * as Graph from '../compiler/Graph.js';
22
24
  import * as Source from '../compiler/Source.js';
@@ -43,7 +45,23 @@ export function zyzz(options = {}) {
43
45
  const states = new WeakMap();
44
46
  const removed = new WeakMap();
45
47
  const reads = new WeakMap();
48
+ const lookups = new WeakMap();
46
49
  const compilers = new WeakMap();
50
+ const projects = new WeakMap();
51
+ // The shared stylesheet each environment last loaded, which an edit compares against.
52
+ const servedShared = new WeakMap();
53
+ // Watcher events advance an environment's generation, which scopes memoized whole-project work.
54
+ const generations = new WeakMap();
55
+ let devServer;
56
+ const watching = new Set();
57
+ // Chokidar matches its ignore patterns on every add, and each compile watches its whole closure.
58
+ const watched = new Set();
59
+ // Each compile reads the catalogs of every contract it saw, which rarely changes text.
60
+ const catalogReads = new Map();
61
+ const dependencyChains = new Map();
62
+ const edgeMemos = new WeakMap();
63
+ // A development entry's last output, reused until a watcher event or a recompiled graph.
64
+ const outputs = new WeakMap();
47
65
  const packages = Contracts.create();
48
66
  function compiler(environment) {
49
67
  let value = compilers.get(environment);
@@ -53,6 +71,144 @@ export function zyzz(options = {}) {
53
71
  }
54
72
  return value;
55
73
  }
74
+ /**
75
+ * Watches a compile input from a hook. Vite checks the filesystem each time a file outside its
76
+ * root is watched, and every compile reads the same package files, so development watches
77
+ * each of those once.
78
+ */
79
+ function watcher(context) {
80
+ return (file) => {
81
+ if (devServer && !file.startsWith(`${devServer.config.root}/`)) {
82
+ if (watching.has(file))
83
+ return;
84
+ watching.add(file);
85
+ }
86
+ context.addWatchFile(file);
87
+ };
88
+ }
89
+ /**
90
+ * The packed stylesheet dependency chains of a library contract. Every compile reads each
91
+ * contract it reaches, and design system contracts are large, so each text parses once.
92
+ */
93
+ function stylesheetDependencies(id, contract) {
94
+ let value = dependencyChains.get(contract);
95
+ if (value)
96
+ return value;
97
+ const metadata = (() => {
98
+ try {
99
+ return JSON.parse(contract);
100
+ }
101
+ catch (error) {
102
+ Graph.compile({ modules: {}, contracts: { [id]: contract } });
103
+ throw error;
104
+ }
105
+ })();
106
+ value = Array.isArray(metadata?.stylesheets)
107
+ ? metadata.stylesheets
108
+ .map((section) => section?.dependency)
109
+ .filter((dependency) => Array.isArray(dependency))
110
+ : [];
111
+ // Rebuilt libraries leave their earlier texts behind, so the cache stays bounded.
112
+ if (dependencyChains.size > 256)
113
+ dependencyChains.clear();
114
+ dependencyChains.set(contract, value);
115
+ return value;
116
+ }
117
+ /**
118
+ * Each module's import edges in an environment, shared by every entry compile that walks the
119
+ * module. Added and removed files and dependency optimization change resolutions, so either
120
+ * starts over.
121
+ */
122
+ function importEdges(environment) {
123
+ const metadata = environment.mode === 'dev'
124
+ ? environment.depsOptimizer?.metadata
125
+ : undefined;
126
+ const key = `${metadata?.browserHash}:${Object.keys(metadata?.discovered ?? {}).length}`;
127
+ let value = edgeMemos.get(environment);
128
+ if (value?.key !== key) {
129
+ value = { files: new Map(), key };
130
+ edgeMemos.set(environment, value);
131
+ }
132
+ return value.files;
133
+ }
134
+ /** Adds a compile input to the development watcher once. */
135
+ function watch(server, file) {
136
+ if (watched.has(file))
137
+ return;
138
+ watched.add(file);
139
+ server.watcher.add(file);
140
+ }
141
+ function read(contract) {
142
+ let value = catalogReads.get(contract);
143
+ if (!value) {
144
+ if (catalogReads.size > 256)
145
+ catalogReads.clear();
146
+ value = Catalogs.read(contract);
147
+ catalogReads.set(contract, value);
148
+ }
149
+ return value;
150
+ }
151
+ function project(environment) {
152
+ let value = projects.get(environment);
153
+ if (!value) {
154
+ value = {
155
+ compiler: Graph.create(),
156
+ contracts: Object.create(null),
157
+ fallback: Graph.create(),
158
+ imports: Object.create(null),
159
+ modules: Object.create(null),
160
+ outcome: undefined,
161
+ };
162
+ projects.set(environment, value);
163
+ }
164
+ return value;
165
+ }
166
+ /**
167
+ * Development merges every module a transform reaches into one graph per environment, so
168
+ * modules compile once per change instead of once per importer. A module's output depends only
169
+ * on its own import closure, so the merged graph returns the output a closure compile would.
170
+ * While some other module fails, the requested closure compiles alone and reports only its own
171
+ * errors, as before.
172
+ */
173
+ function compileProject(environment, input, id) {
174
+ const value = project(environment);
175
+ let changed = value.outcome === undefined;
176
+ for (const [module, source] of Object.entries(input.modules))
177
+ if (value.modules[module] !== source) {
178
+ value.modules[module] = source;
179
+ changed = true;
180
+ }
181
+ for (const [module, resolutions] of Object.entries(input.imports ?? {}))
182
+ if (!same(value.imports[module], resolutions)) {
183
+ value.imports[module] = resolutions;
184
+ changed = true;
185
+ }
186
+ for (const [module, contract] of Object.entries(input.contracts ?? {}))
187
+ if (value.contracts[module] !== contract) {
188
+ value.contracts[module] = contract;
189
+ changed = true;
190
+ }
191
+ if (changed)
192
+ value.outcome = (() => {
193
+ try {
194
+ return {
195
+ result: value.compiler.compile({
196
+ ...input,
197
+ contracts: value.contracts,
198
+ imports: value.imports,
199
+ modules: value.modules,
200
+ }),
201
+ };
202
+ }
203
+ catch (error) {
204
+ return { error };
205
+ }
206
+ })();
207
+ const outcome = value.outcome;
208
+ if ('result' in outcome && Object.hasOwn(outcome.result.modules, id))
209
+ return outcome.result;
210
+ return value.fallback.compile(input);
211
+ }
56
212
  const discoveries = new WeakMap();
57
213
  const contributionFiles = new WeakMap();
58
214
  const assets = new Map();
@@ -62,8 +218,13 @@ export function zyzz(options = {}) {
62
218
  const initializers = new WeakMap();
63
219
  const sourceEntrypoints = new Set();
64
220
  const identities = new Map();
221
+ const exclusions = new Map();
222
+ // Every compile checks each import against the source directories.
223
+ const roots = new Map();
65
224
  const lazyImports = new WeakMap();
66
225
  let directories;
226
+ let filter;
227
+ let optimizeRuntime = false;
67
228
  let root;
68
229
  function snapshot(environment) {
69
230
  let value = snapshots.get(environment);
@@ -125,16 +286,33 @@ export function zyzz(options = {}) {
125
286
  return (Path.isAbsolute(id) &&
126
287
  sourceRoot(id) !== undefined &&
127
288
  /\.[cm]?[jt]sx?$/.test(id) &&
128
- !/\.(?:d|test|test-d|bench|bench-d)\.[cm]?[jt]sx?$/.test(id));
289
+ !/\.(?:d|test|test-d|bench|bench-d)\.[cm]?[jt]sx?$/.test(id) &&
290
+ !excluded(id));
291
+ }
292
+ /** Matches excluded files inside source directories. Package files never match, so packed contracts stay readable. */
293
+ function excluded(id) {
294
+ const file = id.split(/[?#]/)[0];
295
+ let value = exclusions.get(file);
296
+ // Vite's filter recompiles each glob per call, and every compile checks each import.
297
+ if (value === undefined) {
298
+ value =
299
+ Path.isAbsolute(file) && sourceRoot(file) !== undefined && !filter(file);
300
+ exclusions.set(file, value);
301
+ }
302
+ return value;
129
303
  }
130
304
  function sourceRoot(file) {
131
- return directories.find((directory) => {
305
+ if (roots.has(file))
306
+ return roots.get(file);
307
+ const root = directories.find((directory) => {
132
308
  const relative = Path.relative(directory, file);
133
309
  return (relative !== '..' &&
134
310
  !relative.startsWith(`..${Path.sep}`) &&
135
311
  !Path.isAbsolute(relative) &&
136
312
  !relative.split(Path.sep).includes('node_modules'));
137
313
  });
314
+ roots.set(file, root);
315
+ return root;
138
316
  }
139
317
  function discover(environment, host) {
140
318
  let pending = discoveries.get(environment);
@@ -275,6 +453,37 @@ export function zyzz(options = {}) {
275
453
  }
276
454
  async function updateDiscovery(environment, file, event) {
277
455
  reads.delete(environment);
456
+ generations.set(environment, (generations.get(environment) ?? 0) + 1);
457
+ const changed = entries(environment).get(file);
458
+ if (changed)
459
+ changed.source = undefined;
460
+ // An edit keeps every resolution. Adding or removing a file can change where a
461
+ // specifier resolves, and a contract derives again only when a file it read changes.
462
+ // Editors save through temporary files, which no specifier resolves to.
463
+ const resolvable = event !== 'update' &&
464
+ /\.(?:[cm]?[jt]sx?|json|css|svelte|vue)$|(?:^|[/\\])package\.json$/.test(file);
465
+ const cache = lookups.get(environment);
466
+ // Import edges derive from resolutions and module text, which each memo compares.
467
+ if (resolvable)
468
+ edgeMemos.delete(environment);
469
+ if (cache) {
470
+ if (resolvable)
471
+ cache.resolutions.clear();
472
+ for (const [key, files] of cache.read)
473
+ if (files.has(file)) {
474
+ cache.contracts.delete(key);
475
+ cache.read.delete(key);
476
+ }
477
+ }
478
+ if (event === 'delete') {
479
+ const value = projects.get(environment);
480
+ const id = sourceId(file);
481
+ if (value && Object.hasOwn(value.modules, id)) {
482
+ delete value.modules[id];
483
+ delete value.imports[id];
484
+ value.outcome = undefined;
485
+ }
486
+ }
278
487
  if (event === 'delete') {
279
488
  if (entries(environment).has(file)) {
280
489
  let files = removed.get(environment);
@@ -304,36 +513,142 @@ export function zyzz(options = {}) {
304
513
  }
305
514
  }
306
515
  async function compile(entry, host, code, allSources = false, everything = false) {
516
+ const generation = generations.get(entry.environment) ?? 0;
517
+ const memo = outputs.get(entry);
518
+ const current = projects.get(entry.environment)?.outcome;
519
+ // Without a watcher event the closure is unchanged, so a stylesheet load after its
520
+ // transform reuses the output while the merged graph has not recompiled.
521
+ if (memo &&
522
+ !everything &&
523
+ memo.allSources === allSources &&
524
+ memo.generation === generation &&
525
+ (code === undefined || code === memo.code) &&
526
+ current &&
527
+ 'result' in current &&
528
+ current.result === memo.result) {
529
+ for (const file of entry.files)
530
+ host.watch(file);
531
+ return memo.output;
532
+ }
533
+ const output = await compileEntry(entry, host, code, allSources, everything);
534
+ const outcome = projects.get(entry.environment)?.outcome;
535
+ // A concurrent compile can replace the merged result while this one renders, so only an
536
+ // output of the current result is kept.
537
+ if (entry.environment.mode === 'dev' &&
538
+ !everything &&
539
+ outcome &&
540
+ 'result' in outcome &&
541
+ outcome.result.modules === output.modules &&
542
+ generation === (generations.get(entry.environment) ?? 0))
543
+ outputs.set(entry, {
544
+ allSources,
545
+ code,
546
+ generation,
547
+ output,
548
+ result: outcome.result,
549
+ });
550
+ return output;
551
+ }
552
+ async function compileEntry(entry, host, code, allSources = false, everything = false) {
553
+ // Every compile visits the discovered project, so modules share lookups. Watcher events
554
+ // drop the resolutions and contracts a change can affect. Full-project compiles look up
555
+ // afresh, since a document must not trail the watcher.
556
+ const cache = (() => {
557
+ const fresh = {
558
+ contracts: new Map(),
559
+ read: new Map(),
560
+ resolutions: new Map(),
561
+ };
562
+ if (everything)
563
+ return fresh;
564
+ let value = lookups.get(entry.environment);
565
+ if (!value) {
566
+ value = fresh;
567
+ lookups.set(entry.environment, value);
568
+ }
569
+ return value;
570
+ })();
571
+ // Full-project compiles walk afresh, as they look up afresh.
572
+ const memos = everything ? undefined : importEdges(entry.environment);
307
573
  async function resolve(source, importer) {
308
574
  if (isBuiltin(source))
309
575
  return { id: source, external: true };
310
- const resolved = await host.resolve(source, importer);
576
+ const key = `${importer}\0${source}`;
577
+ let pending = cache.resolutions.get(key);
578
+ if (!pending) {
579
+ pending = host.resolve(source, importer);
580
+ cache.resolutions.set(key, pending);
581
+ pending.catch(() => cache.resolutions.delete(key));
582
+ }
583
+ const resolved = await pending;
311
584
  if (!resolved)
312
585
  return resolved;
313
586
  if (entry.environment.mode === 'dev') {
314
- const optimized = Object.values({
315
- ...entry.environment.depsOptimizer?.metadata.optimized,
316
- ...entry.environment.depsOptimizer?.metadata.discovered,
317
- }).find((item) => item.file === resolved.id.split('?')[0]);
318
- if (optimized?.src)
319
- return { ...resolved, id: optimized.src };
587
+ const source = optimized().get(resolved.id.split('?')[0]);
588
+ if (source)
589
+ return { ...resolved, id: source };
320
590
  }
321
591
  return { ...resolved, id: normalize(resolved.id) };
322
592
  }
593
+ // Optimized dependency sources by bundled file, indexed once per compile rather than
594
+ // searched for every resolution.
595
+ let indexed;
596
+ function optimized() {
597
+ if (indexed)
598
+ return indexed;
599
+ indexed = new Map();
600
+ const environment = entry.environment;
601
+ const metadata = environment.mode === 'dev'
602
+ ? environment.depsOptimizer?.metadata
603
+ : undefined;
604
+ // Discovered entries replace optimized ones of the same name, and the first entry
605
+ // bundling a file wins, as a search of the merged record finds it.
606
+ for (const item of Object.values({
607
+ ...metadata?.optimized,
608
+ ...metadata?.discovered,
609
+ }))
610
+ if (!indexed.has(item.file))
611
+ indexed.set(item.file, item.src ?? '');
612
+ return indexed;
613
+ }
323
614
  const imports = Object.create(null);
324
615
  const contracts = Object.create(null);
325
616
  const lazy = Object.create(null);
326
617
  const modules = Object.create(null);
327
618
  const files = new Set();
328
- const contractHost = {
329
- read: async (file) => {
330
- const source = await snapshot(entry.environment).read(file);
331
- host.watch(file);
332
- files.add(file);
333
- return source;
334
- },
335
- resolve: async (specifier, importer) => (await resolve(specifier, importer))?.id.split(/[?#]/)[0],
336
- };
619
+ // A package contract derives from the files it reads and the resolutions
620
+ // above. A cached contract still makes this compile watch those files.
621
+ async function readContract(file, required = false) {
622
+ const key = `${required}\0${file}`;
623
+ let pending = cache.contracts.get(key);
624
+ if (!pending) {
625
+ const read = new Set();
626
+ pending = packages
627
+ .read(file, {
628
+ read: async (path) => {
629
+ const source = await snapshot(entry.environment).read(path);
630
+ read.add(path);
631
+ return source;
632
+ },
633
+ required,
634
+ resolve: async (specifier, importer) => (await resolve(specifier, importer))?.id.split(/[?#]/)[0],
635
+ })
636
+ .then((contract) => {
637
+ cache.read.set(key, read);
638
+ return { contract, files: read };
639
+ });
640
+ cache.contracts.set(key, pending);
641
+ pending.catch(() => cache.contracts.delete(key));
642
+ }
643
+ const derived = await pending;
644
+ for (const path of derived.files) {
645
+ if (files.has(path))
646
+ continue;
647
+ files.add(path);
648
+ host.watch(path);
649
+ }
650
+ return derived.contract;
651
+ }
337
652
  async function visit(file, source) {
338
653
  if (files.has(file))
339
654
  return;
@@ -345,14 +660,37 @@ export function zyzz(options = {}) {
345
660
  cached = new Map();
346
661
  reads.set(entry.environment, cached);
347
662
  }
663
+ // Earlier plugins can rewrite a module before this one sees it, as router plugins drop
664
+ // server routes from client route trees. Its importers read the code Vite transformed, so
665
+ // every graph in the environment holds one version of each module.
348
666
  // Full-project compilation must not reuse reads from concurrent module transforms.
349
667
  const text = source ??
668
+ (everything
669
+ ? undefined
670
+ : entries(entry.environment).get(file)?.source) ??
350
671
  (everything ? undefined : cached.get(file)) ??
351
672
  (await snapshot(entry.environment).read(file));
352
673
  if (!everything)
353
674
  cached.set(file, text);
354
675
  modules[id] = text;
676
+ const memo = memos?.get(file);
677
+ // Replaying the edges keeps the order in which modules and contracts join the compile.
678
+ if (memo?.text === text) {
679
+ imports[id] = { ...memo.resolutions };
680
+ for (const step of memo.steps) {
681
+ if (typeof step === 'string') {
682
+ await visit(step);
683
+ continue;
684
+ }
685
+ const contract = await readContract(step.physical);
686
+ if (contract !== undefined)
687
+ contracts[step.id] = contract;
688
+ }
689
+ lazy[id] = memo.lazy;
690
+ return;
691
+ }
355
692
  const resolutions = Object.create(null);
693
+ const steps = [];
356
694
  imports[id] = resolutions;
357
695
  const parsed = snapshot(entry.environment).parse({
358
696
  moduleId: id,
@@ -401,14 +739,21 @@ export function zyzz(options = {}) {
401
739
  /[/\\]src[/\\]default\.[cm]?ts$/.test(resolved.id)) {
402
740
  sourceEntrypoints.add(normalize(resolved.id));
403
741
  resolutions[specifier] = sourceId(resolved.id);
742
+ steps.push(resolved.id);
404
743
  await visit(resolved.id);
405
744
  continue;
406
745
  }
746
+ // An excluded source stays a runtime import instead of deriving a package contract.
747
+ if (excluded(resolved.id)) {
748
+ resolutions[specifier] = null;
749
+ continue;
750
+ }
407
751
  if (resolved.external || !eligible(resolved.id)) {
408
752
  const physical = resolved.id.split('?')[0].split('#')[0];
409
- const contract = Path.isAbsolute(physical) && /\.[cm]?[jt]sx?$/.test(physical)
410
- ? await packages.read(physical, contractHost)
411
- : undefined;
753
+ const readable = Path.isAbsolute(physical) && /\.[cm]?[jt]sx?$/.test(physical);
754
+ if (readable)
755
+ steps.push({ id: resolved.id, physical });
756
+ const contract = readable ? await readContract(physical) : undefined;
412
757
  if (contract !== undefined) {
413
758
  contracts[resolved.id] = contract;
414
759
  resolutions[specifier] = resolved.id;
@@ -418,6 +763,7 @@ export function zyzz(options = {}) {
418
763
  continue;
419
764
  }
420
765
  resolutions[specifier] = sourceId(resolved.id);
766
+ steps.push(resolved.id);
421
767
  await visit(resolved.id);
422
768
  }
423
769
  // Lazy imports stay outside this compile but scope the documents that load them.
@@ -445,6 +791,7 @@ export function zyzz(options = {}) {
445
791
  targets.add(eligible(resolved.id) ? sourceId(resolved.id) : resolved.id);
446
792
  }
447
793
  lazy[id] = targets;
794
+ memos?.set(file, { lazy: targets, resolutions, steps, text });
448
795
  }
449
796
  await visit(entry.file, code);
450
797
  const connected = new Set(files);
@@ -495,14 +842,16 @@ export function zyzz(options = {}) {
495
842
  (specifier.startsWith('zyzz/') && specifier !== 'zyzz/default'))
496
843
  continue;
497
844
  const resolved = await resolve(specifier, file);
498
- if (!resolved || (!resolved.external && eligible(resolved.id)))
845
+ if (!resolved ||
846
+ (!resolved.external && eligible(resolved.id)) ||
847
+ excluded(resolved.id))
499
848
  continue;
500
849
  if (resource(specifier) || resource(resolved.id))
501
850
  continue;
502
851
  const physical = resolved.id.split(/[?#]/)[0];
503
852
  if (!Path.isAbsolute(physical) || !/\.[cm]?[jt]sx?$/.test(physical))
504
853
  continue;
505
- const contract = await packages.read(physical, contractHost);
854
+ const contract = await readContract(physical);
506
855
  if (contract === undefined)
507
856
  continue;
508
857
  contracts[resolved.id] = contract;
@@ -526,22 +875,9 @@ export function zyzz(options = {}) {
526
875
  if (loaded.has(id))
527
876
  return;
528
877
  loaded.add(id);
529
- const metadata = (() => {
530
- try {
531
- return JSON.parse(contracts[id]);
532
- }
533
- catch (error) {
534
- Graph.compile({ modules: {}, contracts: { [id]: contracts[id] } });
535
- throw error;
536
- }
537
- })();
538
- if (!Array.isArray(metadata?.stylesheets))
539
- return;
540
- for (const section of metadata.stylesheets) {
541
- if (!Array.isArray(section?.dependency))
542
- continue;
878
+ for (const dependency of stylesheetDependencies(id, contracts[id])) {
543
879
  let owner = id;
544
- for (const specifier of section.dependency) {
880
+ for (const specifier of dependency) {
545
881
  if (typeof specifier !== 'string' ||
546
882
  !specifier ||
547
883
  specifier.includes('\0'))
@@ -551,7 +887,7 @@ export function zyzz(options = {}) {
551
887
  throw new Error('Unable to resolve packed stylesheet dependency.');
552
888
  imports[owner] ??= Object.create(null);
553
889
  imports[owner][specifier] = target.id;
554
- contracts[target.id] ??= (await packages.read(target.id.split(/[?#]/)[0], { ...contractHost, required: true }));
890
+ contracts[target.id] ??= (await readContract(target.id.split(/[?#]/)[0], true));
555
891
  await dependencies(target.id);
556
892
  owner = target.id;
557
893
  }
@@ -572,9 +908,9 @@ export function zyzz(options = {}) {
572
908
  ...Object.values(resolutions).filter((target) => target !== null),
573
909
  ...(lazy[id] ?? []),
574
910
  ]));
575
- const result = entry.compiler.compile({
911
+ const input = {
912
+ [Results.lean]: true,
576
913
  [ThemeRules.shared]: native ? undefined : 'all',
577
- [Syntax.cache]: snapshot(entry.environment).programs(modules),
578
914
  compiler: options.compiler,
579
915
  reset: options.reset ? Reset.read() : undefined,
580
916
  native,
@@ -582,7 +918,10 @@ export function zyzz(options = {}) {
582
918
  development: entry.environment.mode !== 'build',
583
919
  imports,
584
920
  modules,
585
- });
921
+ };
922
+ const result = entry.environment.mode === 'dev' && !native && !everything
923
+ ? compileProject(entry.environment, input, sourceId(entry.file))
924
+ : entry.compiler.compile(input);
586
925
  if (native) {
587
926
  const output = result.modules[sourceId(entry.file)];
588
927
  entry.files = files;
@@ -595,6 +934,7 @@ export function zyzz(options = {}) {
595
934
  configurations: new Map(),
596
935
  connections: staged,
597
936
  css: '',
937
+ graphSharedCss: '',
598
938
  sharedCss: '',
599
939
  cssMap: map,
600
940
  sharedCssMap: map,
@@ -622,132 +962,143 @@ export function zyzz(options = {}) {
622
962
  catalogs.delete(key);
623
963
  const configurations = new Map();
624
964
  for (const [id, contract] of Object.entries(compiled))
625
- for (const configuration of Catalogs.read(contract)) {
965
+ for (const configuration of read(contract)) {
626
966
  const key = `${id}\0${configuration.identity}`;
627
967
  catalogs.set(key, configuration);
628
968
  configurations.set(key, configuration);
629
969
  }
630
- const owners = new Map();
631
- for (const id of new Set(Object.values(result.sharedAssetOwners ?? {}))) {
632
- if (!Path.isAbsolute(id))
633
- continue;
634
- let directory = Path.dirname(id.split(/[?#]/)[0]);
635
- for (;;) {
636
- try {
637
- await Fs.access(Path.join(directory, 'package.json'));
638
- owners.set(id, await Fs.realpath(directory));
639
- break;
640
- }
641
- catch (error) {
642
- if (error.code !== 'ENOENT')
643
- throw error;
970
+ // Development renders the shared stylesheet for its own request only; transforms and module
971
+ // stylesheets read module outputs.
972
+ async function renderShared() {
973
+ const owners = new Map();
974
+ for (const id of new Set(Object.values(result.sharedAssetOwners ?? {}))) {
975
+ if (!Path.isAbsolute(id))
976
+ continue;
977
+ let directory = Path.dirname(id.split(/[?#]/)[0]);
978
+ for (;;) {
979
+ try {
980
+ await Fs.access(Path.join(directory, 'package.json'));
981
+ owners.set(id, await Fs.realpath(directory));
982
+ break;
983
+ }
984
+ catch (error) {
985
+ if (error.code !== 'ENOENT')
986
+ throw error;
987
+ }
988
+ const parent = Path.dirname(directory);
989
+ if (parent === directory)
990
+ throw new Error('Packed assets require an owning package.json.');
991
+ directory = parent;
644
992
  }
645
- const parent = Path.dirname(directory);
646
- if (parent === directory)
647
- throw new Error('Packed assets require an owning package.json.');
648
- directory = parent;
649
993
  }
650
- }
651
- const assetUrls = new Map();
652
- for (const [key, target] of Object.entries(result.sharedAssets ?? {})) {
653
- const raw = target.split(/[?#]/)[0];
654
- const identity = result.sharedAssetOwners?.[key];
655
- const path = sourceFile(decodeURIComponent(raw)) ??
656
- (Path.isAbsolute(raw) ? decodeURIComponent(raw) : undefined);
657
- if (!path)
658
- throw new Error('Asset path escapes the Vite graph.');
659
- const asset = await (async () => {
660
- try {
661
- return { file: await Fs.realpath(path), package: false };
662
- }
663
- catch (error) {
664
- if (error.code !== 'ENOENT' ||
665
- !identity ||
666
- !sourceFile(identity))
667
- throw error;
668
- const importer = sourceFile(identity);
669
- const specifier = Path.relative(Path.dirname(importer), path)
670
- .split(Path.sep)
671
- .join('/');
672
- if (specifier.startsWith('.') || Path.isAbsolute(specifier))
673
- throw error;
674
- const resolved = await host.resolve(specifier, importer);
675
- if (!resolved || resolved.external)
676
- throw error;
677
- return {
678
- file: await Fs.realpath(resolved.id.split('?')[0]),
679
- package: true,
680
- };
681
- }
682
- })();
683
- const file = asset.file;
684
- const owner = identity && sourceFile(identity)
685
- ? sourceRoot(sourceFile(identity))
686
- : identity
687
- ? owners.get(identity)
688
- : undefined;
689
- const relative = owner ? Path.relative(owner, file) : '..';
690
- if (!asset.package &&
691
- (relative === '..' ||
692
- relative.startsWith(`..${Path.sep}`) ||
693
- Path.isAbsolute(relative)))
694
- throw new Error('Asset path escapes its owning package.');
695
- host.watch(file);
696
- files.add(file);
697
- let url;
698
- if (asset.package || /[?#%]/.test(file)) {
699
- if (entry.environment.mode === 'build') {
700
- url = await host.asset(file);
701
- if (target.slice(raw.length))
702
- url += `$_${target.slice(raw.length)}__`;
994
+ const assetUrls = new Map();
995
+ for (const [key, target] of Object.entries(result.sharedAssets ?? {})) {
996
+ const raw = target.split(/[?#]/)[0];
997
+ const identity = result.sharedAssetOwners?.[key];
998
+ const path = sourceFile(decodeURIComponent(raw)) ??
999
+ (Path.isAbsolute(raw) ? decodeURIComponent(raw) : undefined);
1000
+ if (!path)
1001
+ throw new Error('Asset path escapes the Vite graph.');
1002
+ const asset = await (async () => {
1003
+ try {
1004
+ return { file: await Fs.realpath(path), package: false };
1005
+ }
1006
+ catch (error) {
1007
+ if (error.code !== 'ENOENT' ||
1008
+ !identity ||
1009
+ !sourceFile(identity))
1010
+ throw error;
1011
+ const importer = sourceFile(identity);
1012
+ const specifier = Path.relative(Path.dirname(importer), path)
1013
+ .split(Path.sep)
1014
+ .join('/');
1015
+ if (specifier.startsWith('.') || Path.isAbsolute(specifier))
1016
+ throw error;
1017
+ const resolved = await host.resolve(specifier, importer);
1018
+ if (!resolved || resolved.external)
1019
+ throw error;
1020
+ return {
1021
+ file: await Fs.realpath(resolved.id.split('?')[0]),
1022
+ package: true,
1023
+ };
1024
+ }
1025
+ })();
1026
+ const file = asset.file;
1027
+ const owner = identity && sourceFile(identity)
1028
+ ? sourceRoot(sourceFile(identity))
1029
+ : identity
1030
+ ? owners.get(identity)
1031
+ : undefined;
1032
+ const relative = owner ? Path.relative(owner, file) : '..';
1033
+ if (!asset.package &&
1034
+ (relative === '..' ||
1035
+ relative.startsWith(`..${Path.sep}`) ||
1036
+ Path.isAbsolute(relative)))
1037
+ throw new Error('Asset path escapes its owning package.');
1038
+ host.watch(file);
1039
+ files.add(file);
1040
+ let url;
1041
+ if (asset.package || /[?#%]/.test(file)) {
1042
+ if (entry.environment.mode === 'build') {
1043
+ url = await host.asset(file);
1044
+ if (target.slice(raw.length))
1045
+ url += `$_${target.slice(raw.length)}__`;
1046
+ }
1047
+ else {
1048
+ url = `/@zyzz/asset/${Crypto.hash('sha256', file)}/${encodeURIComponent(Path.basename(file))}`;
1049
+ assets.set(url, file);
1050
+ url += target.slice(raw.length);
1051
+ }
703
1052
  }
704
1053
  else {
705
- url = `/@zyzz/asset/${Crypto.hash('sha256', file)}/${encodeURIComponent(Path.basename(file))}`;
706
- assets.set(url, file);
707
- url += target.slice(raw.length);
1054
+ // Vite reads `/@fs/` URLs with `decodeURI`, which keeps reserved escapes
1055
+ // such as `%40` in scoped package paths, so those characters stay raw.
1056
+ const pathname = encodeURI(file.replaceAll('\\', '/'));
1057
+ url = `/@fs/${pathname}${target.slice(raw.length)}`;
708
1058
  }
1059
+ assetUrls.set(target, url);
709
1060
  }
710
- else {
711
- // Vite reads `/@fs/` URLs with `decodeURI`, which keeps reserved escapes
712
- // such as `%40` in scoped package paths, so those characters stay raw.
713
- const pathname = encodeURI(file.replaceAll('\\', '/'));
714
- url = `/@fs/${pathname}${target.slice(raw.length)}`;
715
- }
716
- assetUrls.set(target, url);
717
- }
718
- const shared = !Object.keys(result.sharedAssets ?? {}).length
719
- ? {
720
- code: new TextEncoder().encode(result.sharedCss ?? ''),
721
- map: new TextEncoder().encode(JSON.stringify(result.sharedCssMap ??
722
- Mapping.toEncodedMap(new Mapping.GenMapping()))),
723
- }
724
- : AtRules.transform({
725
- filename: 'zyzz.shared.css',
726
- code: new TextEncoder().encode(result.sharedCss ?? ''),
727
- sourceMap: true,
728
- inputSourceMap: JSON.stringify(result.sharedCssMap ??
729
- Mapping.toEncodedMap(new Mapping.GenMapping())),
730
- visitor: {
731
- Rule(rule) {
732
- if (rule.type !== 'import')
733
- return;
734
- const target = result.sharedAssets?.[rule.value.url];
735
- if (target)
736
- return AtRules.relocateImport(rule.value, assetUrls.get(target));
737
- },
738
- Url(url) {
739
- const target = result.sharedAssets?.[url.url];
740
- if (!target)
741
- return;
742
- return { ...url, url: assetUrls.get(target) };
1061
+ const shared = !Object.keys(result.sharedAssets ?? {}).length
1062
+ ? {
1063
+ code: new TextEncoder().encode(result.sharedCss ?? ''),
1064
+ map: new TextEncoder().encode(JSON.stringify(result.sharedCssMap ??
1065
+ Mapping.toEncodedMap(new Mapping.GenMapping()))),
1066
+ }
1067
+ : AtRules.transform({
1068
+ filename: 'zyzz.shared.css',
1069
+ code: new TextEncoder().encode(result.sharedCss ?? ''),
1070
+ sourceMap: true,
1071
+ inputSourceMap: JSON.stringify(result.sharedCssMap ??
1072
+ Mapping.toEncodedMap(new Mapping.GenMapping())),
1073
+ visitor: {
1074
+ Rule(rule) {
1075
+ if (rule.type !== 'import')
1076
+ return;
1077
+ const target = result.sharedAssets?.[rule.value.url];
1078
+ if (target)
1079
+ return AtRules.relocateImport(rule.value, assetUrls.get(target));
1080
+ },
1081
+ Url(url) {
1082
+ const target = result.sharedAssets?.[url.url];
1083
+ if (!target)
1084
+ return;
1085
+ return { ...url, url: assetUrls.get(target) };
1086
+ },
743
1087
  },
744
- },
745
- });
746
- const sharedMap = JSON.parse(new TextDecoder().decode(shared.map));
747
- sharedMap.sources = sharedMap.sources.map((source) => source === null ? null : (sourceFile(source) ?? source));
1088
+ });
1089
+ const sharedMap = JSON.parse(new TextDecoder().decode(shared.map));
1090
+ sharedMap.sources = sharedMap.sources.map((source) => source === null ? null : (sourceFile(source) ?? source));
1091
+ return { css: new TextDecoder().decode(shared.code), map: sharedMap };
1092
+ }
1093
+ const sharedOutput = entry.environment.mode !== 'dev' || allSources
1094
+ ? await renderShared()
1095
+ : {
1096
+ css: '',
1097
+ map: Mapping.toEncodedMap(new Mapping.GenMapping()),
1098
+ };
748
1099
  const output = result.modules[sourceId(entry.file)];
749
1100
  entry.files = files;
750
- return {
1101
+ const value = {
751
1102
  imports,
752
1103
  modules: result.modules,
753
1104
  code: output.code,
@@ -755,8 +1106,10 @@ export function zyzz(options = {}) {
755
1106
  configurations,
756
1107
  connections: staged,
757
1108
  css: output.css,
758
- sharedCss: new TextDecoder().decode(shared.code),
759
- sharedCssMap: sharedMap,
1109
+ // Edits compare the graph's stylesheet, which precedes asset relocation.
1110
+ graphSharedCss: result.sharedCss ?? '',
1111
+ sharedCss: sharedOutput.css,
1112
+ sharedCssMap: sharedOutput.map,
760
1113
  cssMap: {
761
1114
  ...output.cssMap,
762
1115
  sources: output.cssMap.sources.map((source) => source === null ? null : (sourceFile(source) ?? source)),
@@ -766,6 +1119,7 @@ export function zyzz(options = {}) {
766
1119
  sources: output.map.sources.map((source) => source === null ? null : (sourceFile(source) ?? source)),
767
1120
  },
768
1121
  };
1122
+ return value;
769
1123
  }
770
1124
  /** Compiles every discovered source once so index.html can inline each configuration's script before modules load. */
771
1125
  async function initializations(server) {
@@ -773,8 +1127,8 @@ export function zyzz(options = {}) {
773
1127
  reads.delete(environment);
774
1128
  const host = {
775
1129
  asset: async (file) => file,
776
- resolve: (source, importer) => environment.pluginContainer.resolveId(source, importer),
777
- watch: (file) => server.watcher.add(file),
1130
+ resolve: resolver(environment),
1131
+ watch: (file) => watch(server, file),
778
1132
  };
779
1133
  const sources = await discover(environment, host);
780
1134
  let entry = initializers.get(environment);
@@ -783,8 +1137,9 @@ export function zyzz(options = {}) {
783
1137
  const file = sources.keys().next().value;
784
1138
  if (file === undefined)
785
1139
  return { catalogs: new Map(), edges: new Map() };
1140
+ // Whole-project compiles keep their own cache beside the transforms' merged graph.
786
1141
  entry = {
787
- compiler: compiler(environment),
1142
+ compiler: initializers.get(environment)?.compiler ?? Graph.create(),
788
1143
  environment,
789
1144
  file,
790
1145
  files: new Set([file]),
@@ -908,13 +1263,116 @@ export function zyzz(options = {}) {
908
1263
  .filter(([key]) => contracts.has(key.split('\0')[0]))
909
1264
  .map(([, configuration]) => configuration);
910
1265
  }
1266
+ /**
1267
+ * The modules an edit invalidates: those whose code or stylesheet differs from what Vite served.
1268
+ * A concurrent compile can already hold the edit, so outputs are not compared with each other.
1269
+ */
1270
+ async function changes(environment, after, served, modules, timestamp, edited, deleted) {
1271
+ // Portable build adapters share this module and do not require Vite.
1272
+ const { transformWithOxc } = await import('vite');
1273
+ const code = new Set();
1274
+ const css = new Set();
1275
+ for (const [entry, { code: current, css: stylesheet, imports },] of served.entries) {
1276
+ const id = sourceId(entry.file);
1277
+ const next = after.modules[id];
1278
+ if (!next)
1279
+ continue;
1280
+ // A stylesheet Vite never loaded has no module to invalidate.
1281
+ if (stylesheet !== undefined && stylesheet !== next.css)
1282
+ css.add(entry.file);
1283
+ // A stylesheet request can compile a module whose code was never transformed here.
1284
+ if (current === undefined)
1285
+ continue;
1286
+ if (imports !== JSON.stringify(projects.get(environment)?.imports[id])) {
1287
+ code.add(entry.file);
1288
+ continue;
1289
+ }
1290
+ if (current === next.code)
1291
+ continue;
1292
+ // Generated type assertions can change without changing runtime bindings.
1293
+ const [left, right] = await Promise.all([
1294
+ transformWithOxc(current, entry.file, { sourcemap: false }),
1295
+ transformWithOxc(next.code, entry.file, { sourcemap: false }),
1296
+ ]);
1297
+ if (left.code !== right.code)
1298
+ code.add(entry.file);
1299
+ }
1300
+ const stylesheets = new Set();
1301
+ if (deleted !== undefined)
1302
+ stylesheets.add(cssId(deleted));
1303
+ if (served.shared !== undefined &&
1304
+ served.shared !== (after.sharedCss ?? ''))
1305
+ stylesheets.add(sharedId);
1306
+ for (const file of css)
1307
+ stylesheets.add(cssId(file));
1308
+ // Compiled modules the edit left unchanged skip Vite's own update; other modules, such as
1309
+ // plain JavaScript or assets, keep it.
1310
+ const affected = new Set(modules.filter((module) => {
1311
+ if (!module.id)
1312
+ return module.type !== 'asset';
1313
+ const file = normalize(module.id);
1314
+ return Object.hasOwn(after.modules, sourceId(file))
1315
+ ? code.has(file)
1316
+ : true;
1317
+ }));
1318
+ for (const file of code) {
1319
+ const module = environment.moduleGraph.getModuleById(file);
1320
+ if (!module)
1321
+ continue;
1322
+ environment.moduleGraph.invalidateModule(module, new Set(), timestamp, true);
1323
+ affected.add(module);
1324
+ }
1325
+ const sheets = [];
1326
+ // The edited module's stylesheet updates first, ahead of stylesheets other modules share.
1327
+ for (const id of [...stylesheets].sort((left, right) => Number(right === cssId(edited)) - Number(left === cssId(edited)))) {
1328
+ const module = environment.moduleGraph.getModuleById(id);
1329
+ if (!module)
1330
+ continue;
1331
+ environment.moduleGraph.invalidateModule(module, new Set(), timestamp, true);
1332
+ sheets.push(module);
1333
+ }
1334
+ // A stylesheet accepts its own updates and only its module imports it, so it is never within
1335
+ // an import cycle. Vite proves that by walking every importer of the importing module, which
1336
+ // costs each stylesheet of a shared edit the whole graph, so stylesheet-only edits send the
1337
+ // same update directly. Edits that also change code keep Vite's single batched update.
1338
+ if (!affected.size &&
1339
+ sheets.length &&
1340
+ sheets.every((module) => module.isSelfAccepting)) {
1341
+ const updates = sheets.map((module) => {
1342
+ const path = module.url.startsWith('/') || module.url.startsWith('.')
1343
+ ? module.url
1344
+ : `/@id/${module.url.replace('\0', '__x00__')}`;
1345
+ return {
1346
+ acceptedPath: path,
1347
+ explicitImportRequired: false,
1348
+ isWithinCircularImport: false,
1349
+ path,
1350
+ timestamp,
1351
+ type: 'js-update',
1352
+ };
1353
+ });
1354
+ environment.logger.info(`hmr update ${updates.map((update) => update.path).join(', ')}`, { timestamp: true });
1355
+ environment.hot.send({ type: 'update', updates });
1356
+ return [];
1357
+ }
1358
+ for (const module of sheets)
1359
+ affected.add(module);
1360
+ return [...affected];
1361
+ }
911
1362
  return {
912
1363
  buildStart() {
1364
+ edgeMemos.delete(this.environment);
913
1365
  reads.delete(this.environment);
1366
+ lookups.delete(this.environment);
914
1367
  },
915
1368
  config: {
916
1369
  order: 'post',
917
- handler(config) {
1370
+ handler(config, env) {
1371
+ // Ordered after other config hooks, so plugin-provided aliases are visible.
1372
+ optimizeRuntime =
1373
+ env.command === 'serve' &&
1374
+ options.compiler !== false &&
1375
+ installed(config);
918
1376
  if (native)
919
1377
  return;
920
1378
  // Inline color-scheme changes cannot initialize Lightning's lowered helpers.
@@ -943,7 +1401,30 @@ export function zyzz(options = {}) {
943
1401
  };
944
1402
  },
945
1403
  },
1404
+ configEnvironment: {
1405
+ // Plugins without enforce settle their environment options first.
1406
+ order: 'post',
1407
+ handler(name, config) {
1408
+ if (!optimizeRuntime)
1409
+ return;
1410
+ const optimizeDeps = config.optimizeDeps ?? {};
1411
+ const consumer = config.consumer ?? (name === 'client' ? 'client' : 'server');
1412
+ // Vite defaults server environments to noDiscovery. An include list there
1413
+ // would create an explicit optimizer that otherwise never exists.
1414
+ if (optimizeDeps.noDiscovery ?? consumer === 'server')
1415
+ return;
1416
+ if (optimizeDeps.include?.includes(runtime) ||
1417
+ optimizeDeps.exclude?.some((id) => id === 'zyzz' || id === runtime))
1418
+ return;
1419
+ // Vite scans authored source, so the runtime import that compilation
1420
+ // adds first appears while a request transforms a module. Discovering
1421
+ // it then re-runs the optimizer, and modules loaded before the re-run
1422
+ // can keep a separate copy of a shared dependency such as React.
1423
+ return { optimizeDeps: { include: [runtime] } };
1424
+ },
1425
+ },
946
1426
  configureServer(server) {
1427
+ devServer = server;
947
1428
  // Directory watches belong to the server, not module import dependencies.
948
1429
  server.watcher.add(directories.slice(1));
949
1430
  server.middlewares.use((request, response, next) => {
@@ -971,6 +1452,12 @@ export function zyzz(options = {}) {
971
1452
  return file;
972
1453
  }))),
973
1454
  ];
1455
+ // Portable build adapters share this module and do not require Vite.
1456
+ const { createFilter } = await import('vite');
1457
+ const patterns = [...configFiles, ...(options.exclude ?? [])];
1458
+ filter = createFilter(null, patterns, { resolve: root });
1459
+ exclusions.clear();
1460
+ roots.clear();
974
1461
  if (native)
975
1462
  return;
976
1463
  const lightning = (config.css.lightningcss ??= {});
@@ -1033,10 +1520,46 @@ export function zyzz(options = {}) {
1033
1520
  },
1034
1521
  },
1035
1522
  async watchChange(file, change) {
1523
+ // A removed file leaves the watcher, so a later compile that reads it again adds it again.
1524
+ if (change.event === 'delete')
1525
+ watched.delete(file);
1036
1526
  await updateDiscovery(this.environment, file, change.event);
1037
1527
  },
1038
1528
  async hotUpdate({ file, modules, timestamp, type, server }) {
1039
1529
  const state = entries(this.environment);
1530
+ const before = projects.get(this.environment)?.outcome;
1531
+ // Concurrent transforms update entries while this compiles, so the served output is read now.
1532
+ const served = {
1533
+ entries: new Map([...state.values()].map((entry) => [
1534
+ entry,
1535
+ { code: entry.code, css: entry.css, imports: entry.imports },
1536
+ ])),
1537
+ shared: servedShared.get(this.environment),
1538
+ };
1539
+ // The merged development graph compares module outputs before and after the edit, so
1540
+ // only modules whose code or stylesheet changed update.
1541
+ if (!native && before && 'result' in before) {
1542
+ await updateDiscovery(this.environment, file, type);
1543
+ // The edited module's own entry, or one whose closure reads it, merges its new source.
1544
+ // A contribution source no module imports joins every closure.
1545
+ const first = state.get(file) ??
1546
+ [...state.values()].find((entry) => entry.files.has(file)) ??
1547
+ (eager(file, this.environment)
1548
+ ? state.values().next().value
1549
+ : undefined);
1550
+ if (!first && !removed.get(this.environment)?.has(file))
1551
+ return modules;
1552
+ const compiled = first && type === 'update' && eligible(file)
1553
+ ? await compile(first, {
1554
+ resolve: resolver(this.environment),
1555
+ watch: (file) => watch(server, file),
1556
+ asset: async (file) => file,
1557
+ }).then(() => true, () => false)
1558
+ : false;
1559
+ const after = projects.get(this.environment)?.outcome;
1560
+ if (compiled && after && 'result' in after)
1561
+ return await changes(this.environment, after.result, served, modules, timestamp, file, type === 'delete' ? file : undefined);
1562
+ }
1040
1563
  const previous = [...state.values()].map((entry) => ({
1041
1564
  code: entry.code,
1042
1565
  file: entry.file,
@@ -1049,10 +1572,8 @@ export function zyzz(options = {}) {
1049
1572
  // Failed edits retain normal invalidation so Vite reports errors on reload.
1050
1573
  const output = first && type === 'update' && eligible(file)
1051
1574
  ? await compile(first, {
1052
- resolve: (source, importer) => this.environment.pluginContainer.resolveId(source, importer),
1053
- watch: (file) => {
1054
- server.watcher.add(file);
1055
- },
1575
+ resolve: resolver(this.environment),
1576
+ watch: (file) => watch(server, file),
1056
1577
  asset: async (file) => file,
1057
1578
  }, undefined, true, true).catch(() => undefined)
1058
1579
  : undefined;
@@ -1113,17 +1634,40 @@ export function zyzz(options = {}) {
1113
1634
  const file = id === sharedId
1114
1635
  ? undefined
1115
1636
  : decodeURIComponent(id.slice(prefix.length, -4));
1116
- const entry = file === undefined
1637
+ let entry = file === undefined
1117
1638
  ? entries(this.environment).values().next().value
1118
1639
  : entries(this.environment).get(file);
1119
1640
  // A pruned JavaScript module can leave its stylesheet loaded in the browser.
1120
1641
  if (!entry && file && removed.get(this.environment)?.has(file))
1121
1642
  return '';
1643
+ // Server rendering links the stylesheets of modules this environment has not transformed
1644
+ // yet, so a development request compiles the module where the browser loads it.
1645
+ if (!entry && this.environment.mode === 'dev') {
1646
+ const anchor = file ??
1647
+ (await discover(this.environment, {
1648
+ asset: async (file) => file,
1649
+ resolve: resolver(this.environment),
1650
+ watch: watcher(this),
1651
+ }))
1652
+ .keys()
1653
+ .next().value;
1654
+ if (anchor !== undefined && eligible(anchor)) {
1655
+ entry = {
1656
+ compiler: compiler(this.environment),
1657
+ environment: this.environment,
1658
+ file: anchor,
1659
+ files: new Set([anchor]),
1660
+ };
1661
+ entries(this.environment).set(anchor, entry);
1662
+ }
1663
+ }
1122
1664
  if (!entry)
1123
1665
  throw new Error(`Unknown Zyzz stylesheet: ${file}`);
1124
1666
  const output = await compile(entry, {
1125
- resolve: (source, importer) => this.resolve(source, importer),
1126
- watch: (file) => this.addWatchFile(file),
1667
+ resolve: this.environment.mode === 'dev'
1668
+ ? resolver(this.environment)
1669
+ : (source, importer) => this.resolve(source, importer),
1670
+ watch: watcher(this),
1127
1671
  asset: async (file) => {
1128
1672
  const reference = this.emitFile({
1129
1673
  type: 'asset',
@@ -1137,12 +1681,18 @@ export function zyzz(options = {}) {
1137
1681
  const transport = (css) => this.environment.config.command === 'build'
1138
1682
  ? AtRules.rename(AtRules.rename(Namespaces.protect(css), 'font-feature-values', '-zyzz-ffv-000000000'), 'font-palette-values', '-zyzz-fpv-000000000')
1139
1683
  : css;
1140
- if (id === sharedId)
1684
+ if (id === sharedId) {
1685
+ servedShared.set(this.environment, output.graphSharedCss);
1141
1686
  return {
1142
1687
  code: transport(output.sharedCss),
1143
1688
  map: JSON.stringify(output.sharedCssMap),
1144
1689
  };
1145
- return { code: transport(output.css), map: JSON.stringify(output.cssMap) };
1690
+ }
1691
+ entry.css = output.css;
1692
+ return {
1693
+ code: transport(output.css),
1694
+ map: JSON.stringify(output.cssMap),
1695
+ };
1146
1696
  },
1147
1697
  name: 'zyzz',
1148
1698
  resolveId(id) {
@@ -1165,9 +1715,12 @@ export function zyzz(options = {}) {
1165
1715
  state.set(id, entry);
1166
1716
  removed.get(this.environment)?.delete(id);
1167
1717
  }
1718
+ entry.source = code;
1168
1719
  const output = await compile(entry, {
1169
- resolve: (source, importer) => this.resolve(source, importer),
1170
- watch: (file) => this.addWatchFile(file),
1720
+ resolve: this.environment.mode === 'dev'
1721
+ ? resolver(this.environment)
1722
+ : (source, importer) => this.resolve(source, importer),
1723
+ watch: watcher(this),
1171
1724
  asset: async (file) => {
1172
1725
  const reference = this.emitFile({
1173
1726
  type: 'asset',
@@ -1184,53 +1737,23 @@ export function zyzz(options = {}) {
1184
1737
  return { code: output.code, map: JSON.stringify(output.map) };
1185
1738
  // Keep the CSS dependency even when the current graph has no live rules.
1186
1739
  // Later edits can introduce styles without changing this import boundary.
1187
- const parsed = Parser.parseSync('source.tsx', output.code, {
1188
- sourceType: 'module',
1189
- });
1190
- let offset = output.code.startsWith('#!')
1191
- ? output.code.indexOf('\n') + 1
1192
- : 0;
1193
- for (const node of parsed.program.body) {
1194
- if (node.type !== 'ExpressionStatement' || !node.directive)
1195
- break;
1196
- offset = node.end;
1197
- }
1740
+ const offset = prologue(output.code);
1198
1741
  const sharedImport = `\nimport ${JSON.stringify(sharedId)};\n`;
1199
- const map = Mapping.fromMap(JSON.stringify(output.map));
1200
1742
  // The inserted dependency shifts only generated positions after the prologue.
1201
1743
  const before = output.code.slice(0, offset);
1202
1744
  const insertionLine = before.split('\n').length;
1203
1745
  const insertionColumn = before.length - (before.lastIndexOf('\n') + 1);
1204
- const shifted = new Mapping.GenMapping();
1205
- for (const mapping of Mapping.allMappings(map)) {
1206
- const generated = { ...mapping.generated };
1207
- if (generated.line > insertionLine ||
1208
- (generated.line === insertionLine &&
1209
- generated.column >= insertionColumn)) {
1210
- if (generated.line === insertionLine)
1211
- generated.column -= insertionColumn;
1212
- generated.line += 2;
1213
- }
1214
- if (mapping.source !== undefined && mapping.original !== undefined) {
1215
- const location = {
1216
- generated,
1217
- original: mapping.original,
1218
- source: mapping.source,
1219
- };
1220
- if (mapping.name === undefined)
1221
- Mapping.addMapping(shifted, location);
1222
- else
1223
- Mapping.addMapping(shifted, { ...location, name: mapping.name });
1224
- }
1225
- else
1226
- Mapping.addMapping(shifted, { generated });
1227
- }
1228
- for (const [index, source] of output.map.sources.entries())
1229
- if (source !== null)
1230
- Mapping.setSourceContent(shifted, source, output.map.sourcesContent?.[index] ?? null);
1746
+ // A build compiles a fixed project, so an empty module sheet stays empty. Importing it
1747
+ // would link an empty stylesheet from every chunk that holds no styled module.
1748
+ const stylesheetImport = this.environment.mode === 'build' && !output.css.trim()
1749
+ ? ''
1750
+ : `\nimport ${JSON.stringify(cssId(id))};`;
1231
1751
  return {
1232
- code: `${before}${sharedImport}${output.code.slice(offset)}\nimport ${JSON.stringify(cssId(id))};`,
1233
- map: JSON.stringify(Mapping.toEncodedMap(shifted)),
1752
+ code: `${before}${sharedImport}${output.code.slice(offset)}${stylesheetImport}`,
1753
+ map: JSON.stringify({
1754
+ ...output.map,
1755
+ mappings: insert(output.map.mappings, insertionLine - 1, insertionColumn),
1756
+ }),
1234
1757
  };
1235
1758
  },
1236
1759
  transformIndexHtml: {
@@ -1246,7 +1769,7 @@ export function zyzz(options = {}) {
1246
1769
  return entryCatalogs(context.bundle, context.chunk);
1247
1770
  const state = await initializations(context.server);
1248
1771
  const environment = context.server.environments.client;
1249
- return await documentCatalogs(html, context.filename, (source, importer) => environment.pluginContainer.resolveId(source, importer), state);
1772
+ return await documentCatalogs(html, context.filename, resolver(environment), state);
1250
1773
  })();
1251
1774
  // Saved preferences apply before any other script or visible content.
1252
1775
  return Catalogs.scripts(configurations).map((children) => ({
@@ -1258,7 +1781,130 @@ export function zyzz(options = {}) {
1258
1781
  },
1259
1782
  };
1260
1783
  }
1784
+ /**
1785
+ * The end of a module's hashbang and directive prologue, where an added import keeps directives
1786
+ * first. Source without a leading string literal skips parsing.
1787
+ */
1788
+ function prologue(code) {
1789
+ let index = code.startsWith('#!') ? code.indexOf('\n') + 1 : 0;
1790
+ const start = index;
1791
+ for (;;) {
1792
+ while (index < code.length && /\s/.test(code[index]))
1793
+ index++;
1794
+ if (code.startsWith('//', index)) {
1795
+ const end = code.indexOf('\n', index);
1796
+ index = end === -1 ? code.length : end;
1797
+ }
1798
+ else if (code.startsWith('/*', index)) {
1799
+ const end = code.indexOf('*/', index + 2);
1800
+ index = end === -1 ? code.length : end + 2;
1801
+ }
1802
+ else
1803
+ break;
1804
+ }
1805
+ if (code[index] !== "'" && code[index] !== '"')
1806
+ return start;
1807
+ let offset = start;
1808
+ const { program } = Parser.parseSync('source.tsx', code, {
1809
+ sourceType: 'module',
1810
+ });
1811
+ for (const node of program.body) {
1812
+ if (node.type !== 'ExpressionStatement' || !node.directive)
1813
+ break;
1814
+ offset = node.end;
1815
+ }
1816
+ return offset;
1817
+ }
1818
+ /**
1819
+ * Moves encoded mappings for two lines inserted at a generated position. Segments at or past
1820
+ * `column` on `line` start the line after the insertion, and every later line moves down by two.
1821
+ * Other fields stay relative to the segment before them, which keeps its order.
1822
+ */
1823
+ function insert(mappings, line, column) {
1824
+ const lines = mappings.split(';');
1825
+ const text = lines[line];
1826
+ if (text === undefined)
1827
+ return mappings;
1828
+ let generated = 0;
1829
+ let split = -1;
1830
+ let position = 0;
1831
+ const segments = text ? text.split(',') : [];
1832
+ for (const [index, segment] of segments.entries()) {
1833
+ const [delta] = Vlq.decode(segment);
1834
+ generated += delta;
1835
+ if (generated >= column) {
1836
+ split = index;
1837
+ position = generated;
1838
+ break;
1839
+ }
1840
+ }
1841
+ if (split === -1) {
1842
+ lines.splice(line + 1, 0, '', '');
1843
+ return lines.join(';');
1844
+ }
1845
+ const moved = segments.slice(split);
1846
+ const fields = Vlq.decode(moved[0]);
1847
+ fields[0] = position - column;
1848
+ moved[0] = Vlq.encode(fields);
1849
+ lines.splice(line, 1, segments.slice(0, split).join(','), '', moved.join(','));
1850
+ return lines.join(';');
1851
+ }
1852
+ /** Whether two import resolution records hold the same entries. */
1853
+ function same(left, right) {
1854
+ if (!left)
1855
+ return false;
1856
+ const keys = Object.keys(right);
1857
+ if (Object.keys(left).length !== keys.length)
1858
+ return false;
1859
+ return keys.every((key) => left[key] === right[key]);
1860
+ }
1861
+ /**
1862
+ * Whether `zyzz/runtime` resolves inside node_modules from the Vite root. Vite
1863
+ * optimizes a discovered import only there, so linked and aliased copies stay
1864
+ * source modules beside the other entrypoints.
1865
+ */
1866
+ function installed(config) {
1867
+ const aliases = config.resolve?.alias ?? {};
1868
+ const entries = Array.isArray(aliases)
1869
+ ? aliases
1870
+ : Object.entries(aliases).map(([find, replacement]) => ({
1871
+ find,
1872
+ replacement,
1873
+ }));
1874
+ // Matches Vite's alias semantics: a string entry covers its subpaths.
1875
+ if (entries.some((entry) => typeof entry.find === 'string'
1876
+ ? runtime === entry.find || runtime.startsWith(`${entry.find}/`)
1877
+ : entry.find.test(runtime)))
1878
+ return false;
1879
+ try {
1880
+ const require = createRequire(Path.join(Path.resolve(config.root ?? ''), 'package.json'));
1881
+ // Vite keeps a symlinked package at its node_modules path, which Node dereferences.
1882
+ if (config.resolve?.preserveSymlinks)
1883
+ return (require.resolve.paths('zyzz') ?? []).some((directory) => existsSync(Path.join(directory, 'zyzz/package.json')));
1884
+ return require.resolve(runtime).split(Path.sep).includes('node_modules');
1885
+ }
1886
+ catch {
1887
+ return false;
1888
+ }
1889
+ }
1890
+ const scan = { scan: true };
1891
+ /**
1892
+ * Resolves imports read from source before other plugins transform their
1893
+ * importers. Marking them as scans, as Vite's dependency scanner does, keeps the
1894
+ * optimizer from discovering their dependencies and lets plugins tell them apart
1895
+ * from imports that reach the module graph.
1896
+ */
1897
+ function resolver(environment) {
1898
+ return (source, importer) => environment.pluginContainer.resolveId(source, importer, scan);
1899
+ }
1900
+ // Vite and Vitest load their own configuration modules, which application code never imports.
1901
+ const configFiles = [
1902
+ '**/vite.config.*',
1903
+ '**/vitest.config.*',
1904
+ '**/vitest.workspace.*',
1905
+ ];
1261
1906
  const prefix = '\0zyzz:';
1907
+ const runtime = 'zyzz/runtime';
1262
1908
  const sharedId = `${prefix}shared.css`;
1263
1909
  function cssId(file) {
1264
1910
  return `${prefix}${encodeURIComponent(file)}.css`;