zyzz 0.0.6 → 0.0.8

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 (104) hide show
  1. package/README.md +26 -1
  2. package/dist/Style.d.ts +3 -3
  3. package/dist/Style.d.ts.map +1 -1
  4. package/dist/Style.js +12 -1
  5. package/dist/Style.js.map +1 -1
  6. package/dist/babel/index.d.ts +4 -1
  7. package/dist/babel/index.d.ts.map +1 -1
  8. package/dist/babel/index.js +6 -3
  9. package/dist/babel/index.js.map +1 -1
  10. package/dist/babel/internal/Compilation.d.ts +12 -0
  11. package/dist/babel/internal/Compilation.d.ts.map +1 -0
  12. package/dist/babel/internal/Compilation.js +3 -0
  13. package/dist/babel/internal/Compilation.js.map +1 -0
  14. package/dist/compiler/Graph.d.ts +6 -1
  15. package/dist/compiler/Graph.d.ts.map +1 -1
  16. package/dist/compiler/Graph.js +160 -36
  17. package/dist/compiler/Graph.js.map +1 -1
  18. package/dist/compiler/Transform.d.ts.map +1 -1
  19. package/dist/compiler/Transform.js +2 -1
  20. package/dist/compiler/Transform.js.map +1 -1
  21. package/dist/compiler/internal/Stylesheets.d.ts +8 -0
  22. package/dist/compiler/internal/Stylesheets.d.ts.map +1 -1
  23. package/dist/compiler/internal/Stylesheets.js +4 -0
  24. package/dist/compiler/internal/Stylesheets.js.map +1 -1
  25. package/dist/internal/Authoring.d.ts.map +1 -1
  26. package/dist/internal/Authoring.js +14 -0
  27. package/dist/internal/Authoring.js.map +1 -1
  28. package/dist/internal/Binding.d.ts +7 -0
  29. package/dist/internal/Binding.d.ts.map +1 -1
  30. package/dist/internal/Binding.js.map +1 -1
  31. package/dist/internal/Configuration.d.ts +1 -1
  32. package/dist/internal/Configuration.d.ts.map +1 -1
  33. package/dist/internal/Configuration.js +7 -6
  34. package/dist/internal/Configuration.js.map +1 -1
  35. package/dist/internal/Theme.d.ts +1 -1
  36. package/dist/internal/Theme.d.ts.map +1 -1
  37. package/dist/internal/Theme.js.map +1 -1
  38. package/dist/internal/Token.d.ts +3 -1
  39. package/dist/internal/Token.d.ts.map +1 -1
  40. package/dist/internal/Token.js +28 -3
  41. package/dist/internal/Token.js.map +1 -1
  42. package/dist/internal/Value.d.ts +22 -0
  43. package/dist/internal/Value.d.ts.map +1 -1
  44. package/dist/internal/Value.js +76 -9
  45. package/dist/internal/Value.js.map +1 -1
  46. package/dist/metro/Graph.d.ts +9 -1
  47. package/dist/metro/Graph.d.ts.map +1 -1
  48. package/dist/metro/Graph.js +5 -4
  49. package/dist/metro/Graph.js.map +1 -1
  50. package/dist/metro/transformer.d.ts +0 -1
  51. package/dist/metro/transformer.d.ts.map +1 -1
  52. package/dist/metro/transformer.js +19 -1
  53. package/dist/metro/transformer.js.map +1 -1
  54. package/dist/next/index.d.ts +3 -3
  55. package/dist/next/index.d.ts.map +1 -1
  56. package/dist/next/index.js +25 -30
  57. package/dist/next/index.js.map +1 -1
  58. package/dist/next/internal/Project.d.ts +28 -0
  59. package/dist/next/internal/Project.d.ts.map +1 -0
  60. package/dist/next/internal/Project.js +295 -0
  61. package/dist/next/internal/Project.js.map +1 -0
  62. package/dist/next/loader.d.ts +5 -6
  63. package/dist/next/loader.d.ts.map +1 -1
  64. package/dist/next/loader.js +161 -247
  65. package/dist/next/loader.js.map +1 -1
  66. package/dist/node/Host.d.ts.map +1 -1
  67. package/dist/node/Host.js +11 -3
  68. package/dist/node/Host.js.map +1 -1
  69. package/dist/node/internal/Snapshot.d.ts +12 -0
  70. package/dist/node/internal/Snapshot.d.ts.map +1 -0
  71. package/dist/node/internal/Snapshot.js +74 -0
  72. package/dist/node/internal/Snapshot.js.map +1 -0
  73. package/dist/unplugin/index.d.ts.map +1 -1
  74. package/dist/unplugin/index.js +10 -5
  75. package/dist/unplugin/index.js.map +1 -1
  76. package/dist/variants.d.ts +1 -1
  77. package/dist/variants.d.ts.map +1 -1
  78. package/dist/variants.js.map +1 -1
  79. package/dist/vite/index.d.ts.map +1 -1
  80. package/dist/vite/index.js +19 -4
  81. package/dist/vite/index.js.map +1 -1
  82. package/package.json +1 -1
  83. package/src/Style.ts +42 -8
  84. package/src/babel/index.ts +10 -4
  85. package/src/babel/internal/Compilation.ts +13 -0
  86. package/src/compiler/Graph.ts +213 -47
  87. package/src/compiler/Transform.ts +2 -1
  88. package/src/compiler/internal/Stylesheets.ts +9 -0
  89. package/src/internal/Authoring.ts +21 -0
  90. package/src/internal/Binding.ts +30 -0
  91. package/src/internal/Configuration.ts +19 -13
  92. package/src/internal/Theme.ts +5 -1
  93. package/src/internal/Token.ts +34 -3
  94. package/src/internal/Value.ts +180 -10
  95. package/src/metro/Graph.ts +5 -3
  96. package/src/metro/transformer.ts +26 -1
  97. package/src/next/index.ts +26 -31
  98. package/src/next/internal/Project.ts +378 -0
  99. package/src/next/loader.ts +195 -315
  100. package/src/node/Host.ts +15 -3
  101. package/src/node/internal/Snapshot.ts +79 -0
  102. package/src/unplugin/index.ts +10 -5
  103. package/src/variants.ts +1 -0
  104. package/src/vite/index.ts +24 -4
