zyzz 0.0.28 → 0.0.30

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 (302) 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 -1267
  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 -961
  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/Literal.d.ts +4 -1
  51. package/dist/internal/Literal.d.ts.map +1 -1
  52. package/dist/internal/Literal.js +4 -2
  53. package/dist/internal/Literal.js.map +1 -1
  54. package/dist/internal/Token.d.ts +2 -1
  55. package/dist/internal/Token.d.ts.map +1 -1
  56. package/dist/internal/Token.js +2 -2
  57. package/dist/internal/Token.js.map +1 -1
  58. package/dist/internal/Vlq.d.ts +14 -0
  59. package/dist/internal/Vlq.d.ts.map +1 -0
  60. package/dist/internal/Vlq.js +47 -0
  61. package/dist/internal/Vlq.js.map +1 -0
  62. package/dist/internal/Wasm.d.ts +22 -0
  63. package/dist/internal/Wasm.d.ts.map +1 -0
  64. package/dist/internal/Wasm.js +83 -0
  65. package/dist/internal/Wasm.js.map +1 -0
  66. package/dist/metro/Graph.d.ts +0 -1
  67. package/dist/metro/Graph.d.ts.map +1 -1
  68. package/dist/metro/transformer.d.ts.map +1 -1
  69. package/dist/metro/transformer.js +0 -1
  70. package/dist/metro/transformer.js.map +1 -1
  71. package/dist/next/internal/Project.d.ts.map +1 -1
  72. package/dist/next/internal/Project.js +2 -5
  73. package/dist/next/internal/Project.js.map +1 -1
  74. package/dist/node/Host.d.ts.map +1 -1
  75. package/dist/node/Host.js +0 -2
  76. package/dist/node/Host.js.map +1 -1
  77. package/dist/node/internal/Contracts.d.ts.map +1 -1
  78. package/dist/node/internal/Contracts.js +3 -7
  79. package/dist/node/internal/Contracts.js.map +1 -1
  80. package/dist/node/internal/Snapshot.d.ts +0 -1
  81. package/dist/node/internal/Snapshot.d.ts.map +1 -1
  82. package/dist/node/internal/Snapshot.js +0 -6
  83. package/dist/node/internal/Snapshot.js.map +1 -1
  84. package/dist/runtime/Composition.d.ts +2 -3
  85. package/dist/runtime/Composition.d.ts.map +1 -1
  86. package/dist/runtime/Composition.js +51 -30
  87. package/dist/runtime/Composition.js.map +1 -1
  88. package/dist/runtime/Recipe.d.ts.map +1 -1
  89. package/dist/runtime/Recipe.js +16 -12
  90. package/dist/runtime/Recipe.js.map +1 -1
  91. package/dist/unplugin/index.d.ts.map +1 -1
  92. package/dist/unplugin/index.js +3 -6
  93. package/dist/unplugin/index.js.map +1 -1
  94. package/dist/vite/index.d.ts.map +1 -1
  95. package/dist/vite/index.js +696 -201
  96. package/dist/vite/index.js.map +1 -1
  97. package/dist/web/Css.d.ts +8 -3
  98. package/dist/web/Css.d.ts.map +1 -1
  99. package/dist/web/Css.js +5 -721
  100. package/dist/web/Css.js.map +1 -1
  101. package/dist/web/internal/Block.d.ts +0 -2
  102. package/dist/web/internal/Block.d.ts.map +1 -1
  103. package/dist/web/internal/Block.js +1 -12
  104. package/dist/web/internal/Block.js.map +1 -1
  105. package/dist/web/internal/ClassName.d.ts +23 -18
  106. package/dist/web/internal/ClassName.d.ts.map +1 -1
  107. package/dist/web/internal/ClassName.js +26 -55
  108. package/dist/web/internal/ClassName.js.map +1 -1
  109. package/dist/web/internal/Contributions.d.ts +3 -5
  110. package/dist/web/internal/Contributions.d.ts.map +1 -1
  111. package/dist/web/internal/Contributions.js +0 -45
  112. package/dist/web/internal/Contributions.js.map +1 -1
  113. package/dist/web/internal/Engine.browser.d.ts +8 -0
  114. package/dist/web/internal/Engine.browser.d.ts.map +1 -0
  115. package/dist/web/internal/Engine.browser.js +42 -0
  116. package/dist/web/internal/Engine.browser.js.map +1 -0
  117. package/dist/web/internal/Engine.d.ts +9 -0
  118. package/dist/web/internal/Engine.d.ts.map +1 -0
  119. package/dist/web/internal/Engine.js +14 -0
  120. package/dist/web/internal/Engine.js.map +1 -0
  121. package/dist/web/internal/Themes.d.ts +1 -9
  122. package/dist/web/internal/Themes.d.ts.map +1 -1
  123. package/dist/web/internal/Themes.js +3 -221
  124. package/dist/web/internal/Themes.js.map +1 -1
  125. package/dist/web/internal/Wasm.generated.d.ts +4 -0
  126. package/dist/web/internal/Wasm.generated.d.ts.map +1 -0
  127. package/dist/web/internal/Wasm.generated.js +4 -0
  128. package/dist/web/internal/Wasm.generated.js.map +1 -0
  129. package/dist/web/internal/Wire.d.ts +19 -0
  130. package/dist/web/internal/Wire.d.ts.map +1 -0
  131. package/dist/web/internal/Wire.js +387 -0
  132. package/dist/web/internal/Wire.js.map +1 -0
  133. package/package.json +20 -1
  134. package/src/babel/index.ts +2 -5
  135. package/src/babel/internal/Compilation.ts +0 -1
  136. package/src/compiler/Graph.ts +89 -1930
  137. package/src/compiler/Native.ts +18 -871
  138. package/src/compiler/Source.ts +144 -1803
  139. package/src/compiler/Transform.ts +16 -1315
  140. package/src/compiler/internal/AtRules.ts +27 -115
  141. package/src/compiler/internal/Context.ts +11 -0
  142. package/src/compiler/internal/Results.ts +406 -0
  143. package/src/compiler/internal/Syntax.ts +0 -3
  144. package/src/compiler/internal/Wire.ts +1045 -0
  145. package/src/internal/Addon.ts +118 -0
  146. package/src/internal/Literal.ts +4 -2
  147. package/src/internal/Token.ts +2 -2
  148. package/src/internal/Vlq.ts +59 -0
  149. package/src/internal/Wasm.ts +111 -0
  150. package/src/metro/transformer.ts +0 -1
  151. package/src/next/internal/Project.ts +2 -9
  152. package/src/node/Host.ts +0 -2
  153. package/src/node/internal/Contracts.ts +3 -7
  154. package/src/node/internal/Snapshot.ts +0 -8
  155. package/src/runtime/Composition.ts +48 -25
  156. package/src/runtime/Recipe.ts +21 -14
  157. package/src/unplugin/index.ts +6 -6
  158. package/src/vite/index.ts +922 -221
  159. package/src/web/Css.ts +12 -908
  160. package/src/web/internal/Block.ts +0 -14
  161. package/src/web/internal/ClassName.ts +52 -85
  162. package/src/web/internal/Contributions.ts +3 -59
  163. package/src/web/internal/Engine.browser.ts +51 -0
  164. package/src/web/internal/Engine.ts +17 -0
  165. package/src/web/internal/Themes.ts +3 -279
  166. package/src/web/internal/Wasm.generated.ts +3 -0
  167. package/src/web/internal/Wire.ts +466 -0
  168. package/src/web/internal/wasm.d.ts +9 -0
  169. package/dist/compiler/internal/Applications.d.ts +0 -55
  170. package/dist/compiler/internal/Applications.d.ts.map +0 -1
  171. package/dist/compiler/internal/Applications.js +0 -393
  172. package/dist/compiler/internal/Applications.js.map +0 -1
  173. package/dist/compiler/internal/ColorProfiles.d.ts +0 -3
  174. package/dist/compiler/internal/ColorProfiles.d.ts.map +0 -1
  175. package/dist/compiler/internal/ColorProfiles.js +0 -84
  176. package/dist/compiler/internal/ColorProfiles.js.map +0 -1
  177. package/dist/compiler/internal/CompositionBindings.d.ts +0 -16
  178. package/dist/compiler/internal/CompositionBindings.d.ts.map +0 -1
  179. package/dist/compiler/internal/CompositionBindings.js +0 -89
  180. package/dist/compiler/internal/CompositionBindings.js.map +0 -1
  181. package/dist/compiler/internal/Compositions.d.ts +0 -37
  182. package/dist/compiler/internal/Compositions.d.ts.map +0 -1
  183. package/dist/compiler/internal/Compositions.js +0 -494
  184. package/dist/compiler/internal/Compositions.js.map +0 -1
  185. package/dist/compiler/internal/Configurations.d.ts +0 -26
  186. package/dist/compiler/internal/Configurations.d.ts.map +0 -1
  187. package/dist/compiler/internal/Configurations.js +0 -259
  188. package/dist/compiler/internal/Configurations.js.map +0 -1
  189. package/dist/compiler/internal/Contract.d.ts +0 -30
  190. package/dist/compiler/internal/Contract.d.ts.map +0 -1
  191. package/dist/compiler/internal/Contract.js +0 -753
  192. package/dist/compiler/internal/Contract.js.map +0 -1
  193. package/dist/compiler/internal/Contributions.d.ts +0 -54
  194. package/dist/compiler/internal/Contributions.d.ts.map +0 -1
  195. package/dist/compiler/internal/Contributions.js +0 -1151
  196. package/dist/compiler/internal/Contributions.js.map +0 -1
  197. package/dist/compiler/internal/Descriptors.d.ts +0 -15
  198. package/dist/compiler/internal/Descriptors.d.ts.map +0 -1
  199. package/dist/compiler/internal/Descriptors.js +0 -192
  200. package/dist/compiler/internal/Descriptors.js.map +0 -1
  201. package/dist/compiler/internal/Dynamic.d.ts +0 -14
  202. package/dist/compiler/internal/Dynamic.d.ts.map +0 -1
  203. package/dist/compiler/internal/Dynamic.js +0 -175
  204. package/dist/compiler/internal/Dynamic.js.map +0 -1
  205. package/dist/compiler/internal/Expression.d.ts +0 -19
  206. package/dist/compiler/internal/Expression.d.ts.map +0 -1
  207. package/dist/compiler/internal/Expression.js +0 -164
  208. package/dist/compiler/internal/Expression.js.map +0 -1
  209. package/dist/compiler/internal/Functions.d.ts +0 -5
  210. package/dist/compiler/internal/Functions.d.ts.map +0 -1
  211. package/dist/compiler/internal/Functions.js +0 -191
  212. package/dist/compiler/internal/Functions.js.map +0 -1
  213. package/dist/compiler/internal/Keyframes.d.ts +0 -5
  214. package/dist/compiler/internal/Keyframes.d.ts.map +0 -1
  215. package/dist/compiler/internal/Keyframes.js +0 -51
  216. package/dist/compiler/internal/Keyframes.js.map +0 -1
  217. package/dist/compiler/internal/Names.d.ts +0 -5
  218. package/dist/compiler/internal/Names.d.ts.map +0 -1
  219. package/dist/compiler/internal/Names.js +0 -37
  220. package/dist/compiler/internal/Names.js.map +0 -1
  221. package/dist/compiler/internal/NativeBindings.d.ts +0 -72
  222. package/dist/compiler/internal/NativeBindings.d.ts.map +0 -1
  223. package/dist/compiler/internal/NativeBindings.js +0 -171
  224. package/dist/compiler/internal/NativeBindings.js.map +0 -1
  225. package/dist/compiler/internal/NativeMedia.d.ts +0 -14
  226. package/dist/compiler/internal/NativeMedia.d.ts.map +0 -1
  227. package/dist/compiler/internal/NativeMedia.js +0 -287
  228. package/dist/compiler/internal/NativeMedia.js.map +0 -1
  229. package/dist/compiler/internal/NativeVars.d.ts +0 -28
  230. package/dist/compiler/internal/NativeVars.d.ts.map +0 -1
  231. package/dist/compiler/internal/NativeVars.js +0 -160
  232. package/dist/compiler/internal/NativeVars.js.map +0 -1
  233. package/dist/compiler/internal/PackedStyles.d.ts +0 -37
  234. package/dist/compiler/internal/PackedStyles.d.ts.map +0 -1
  235. package/dist/compiler/internal/PackedStyles.js +0 -344
  236. package/dist/compiler/internal/PackedStyles.js.map +0 -1
  237. package/dist/compiler/internal/Properties.d.ts +0 -5
  238. package/dist/compiler/internal/Properties.d.ts.map +0 -1
  239. package/dist/compiler/internal/Properties.js +0 -82
  240. package/dist/compiler/internal/Properties.js.map +0 -1
  241. package/dist/compiler/internal/RecipeConditions.d.ts +0 -41
  242. package/dist/compiler/internal/RecipeConditions.d.ts.map +0 -1
  243. package/dist/compiler/internal/RecipeConditions.js +0 -82
  244. package/dist/compiler/internal/RecipeConditions.js.map +0 -1
  245. package/dist/compiler/internal/RecipePayloads.d.ts +0 -31
  246. package/dist/compiler/internal/RecipePayloads.d.ts.map +0 -1
  247. package/dist/compiler/internal/RecipePayloads.js +0 -99
  248. package/dist/compiler/internal/RecipePayloads.js.map +0 -1
  249. package/dist/compiler/internal/Recipes.d.ts +0 -32
  250. package/dist/compiler/internal/Recipes.d.ts.map +0 -1
  251. package/dist/compiler/internal/Recipes.js +0 -339
  252. package/dist/compiler/internal/Recipes.js.map +0 -1
  253. package/dist/compiler/internal/Selectors.d.ts +0 -13
  254. package/dist/compiler/internal/Selectors.d.ts.map +0 -1
  255. package/dist/compiler/internal/Selectors.js +0 -280
  256. package/dist/compiler/internal/Selectors.js.map +0 -1
  257. package/dist/compiler/internal/Static.d.ts +0 -39
  258. package/dist/compiler/internal/Static.d.ts.map +0 -1
  259. package/dist/compiler/internal/Static.js +0 -534
  260. package/dist/compiler/internal/Static.js.map +0 -1
  261. package/dist/compiler/internal/Themes.d.ts +0 -190
  262. package/dist/compiler/internal/Themes.d.ts.map +0 -1
  263. package/dist/compiler/internal/Themes.js +0 -1577
  264. package/dist/compiler/internal/Themes.js.map +0 -1
  265. package/dist/compiler/internal/Vars.d.ts +0 -33
  266. package/dist/compiler/internal/Vars.d.ts.map +0 -1
  267. package/dist/compiler/internal/Vars.js +0 -387
  268. package/dist/compiler/internal/Vars.js.map +0 -1
  269. package/dist/compiler/internal/ViewTransitions.d.ts +0 -3
  270. package/dist/compiler/internal/ViewTransitions.d.ts.map +0 -1
  271. package/dist/compiler/internal/ViewTransitions.js +0 -68
  272. package/dist/compiler/internal/ViewTransitions.js.map +0 -1
  273. package/dist/internal/Cascade.d.ts +0 -74
  274. package/dist/internal/Cascade.d.ts.map +0 -1
  275. package/dist/internal/Cascade.js +0 -210
  276. package/dist/internal/Cascade.js.map +0 -1
  277. package/src/compiler/internal/Applications.ts +0 -610
  278. package/src/compiler/internal/ColorProfiles.ts +0 -93
  279. package/src/compiler/internal/CompositionBindings.ts +0 -120
  280. package/src/compiler/internal/Compositions.ts +0 -678
  281. package/src/compiler/internal/Configurations.ts +0 -349
  282. package/src/compiler/internal/Contract.ts +0 -1028
  283. package/src/compiler/internal/Contributions.ts +0 -1561
  284. package/src/compiler/internal/Descriptors.ts +0 -242
  285. package/src/compiler/internal/Dynamic.ts +0 -275
  286. package/src/compiler/internal/Expression.ts +0 -218
  287. package/src/compiler/internal/Functions.ts +0 -206
  288. package/src/compiler/internal/Keyframes.ts +0 -62
  289. package/src/compiler/internal/Names.ts +0 -48
  290. package/src/compiler/internal/NativeBindings.ts +0 -235
  291. package/src/compiler/internal/NativeMedia.ts +0 -342
  292. package/src/compiler/internal/NativeVars.ts +0 -231
  293. package/src/compiler/internal/PackedStyles.ts +0 -473
  294. package/src/compiler/internal/Properties.ts +0 -121
  295. package/src/compiler/internal/RecipeConditions.ts +0 -139
  296. package/src/compiler/internal/RecipePayloads.ts +0 -132
  297. package/src/compiler/internal/Recipes.ts +0 -531
  298. package/src/compiler/internal/Selectors.ts +0 -353
  299. package/src/compiler/internal/Static.ts +0 -784
  300. package/src/compiler/internal/Themes.ts +0 -2398
  301. package/src/compiler/internal/Vars.ts +0 -516
  302. package/src/compiler/internal/ViewTransitions.ts +0 -82
