zyzz 0.0.3 → 0.0.5

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 (247) hide show
  1. package/README.md +34 -19
  2. package/dist/Config.d.ts +16 -167
  3. package/dist/Config.d.ts.map +1 -1
  4. package/dist/Config.js +7 -246
  5. package/dist/Config.js.map +1 -1
  6. package/dist/Style.d.ts +15 -12
  7. package/dist/Style.d.ts.map +1 -1
  8. package/dist/Style.js +51 -26
  9. package/dist/Style.js.map +1 -1
  10. package/dist/Vars.d.ts +114 -0
  11. package/dist/Vars.d.ts.map +1 -0
  12. package/dist/Vars.js +42 -0
  13. package/dist/Vars.js.map +1 -0
  14. package/dist/compiler/Graph.js +11 -8
  15. package/dist/compiler/Graph.js.map +1 -1
  16. package/dist/compiler/Native.d.ts +5 -5
  17. package/dist/compiler/Native.d.ts.map +1 -1
  18. package/dist/compiler/Native.js +21 -21
  19. package/dist/compiler/Native.js.map +1 -1
  20. package/dist/compiler/Source.d.ts +8 -6
  21. package/dist/compiler/Source.d.ts.map +1 -1
  22. package/dist/compiler/Source.js +47 -14
  23. package/dist/compiler/Source.js.map +1 -1
  24. package/dist/compiler/Transform.d.ts +1 -1
  25. package/dist/compiler/Transform.d.ts.map +1 -1
  26. package/dist/compiler/Transform.js +58 -22
  27. package/dist/compiler/Transform.js.map +1 -1
  28. package/dist/compiler/internal/Compositions.d.ts +5 -0
  29. package/dist/compiler/internal/Compositions.d.ts.map +1 -1
  30. package/dist/compiler/internal/Compositions.js +22 -3
  31. package/dist/compiler/internal/Compositions.js.map +1 -1
  32. package/dist/compiler/internal/Configurations.d.ts.map +1 -1
  33. package/dist/compiler/internal/Configurations.js +73 -12
  34. package/dist/compiler/internal/Configurations.js.map +1 -1
  35. package/dist/compiler/internal/Contract.d.ts +1 -1
  36. package/dist/compiler/internal/Contract.d.ts.map +1 -1
  37. package/dist/compiler/internal/Contract.js +155 -13
  38. package/dist/compiler/internal/Contract.js.map +1 -1
  39. package/dist/compiler/internal/Contributions.js +1 -1
  40. package/dist/compiler/internal/Contributions.js.map +1 -1
  41. package/dist/compiler/internal/Dynamic.js +1 -1
  42. package/dist/compiler/internal/Dynamic.js.map +1 -1
  43. package/dist/compiler/internal/Expression.d.ts +3 -0
  44. package/dist/compiler/internal/Expression.d.ts.map +1 -1
  45. package/dist/compiler/internal/Expression.js +21 -0
  46. package/dist/compiler/internal/Expression.js.map +1 -1
  47. package/dist/compiler/internal/Identifiers.d.ts +1 -1
  48. package/dist/compiler/internal/Identifiers.d.ts.map +1 -1
  49. package/dist/compiler/internal/Identifiers.js +2 -2
  50. package/dist/compiler/internal/Identifiers.js.map +1 -1
  51. package/dist/compiler/internal/NativeBindings.d.ts +1 -1
  52. package/dist/compiler/internal/NativeBindings.d.ts.map +1 -1
  53. package/dist/compiler/internal/NativeBindings.js +3 -3
  54. package/dist/compiler/internal/NativeBindings.js.map +1 -1
  55. package/dist/compiler/internal/PackedStyles.d.ts +1 -1
  56. package/dist/compiler/internal/PackedStyles.d.ts.map +1 -1
  57. package/dist/compiler/internal/PackedStyles.js +15 -25
  58. package/dist/compiler/internal/PackedStyles.js.map +1 -1
  59. package/dist/compiler/internal/Recipes.js +1 -1
  60. package/dist/compiler/internal/Recipes.js.map +1 -1
  61. package/dist/compiler/internal/Selectors.js +1 -1
  62. package/dist/compiler/internal/Selectors.js.map +1 -1
  63. package/dist/compiler/internal/Themes.d.ts +12 -3
  64. package/dist/compiler/internal/Themes.d.ts.map +1 -1
  65. package/dist/compiler/internal/Themes.js +192 -52
  66. package/dist/compiler/internal/Themes.js.map +1 -1
  67. package/dist/compiler/internal/{Variables.d.ts → Vars.d.ts} +1 -1
  68. package/dist/compiler/internal/Vars.d.ts.map +1 -0
  69. package/dist/compiler/internal/{Variables.js → Vars.js} +3 -3
  70. package/dist/compiler/internal/Vars.js.map +1 -0
  71. package/dist/default.d.ts +147 -2562
  72. package/dist/default.d.ts.map +1 -1
  73. package/dist/default.js +85 -80
  74. package/dist/default.js.map +2 -2
  75. package/dist/default.js.zyzz.json +1 -1
  76. package/dist/esbuild/index.d.ts +3 -0
  77. package/dist/esbuild/index.d.ts.map +1 -0
  78. package/dist/esbuild/index.js +5 -0
  79. package/dist/esbuild/index.js.map +1 -0
  80. package/dist/index.d.ts +4 -4
  81. package/dist/index.d.ts.map +1 -1
  82. package/dist/index.js +4 -4
  83. package/dist/index.js.map +1 -1
  84. package/dist/internal/Authoring.d.ts +1 -1
  85. package/dist/internal/Authoring.d.ts.map +1 -1
  86. package/dist/internal/Authoring.js +4 -4
  87. package/dist/internal/Authoring.js.map +1 -1
  88. package/dist/internal/Binding.d.ts +1 -1
  89. package/dist/internal/Binding.d.ts.map +1 -1
  90. package/dist/internal/Completion.d.ts +19 -0
  91. package/dist/internal/Completion.d.ts.map +1 -0
  92. package/dist/internal/Completion.js +2 -0
  93. package/dist/internal/Completion.js.map +1 -0
  94. package/dist/internal/Condition.d.ts +3 -1
  95. package/dist/internal/Condition.d.ts.map +1 -1
  96. package/dist/internal/Condition.js.map +1 -1
  97. package/dist/internal/Configuration.d.ts +248 -0
  98. package/dist/internal/Configuration.d.ts.map +1 -0
  99. package/dist/internal/Configuration.js +343 -0
  100. package/dist/internal/Configuration.js.map +1 -0
  101. package/dist/{Theme.d.ts → internal/Theme.d.ts} +15 -13
  102. package/dist/internal/Theme.d.ts.map +1 -0
  103. package/dist/{Theme.js → internal/Theme.js} +39 -12
  104. package/dist/internal/Theme.js.map +1 -0
  105. package/dist/internal/Token.d.ts +46 -16
  106. package/dist/internal/Token.d.ts.map +1 -1
  107. package/dist/internal/Token.js +89 -5
  108. package/dist/internal/Token.js.map +1 -1
  109. package/dist/internal/Typography.d.ts +13 -5
  110. package/dist/internal/Typography.d.ts.map +1 -1
  111. package/dist/internal/Typography.js +42 -6
  112. package/dist/internal/Typography.js.map +1 -1
  113. package/dist/internal/Value.d.ts +6 -1
  114. package/dist/internal/Value.d.ts.map +1 -1
  115. package/dist/internal/Value.js.map +1 -1
  116. package/dist/internal/VariableSets.d.ts +20 -0
  117. package/dist/internal/VariableSets.d.ts.map +1 -0
  118. package/dist/internal/VariableSets.js +317 -0
  119. package/dist/internal/VariableSets.js.map +1 -0
  120. package/dist/next/loader.js +15 -5
  121. package/dist/next/loader.js.map +1 -1
  122. package/dist/node/Host.d.ts.map +1 -1
  123. package/dist/node/Host.js +1 -1
  124. package/dist/node/Host.js.map +1 -1
  125. package/dist/react-native/Host.d.ts +2 -2
  126. package/dist/react-native/Host.d.ts.map +1 -1
  127. package/dist/react-native/Host.js +4 -4
  128. package/dist/react-native/Host.js.map +1 -1
  129. package/dist/react-native/StyleSheet.d.ts +9 -8
  130. package/dist/react-native/StyleSheet.d.ts.map +1 -1
  131. package/dist/react-native/StyleSheet.js +29 -23
  132. package/dist/react-native/StyleSheet.js.map +1 -1
  133. package/dist/react-native/Variants.d.ts +3 -3
  134. package/dist/react-native/Variants.d.ts.map +1 -1
  135. package/dist/react-native/Variants.js +2 -2
  136. package/dist/react-native/react.d.ts +1 -1
  137. package/dist/react-native/react.d.ts.map +1 -1
  138. package/dist/react-native/react.js +5 -5
  139. package/dist/react-native/react.js.map +1 -1
  140. package/dist/rollup/index.d.ts +3 -0
  141. package/dist/rollup/index.d.ts.map +1 -0
  142. package/dist/rollup/index.js +5 -0
  143. package/dist/rollup/index.js.map +1 -0
  144. package/dist/runtime/Appearance.d.ts +7 -7
  145. package/dist/runtime/Appearance.d.ts.map +1 -1
  146. package/dist/runtime/Appearance.js +15 -15
  147. package/dist/runtime/Appearance.js.map +1 -1
  148. package/dist/runtime/Dynamic.d.ts.map +1 -1
  149. package/dist/runtime/Dynamic.js +4 -1
  150. package/dist/runtime/Dynamic.js.map +1 -1
  151. package/dist/runtime/NativeContext.d.ts +4 -4
  152. package/dist/runtime/NativeContext.d.ts.map +1 -1
  153. package/dist/runtime/NativeContext.js +5 -5
  154. package/dist/runtime/NativeContext.js.map +1 -1
  155. package/dist/runtime/NativeDynamic.d.ts +1 -1
  156. package/dist/runtime/NativeDynamic.d.ts.map +1 -1
  157. package/dist/runtime/NativeDynamic.js.map +1 -1
  158. package/dist/runtime/Props.d.ts +1 -1
  159. package/dist/runtime/Props.js +3 -3
  160. package/dist/runtime/Props.js.map +1 -1
  161. package/dist/runtime/Selection.d.ts +14 -10
  162. package/dist/runtime/Selection.d.ts.map +1 -1
  163. package/dist/runtime/Selection.js +19 -18
  164. package/dist/runtime/Selection.js.map +1 -1
  165. package/dist/styleFunction.d.ts +14 -14
  166. package/dist/styleFunction.d.ts.map +1 -1
  167. package/dist/styleFunction.js.map +1 -1
  168. package/dist/unplugin/index.d.ts +20 -0
  169. package/dist/unplugin/index.d.ts.map +1 -0
  170. package/dist/unplugin/index.js +498 -0
  171. package/dist/unplugin/index.js.map +1 -0
  172. package/dist/variants.d.ts +3 -3
  173. package/dist/variants.d.ts.map +1 -1
  174. package/dist/variants.js.map +1 -1
  175. package/dist/vite/index.d.ts.map +1 -1
  176. package/dist/vite/index.js +6 -3
  177. package/dist/vite/index.js.map +1 -1
  178. package/dist/web/Css.d.ts +4 -3
  179. package/dist/web/Css.d.ts.map +1 -1
  180. package/dist/web/Css.js +4 -4
  181. package/dist/web/Css.js.map +1 -1
  182. package/dist/web/internal/Themes.d.ts +3 -2
  183. package/dist/web/internal/Themes.d.ts.map +1 -1
  184. package/dist/web/internal/Themes.js +42 -6
  185. package/dist/web/internal/Themes.js.map +1 -1
  186. package/dist/webpack/index.d.ts +3 -0
  187. package/dist/webpack/index.d.ts.map +1 -0
  188. package/dist/webpack/index.js +5 -0
  189. package/dist/webpack/index.js.map +1 -0
  190. package/package.json +23 -2
  191. package/src/Config.ts +43 -685
  192. package/src/Style.ts +97 -34
  193. package/src/Vars.ts +299 -0
  194. package/src/compiler/Graph.ts +14 -9
  195. package/src/compiler/Native.ts +29 -32
  196. package/src/compiler/Source.ts +59 -21
  197. package/src/compiler/Transform.ts +73 -23
  198. package/src/compiler/internal/Compositions.ts +36 -3
  199. package/src/compiler/internal/Configurations.ts +83 -11
  200. package/src/compiler/internal/Contract.ts +207 -15
  201. package/src/compiler/internal/Contributions.ts +1 -1
  202. package/src/compiler/internal/Dynamic.ts +1 -1
  203. package/src/compiler/internal/Expression.ts +26 -0
  204. package/src/compiler/internal/Identifiers.ts +2 -1
  205. package/src/compiler/internal/NativeBindings.ts +3 -3
  206. package/src/compiler/internal/PackedStyles.ts +22 -26
  207. package/src/compiler/internal/Recipes.ts +1 -1
  208. package/src/compiler/internal/Selectors.ts +1 -1
  209. package/src/compiler/internal/Themes.ts +266 -57
  210. package/src/compiler/internal/{Variables.ts → Vars.ts} +2 -2
  211. package/src/default.ts +173 -154
  212. package/src/esbuild/index.ts +5 -0
  213. package/src/index.ts +4 -4
  214. package/src/internal/Authoring.ts +5 -7
  215. package/src/internal/Binding.ts +1 -1
  216. package/src/internal/Completion.ts +42 -0
  217. package/src/internal/Condition.ts +31 -1
  218. package/src/internal/Configuration.ts +970 -0
  219. package/src/{Theme.ts → internal/Theme.ts} +75 -20
  220. package/src/internal/Token.ts +258 -85
  221. package/src/internal/Typography.ts +71 -16
  222. package/src/internal/Value.ts +45 -26
  223. package/src/internal/VariableSets.ts +458 -0
  224. package/src/next/loader.ts +19 -5
  225. package/src/node/Host.ts +1 -2
  226. package/src/react-native/Host.ts +6 -6
  227. package/src/react-native/StyleSheet.ts +49 -42
  228. package/src/react-native/Variants.ts +3 -3
  229. package/src/react-native/react.ts +6 -6
  230. package/src/rollup/index.ts +5 -0
  231. package/src/runtime/Appearance.ts +20 -20
  232. package/src/runtime/Dynamic.ts +4 -1
  233. package/src/runtime/NativeContext.ts +8 -8
  234. package/src/runtime/NativeDynamic.ts +2 -4
  235. package/src/runtime/Props.ts +3 -3
  236. package/src/runtime/Selection.ts +44 -33
  237. package/src/styleFunction.ts +22 -19
  238. package/src/unplugin/index.ts +600 -0
  239. package/src/variants.ts +7 -5
  240. package/src/vite/index.ts +5 -4
  241. package/src/web/Css.ts +10 -7
  242. package/src/web/internal/Themes.ts +52 -9
  243. package/src/webpack/index.ts +5 -0
  244. package/dist/Theme.d.ts.map +0 -1
  245. package/dist/Theme.js.map +0 -1
  246. package/dist/compiler/internal/Variables.d.ts.map +0 -1
  247. package/dist/compiler/internal/Variables.js.map +0 -1
