zyzz 0.0.2 → 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 (252) 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 +19 -13
  7. package/dist/Style.d.ts.map +1 -1
  8. package/dist/Style.js +73 -7
  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 +10 -6
  21. package/dist/compiler/Source.d.ts.map +1 -1
  22. package/dist/compiler/Source.js +66 -11
  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 +59 -21
  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 +162 -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 +451 -1649
  72. package/dist/default.d.ts.map +1 -1
  73. package/dist/default.js +389 -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/internal/Shorthands.d.ts +1 -1
  102. package/dist/internal/Shorthands.d.ts.map +1 -1
  103. package/dist/internal/Shorthands.js +1 -0
  104. package/dist/internal/Shorthands.js.map +1 -1
  105. package/dist/{Theme.d.ts → internal/Theme.d.ts} +24 -19
  106. package/dist/internal/Theme.d.ts.map +1 -0
  107. package/dist/{Theme.js → internal/Theme.js} +57 -14
  108. package/dist/internal/Theme.js.map +1 -0
  109. package/dist/internal/Token.d.ts +46 -16
  110. package/dist/internal/Token.d.ts.map +1 -1
  111. package/dist/internal/Token.js +89 -5
  112. package/dist/internal/Token.js.map +1 -1
  113. package/dist/internal/Typography.d.ts +31 -0
  114. package/dist/internal/Typography.d.ts.map +1 -0
  115. package/dist/internal/Typography.js +54 -0
  116. package/dist/internal/Typography.js.map +1 -0
  117. package/dist/internal/Value.d.ts +6 -1
  118. package/dist/internal/Value.d.ts.map +1 -1
  119. package/dist/internal/Value.js.map +1 -1
  120. package/dist/internal/VariableSets.d.ts +20 -0
  121. package/dist/internal/VariableSets.d.ts.map +1 -0
  122. package/dist/internal/VariableSets.js +317 -0
  123. package/dist/internal/VariableSets.js.map +1 -0
  124. package/dist/next/loader.js +15 -5
  125. package/dist/next/loader.js.map +1 -1
  126. package/dist/node/Host.d.ts.map +1 -1
  127. package/dist/node/Host.js +1 -1
  128. package/dist/node/Host.js.map +1 -1
  129. package/dist/react-native/Host.d.ts +2 -2
  130. package/dist/react-native/Host.d.ts.map +1 -1
  131. package/dist/react-native/Host.js +4 -4
  132. package/dist/react-native/Host.js.map +1 -1
  133. package/dist/react-native/StyleSheet.d.ts +9 -8
  134. package/dist/react-native/StyleSheet.d.ts.map +1 -1
  135. package/dist/react-native/StyleSheet.js +29 -23
  136. package/dist/react-native/StyleSheet.js.map +1 -1
  137. package/dist/react-native/Variants.d.ts +3 -3
  138. package/dist/react-native/Variants.d.ts.map +1 -1
  139. package/dist/react-native/Variants.js +2 -2
  140. package/dist/react-native/react.d.ts +1 -1
  141. package/dist/react-native/react.d.ts.map +1 -1
  142. package/dist/react-native/react.js +5 -5
  143. package/dist/react-native/react.js.map +1 -1
  144. package/dist/rollup/index.d.ts +3 -0
  145. package/dist/rollup/index.d.ts.map +1 -0
  146. package/dist/rollup/index.js +5 -0
  147. package/dist/rollup/index.js.map +1 -0
  148. package/dist/runtime/Appearance.d.ts +7 -7
  149. package/dist/runtime/Appearance.d.ts.map +1 -1
  150. package/dist/runtime/Appearance.js +15 -15
  151. package/dist/runtime/Appearance.js.map +1 -1
  152. package/dist/runtime/Dynamic.d.ts.map +1 -1
  153. package/dist/runtime/Dynamic.js +4 -1
  154. package/dist/runtime/Dynamic.js.map +1 -1
  155. package/dist/runtime/NativeContext.d.ts +4 -4
  156. package/dist/runtime/NativeContext.d.ts.map +1 -1
  157. package/dist/runtime/NativeContext.js +5 -5
  158. package/dist/runtime/NativeContext.js.map +1 -1
  159. package/dist/runtime/NativeDynamic.d.ts +1 -1
  160. package/dist/runtime/NativeDynamic.d.ts.map +1 -1
  161. package/dist/runtime/NativeDynamic.js.map +1 -1
  162. package/dist/runtime/Props.d.ts +1 -1
  163. package/dist/runtime/Props.js +3 -3
  164. package/dist/runtime/Props.js.map +1 -1
  165. package/dist/runtime/Selection.d.ts +14 -10
  166. package/dist/runtime/Selection.d.ts.map +1 -1
  167. package/dist/runtime/Selection.js +19 -18
  168. package/dist/runtime/Selection.js.map +1 -1
  169. package/dist/styleFunction.d.ts +14 -14
  170. package/dist/styleFunction.d.ts.map +1 -1
  171. package/dist/styleFunction.js.map +1 -1
  172. package/dist/unplugin/index.d.ts +20 -0
  173. package/dist/unplugin/index.d.ts.map +1 -0
  174. package/dist/unplugin/index.js +498 -0
  175. package/dist/unplugin/index.js.map +1 -0
  176. package/dist/variants.d.ts +3 -3
  177. package/dist/variants.d.ts.map +1 -1
  178. package/dist/variants.js.map +1 -1
  179. package/dist/vite/index.d.ts.map +1 -1
  180. package/dist/vite/index.js +6 -3
  181. package/dist/vite/index.js.map +1 -1
  182. package/dist/web/Css.d.ts +4 -3
  183. package/dist/web/Css.d.ts.map +1 -1
  184. package/dist/web/Css.js +4 -4
  185. package/dist/web/Css.js.map +1 -1
  186. package/dist/web/internal/Themes.d.ts +3 -2
  187. package/dist/web/internal/Themes.d.ts.map +1 -1
  188. package/dist/web/internal/Themes.js +42 -6
  189. package/dist/web/internal/Themes.js.map +1 -1
  190. package/dist/webpack/index.d.ts +3 -0
  191. package/dist/webpack/index.d.ts.map +1 -0
  192. package/dist/webpack/index.js +5 -0
  193. package/dist/webpack/index.js.map +1 -0
  194. package/package.json +23 -2
  195. package/src/Config.ts +43 -684
  196. package/src/Style.ts +167 -40
  197. package/src/Vars.ts +299 -0
  198. package/src/compiler/Graph.ts +14 -9
  199. package/src/compiler/Native.ts +29 -32
  200. package/src/compiler/Source.ts +85 -18
  201. package/src/compiler/Transform.ts +78 -22
  202. package/src/compiler/internal/Compositions.ts +36 -3
  203. package/src/compiler/internal/Configurations.ts +83 -11
  204. package/src/compiler/internal/Contract.ts +223 -17
  205. package/src/compiler/internal/Contributions.ts +1 -1
  206. package/src/compiler/internal/Dynamic.ts +1 -1
  207. package/src/compiler/internal/Expression.ts +26 -0
  208. package/src/compiler/internal/Identifiers.ts +2 -1
  209. package/src/compiler/internal/NativeBindings.ts +3 -3
  210. package/src/compiler/internal/PackedStyles.ts +22 -26
  211. package/src/compiler/internal/Recipes.ts +1 -1
  212. package/src/compiler/internal/Selectors.ts +1 -1
  213. package/src/compiler/internal/Themes.ts +266 -57
  214. package/src/compiler/internal/{Variables.ts → Vars.ts} +2 -2
  215. package/src/default.ts +868 -156
  216. package/src/esbuild/index.ts +5 -0
  217. package/src/index.ts +4 -4
  218. package/src/internal/Authoring.ts +5 -7
  219. package/src/internal/Binding.ts +1 -1
  220. package/src/internal/Completion.ts +42 -0
  221. package/src/internal/Condition.ts +31 -1
  222. package/src/internal/Configuration.ts +970 -0
  223. package/src/internal/Shorthands.ts +2 -0
  224. package/src/{Theme.ts → internal/Theme.ts} +174 -48
  225. package/src/internal/Token.ts +258 -85
  226. package/src/internal/Typography.ts +106 -0
  227. package/src/internal/Value.ts +45 -26
  228. package/src/internal/VariableSets.ts +458 -0
  229. package/src/next/loader.ts +19 -5
  230. package/src/node/Host.ts +1 -2
  231. package/src/react-native/Host.ts +6 -6
  232. package/src/react-native/StyleSheet.ts +49 -42
  233. package/src/react-native/Variants.ts +3 -3
  234. package/src/react-native/react.ts +6 -6
  235. package/src/rollup/index.ts +5 -0
  236. package/src/runtime/Appearance.ts +20 -20
  237. package/src/runtime/Dynamic.ts +4 -1
  238. package/src/runtime/NativeContext.ts +8 -8
  239. package/src/runtime/NativeDynamic.ts +2 -4
  240. package/src/runtime/Props.ts +3 -3
  241. package/src/runtime/Selection.ts +44 -33
  242. package/src/styleFunction.ts +22 -19
  243. package/src/unplugin/index.ts +600 -0
  244. package/src/variants.ts +7 -5
  245. package/src/vite/index.ts +5 -4
  246. package/src/web/Css.ts +10 -7
  247. package/src/web/internal/Themes.ts +52 -9
  248. package/src/webpack/index.ts +5 -0
  249. package/dist/Theme.d.ts.map +0 -1
  250. package/dist/Theme.js.map +0 -1
  251. package/dist/compiler/internal/Variables.d.ts.map +0 -1
  252. package/dist/compiler/internal/Variables.js.map +0 -1