@@ -5,6 +5,7 @@
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';
@@ -13,11 +14,11 @@ import { existsSync } from 'node:fs';
13
14
  import * as Fs from 'node:fs/promises';
14
15
  import * as Path from 'node:path';
15
16
  import * as Reset from '../node/Reset.js';
17
+ import * as Vlq from '../internal/Vlq.js';
16
18
  import { createRequire, isBuiltin } from 'node:module';
17
19
  import * as Parser from 'oxc-parser';
18
20
  import * as Walker from 'oxc-walker';
19
21
  import * as Snapshot from '../node/internal/Snapshot.js';
20
- import * as Syntax from '../compiler/internal/Syntax.js';
21
22
  import * as Scope from '../compiler/internal/Scope.js';
22
23
  import * as Graph from '../compiler/Graph.js';
23
24
  import * as Source from '../compiler/Source.js';
@@ -46,6 +47,21 @@ export function zyzz(options = {}) {
46
47
  const reads = new WeakMap();
47
48
  const lookups = new WeakMap();
48
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();
49
65
  const packages = Contracts.create();
50
66
  function compiler(environment) {
51
67
  let value = compilers.get(environment);
@@ -55,6 +71,144 @@ export function zyzz(options = {}) {
55
71
  }
56
72
  return value;
57
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
+ }
58
212
  const discoveries = new WeakMap();
59
213
  const contributionFiles = new WeakMap();
60
214
  const assets = new Map();
@@ -65,6 +219,8 @@ export function zyzz(options = {}) {
65
219
  const sourceEntrypoints = new Set();
66
220
  const identities = new Map();
67
221
  const exclusions = new Map();
222
+ // Every compile checks each import against the source directories.
223
+ const roots = new Map();
68
224
  const lazyImports = new WeakMap();
69
225
  let directories;
70
226
  let filter;
@@ -146,13 +302,17 @@ export function zyzz(options = {}) {
146
302
  return value;
147
303
  }
148
304
  function sourceRoot(file) {
149
- return directories.find((directory) => {
305
+ if (roots.has(file))
306
+ return roots.get(file);
307
+ const root = directories.find((directory) => {
150
308
  const relative = Path.relative(directory, file);
151
309
  return (relative !== '..' &&
152
310
  !relative.startsWith(`..${Path.sep}`) &&
153
311
  !Path.isAbsolute(relative) &&
154
312
  !relative.split(Path.sep).includes('node_modules'));
155
313
  });
314
+ roots.set(file, root);
315
+ return root;
156
316
  }
157
317
  function discover(environment, host) {
158
318
  let pending = discoveries.get(environment);
@@ -293,7 +453,37 @@ export function zyzz(options = {}) {
293
453
  }
294
454
  async function updateDiscovery(environment, file, event) {
295
455
  reads.delete(environment);
296
- lookups.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
+ }
297
487
  if (event === 'delete') {
298
488
  if (entries(environment).has(file)) {
299
489
  let files = removed.get(environment);
@@ -323,10 +513,52 @@ export function zyzz(options = {}) {
323
513
  }
324
514
  }
325
515
  async function compile(entry, host, code, allSources = false, everything = false) {
326
- // Every compile visits the discovered project, so modules share lookups
327
- // until the watcher reports a change. Full-project compiles look up afresh.
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.
328
556
  const cache = (() => {
329
- const fresh = { contracts: new Map(), resolutions: new Map() };
557
+ const fresh = {
558
+ contracts: new Map(),
559
+ read: new Map(),
560
+ resolutions: new Map(),
561
+ };
330
562
  if (everything)
331
563
  return fresh;
332
564
  let value = lookups.get(entry.environment);
@@ -336,6 +568,8 @@ export function zyzz(options = {}) {
336
568
  }
337
569
  return value;
338
570
  })();
571
+ // Full-project compiles walk afresh, as they look up afresh.
572
+ const memos = everything ? undefined : importEdges(entry.environment);
339
573
  async function resolve(source, importer) {
340
574
  if (isBuiltin(source))
341
575
  return { id: source, external: true };
@@ -350,15 +584,33 @@ export function zyzz(options = {}) {
350
584
  if (!resolved)
351
585
  return resolved;
352
586
  if (entry.environment.mode === 'dev') {
353
- const optimized = Object.values({
354
- ...entry.environment.depsOptimizer?.metadata.optimized,
355
- ...entry.environment.depsOptimizer?.metadata.discovered,
356
- }).find((item) => item.file === resolved.id.split('?')[0]);
357
- if (optimized?.src)
358
- return { ...resolved, id: optimized.src };
587
+ const source = optimized().get(resolved.id.split('?')[0]);
588
+ if (source)
589
+ return { ...resolved, id: source };
359
590
  }
360
591
  return { ...resolved, id: normalize(resolved.id) };
361
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
+ }
362
614
  const imports = Object.create(null);
363
615
  const contracts = Object.create(null);
364
616
  const lazy = Object.create(null);
@@ -381,7 +633,10 @@ export function zyzz(options = {}) {
381
633
  required,
382
634
  resolve: async (specifier, importer) => (await resolve(specifier, importer))?.id.split(/[?#]/)[0],
383
635
  })
384
- .then((contract) => ({ contract, files: read }));
636
+ .then((contract) => {
637
+ cache.read.set(key, read);
638
+ return { contract, files: read };
639
+ });
385
640
  cache.contracts.set(key, pending);
386
641
  pending.catch(() => cache.contracts.delete(key));
387
642
  }
@@ -405,14 +660,37 @@ export function zyzz(options = {}) {
405
660
  cached = new Map();
406
661
  reads.set(entry.environment, cached);
407
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.
408
666
  // Full-project compilation must not reuse reads from concurrent module transforms.
409
667
  const text = source ??
668
+ (everything
669
+ ? undefined
670
+ : entries(entry.environment).get(file)?.source) ??
410
671
  (everything ? undefined : cached.get(file)) ??
411
672
  (await snapshot(entry.environment).read(file));
412
673
  if (!everything)
413
674
  cached.set(file, text);
414
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
+ }
415
692
  const resolutions = Object.create(null);
693
+ const steps = [];
416
694
  imports[id] = resolutions;
417
695
  const parsed = snapshot(entry.environment).parse({
418
696
  moduleId: id,
@@ -461,6 +739,7 @@ export function zyzz(options = {}) {
461
739
  /[/\\]src[/\\]default\.[cm]?ts$/.test(resolved.id)) {
462
740
  sourceEntrypoints.add(normalize(resolved.id));
463
741
  resolutions[specifier] = sourceId(resolved.id);
742
+ steps.push(resolved.id);
464
743
  await visit(resolved.id);
465
744
  continue;
466
745
  }
@@ -471,9 +750,10 @@ export function zyzz(options = {}) {
471
750
  }
472
751
  if (resolved.external || !eligible(resolved.id)) {
473
752
  const physical = resolved.id.split('?')[0].split('#')[0];
474
- const contract = Path.isAbsolute(physical) && /\.[cm]?[jt]sx?$/.test(physical)
475
- ? await readContract(physical)
476
- : 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;
477
757
  if (contract !== undefined) {
478
758
  contracts[resolved.id] = contract;
479
759
  resolutions[specifier] = resolved.id;
@@ -483,6 +763,7 @@ export function zyzz(options = {}) {
483
763
  continue;
484
764
  }
485
765
  resolutions[specifier] = sourceId(resolved.id);
766
+ steps.push(resolved.id);
486
767
  await visit(resolved.id);
487
768
  }
488
769
  // Lazy imports stay outside this compile but scope the documents that load them.
@@ -510,6 +791,7 @@ export function zyzz(options = {}) {
510
791
  targets.add(eligible(resolved.id) ? sourceId(resolved.id) : resolved.id);
511
792
  }
512
793
  lazy[id] = targets;
794
+ memos?.set(file, { lazy: targets, resolutions, steps, text });
513
795
  }
514
796
  await visit(entry.file, code);
515
797
  const connected = new Set(files);
@@ -593,22 +875,9 @@ export function zyzz(options = {}) {
593
875
  if (loaded.has(id))
594
876
  return;
595
877
  loaded.add(id);
596
- const metadata = (() => {
597
- try {
598
- return JSON.parse(contracts[id]);
599
- }
600
- catch (error) {
601
- Graph.compile({ modules: {}, contracts: { [id]: contracts[id] } });
602
- throw error;
603
- }
604
- })();
605
- if (!Array.isArray(metadata?.stylesheets))
606
- return;
607
- for (const section of metadata.stylesheets) {
608
- if (!Array.isArray(section?.dependency))
609
- continue;
878
+ for (const dependency of stylesheetDependencies(id, contracts[id])) {
610
879
  let owner = id;
611
- for (const specifier of section.dependency) {
880
+ for (const specifier of dependency) {
612
881
  if (typeof specifier !== 'string' ||
613
882
  !specifier ||
614
883
  specifier.includes('\0'))
@@ -639,9 +908,9 @@ export function zyzz(options = {}) {
639
908
  ...Object.values(resolutions).filter((target) => target !== null),
640
909
  ...(lazy[id] ?? []),
641
910
  ]));
642
- const result = entry.compiler.compile({
911
+ const input = {
912
+ [Results.lean]: true,
643
913
  [ThemeRules.shared]: native ? undefined : 'all',
644
- [Syntax.cache]: snapshot(entry.environment).programs(modules),
645
914
  compiler: options.compiler,
646
915
  reset: options.reset ? Reset.read() : undefined,
647
916
  native,
@@ -649,7 +918,10 @@ export function zyzz(options = {}) {
649
918
  development: entry.environment.mode !== 'build',
650
919
  imports,
651
920
  modules,
652
- });
921
+ };
922
+ const result = entry.environment.mode === 'dev' && !native && !everything
923
+ ? compileProject(entry.environment, input, sourceId(entry.file))
924
+ : entry.compiler.compile(input);
653
925
  if (native) {
654
926
  const output = result.modules[sourceId(entry.file)];
655
927
  entry.files = files;
@@ -662,6 +934,7 @@ export function zyzz(options = {}) {
662
934
  configurations: new Map(),
663
935
  connections: staged,
664
936
  css: '',
937
+ graphSharedCss: '',
665
938
  sharedCss: '',
666
939
  cssMap: map,
667
940
  sharedCssMap: map,
@@ -689,132 +962,143 @@ export function zyzz(options = {}) {
689
962
  catalogs.delete(key);
690
963
  const configurations = new Map();
691
964
  for (const [id, contract] of Object.entries(compiled))
692
- for (const configuration of Catalogs.read(contract)) {
965
+ for (const configuration of read(contract)) {
693
966
  const key = `${id}\0${configuration.identity}`;
694
967
  catalogs.set(key, configuration);
695
968
  configurations.set(key, configuration);
696
969
  }
697
- const owners = new Map();
698
- for (const id of new Set(Object.values(result.sharedAssetOwners ?? {}))) {
699
- if (!Path.isAbsolute(id))
700
- continue;
701
- let directory = Path.dirname(id.split(/[?#]/)[0]);
702
- for (;;) {
703
- try {
704
- await Fs.access(Path.join(directory, 'package.json'));
705
- owners.set(id, await Fs.realpath(directory));
706
- break;
707
- }
708
- catch (error) {
709
- if (error.code !== 'ENOENT')
710
- 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;
711
992
  }
712
- const parent = Path.dirname(directory);
713
- if (parent === directory)
714
- throw new Error('Packed assets require an owning package.json.');
715
- directory = parent;
716
993
  }
717
- }
718
- const assetUrls = new Map();
719
- for (const [key, target] of Object.entries(result.sharedAssets ?? {})) {
720
- const raw = target.split(/[?#]/)[0];
721
- const identity = result.sharedAssetOwners?.[key];
722
- const path = sourceFile(decodeURIComponent(raw)) ??
723
- (Path.isAbsolute(raw) ? decodeURIComponent(raw) : undefined);
724
- if (!path)
725
- throw new Error('Asset path escapes the Vite graph.');
726
- const asset = await (async () => {
727
- try {
728
- return { file: await Fs.realpath(path), package: false };
729
- }
730
- catch (error) {
731
- if (error.code !== 'ENOENT' ||
732
- !identity ||
733
- !sourceFile(identity))
734
- throw error;
735
- const importer = sourceFile(identity);
736
- const specifier = Path.relative(Path.dirname(importer), path)
737
- .split(Path.sep)
738
- .join('/');
739
- if (specifier.startsWith('.') || Path.isAbsolute(specifier))
740
- throw error;
741
- const resolved = await host.resolve(specifier, importer);
742
- if (!resolved || resolved.external)
743
- throw error;
744
- return {
745
- file: await Fs.realpath(resolved.id.split('?')[0]),
746
- package: true,
747
- };
748
- }
749
- })();
750
- const file = asset.file;
751
- const owner = identity && sourceFile(identity)
752
- ? sourceRoot(sourceFile(identity))
753
- : identity
754
- ? owners.get(identity)
755
- : undefined;
756
- const relative = owner ? Path.relative(owner, file) : '..';
757
- if (!asset.package &&
758
- (relative === '..' ||
759
- relative.startsWith(`..${Path.sep}`) ||
760
- Path.isAbsolute(relative)))
761
- throw new Error('Asset path escapes its owning package.');
762
- host.watch(file);
763
- files.add(file);
764
- let url;
765
- if (asset.package || /[?#%]/.test(file)) {
766
- if (entry.environment.mode === 'build') {
767
- url = await host.asset(file);
768
- if (target.slice(raw.length))
769
- 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
+ }
770
1052
  }
771
1053
  else {
772
- url = `/@zyzz/asset/${Crypto.hash('sha256', file)}/${encodeURIComponent(Path.basename(file))}`;
773
- assets.set(url, file);
774
- 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)}`;
775
1058
  }
1059
+ assetUrls.set(target, url);
776
1060
  }
777
- else {
778
- // Vite reads `/@fs/` URLs with `decodeURI`, which keeps reserved escapes
779
- // such as `%40` in scoped package paths, so those characters stay raw.
780
- const pathname = encodeURI(file.replaceAll('\\', '/'));
781
- url = `/@fs/${pathname}${target.slice(raw.length)}`;
782
- }
783
- assetUrls.set(target, url);
784
- }
785
- const shared = !Object.keys(result.sharedAssets ?? {}).length
786
- ? {
787
- code: new TextEncoder().encode(result.sharedCss ?? ''),
788
- map: new TextEncoder().encode(JSON.stringify(result.sharedCssMap ??
789
- Mapping.toEncodedMap(new Mapping.GenMapping()))),
790
- }
791
- : AtRules.transform({
792
- filename: 'zyzz.shared.css',
793
- code: new TextEncoder().encode(result.sharedCss ?? ''),
794
- sourceMap: true,
795
- inputSourceMap: JSON.stringify(result.sharedCssMap ??
796
- Mapping.toEncodedMap(new Mapping.GenMapping())),
797
- visitor: {
798
- Rule(rule) {
799
- if (rule.type !== 'import')
800
- return;
801
- const target = result.sharedAssets?.[rule.value.url];
802
- if (target)
803
- return AtRules.relocateImport(rule.value, assetUrls.get(target));
804
- },
805
- Url(url) {
806
- const target = result.sharedAssets?.[url.url];
807
- if (!target)
808
- return;
809
- 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
+ },
810
1087
  },
811
- },
812
- });
813
- const sharedMap = JSON.parse(new TextDecoder().decode(shared.map));
814
- 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
+ };
815
1099
  const output = result.modules[sourceId(entry.file)];
816
1100
  entry.files = files;
817
- return {
1101
+ const value = {
818
1102
  imports,
819
1103
  modules: result.modules,
820
1104
  code: output.code,
@@ -822,8 +1106,10 @@ export function zyzz(options = {}) {
822
1106
  configurations,
823
1107
  connections: staged,
824
1108
  css: output.css,
825
- sharedCss: new TextDecoder().decode(shared.code),
826
- 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,
827
1113
  cssMap: {
828
1114
  ...output.cssMap,
829
1115
  sources: output.cssMap.sources.map((source) => source === null ? null : (sourceFile(source) ?? source)),
@@ -833,16 +1119,16 @@ export function zyzz(options = {}) {
833
1119
  sources: output.map.sources.map((source) => source === null ? null : (sourceFile(source) ?? source)),
834
1120
  },
835
1121
  };
1122
+ return value;
836
1123
  }
837
1124
  /** Compiles every discovered source once so index.html can inline each configuration's script before modules load. */
838
1125
  async function initializations(server) {
839
1126
  const environment = server.environments.client;
840
1127
  reads.delete(environment);
841
- lookups.delete(environment);
842
1128
  const host = {
843
1129
  asset: async (file) => file,
844
1130
  resolve: resolver(environment),
845
- watch: (file) => server.watcher.add(file),
1131
+ watch: (file) => watch(server, file),
846
1132
  };
847
1133
  const sources = await discover(environment, host);
848
1134
  let entry = initializers.get(environment);
@@ -851,8 +1137,9 @@ export function zyzz(options = {}) {
851
1137
  const file = sources.keys().next().value;
852
1138
  if (file === undefined)
853
1139
  return { catalogs: new Map(), edges: new Map() };
1140
+ // Whole-project compiles keep their own cache beside the transforms' merged graph.
854
1141
  entry = {
855
- compiler: compiler(environment),
1142
+ compiler: initializers.get(environment)?.compiler ?? Graph.create(),
856
1143
  environment,
857
1144
  file,
858
1145
  files: new Set([file]),
@@ -976,8 +1263,105 @@ export function zyzz(options = {}) {
976
1263
  .filter(([key]) => contracts.has(key.split('\0')[0]))
977
1264
  .map(([, configuration]) => configuration);
978
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
+ }
979
1362
  return {
980
1363
  buildStart() {
1364
+ edgeMemos.delete(this.environment);
981
1365
  reads.delete(this.environment);
982
1366
  lookups.delete(this.environment);
983
1367
  },
@@ -1040,6 +1424,7 @@ export function zyzz(options = {}) {
1040
1424
  },
1041
1425
  },
1042
1426
  configureServer(server) {
1427
+ devServer = server;
1043
1428
  // Directory watches belong to the server, not module import dependencies.
1044
1429
  server.watcher.add(directories.slice(1));
1045
1430
  server.middlewares.use((request, response, next) => {
@@ -1072,6 +1457,7 @@ export function zyzz(options = {}) {
1072
1457
  const patterns = [...configFiles, ...(options.exclude ?? [])];
1073
1458
  filter = createFilter(null, patterns, { resolve: root });
1074
1459
  exclusions.clear();
1460
+ roots.clear();
1075
1461
  if (native)
1076
1462
  return;
1077
1463
  const lightning = (config.css.lightningcss ??= {});
@@ -1134,10 +1520,46 @@ export function zyzz(options = {}) {
1134
1520
  },
1135
1521
  },
1136
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);
1137
1526
  await updateDiscovery(this.environment, file, change.event);
1138
1527
  },
1139
1528
  async hotUpdate({ file, modules, timestamp, type, server }) {
1140
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
+ }
1141
1563
  const previous = [...state.values()].map((entry) => ({
1142
1564
  code: entry.code,
1143
1565
  file: entry.file,
@@ -1151,9 +1573,7 @@ export function zyzz(options = {}) {
1151
1573
  const output = first && type === 'update' && eligible(file)
1152
1574
  ? await compile(first, {
1153
1575
  resolve: resolver(this.environment),
1154
- watch: (file) => {
1155
- server.watcher.add(file);
1156
- },
1576
+ watch: (file) => watch(server, file),
1157
1577
  asset: async (file) => file,
1158
1578
  }, undefined, true, true).catch(() => undefined)
1159
1579
  : undefined;
@@ -1214,19 +1634,40 @@ export function zyzz(options = {}) {
1214
1634
  const file = id === sharedId
1215
1635
  ? undefined
1216
1636
  : decodeURIComponent(id.slice(prefix.length, -4));
1217
- const entry = file === undefined
1637
+ let entry = file === undefined
1218
1638
  ? entries(this.environment).values().next().value
1219
1639
  : entries(this.environment).get(file);
1220
1640
  // A pruned JavaScript module can leave its stylesheet loaded in the browser.
1221
1641
  if (!entry && file && removed.get(this.environment)?.has(file))
1222
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
+ }
1223
1664
  if (!entry)
1224
1665
  throw new Error(`Unknown Zyzz stylesheet: ${file}`);
1225
1666
  const output = await compile(entry, {
1226
1667
  resolve: this.environment.mode === 'dev'
1227
1668
  ? resolver(this.environment)
1228
1669
  : (source, importer) => this.resolve(source, importer),
1229
- watch: (file) => this.addWatchFile(file),
1670
+ watch: watcher(this),
1230
1671
  asset: async (file) => {
1231
1672
  const reference = this.emitFile({
1232
1673
  type: 'asset',
@@ -1240,12 +1681,18 @@ export function zyzz(options = {}) {
1240
1681
  const transport = (css) => this.environment.config.command === 'build'
1241
1682
  ? AtRules.rename(AtRules.rename(Namespaces.protect(css), 'font-feature-values', '-zyzz-ffv-000000000'), 'font-palette-values', '-zyzz-fpv-000000000')
1242
1683
  : css;
1243
- if (id === sharedId)
1684
+ if (id === sharedId) {
1685
+ servedShared.set(this.environment, output.graphSharedCss);
1244
1686
  return {
1245
1687
  code: transport(output.sharedCss),
1246
1688
  map: JSON.stringify(output.sharedCssMap),
1247
1689
  };
1248
- 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
+ };
1249
1696
  },
1250
1697
  name: 'zyzz',
1251
1698
  resolveId(id) {
@@ -1268,11 +1715,12 @@ export function zyzz(options = {}) {
1268
1715
  state.set(id, entry);
1269
1716
  removed.get(this.environment)?.delete(id);
1270
1717
  }
1718
+ entry.source = code;
1271
1719
  const output = await compile(entry, {
1272
1720
  resolve: this.environment.mode === 'dev'
1273
1721
  ? resolver(this.environment)
1274
1722
  : (source, importer) => this.resolve(source, importer),
1275
- watch: (file) => this.addWatchFile(file),
1723
+ watch: watcher(this),
1276
1724
  asset: async (file) => {
1277
1725
  const reference = this.emitFile({
1278
1726
  type: 'asset',
@@ -1289,53 +1737,23 @@ export function zyzz(options = {}) {
1289
1737
  return { code: output.code, map: JSON.stringify(output.map) };
1290
1738
  // Keep the CSS dependency even when the current graph has no live rules.
1291
1739
  // Later edits can introduce styles without changing this import boundary.
1292
- const parsed = Parser.parseSync('source.tsx', output.code, {
1293
- sourceType: 'module',
1294
- });
1295
- let offset = output.code.startsWith('#!')
1296
- ? output.code.indexOf('\n') + 1
1297
- : 0;
1298
- for (const node of parsed.program.body) {
1299
- if (node.type !== 'ExpressionStatement' || !node.directive)
1300
- break;
1301
- offset = node.end;
1302
- }
1740
+ const offset = prologue(output.code);
1303
1741
  const sharedImport = `\nimport ${JSON.stringify(sharedId)};\n`;
1304
- const map = Mapping.fromMap(JSON.stringify(output.map));
1305
1742
  // The inserted dependency shifts only generated positions after the prologue.
1306
1743
  const before = output.code.slice(0, offset);
1307
1744
  const insertionLine = before.split('\n').length;
1308
1745
  const insertionColumn = before.length - (before.lastIndexOf('\n') + 1);
1309
- const shifted = new Mapping.GenMapping();
1310
- for (const mapping of Mapping.allMappings(map)) {
1311
- const generated = { ...mapping.generated };
1312
- if (generated.line > insertionLine ||
1313
- (generated.line === insertionLine &&
1314
- generated.column >= insertionColumn)) {
1315
- if (generated.line === insertionLine)
1316
- generated.column -= insertionColumn;
1317
- generated.line += 2;
1318
- }
1319
- if (mapping.source !== undefined && mapping.original !== undefined) {
1320
- const location = {
1321
- generated,
1322
- original: mapping.original,
1323
- source: mapping.source,
1324
- };
1325
- if (mapping.name === undefined)
1326
- Mapping.addMapping(shifted, location);
1327
- else
1328
- Mapping.addMapping(shifted, { ...location, name: mapping.name });
1329
- }
1330
- else
1331
- Mapping.addMapping(shifted, { generated });
1332
- }
1333
- for (const [index, source] of output.map.sources.entries())
1334
- if (source !== null)
1335
- 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))};`;
1336
1751
  return {
1337
- code: `${before}${sharedImport}${output.code.slice(offset)}\nimport ${JSON.stringify(cssId(id))};`,
1338
- 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
+ }),
1339
1757
  };
1340
1758
  },
1341
1759
  transformIndexHtml: {
@@ -1363,6 +1781,83 @@ export function zyzz(options = {}) {
1363
1781
  },
1364
1782
  };
1365
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
+ }
1366
1861
  /**
1367
1862
  * Whether `zyzz/runtime` resolves inside node_modules from the Vite root. Vite
1368
1863
  * optimizes a discovered import only there, so linked and aliased copies stay