@@ -0,0 +1,600 @@
1
+ /** Connects the static compiler to portable bundler hooks and the Vite adapter. @module */
2
+ import * as Mapping from '@jridgewell/gen-mapping'
3
+ import * as Crypto from 'node:crypto'
4
+ import * as Fs from 'node:fs/promises'
5
+ import * as Path from 'node:path'
6
+ import * as Parser from 'oxc-parser'
7
+ import { createUnplugin } from 'unplugin'
8
+ import type { UnpluginBuildContext } from 'unplugin'
9
+ import * as Graph from '../compiler/Graph.js'
10
+ import * as AtRules from '../compiler/internal/AtRules.js'
11
+ import * as Catalogs from '../compiler/internal/Catalogs.js'
12
+ import * as Contract from '../compiler/internal/Contract.js'
13
+ import * as Vite from '../vite/index.js'
14
+
15
+ /** Portable build configuration. Vite uses the options from `zyzz/vite` instead. */
16
+ export type Options = {
17
+ /** Rewrite authoring calls. Defaults to true. */
18
+ readonly compiler?: boolean | undefined
19
+ /** Source directory scanned for modules and global contributions. Defaults to the working directory. */
20
+ readonly root?: string | undefined
21
+ }
22
+
23
+ const excludedPattern = /\.(?:d|test|test-d|bench|bench-d)\.[cm]?[jt]sx?$/
24
+ const sourcePattern = /\.[cm]?[jt]sx?$/
25
+
26
+ const portable = createUnplugin<Options | undefined, false>(
27
+ (options = {}, meta) => {
28
+ const root = Path.resolve(options.root ?? process.cwd())
29
+ const compiler = Graph.create()
30
+ let graph: Graph.compile.ReturnType | undefined
31
+ let pending: Promise<void> | undefined
32
+ let files = new Set<string>()
33
+ let directories = new Set<string>()
34
+ let sources = new Map<string, string>()
35
+ let assets = new Map<string, Uint8Array>()
36
+ let css = ''
37
+ let cssMap = ''
38
+ let script = ''
39
+
40
+ function identity(file: string) {
41
+ return `app/${Path.relative(root, file).split(Path.sep).join('/')}`
42
+ }
43
+
44
+ function emit(context: UnpluginBuildContext) {
45
+ if (!graph) return
46
+ for (const [fileName, source] of assets)
47
+ context.emitFile({
48
+ type: 'asset',
49
+ fileName,
50
+ source: Uint8Array.from(source),
51
+ })
52
+ context.emitFile({
53
+ type: 'asset',
54
+ fileName: 'zyzz.css',
55
+ source: `${css}/*# sourceMappingURL=zyzz.css.map */\n`,
56
+ })
57
+ context.emitFile({
58
+ type: 'asset',
59
+ fileName: 'zyzz.css.map',
60
+ source: cssMap,
61
+ })
62
+ context.emitFile({
63
+ type: 'asset',
64
+ fileName: 'zyzz.js',
65
+ source: script || '/* No saved theme selections. */\n',
66
+ })
67
+ }
68
+
69
+ function transform(code: string, id: string) {
70
+ const output = graph?.modules[identity(id)]
71
+ if (!output) return
72
+ if (code !== sources.get(id))
73
+ throw new Error(`Zyzz must run before source transforms: ${id}`)
74
+ return {
75
+ code: output.code,
76
+ map: JSON.stringify({
77
+ ...output.map,
78
+ sources: output.map.sources.map((source) =>
79
+ source?.startsWith('app/')
80
+ ? Path.join(root, source.slice(4))
81
+ : source,
82
+ ),
83
+ }),
84
+ }
85
+ }
86
+
87
+ return {
88
+ buildEnd:
89
+ meta.framework === 'webpack'
90
+ ? undefined
91
+ : function () {
92
+ emit(this)
93
+ },
94
+ async buildStart() {
95
+ pending = (async () => {
96
+ graph = undefined
97
+ files = new Set()
98
+ directories = new Set()
99
+ sources = new Map()
100
+ assets = new Map()
101
+ const modules: Record<string, string> = Object.create(null)
102
+ const imports: Record<
103
+ string,
104
+ Record<string, string | null>
105
+ > = Object.create(null)
106
+ const contracts: Record<string, string> = Object.create(null)
107
+
108
+ async function scan(directory: string): Promise<void> {
109
+ directories.add(directory)
110
+ const entries = await Fs.readdir(directory, { withFileTypes: true })
111
+
112
+ for (const entry of entries.sort((a, b) =>
113
+ a.name < b.name ? -1 : a.name > b.name ? 1 : 0,
114
+ )) {
115
+ if (
116
+ entry.name.startsWith('.') ||
117
+ [
118
+ 'node_modules',
119
+ 'dist',
120
+ 'build',
121
+ 'coverage',
122
+ 'test',
123
+ 'tests',
124
+ '__tests__',
125
+ 'fixtures',
126
+ '__fixtures__',
127
+ ].includes(entry.name)
128
+ )
129
+ continue
130
+ const file = Path.join(directory, entry.name)
131
+
132
+ if (entry.isDirectory()) await scan(file)
133
+ else if (
134
+ entry.isFile() &&
135
+ sourcePattern.test(file) &&
136
+ !excludedPattern.test(file)
137
+ ) {
138
+ const source = await Fs.readFile(file, 'utf8')
139
+
140
+ files.add(file)
141
+ sources.set(file, source)
142
+ modules[identity(file)] = source
143
+ }
144
+ }
145
+ }
146
+
147
+ const resolve = async (specifier: string, importer: string) => {
148
+ if ('resolve' in this && typeof this.resolve === 'function') {
149
+ const result = await (
150
+ this as UnpluginBuildContext & {
151
+ resolve: (
152
+ id: string,
153
+ importer: string,
154
+ ) => Promise<{
155
+ id: string
156
+ external?: boolean | 'absolute' | 'relative'
157
+ } | null>
158
+ }
159
+ ).resolve(specifier, importer)
160
+ return result && !result.external ? result.id : undefined
161
+ }
162
+
163
+ const native = this.getNativeBuildContext?.()
164
+ if (native?.framework === 'esbuild') {
165
+ const result = await native.build.resolve(specifier, {
166
+ importer,
167
+ kind: 'import-statement',
168
+ resolveDir: Path.dirname(importer),
169
+ })
170
+ if (result.errors.length)
171
+ throw new Error(
172
+ result.errors.map((error) => error.text).join('\n'),
173
+ )
174
+ return result.external ? undefined : result.path
175
+ }
176
+ if (native?.framework === 'webpack') {
177
+ const resolver = native.compiler.resolverFactory.get('normal', {
178
+ dependencyType: 'esm',
179
+ })
180
+ return new Promise<string | undefined>((accept, reject) => {
181
+ resolver.resolve(
182
+ {},
183
+ Path.dirname(importer),
184
+ specifier,
185
+ {},
186
+ (error, result) => {
187
+ if (error) reject(error)
188
+ else accept(result || undefined)
189
+ },
190
+ )
191
+ })
192
+ }
193
+ throw new Error('The bundler does not expose module resolution.')
194
+ }
195
+
196
+ async function contract(
197
+ file: string,
198
+ required = false,
199
+ ): Promise<boolean> {
200
+ if (Object.hasOwn(contracts, file)) return true
201
+ const sidecar = `${file}.zyzz.json`
202
+
203
+ files.add(sidecar)
204
+ try {
205
+ contracts[file] = await Fs.readFile(sidecar, 'utf8')
206
+ } catch (error) {
207
+ if (
208
+ !required &&
209
+ (error as NodeJS.ErrnoException).code === 'ENOENT'
210
+ )
211
+ return false
212
+
213
+ throw error
214
+ }
215
+ const metadata = Contract.read(contracts[file]!, new Map(), file)
216
+ for (const section of metadata.stylesheets) {
217
+ let owner = file
218
+ for (const specifier of section.dependency ?? []) {
219
+ const target = await resolve(specifier, owner)
220
+ if (!target)
221
+ throw new Error(
222
+ `Unable to resolve packed stylesheet dependency: ${specifier}`,
223
+ )
224
+ ;(imports[owner] ??= Object.create(null))[specifier] = target
225
+ await contract(target, true)
226
+ owner = target
227
+ }
228
+ }
229
+ return true
230
+ }
231
+
232
+ await scan(root)
233
+ for (const [file, source] of sources) {
234
+ const links = (imports[identity(file)] = Object.create(null))
235
+ for (const node of Parser.parseSync(file, source, {
236
+ sourceType: 'module',
237
+ }).program.body) {
238
+ if (
239
+ (node.type !== 'ImportDeclaration' &&
240
+ node.type !== 'ExportNamedDeclaration' &&
241
+ node.type !== 'ExportAllDeclaration') ||
242
+ !node.source
243
+ )
244
+ continue
245
+ if (
246
+ node.type === 'ImportDeclaration'
247
+ ? node.importKind === 'type'
248
+ : node.exportKind === 'type'
249
+ )
250
+ continue
251
+ if (
252
+ 'specifiers' in node &&
253
+ node.specifiers.length &&
254
+ node.specifiers.every((specifier) =>
255
+ specifier.type === 'ImportSpecifier'
256
+ ? specifier.importKind === 'type'
257
+ : specifier.type === 'ExportSpecifier' &&
258
+ specifier.exportKind === 'type',
259
+ )
260
+ )
261
+ continue
262
+
263
+ const specifier = node.source.value
264
+ links[specifier] = null
265
+ if (
266
+ specifier === 'zyzz' ||
267
+ (specifier.startsWith('zyzz/') &&
268
+ specifier !== 'zyzz/default') ||
269
+ /^(?:[a-z][a-z\d+.-]*:|\/\/)/i.test(specifier) ||
270
+ specifier.includes('?')
271
+ )
272
+ continue
273
+ const target = await resolve(specifier, file)
274
+ if (!target || !sourcePattern.test(target)) continue
275
+ files.add(target)
276
+ if (sources.has(target)) links[specifier] = identity(target)
277
+ else if (await contract(target)) links[specifier] = target
278
+ }
279
+ }
280
+
281
+ const result = compiler.compile({
282
+ compiler: options.compiler,
283
+ contracts,
284
+ imports,
285
+ modules,
286
+ })
287
+ const copied = new Map<string, string>()
288
+ const owners = new Map<string, string>()
289
+
290
+ async function owner(id: string): Promise<string> {
291
+ if (id.startsWith('app/')) return Fs.realpath(root)
292
+ const cached = owners.get(id)
293
+ if (cached) return cached
294
+
295
+ let directory = Path.dirname(id)
296
+ for (;;) {
297
+ try {
298
+ await Fs.access(Path.join(directory, 'package.json'))
299
+ const real = await Fs.realpath(directory)
300
+ owners.set(id, real)
301
+ return real
302
+ } catch (error) {
303
+ if ((error as NodeJS.ErrnoException).code !== 'ENOENT')
304
+ throw error
305
+ const parent = Path.dirname(directory)
306
+ if (parent === directory)
307
+ throw new Error(
308
+ 'Packed assets require an owning package.json.',
309
+ )
310
+ directory = parent
311
+ }
312
+ }
313
+ }
314
+
315
+ async function copy(file: string, boundary: string): Promise<string> {
316
+ const real = await Fs.realpath(file)
317
+ if (!inside(boundary, real))
318
+ throw new Error('Asset path escapes its owning package.')
319
+ files.add(real)
320
+ const cached = copied.get(real)
321
+ if (cached) return cached
322
+
323
+ const content = await Fs.readFile(real)
324
+ const name = `zyzz-assets/${Crypto.hash('sha256', real + '\0' + content.toString('base64')).slice(0, 16)}${Path.extname(real)}`
325
+ copied.set(real, name)
326
+ if (real.endsWith('.css')) {
327
+ const processed = await stylesheet(
328
+ content.toString(),
329
+ undefined,
330
+ real,
331
+ boundary,
332
+ Path.posix.dirname(name),
333
+ )
334
+ assets.set(name, processed.code)
335
+ } else assets.set(name, content)
336
+ return name
337
+ }
338
+
339
+ async function stylesheet(
340
+ source: string,
341
+ map: string | undefined,
342
+ file: string,
343
+ boundary: string,
344
+ outputDirectory = '.',
345
+ targets?: Readonly<Record<string, string>>,
346
+ ) {
347
+ const urls = new Set<string>()
348
+ AtRules.transform({
349
+ code: Buffer.from(source),
350
+ filename: file,
351
+ visitor: {
352
+ Rule(rule) {
353
+ if (rule.type === 'import') urls.add(rule.value.url)
354
+ },
355
+ Url(url) {
356
+ urls.add(url.url)
357
+ },
358
+ },
359
+ })
360
+ const replacements = new Map<string, string>()
361
+ for (const url of urls) {
362
+ const target = targets?.[url]
363
+ if (
364
+ !target &&
365
+ (!url || /^(?:[a-z][a-z\d+.-]*:|[/?#])/i.test(url))
366
+ )
367
+ continue
368
+ const raw = (target ?? url).split(/[?#]/)[0]!
369
+ const path = (() => {
370
+ if (!target)
371
+ return Path.resolve(
372
+ Path.dirname(file),
373
+ decodeURIComponent(raw),
374
+ )
375
+ if (raw.startsWith('app/'))
376
+ return Path.join(root, decodeURIComponent(raw.slice(4)))
377
+ return decodeURIComponent(raw)
378
+ })()
379
+ const assetOwner = target
380
+ ? await owner(result.sharedAssetOwners?.[url] ?? 'app/')
381
+ : boundary
382
+ const name = await copy(path, assetOwner)
383
+ replacements.set(
384
+ url,
385
+ Path.posix.relative(outputDirectory, name) +
386
+ (target ?? url).slice(raw.length),
387
+ )
388
+ }
389
+ return AtRules.transform({
390
+ code: Buffer.from(source),
391
+ filename: file,
392
+ ...(map === undefined
393
+ ? {}
394
+ : { inputSourceMap: map, sourceMap: true }),
395
+ visitor: {
396
+ Rule(rule) {
397
+ if (rule.type !== 'import') return
398
+ const url = replacements.get(rule.value.url)
399
+ if (url) return AtRules.relocateImport(rule.value, url)
400
+ },
401
+ Url(url) {
402
+ const target = replacements.get(url.url)
403
+ if (target) return { ...url, url: target }
404
+ },
405
+ },
406
+ })
407
+ }
408
+
409
+ const shared = await stylesheet(
410
+ result.sharedCss ?? '',
411
+ JSON.stringify(
412
+ result.sharedCssMap ??
413
+ Mapping.toEncodedMap(new Mapping.GenMapping()),
414
+ ),
415
+ Path.join(root, 'zyzz.css'),
416
+ await Fs.realpath(root),
417
+ '.',
418
+ result.sharedAssets,
419
+ )
420
+ const chunks = [
421
+ {
422
+ code: Buffer.from(shared.code).toString(),
423
+ map: Buffer.from(shared.map!).toString(),
424
+ },
425
+ ]
426
+ const seen = new Set<string>()
427
+ async function visit(id: string): Promise<void> {
428
+ if (seen.has(id)) return
429
+ seen.add(id)
430
+ for (const dependency of result.dependencies[id] ?? [])
431
+ await visit(dependency)
432
+ const output = result.modules[id]
433
+ if (output) {
434
+ const processed = await stylesheet(
435
+ output.css,
436
+ JSON.stringify(output.cssMap),
437
+ Path.join(root, id.slice(4)),
438
+ await Fs.realpath(root),
439
+ )
440
+ chunks.push({
441
+ code: Buffer.from(processed.code).toString(),
442
+ map: Buffer.from(processed.map!).toString(),
443
+ })
444
+ }
445
+ }
446
+ const ids = Object.keys(result.modules)
447
+ const imported = new Set(Object.values(result.dependencies).flat())
448
+ for (const id of ids) if (!imported.has(id)) await visit(id)
449
+ for (const id of ids) await visit(id)
450
+
451
+ const map = new Mapping.GenMapping({ file: 'zyzz.css' })
452
+ let line = 0
453
+ css = ''
454
+ for (const chunk of chunks) {
455
+ if (!chunk.code) continue
456
+ const traced = Mapping.fromMap(chunk.map)
457
+ for (const mapping of Mapping.allMappings(traced)) {
458
+ const generated = {
459
+ line: mapping.generated.line + line,
460
+ column: mapping.generated.column,
461
+ }
462
+ if (
463
+ mapping.source !== undefined &&
464
+ mapping.original !== undefined
465
+ ) {
466
+ const location = {
467
+ generated,
468
+ original: mapping.original,
469
+ source: mapping.source.startsWith('app/')
470
+ ? Path.join(root, mapping.source.slice(4))
471
+ : mapping.source,
472
+ }
473
+ if (mapping.name === undefined)
474
+ Mapping.addMapping(map, location)
475
+ else
476
+ Mapping.addMapping(map, { ...location, name: mapping.name })
477
+ } else Mapping.addMapping(map, { generated })
478
+ }
479
+ const encoded = Mapping.toEncodedMap(traced)
480
+ encoded.sources.forEach((source, index) => {
481
+ if (source !== null)
482
+ Mapping.setSourceContent(
483
+ map,
484
+ source.startsWith('app/')
485
+ ? Path.join(root, source.slice(4))
486
+ : source,
487
+ encoded.sourcesContent?.[index] ?? null,
488
+ )
489
+ })
490
+ css += chunk.code + '\n'
491
+ line += chunk.code.split('\n').length
492
+ }
493
+ cssMap = JSON.stringify(Mapping.toEncodedMap(map))
494
+ script = Catalogs.scripts(
495
+ Object.values({ ...contracts, ...result.contracts }).flatMap(
496
+ Catalogs.read,
497
+ ),
498
+ ).join('\n')
499
+ graph = result
500
+ if (meta.framework === 'webpack') emit(this)
501
+ if (meta.framework === 'rollup')
502
+ for (const file of [...files, ...directories])
503
+ this.addWatchFile(file)
504
+ })()
505
+ await pending
506
+ },
507
+ enforce: 'pre',
508
+ esbuild: {
509
+ config(options) {
510
+ if (!options.outdir || options.write === false)
511
+ throw new Error(
512
+ 'Zyzz requires esbuild outdir and write: true to emit CSS assets.',
513
+ )
514
+ options.outdir = Path.resolve(
515
+ options.absWorkingDir ?? process.cwd(),
516
+ options.outdir,
517
+ )
518
+ },
519
+ setup(build) {
520
+ // Unplugin forwards watchFiles but not watchDirs from its transform hook.
521
+ build.onLoad({ filter: sourcePattern }, async ({ path }) => {
522
+ await pending
523
+ if (!sources.has(path)) return
524
+ const output = transform(await Fs.readFile(path, 'utf8'), path)
525
+ if (!output) return
526
+ return {
527
+ contents: `${output.code}\n//# sourceMappingURL=data:application/json;base64,${Buffer.from(output.map).toString('base64')}`,
528
+ loader: (() => {
529
+ if (path.endsWith('.tsx')) return 'tsx'
530
+ if (path.endsWith('.jsx')) return 'jsx'
531
+ if (/\.[cm]?ts$/.test(path)) return 'ts'
532
+ return 'js'
533
+ })(),
534
+ resolveDir: Path.dirname(path),
535
+ watchDirs: [...directories],
536
+ watchFiles: [...files],
537
+ }
538
+ })
539
+ },
540
+ },
541
+ name: 'zyzz',
542
+ rollup: {
543
+ shouldTransformCachedModule() {
544
+ return true
545
+ },
546
+ },
547
+ transform: {
548
+ filter: {
549
+ id: {
550
+ include: sourcePattern,
551
+ exclude:
552
+ /(?:^|[/\\])node_modules[/\\]|\.(?:d|test|test-d|bench|bench-d)\.[cm]?[jt]sx?$/,
553
+ },
554
+ },
555
+ async handler(code, id) {
556
+ await pending
557
+ const output = transform(code, id)
558
+ if (!output) return
559
+ for (const file of files) this.addWatchFile(file)
560
+ const native = this.getNativeBuildContext?.()
561
+ for (const directory of directories) {
562
+ if (native?.framework === 'webpack')
563
+ native.loaderContext?.addContextDependency(directory)
564
+ else if (meta.framework === 'rollup') this.addWatchFile(directory)
565
+ }
566
+ return output
567
+ },
568
+ },
569
+ webpack(compiler) {
570
+ compiler.hooks.watchRun.tap('zyzz', () => {
571
+ // Directory invalidations must also discard cached source reads before the graph and loaders rebuild.
572
+ for (const file of compiler.modifiedFiles ?? [])
573
+ if (directories.has(file)) compiler.inputFileSystem?.purge?.(file)
574
+ })
575
+ },
576
+ }
577
+ },
578
+ )
579
+
580
+ /** Bundler factories. Vite retains its native CSS delivery and environment-specific HMR. */
581
+ export const zyzz = {
582
+ /** Creates an esbuild plugin that emits stylesheets and watches source directories. */
583
+ esbuild: portable.esbuild,
584
+ /** Creates a Rollup plugin with dependency-aware cached transforms. */
585
+ rollup: portable.rollup,
586
+ /** Creates the existing Vite plugin with automatic CSS delivery and HMR. */
587
+ vite: Vite.zyzz,
588
+ /** Creates a Webpack plugin that compiles before language loaders. */
589
+ webpack: portable.webpack,
590
+ }
591
+
592
+ function inside(parent: string, child: string) {
593
+ const relative = Path.relative(parent, child)
594
+ return (
595
+ relative === '' ||
596
+ (relative !== '..' &&
597
+ !relative.startsWith(`..${Path.sep}`) &&
598
+ !Path.isAbsolute(relative))
599
+ )
600
+ }
package/src/variants.ts CHANGED
@@ -6,7 +6,7 @@ import type * as Binding from './internal/Binding.js'
6
6
  import type * as Condition from './internal/Condition.js'
7
7
  import type * as Shorthands from './internal/Shorthands.js'
8
8
  import type * as Style from './Style.js'
9
- import type * as Theme from './Theme.js'
9
+ import type * as Theme from './internal/Theme.js'
10
10
 
11
11
  type Axes<definition> = definition extends { readonly variants: infer axes }
12
12
  ? axes
@@ -62,8 +62,10 @@ type Keys<value> = value extends unknown ? keyof value : never
62
62
  type Selections<axes> = {
63
63
  readonly [axis in keyof axes]?: Choice<axes[axis]> | null | undefined
64
64
  }
65
- type Input<definition> = Selections<Axes<definition>> &
66
- style.Options &
65
+ type Input<definition, output extends style.Output> = Selections<
66
+ Axes<definition>
67
+ > &
68
+ style.Options<output> &
67
69
  (keyof Conditions<definition> extends never
68
70
  ? {}
69
71
  : {
@@ -191,10 +193,10 @@ export declare namespace variants {
191
193
 
192
194
  /** Callable selection; omitted values use defaults and null suppresses them. */
193
195
  type ReturnType<definition, output extends style.Output = 'react'> = <
194
- const input extends Input<definition> = Input<definition>,
196
+ const input extends Input<definition, output> = Input<definition, output>,
195
197
  >(
196
198
  input?: input &
197
- Record<Exclude<Keys<input>, keyof Input<definition>>, never>,
199
+ Record<Exclude<Keys<input>, keyof Input<definition, output>>, never>,
198
200
  ) => style.Props<output> & {
199
201
  readonly [axis in keyof Axes<definition> as `data-${axis & string}`]?:
200
202
  | string
package/src/vite/index.ts CHANGED
@@ -10,6 +10,7 @@ import * as Lightning from 'lightningcss'
10
10
  import * as Crypto from 'node:crypto'
11
11
  import * as Fs from 'node:fs/promises'
12
12
  import * as Path from 'node:path'
13
+ import { isBuiltin } from 'node:module'
13
14
  import * as Parser from 'oxc-parser'
14
15
  import * as Walker from 'oxc-walker'
15
16
  import * as Scope from '../compiler/internal/Scope.js'
@@ -28,8 +29,7 @@ export function zyzz(options: zyzz.Options = {}): Plugin {
28
29
  ...options.native,
29
30
  fonts:
30
31
  options.native.fonts && Object.freeze({ ...options.native.fonts }),
31
- themes:
32
- options.native.themes && Object.freeze({ ...options.native.themes }),
32
+ vars: options.native.vars && Object.freeze({ ...options.native.vars }),
33
33
  units:
34
34
  options.native.units && Object.freeze({ ...options.native.units }),
35
35
  })
@@ -296,6 +296,7 @@ export function zyzz(options: zyzz.Options = {}): Plugin {
296
296
  everything = false,
297
297
  ) {
298
298
  async function resolve(source: string, importer: string) {
299
+ if (isBuiltin(source)) return { id: source, external: true }
299
300
  const resolved = await host.resolve(source, importer)
300
301
  if (!resolved) return resolved
301
302
 
@@ -523,8 +524,7 @@ export function zyzz(options: zyzz.Options = {}): Plugin {
523
524
  const specifier = node.source.value
524
525
  if (
525
526
  specifier === 'zyzz' ||
526
- (specifier.startsWith('zyzz/') &&
527
- specifier !== 'zyzz/default')
527
+ (specifier.startsWith('zyzz/') && specifier !== 'zyzz/default')
528
528
  )
529
529
  continue
530
530
 
@@ -1241,6 +1241,7 @@ export function zyzz(options: zyzz.Options = {}): Plugin {
1241
1241
  },
1242
1242
  async load(id) {
1243
1243
  if (!id.startsWith(prefix)) return
1244
+ id = id.split('?', 1)[0]!
1244
1245
 
1245
1246
  const file =
1246
1247
  id === sharedId