@@ -21,6 +21,60 @@ export type Atom<value> =
21
21
  * Checks authored properties and retains validated input types to avoid expanding whole-property fallback unions.
22
22
  */
23
23
  export type Accepted<style, properties> = {
24
+ [property in keyof style]: AcceptedValue<
25
+ style[property],
26
+ property,
27
+ properties
28
+ >
29
+ }
30
+
31
+ type AcceptedValue<value, property extends PropertyKey, properties> =
32
+ Extract<value, `[${string}]` | `[${string}] !important`> extends never
33
+ ? AcceptedMember<Record<property, value>, properties>[property]
34
+ : value extends unknown
35
+ ? AcceptedMember<Record<property, value>, properties>[property]
36
+ : never
37
+
38
+ type AcceptedMember<style, properties> = {
39
+ [property in keyof style]: property extends keyof properties
40
+ ? style[property] extends `[${infer value}]` | `[${infer value}] !important`
41
+ ?
42
+ | (property extends 'gridTemplateColumns' | 'gridTemplateRows'
43
+ ? style[property] extends LiteralAccepted<
44
+ Pick<style, property>,
45
+ Literal.Properties
46
+ >[property]
47
+ ? style[property]
48
+ : never
49
+ : never)
50
+ | (ScalarValue<value, property> extends LiteralAccepted<
51
+ Record<property, ScalarValue<value, property>>,
52
+ Literal.Properties
53
+ >[property]
54
+ ? style[property]
55
+ : never)
56
+ : style[property] extends readonly [unknown, ...unknown[]]
57
+ ? AcceptedArray<style[property], property, properties>
58
+ : LiteralAccepted<Pick<style, property>, properties>[property]
59
+ : never
60
+ }
61
+
62
+ type ScalarValue<value, property> =
63
+ value extends `${infer numeric extends number}`
64
+ ? property extends keyof Literal.Properties
65
+ ? number extends Literal.Properties[property]
66
+ ? numeric
67
+ : value
68
+ : value
69
+ : value
70
+
71
+ type AcceptedArray<values, property extends PropertyKey, properties> = {
72
+ [index in keyof values]: values[index] extends readonly unknown[]
73
+ ? never
74
+ : Accepted<Record<property, values[index]>, properties>[property]
75
+ }
76
+
77
+ type LiteralAccepted<style, properties> = {
24
78
  [property in keyof style]: property extends keyof properties
25
79
  ? style[property] extends { readonly [Token.scalar]: infer scalar }
26
80
  ? property extends keyof Literal.Properties
@@ -80,6 +134,22 @@ type Fold<value> = value extends string
80
134
 
81
135
  /** Refines concrete scalar spellings; already-broad property contracts need no literal refinement. */
82
136
  export type Checked<style, tokens = {}> = {
137
+ [property in keyof style]: style[property] extends
138
+ | `[${infer value}]`
139
+ | `[${infer value}] !important`
140
+ ?
141
+ | (property extends 'gridTemplateColumns' | 'gridTemplateRows'
142
+ ? LiteralChecked<Pick<style, property>>[property]
143
+ : never)
144
+ | LiteralChecked<
145
+ Record<property, ScalarValue<value, property>>
146
+ >[property]
147
+ : style[property] extends readonly [unknown, ...unknown[]]
148
+ ? CheckedArray<style[property], property, tokens>
149
+ : LiteralChecked<Pick<style, property>, tokens>[property]
150
+ }
151
+
152
+ type LiteralChecked<style, tokens = {}> = {
83
153
  [property in keyof style]: style[property] extends string & Binding.Reference
84
154
  ? style[property] extends Binding.Reference<'*'>
85
155
  ? unknown
@@ -121,6 +191,40 @@ export type Checked<style, tokens = {}> = {
121
191
  : unknown)
122
192
  }
123
193
 
194
+ type CheckedArray<values, property extends PropertyKey, tokens> = {
195
+ [index in keyof values]: Checked<
196
+ Record<property, values[index]>,
197
+ tokens
198
+ >[property]
199
+ }
200
+
201
+ /** Requires tokens for properties with configured values. */
202
+ export type Tokens<
203
+ value,
204
+ tokens,
205
+ property extends keyof Literal.Properties,
206
+ > = property extends `--${string}`
207
+ ? unknown
208
+ : [Token.Names<tokens, property>] extends [never]
209
+ ? unknown
210
+ : value extends readonly unknown[]
211
+ ? { [index in keyof value]: Tokens<value[index], tokens, property> }
212
+ : value extends
213
+ | Binding.Reference
214
+ | Token.Reference
215
+ | Token.Variable
216
+ | `${Token.Variable} !important`
217
+ | `[${string}]`
218
+ | `[${string}] !important`
219
+ ? unknown
220
+ : value extends Atom<Token.Names<tokens, property>>
221
+ ? unknown
222
+ : value extends number
223
+ ? `${value}` extends Token.Names<tokens, property>
224
+ ? unknown
225
+ : never
226
+ : never
227
+
124
228
  type Importance<value> = value extends readonly unknown[]
125
229
  ? { [key in keyof value]: Importance<value[key]> }
126
230
  : value extends `${infer body}!${infer suffix}`
@@ -263,7 +367,11 @@ export function parse(
263
367
  property: keyof Literal.Properties,
264
368
  ):
265
369
  | { invalid: true }
266
- | { important: boolean; value: number | string | Token.Expression }
370
+ | {
371
+ custom?: true
372
+ important: boolean
373
+ value: number | string | Token.Expression
374
+ }
267
375
  | undefined {
268
376
  if (Token.isExpression(input)) {
269
377
  const text = input.parts
@@ -275,6 +383,16 @@ export function parse(
275
383
 
276
384
  const parts = [...input.parts]
277
385
  let remaining = text.length - String(parsed.value).length
386
+ if (parsed.custom) {
387
+ for (let index = 0; index < parts.length; index++) {
388
+ const part = parts[index]
389
+ if (part === '') continue
390
+ if (typeof part !== 'string') return undefined
391
+ parts[index] = part.slice(1)
392
+ break
393
+ }
394
+ remaining--
395
+ }
278
396
 
279
397
  for (let index = parts.length - 1; remaining > 0 && index >= 0; index--) {
280
398
  const part = parts[index]
@@ -286,7 +404,11 @@ export function parse(
286
404
  remaining -= count
287
405
  }
288
406
 
289
- return { important: parsed.important, value: Token.compose(parts) }
407
+ return {
408
+ ...(parsed.custom ? { custom: true } : {}),
409
+ important: parsed.important,
410
+ value: Token.compose(parts.filter((part) => part !== '')),
411
+ }
290
412
  }
291
413
 
292
414
  if (typeof input !== 'string') return undefined
@@ -317,7 +439,15 @@ export function parse(
317
439
  marker = marker === 0 ? -1 : input.lastIndexOf('!', marker - 1)
318
440
  }
319
441
 
320
- if (marker < 0) return undefined
442
+ if (marker < 0) {
443
+ if (bracketed(input))
444
+ return {
445
+ custom: true,
446
+ important: false,
447
+ value: scalar(input.slice(1, -1)),
448
+ }
449
+ return undefined
450
+ }
321
451
  if (input.slice(marker - 1) !== ' !important' || escaped(marker - 1))
322
452
  return { invalid: true }
323
453
 
@@ -326,14 +456,54 @@ export function parse(
326
456
  while (end > 0 && /[ \t\n\r\f]/.test(input[end - 1]!) && !escaped(end - 1))
327
457
  end--
328
458
 
329
- const text = input.slice(0, end)
330
- const numeric =
331
- Literal.rule(property)?.kind === 'number' ||
332
- Literal.rule(property)?.kind === 'grid-line'
333
- const value =
334
- numeric && /^[+-]?(?:\d*\.\d+|\d+)(?:[eE][+-]?\d+)?$/.test(text)
459
+ const body = input.slice(0, end)
460
+ const custom = bracketed(body)
461
+ const text = custom ? body.slice(1, -1) : body
462
+ const value = scalar(text)
463
+
464
+ return {
465
+ ...(custom ? { custom: true } : {}),
466
+ important: true as const,
467
+ value,
468
+ }
469
+
470
+ function scalar(text: string): number | string {
471
+ const numeric =
472
+ Literal.rule(property)?.kind === 'number' ||
473
+ Literal.rule(property)?.kind === 'grid-line'
474
+ return numeric && /^[+-]?(?:\d*\.\d+|\d+)(?:[eE][+-]?\d+)?$/.test(text)
335
475
  ? Number(text)
336
476
  : text
477
+ }
478
+ }
337
479
 
338
- return { important: true as const, value }
480
+ /** Recognizes one enclosing pair without consuming native grid line lists. */
481
+ function bracketed(value: string): boolean {
482
+ if (!value.startsWith('[') || !value.endsWith(']')) return false
483
+ let depth = 0
484
+ let quote = ''
485
+ for (let index = 0; index < value.length; index++) {
486
+ const character = value[index]!
487
+ if (character === '\\') {
488
+ index++
489
+ continue
490
+ }
491
+ if (quote) {
492
+ if (character === quote) quote = ''
493
+ continue
494
+ }
495
+ if (character === '/' && value[index + 1] === '*') {
496
+ const end = value.indexOf('*/', index + 2)
497
+ if (end < 0) return false
498
+ index = end + 1
499
+ continue
500
+ }
501
+ if (character === '"' || character === "'") {
502
+ quote = character
503
+ continue
504
+ }
505
+ if (character === '[') depth++
506
+ if (character === ']' && --depth === 0) return index === value.length - 1
507
+ }
508
+ return false
339
509
  }
@@ -1,7 +1,7 @@
1
1
  /** Reads a closed local source graph for native authoring without evaluating modules. @module */
2
2
  import * as Fs from 'node:fs'
3
3
  import * as Path from 'node:path'
4
- import * as Parser from 'oxc-parser'
4
+ import * as Snapshot from '../node/internal/Snapshot.js'
5
5
  import type * as Graph from '../compiler/Graph.js'
6
6
 
7
7
  /** Collects relative source imports; package and asset imports remain owned by Metro. */
@@ -10,6 +10,7 @@ export function read(
10
10
  source: string,
11
11
  platform: string,
12
12
  root: string,
13
+ snapshot = Snapshot.create(),
13
14
  ) {
14
15
  const files = new Set<string>()
15
16
  const modules: Record<string, string> = Object.create(null)
@@ -31,7 +32,8 @@ export function read(
31
32
  modules[id] = source
32
33
  const resolved: Record<string, string | null> = Object.create(null)
33
34
  imports[id] = resolved
34
- for (const statement of Parser.parseSync(filename, source).program.body) {
35
+ for (const statement of snapshot.parse({ moduleId: id, source }).program
36
+ .body) {
35
37
  if (
36
38
  !('source' in statement) ||
37
39
  !statement.source ||
@@ -62,7 +64,7 @@ export function read(
62
64
  )
63
65
  if (!target || !/\.[cm]?[jt]sx?$/.test(target)) continue
64
66
  resolved[specifier] = identity(target)
65
- visit(target, Fs.readFileSync(target, 'utf8'))
67
+ visit(target, snapshot.readSync(target))
66
68
  }
67
69
  }
68
70
  visit(filename, source)
@@ -1,4 +1,7 @@
1
1
  /** Chains native authoring compilation into the configured Metro Babel transformer. @module */
2
+ import * as Compilation from '../babel/internal/Compilation.js'
3
+ import * as Compiler from '../compiler/Graph.js'
4
+ import * as Snapshot from '../node/internal/Snapshot.js'
2
5
  import type * as Babel from '@babel/core'
3
6
  import * as Crypto from 'node:crypto'
4
7
  import * as Fs from 'node:fs'
@@ -28,6 +31,8 @@ export function create(
28
31
  'platform' | 'target' | 'moduleId' | 'modules' | 'imports' | 'colorScheme'
29
32
  > & { readonly root: string },
30
33
  ) {
34
+ const compilers = new Map<string, ReturnType<typeof Compiler.create>>()
35
+ const snapshot = Snapshot.create()
31
36
  const upstream: Upstream = Module.createRequire(import.meta.url)(upstreamPath)
32
37
  return {
33
38
  getCacheKey(...args: readonly unknown[]) {
@@ -77,6 +82,21 @@ export function create(
77
82
  Path.relative(options.root, filename).startsWith(`..${Path.sep}`)
78
83
  )
79
84
  return upstream.transform(input)
85
+
86
+ const graph = Graph.read(
87
+ filename,
88
+ input.src,
89
+ platform,
90
+ options.root,
91
+ snapshot,
92
+ )
93
+ const key = `${platform}:${filename}`
94
+ let compiler = compilers.get(key)
95
+ if (!compiler) {
96
+ compiler = Compiler.create()
97
+ compilers.set(key, compiler)
98
+ }
99
+
80
100
  return upstream.transform({
81
101
  ...input,
82
102
  plugins: [
@@ -86,7 +106,12 @@ export function create(
86
106
  ...options,
87
107
  platform,
88
108
  target: 'native',
89
- ...Graph.read(filename, input.src, platform, options.root),
109
+ ...graph,
110
+ [Compilation.key]: {
111
+ compile: compiler.compile,
112
+ parse: snapshot.parse,
113
+ programs: snapshot.programs(graph.modules),
114
+ },
90
115
  },
91
116
  ],
92
117
  ...(input.plugins ?? []),
package/src/next/index.ts CHANGED
@@ -5,9 +5,9 @@ import * as Module from 'node:module'
5
5
  import * as Path from 'node:path'
6
6
 
7
7
  /**
8
- * Adds source transformation, CSS delivery, and project dependency watching to both bundlers.
9
- * Preserves existing configuration hooks and creates `.zyzz/next` beneath the working directory.
10
- * Throws file-system errors during setup; compilation errors are reported by the bundler.
8
+ * Adds source transformation, CSS delivery, and imported dependency watching to both bundlers.
9
+ * Preserves configuration hooks. Compilation emits stylesheets beneath `.zyzz/next`.
10
+ * Compilation and file-system errors are reported by the bundler.
11
11
  */
12
12
  export function zyzz(config: NextConfig, options?: zyzz.Options): NextConfig
13
13
  export function zyzz(config: zyzz.Factory, options?: zyzz.Options): zyzz.Factory
@@ -27,24 +27,32 @@ export function zyzz(
27
27
  const loader = Module.createRequire(import.meta.url).resolve(
28
28
  'zyzz/next/loader',
29
29
  )
30
- const loaderOptions = { reset: options.reset ?? false, root: process.cwd() }
31
- const shared = Path.join(loaderOptions.root, '.zyzz', 'next', 'shared.css')
32
- Fs.mkdirSync(Path.dirname(shared), { recursive: true })
33
- try {
34
- Fs.writeFileSync(shared, '', { flag: 'wx' })
35
- } catch (error) {
36
- if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error
30
+ const loaderOptions = {
31
+ development: process.env.NODE_ENV === 'development',
32
+ reset: options.reset ?? false,
33
+ root: process.cwd(),
34
+ }
35
+ // Turbopack must observe the output directory before taking its filesystem snapshot.
36
+ const directory = Path.join(loaderOptions.root, '.zyzz', 'next')
37
+ Fs.mkdirSync(directory, { recursive: true })
38
+ for (const [name, source] of Object.entries({
39
+ 'package.json': JSON.stringify({ sideEffects: true, type: 'module' }),
40
+ 'style.css': '',
41
+ })) {
42
+ try {
43
+ Fs.writeFileSync(Path.join(directory, name), source, { flag: 'wx' })
44
+ } catch (error) {
45
+ if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error
46
+ }
37
47
  }
38
48
  const pattern = '*.{ts,tsx,js,jsx,mts,cts,mjs,cjs}'
39
49
  const existing = config.turbopack?.rules?.[pattern]
40
50
  const rule = {
41
- condition: {
42
- all: [{ not: 'foreign' as const }, { not: { query: /zyzz-style/ } }],
43
- },
51
+ condition: { not: 'foreign' as const },
44
52
  loaders: [
45
53
  {
46
54
  loader,
47
- options: { ...loaderOptions, bundler: 'turbopack', mode: 'source' },
55
+ options: { ...loaderOptions, bundler: 'turbopack' },
48
56
  },
49
57
  ],
50
58
  }
@@ -70,29 +78,16 @@ export function zyzz(
70
78
  [pattern]: [
71
79
  ...(Array.isArray(existing) ? existing : existing ? [existing] : []),
72
80
  rule,
73
- {
74
- condition: { query: /zyzz-style/ },
75
- loaders: [
76
- {
77
- loader,
78
- options: {
79
- ...loaderOptions,
80
- bundler: 'turbopack',
81
- mode: 'style',
82
- },
83
- },
84
- ],
85
- as: '*.css',
86
- },
87
81
  ],
88
- '**/.zyzz/next/shared.css': {
82
+ '**/.zyzz/next/style.css': {
83
+ condition: { query: /zyzz=/ },
89
84
  loaders: [
90
85
  {
91
86
  loader,
92
87
  options: {
93
88
  ...loaderOptions,
94
89
  bundler: 'turbopack',
95
- mode: 'shared',
90
+ mode: 'style',
96
91
  },
97
92
  },
98
93
  ],
@@ -111,7 +106,7 @@ export function zyzz(
111
106
  use: [
112
107
  {
113
108
  loader,
114
- options: { ...loaderOptions, bundler: 'webpack', mode: 'source' },
109
+ options: { ...loaderOptions, bundler: 'webpack' },
115
110
  },
116
111
  ],
117
112
  })