@@ -4,25 +4,22 @@
4
4
  */
5
5
  import * as Authoring from './internal/Authoring.js'
6
6
  import type * as Binding from './internal/Binding.js'
7
+ import type * as Completion from './internal/Completion.js'
7
8
  import type * as Literal from './internal/Literal.js'
8
9
  import type * as Style from './Style.js'
9
10
 
10
- /** Restricts callback hints to inferred declarations instead of expanding every property grammar. */
11
- type CompletionProperties<styles = Record<string, unknown>> = {
12
- readonly [property in keyof typeof Literal.rules & keyof styles]?:
13
- | Literal.Properties[property]
14
- | (string & {})
15
- | number
16
- | object
17
- }
18
-
19
11
  type Keys<value> = value extends unknown ? keyof value : never
20
12
 
13
+ // Keep named and custom-property instantiations separate so framework interfaces need no index signature.
14
+ type InlineProperties<properties extends string> = {
15
+ readonly [property in properties]?: string | number | undefined
16
+ }
17
+
21
18
  type VariableOptions<input> = input extends {
22
- readonly variables: infer variables
19
+ readonly vars: infer variables
23
20
  }
24
21
  ? {
25
- readonly variables: {
22
+ readonly vars: {
26
23
  readonly [key in keyof variables]: string extends key
27
24
  ? variables[key]
28
25
  : key extends `--${string}`
@@ -49,7 +46,7 @@ export function style<
49
46
  values: values,
50
47
  ) => styles &
51
48
  NoInfer<Style.Accepted<styles, {}, true> & Binding.Checked<styles>>) &
52
- CompletionProperties<NoInfer<styles>>,
49
+ Completion.Properties<{}, NoInfer<styles>>,
53
50
  // Exclude invalid callbacks by arity so they cannot mask object-property diagnostics.
54
51
  ...options: Parameters<callback> extends [Record<string, string | number>]
55
52
  ? values extends Binding.Inputs<values>
@@ -60,7 +57,7 @@ export function style<
60
57
  export function style<const styles extends Record<string, unknown>>(
61
58
  styles: styles &
62
59
  NoInfer<Style.Accepted<styles, {}, true>> &
63
- CompletionProperties,
60
+ Completion.Properties,
64
61
  options?: style.DefinitionOptions,
65
62
  ): style.ReturnType
66
63
  export function style(
@@ -80,7 +77,7 @@ export declare namespace style {
80
77
  type DefinitionOptions = { readonly id?: string | undefined }
81
78
  /** Callable compiled bindings with required scalar inputs and styling overrides. */
82
79
  type Dynamic<values, output extends Output = 'react'> = Reference &
83
- (<const input extends values & Options>(
80
+ (<const input extends values & Options<output>>(
84
81
  input: input &
85
82
  VariableOptions<input> &
86
83
  Record<Exclude<keyof input, keyof values | keyof Options>, never>,
@@ -90,13 +87,19 @@ export declare namespace style {
90
87
  type ErrorType = Error
91
88
 
92
89
  /** Styling overrides consumed by a transformed definition. */
93
- type Options = {
90
+ type Options<output extends Output = 'react'> = {
94
91
  /** External class names appended to the generated classes. */
95
92
  readonly className?: string | undefined
96
- /** Literal inline styling overrides. */
97
- readonly style?: Literal.Properties | undefined
93
+ /** Inline overrides. React accepts scalar values, while HTML requires CSS units. */
94
+ readonly style?:
95
+ | (output extends 'html'
96
+ ? Literal.Properties
97
+ :
98
+ | InlineProperties<keyof typeof Literal.rules>
99
+ | InlineProperties<keyof Literal.Properties>)
100
+ | undefined
98
101
  /** Inline custom-property assignments, merged before explicit style overrides. */
99
- readonly variables?:
102
+ readonly vars?:
100
103
  | Readonly<Record<`--${string}`, string | number | undefined>>
101
104
  | undefined
102
105
  }
@@ -118,7 +121,7 @@ export declare namespace style {
118
121
 
119
122
  /** Callable definition; source rewriting supplies its implementation. */
120
123
  type ReturnType<output extends Output = 'react'> = Reference &
121
- (<const options extends Options = Options>(
124
+ (<const options extends Options<output> = Options<output>>(
122
125
  options?: options &
123
126
  VariableOptions<options> &
124
127
  Record<Exclude<Keys<options>, keyof Options>, never>,
@@ -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
+ }