zyzz 0.0.24 → 0.0.25
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.
- package/README.md +23 -365
- package/dist/Style.d.ts +2 -0
- package/dist/Style.d.ts.map +1 -1
- package/dist/compiler/Graph.d.ts +3 -0
- package/dist/compiler/Graph.d.ts.map +1 -1
- package/dist/compiler/Graph.js +38 -2
- package/dist/compiler/Graph.js.map +1 -1
- package/dist/compiler/Source.d.ts.map +1 -1
- package/dist/compiler/Source.js +11 -3
- package/dist/compiler/Source.js.map +1 -1
- package/dist/compiler/Transform.d.ts +4 -0
- package/dist/compiler/Transform.d.ts.map +1 -1
- package/dist/compiler/Transform.js +79 -15
- package/dist/compiler/Transform.js.map +1 -1
- package/dist/compiler/internal/Applications.d.ts +20 -0
- package/dist/compiler/internal/Applications.d.ts.map +1 -1
- package/dist/compiler/internal/Applications.js +151 -47
- package/dist/compiler/internal/Applications.js.map +1 -1
- package/dist/compiler/internal/Compositions.d.ts.map +1 -1
- package/dist/compiler/internal/Compositions.js +6 -0
- package/dist/compiler/internal/Compositions.js.map +1 -1
- package/dist/compiler/internal/Configurations.d.ts.map +1 -1
- package/dist/compiler/internal/Configurations.js +4 -0
- package/dist/compiler/internal/Configurations.js.map +1 -1
- package/dist/compiler/internal/Contract.d.ts.map +1 -1
- package/dist/compiler/internal/Contract.js +34 -7
- package/dist/compiler/internal/Contract.js.map +1 -1
- package/dist/compiler/internal/PackedStyles.d.ts.map +1 -1
- package/dist/compiler/internal/PackedStyles.js +7 -0
- package/dist/compiler/internal/PackedStyles.js.map +1 -1
- package/dist/compiler/internal/Themes.d.ts.map +1 -1
- package/dist/compiler/internal/Themes.js +20 -8
- package/dist/compiler/internal/Themes.js.map +1 -1
- package/dist/internal/Configuration.d.ts +10 -0
- package/dist/internal/Configuration.d.ts.map +1 -1
- package/dist/internal/Configuration.js +8 -0
- package/dist/internal/Configuration.js.map +1 -1
- package/dist/internal/Token.d.ts +2 -0
- package/dist/internal/Token.d.ts.map +1 -1
- package/dist/internal/Token.js.map +1 -1
- package/dist/next/internal/Project.d.ts.map +1 -1
- package/dist/next/internal/Project.js +30 -25
- package/dist/next/internal/Project.js.map +1 -1
- package/dist/next/loader.js +2 -1
- package/dist/next/loader.js.map +1 -1
- package/dist/node/Host.d.ts.map +1 -1
- package/dist/node/Host.js +32 -23
- package/dist/node/Host.js.map +1 -1
- package/dist/node/internal/Contracts.d.ts +22 -0
- package/dist/node/internal/Contracts.d.ts.map +1 -0
- package/dist/node/internal/Contracts.js +287 -0
- package/dist/node/internal/Contracts.js.map +1 -0
- package/dist/unplugin/index.d.ts.map +1 -1
- package/dist/unplugin/index.js +31 -13
- package/dist/unplugin/index.js.map +1 -1
- package/dist/vite/index.d.ts.map +1 -1
- package/dist/vite/index.js +30 -37
- package/dist/vite/index.js.map +1 -1
- package/dist/web/Css.d.ts +10 -5
- package/dist/web/Css.d.ts.map +1 -1
- package/dist/web/Css.js +170 -31
- package/dist/web/Css.js.map +1 -1
- package/dist/web/internal/ClassName.d.ts +49 -0
- package/dist/web/internal/ClassName.d.ts.map +1 -1
- package/dist/web/internal/ClassName.js +57 -0
- package/dist/web/internal/ClassName.js.map +1 -1
- package/package.json +1 -1
- package/src/Style.ts +2 -0
- package/src/compiler/Graph.ts +42 -2
- package/src/compiler/Source.ts +11 -3
- package/src/compiler/Transform.ts +130 -29
- package/src/compiler/internal/Applications.ts +239 -63
- package/src/compiler/internal/Compositions.ts +9 -0
- package/src/compiler/internal/Configurations.ts +4 -0
- package/src/compiler/internal/Contract.ts +59 -7
- package/src/compiler/internal/PackedStyles.ts +9 -0
- package/src/compiler/internal/Themes.ts +25 -7
- package/src/internal/Configuration.ts +21 -0
- package/src/internal/Token.ts +2 -0
- package/src/next/internal/Project.ts +28 -23
- package/src/next/loader.ts +2 -1
- package/src/node/Host.ts +31 -24
- package/src/node/internal/Contracts.ts +384 -0
- package/src/unplugin/index.ts +35 -13
- package/src/vite/index.ts +35 -36
- package/src/web/Css.ts +228 -42
- package/src/web/internal/ClassName.ts +114 -0
package/src/unplugin/index.ts
CHANGED
|
@@ -11,7 +11,9 @@ import type { UnpluginBuildContext } from 'unplugin'
|
|
|
11
11
|
import * as Graph from '../compiler/Graph.js'
|
|
12
12
|
import * as AtRules from '../compiler/internal/AtRules.js'
|
|
13
13
|
import * as Catalogs from '../compiler/internal/Catalogs.js'
|
|
14
|
+
import * as ClassName from '../web/internal/ClassName.js'
|
|
14
15
|
import * as Contract from '../compiler/internal/Contract.js'
|
|
16
|
+
import * as Contracts from '../node/internal/Contracts.js'
|
|
15
17
|
import * as Vite from '../vite/index.js'
|
|
16
18
|
import * as ThemeRules from '../web/internal/Themes.js'
|
|
17
19
|
|
|
@@ -32,6 +34,7 @@ const portable = createUnplugin<Options | undefined, false>(
|
|
|
32
34
|
(options = {}, meta) => {
|
|
33
35
|
const root = Path.resolve(options.root ?? process.cwd())
|
|
34
36
|
const compiler = Graph.create()
|
|
37
|
+
const packages = Contracts.create()
|
|
35
38
|
const snapshot = Snapshot.create()
|
|
36
39
|
let graph: Graph.compile.ReturnType | undefined
|
|
37
40
|
let pending: Promise<void> | undefined
|
|
@@ -204,20 +207,17 @@ const portable = createUnplugin<Options | undefined, false>(
|
|
|
204
207
|
required = false,
|
|
205
208
|
): Promise<boolean> {
|
|
206
209
|
if (Object.hasOwn(contracts, file)) return true
|
|
207
|
-
const sidecar = `${file}.zyzz.json`
|
|
208
210
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
throw error
|
|
220
|
-
}
|
|
211
|
+
const source = await packages.read(file, {
|
|
212
|
+
read(path) {
|
|
213
|
+
files.add(path)
|
|
214
|
+
return snapshot.read(path)
|
|
215
|
+
},
|
|
216
|
+
required,
|
|
217
|
+
resolve,
|
|
218
|
+
})
|
|
219
|
+
if (source === undefined) return false
|
|
220
|
+
contracts[file] = source
|
|
221
221
|
const metadata = Contract.read(contracts[file]!, new Map(), file)
|
|
222
222
|
for (const section of metadata.stylesheets) {
|
|
223
223
|
let owner = file
|
|
@@ -287,6 +287,7 @@ const portable = createUnplugin<Options | undefined, false>(
|
|
|
287
287
|
|
|
288
288
|
// Only the combined stylesheet is emitted, so generated theme rules are shared once.
|
|
289
289
|
const result = compiler.compile({
|
|
290
|
+
[ClassName.complete]: true,
|
|
290
291
|
[ThemeRules.shared]: 'all',
|
|
291
292
|
[Syntax.cache]: snapshot.programs(modules),
|
|
292
293
|
compiler: options.compiler,
|
|
@@ -354,6 +355,7 @@ const portable = createUnplugin<Options | undefined, false>(
|
|
|
354
355
|
boundary: string,
|
|
355
356
|
outputDirectory = '.',
|
|
356
357
|
targets?: Readonly<Record<string, string>>,
|
|
358
|
+
emitted: ReadonlySet<string> = new Set(),
|
|
357
359
|
) {
|
|
358
360
|
const urls = new Set<string>()
|
|
359
361
|
AtRules.transform({
|
|
@@ -405,6 +407,18 @@ const portable = createUnplugin<Options | undefined, false>(
|
|
|
405
407
|
: { inputSourceMap: map, sourceMap: true }),
|
|
406
408
|
visitor: {
|
|
407
409
|
Rule(rule) {
|
|
410
|
+
// Shared rules repeat in each module that uses them, so keep the first.
|
|
411
|
+
if (rule.type === 'style') {
|
|
412
|
+
const [selector, ...others] = rule.value.selectors
|
|
413
|
+
const [component, ...rest] = selector ?? []
|
|
414
|
+
if (
|
|
415
|
+
!others.length &&
|
|
416
|
+
!rest.length &&
|
|
417
|
+
component?.type === 'class' &&
|
|
418
|
+
emitted.has(component.name)
|
|
419
|
+
)
|
|
420
|
+
return []
|
|
421
|
+
}
|
|
408
422
|
if (rule.type !== 'import') return
|
|
409
423
|
const url = replacements.get(rule.value.url)
|
|
410
424
|
if (url) return AtRules.relocateImport(rule.value, url)
|
|
@@ -435,6 +449,7 @@ const portable = createUnplugin<Options | undefined, false>(
|
|
|
435
449
|
},
|
|
436
450
|
]
|
|
437
451
|
const seen = new Set<string>()
|
|
452
|
+
const units = new Set<string>()
|
|
438
453
|
async function visit(id: string): Promise<void> {
|
|
439
454
|
if (seen.has(id)) return
|
|
440
455
|
seen.add(id)
|
|
@@ -442,12 +457,19 @@ const portable = createUnplugin<Options | undefined, false>(
|
|
|
442
457
|
await visit(dependency)
|
|
443
458
|
const output = result.modules[id]
|
|
444
459
|
if (output) {
|
|
460
|
+
const names = [...(output[ClassName.units]?.values() ?? [])].map(
|
|
461
|
+
(decision) => decision.name,
|
|
462
|
+
)
|
|
445
463
|
const processed = await stylesheet(
|
|
446
464
|
output.css,
|
|
447
465
|
JSON.stringify(output.cssMap),
|
|
448
466
|
Path.join(root, id.slice(4)),
|
|
449
467
|
await Fs.realpath(root),
|
|
468
|
+
'.',
|
|
469
|
+
undefined,
|
|
470
|
+
new Set(names.filter((name) => units.has(name))),
|
|
450
471
|
)
|
|
472
|
+
for (const name of names) units.add(name)
|
|
451
473
|
chunks.push({
|
|
452
474
|
code: Buffer.from(processed.code).toString(),
|
|
453
475
|
map: Buffer.from(processed.map!).toString(),
|
package/src/vite/index.ts
CHANGED
|
@@ -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 Contracts from '../node/internal/Contracts.js'
|
|
8
9
|
import * as Mapping from '@jridgewell/gen-mapping'
|
|
9
10
|
import * as Lightning from 'lightningcss'
|
|
10
11
|
import * as Crypto from 'node:crypto'
|
|
@@ -51,6 +52,7 @@ export function zyzz(options: zyzz.Options = {}): Plugin {
|
|
|
51
52
|
const removed = new WeakMap<Environment, Set<string>>()
|
|
52
53
|
const reads = new WeakMap<Environment, Map<string, string>>()
|
|
53
54
|
const compilers = new WeakMap<Environment, Graph.create.ReturnType>()
|
|
55
|
+
const packages = Contracts.create()
|
|
54
56
|
|
|
55
57
|
function compiler(environment: Environment) {
|
|
56
58
|
let value = compilers.get(environment)
|
|
@@ -414,6 +416,16 @@ export function zyzz(options: zyzz.Options = {}): Plugin {
|
|
|
414
416
|
const lazy: Record<string, ReadonlySet<string>> = Object.create(null)
|
|
415
417
|
const modules: Record<string, string> = Object.create(null)
|
|
416
418
|
const files = new Set<string>()
|
|
419
|
+
const contractHost: Contracts.read.Options = {
|
|
420
|
+
read: async (file) => {
|
|
421
|
+
const source = await snapshot(entry.environment).read(file)
|
|
422
|
+
host.watch(file)
|
|
423
|
+
files.add(file)
|
|
424
|
+
return source
|
|
425
|
+
},
|
|
426
|
+
resolve: async (specifier, importer) =>
|
|
427
|
+
(await resolve(specifier, importer))?.id.split(/[?#]/)[0],
|
|
428
|
+
}
|
|
417
429
|
|
|
418
430
|
async function visit(file: string, source?: string) {
|
|
419
431
|
if (files.has(file)) return
|
|
@@ -512,19 +524,15 @@ export function zyzz(options: zyzz.Options = {}): Plugin {
|
|
|
512
524
|
}
|
|
513
525
|
if (resolved.external || !eligible(resolved.id)) {
|
|
514
526
|
const physical = resolved.id.split('?')[0]!.split('#')[0]!
|
|
515
|
-
const
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
} catch (error) {
|
|
525
|
-
if ((error as NodeJS.ErrnoException).code !== 'ENOENT')
|
|
526
|
-
throw error
|
|
527
|
-
}
|
|
527
|
+
const contract =
|
|
528
|
+
Path.isAbsolute(physical) && /\.[cm]?[jt]sx?$/.test(physical)
|
|
529
|
+
? await packages.read(physical, contractHost)
|
|
530
|
+
: undefined
|
|
531
|
+
|
|
532
|
+
if (contract !== undefined) {
|
|
533
|
+
contracts[resolved.id] = contract
|
|
534
|
+
resolutions[specifier] = resolved.id
|
|
535
|
+
continue
|
|
528
536
|
}
|
|
529
537
|
|
|
530
538
|
resolutions[specifier] = null
|
|
@@ -649,25 +657,19 @@ export function zyzz(options: zyzz.Options = {}): Plugin {
|
|
|
649
657
|
if (!Path.isAbsolute(physical) || !/\.[cm]?[jt]sx?$/.test(physical))
|
|
650
658
|
continue
|
|
651
659
|
|
|
652
|
-
|
|
653
|
-
|
|
660
|
+
const contract = await packages.read(physical, contractHost)
|
|
661
|
+
if (contract === undefined) continue
|
|
654
662
|
|
|
655
|
-
|
|
656
|
-
host.watch(sidecar)
|
|
657
|
-
files.add(sidecar)
|
|
663
|
+
contracts[resolved.id] = contract
|
|
658
664
|
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
665
|
+
// Discover the package contribution without compiling unrelated authoring
|
|
666
|
+
// expressions in an otherwise unreachable source file.
|
|
667
|
+
const discovery = `${sourceId(file)}.zyzz-discovery`
|
|
662
668
|
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
resolved.id
|
|
668
|
-
} catch (error) {
|
|
669
|
-
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error
|
|
670
|
-
}
|
|
669
|
+
modules[discovery] =
|
|
670
|
+
(modules[discovery] ?? '') +
|
|
671
|
+
`import ${JSON.stringify(specifier)};\n`
|
|
672
|
+
;(imports[discovery] ??= Object.create(null))[specifier] = resolved.id
|
|
671
673
|
}
|
|
672
674
|
}
|
|
673
675
|
|
|
@@ -713,13 +715,10 @@ export function zyzz(options: zyzz.Options = {}): Plugin {
|
|
|
713
715
|
imports[owner] ??= Object.create(null)
|
|
714
716
|
imports[owner]![specifier] = target.id
|
|
715
717
|
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
host.watch(sidecar)
|
|
721
|
-
files.add(sidecar)
|
|
722
|
-
}
|
|
718
|
+
contracts[target.id] ??= (await packages.read(
|
|
719
|
+
target.id.split(/[?#]/)[0]!,
|
|
720
|
+
{ ...contractHost, required: true },
|
|
721
|
+
))!
|
|
723
722
|
|
|
724
723
|
await dependencies(target.id)
|
|
725
724
|
owner = target.id
|
package/src/web/Css.ts
CHANGED
|
@@ -21,7 +21,8 @@ export type Contribution = Contributions.Definition
|
|
|
21
21
|
/**
|
|
22
22
|
* Emits atomic or grouped CSS without reading files or generating runtime code.
|
|
23
23
|
* Shares only nonconflicting declaration domains; conflicting rules retain authored
|
|
24
|
-
* order by default. Independent composition deduplicates complete applications
|
|
24
|
+
* order by default. Independent composition deduplicates complete applications and
|
|
25
|
+
* shares equal atomic domain declarations under short names.
|
|
25
26
|
* Class lists are scoped to the complete compilation input. Empty styles
|
|
26
27
|
* return an empty class list and no rule.
|
|
27
28
|
* @param options - Validated, ordered definitions from Style.define.
|
|
@@ -35,6 +36,9 @@ export function compile<
|
|
|
35
36
|
options: compile.Options<name, themeName>,
|
|
36
37
|
): compile.ReturnType<name, themeName> {
|
|
37
38
|
const labels = options[ClassName.labels] ?? options.styles[ClassName.labels]
|
|
39
|
+
const units =
|
|
40
|
+
options[ClassName.units] ??
|
|
41
|
+
ClassName.registry().scope({ owner: '', qualifier: '' })
|
|
38
42
|
options = {
|
|
39
43
|
...options,
|
|
40
44
|
styles: { styles: options.styles.styles.map(Targets.web) },
|
|
@@ -92,11 +96,17 @@ export function compile<
|
|
|
92
96
|
const classes = Object.create(null) as Record<name, string>
|
|
93
97
|
const diagnostics: Diagnostic[] = []
|
|
94
98
|
const groups = new Map<string, false | string>()
|
|
95
|
-
const nestedComposition = options.styles.styles.some(
|
|
99
|
+
const nestedComposition = options.styles.styles.some(
|
|
100
|
+
(style) => style.rules && !isolated(style),
|
|
101
|
+
)
|
|
96
102
|
|
|
97
103
|
const properties = new Set<Style.Declaration['property']>()
|
|
98
|
-
|
|
104
|
+
// Conditional and composed declarations join the same conflict domains.
|
|
105
|
+
function collect(style: Style.NamedStyle): void {
|
|
99
106
|
for (const { property } of style.declarations) properties.add(property)
|
|
107
|
+
for (const rule of style.rules ?? []) collect(rule.style)
|
|
108
|
+
}
|
|
109
|
+
for (const style of analyzed) collect(style)
|
|
100
110
|
|
|
101
111
|
// Logical dimensions may alias either physical axis in inherited writing modes.
|
|
102
112
|
// Preserve physical-only factoring when no logical dimension is authored.
|
|
@@ -361,32 +371,39 @@ export function compile<
|
|
|
361
371
|
|
|
362
372
|
// Sharing is safe only when every use of a conflict domain has the same
|
|
363
373
|
// declaration sequence. Conditions conservatively retain contextual identities.
|
|
374
|
+
// Isolated class lists never combine, so atomic sharing considers ordered styles only.
|
|
375
|
+
const ordered = new Map<string, false | string>()
|
|
376
|
+
const combinable = new Set<Style.NamedStyle>()
|
|
377
|
+
for (const [index, style] of options.styles.styles.entries())
|
|
378
|
+
if (!isolated(style)) combinable.add(canonicalStyles[index]!)
|
|
379
|
+
|
|
364
380
|
for (const style of analyzed) {
|
|
365
|
-
const domains = new Map<
|
|
381
|
+
const domains = new Map<
|
|
382
|
+
string,
|
|
383
|
+
{ body: string; mixed: boolean; property: string }
|
|
384
|
+
>()
|
|
366
385
|
|
|
367
386
|
for (const item of style.declarations) {
|
|
368
387
|
const property = item.property
|
|
369
388
|
const key = conflict(property)
|
|
370
|
-
const
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
body: '',
|
|
374
|
-
property,
|
|
375
|
-
}
|
|
376
|
-
entry.body += declaration
|
|
377
|
-
if (entry.property !== property) groups.set(key, false)
|
|
389
|
+
const entry = domains.get(key) ?? { body: '', mixed: false, property }
|
|
390
|
+
entry.body += declarationBody(item)
|
|
391
|
+
entry.mixed ||= entry.property !== property
|
|
378
392
|
domains.set(key, entry)
|
|
379
393
|
}
|
|
380
394
|
|
|
381
|
-
for (const [key, entry] of domains)
|
|
382
|
-
const
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
395
|
+
for (const [key, entry] of domains)
|
|
396
|
+
for (const map of combinable.has(style) ? [groups, ordered] : [groups]) {
|
|
397
|
+
const previous = map.get(key)
|
|
398
|
+
map.set(
|
|
399
|
+
key,
|
|
400
|
+
previous === false ||
|
|
401
|
+
entry.mixed ||
|
|
402
|
+
(previous !== undefined && previous !== entry.body)
|
|
403
|
+
? false
|
|
404
|
+
: entry.body,
|
|
405
|
+
)
|
|
406
|
+
}
|
|
390
407
|
}
|
|
391
408
|
|
|
392
409
|
const defaultMode = options.cssOutput ?? 'atomic'
|
|
@@ -406,6 +423,75 @@ export function compile<
|
|
|
406
423
|
| undefined
|
|
407
424
|
}
|
|
408
425
|
|
|
426
|
+
function compositionMode(style: Style.NamedStyle) {
|
|
427
|
+
const mode = Object.getOwnPropertyDescriptor(style, 'composition')
|
|
428
|
+
?.value as 'independent' | 'ordered' | undefined
|
|
429
|
+
|
|
430
|
+
return mode ?? options.composition ?? 'ordered'
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Development keeps every style ordered so names survive value edits.
|
|
434
|
+
function isolated(style: Style.NamedStyle) {
|
|
435
|
+
return compositionMode(style) === 'independent' && !options.development
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
type Item = {
|
|
439
|
+
readonly conditions: readonly string[]
|
|
440
|
+
readonly property: string
|
|
441
|
+
readonly value: string
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Lists declaration groups in authored order; undefined marks styles whose
|
|
445
|
+
// nested grouped output or anonymous layers emit one opaque rule.
|
|
446
|
+
function flatten(
|
|
447
|
+
style: Style.NamedStyle,
|
|
448
|
+
conditions: readonly string[] = [],
|
|
449
|
+
): Item[] | undefined {
|
|
450
|
+
if (outputMode(style) === 'grouped') return undefined
|
|
451
|
+
|
|
452
|
+
if (style.rules) {
|
|
453
|
+
const items: Item[] = []
|
|
454
|
+
for (const rule of style.rules) {
|
|
455
|
+
if (rule.condition?.trim() === '@layer') return undefined
|
|
456
|
+
|
|
457
|
+
const nested = flatten(
|
|
458
|
+
rule.style,
|
|
459
|
+
rule.condition === undefined
|
|
460
|
+
? conditions
|
|
461
|
+
: [...conditions, rule.condition],
|
|
462
|
+
)
|
|
463
|
+
if (!nested) return undefined
|
|
464
|
+
|
|
465
|
+
items.push(...nested)
|
|
466
|
+
}
|
|
467
|
+
return items
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
const items: Item[] = []
|
|
471
|
+
for (let index = 0; index < style.declarations.length; ) {
|
|
472
|
+
const property = style.declarations[index]!.property
|
|
473
|
+
let value = ''
|
|
474
|
+
// Keep same-property fallbacks ordered; they form one semantic value.
|
|
475
|
+
while (style.declarations[index]?.property === property) {
|
|
476
|
+
const declaration = style.declarations[index++]!
|
|
477
|
+
value += `${Literal.name(property)}:${serialize(declaration.value)}${declaration.important ? '!important' : ''};`
|
|
478
|
+
}
|
|
479
|
+
items.push({ conditions, property, value })
|
|
480
|
+
}
|
|
481
|
+
return items
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function byDomain(items: readonly Item[]) {
|
|
485
|
+
const result = new Map<string, Item[]>()
|
|
486
|
+
for (const item of items) {
|
|
487
|
+
const key = conflict(item.property)
|
|
488
|
+
const domain = result.get(key)
|
|
489
|
+
if (domain) domain.push(item)
|
|
490
|
+
else result.set(key, [item])
|
|
491
|
+
}
|
|
492
|
+
return result
|
|
493
|
+
}
|
|
494
|
+
|
|
409
495
|
function representation(style: Style.NamedStyle): unknown {
|
|
410
496
|
return [
|
|
411
497
|
outputMode(style),
|
|
@@ -444,8 +530,48 @@ export function compile<
|
|
|
444
530
|
occurrences.set(key, (occurrences.get(key) ?? 0) + 1)
|
|
445
531
|
}
|
|
446
532
|
|
|
533
|
+
// An ordered atomic style reuses the rules of the previous style that set a
|
|
534
|
+
// domain to the same sequence: no differing rule sits between them.
|
|
535
|
+
const runs = new Map<number, Map<string, number>>()
|
|
536
|
+
const owned = new Map<number, Map<string, string[]>>()
|
|
537
|
+
if (!options.development) {
|
|
538
|
+
const last = new Map<string, { key: string; owner: number }>()
|
|
539
|
+
|
|
540
|
+
for (const [index, style] of options.styles.styles.entries()) {
|
|
541
|
+
if (isolated(style)) continue
|
|
542
|
+
|
|
543
|
+
const items =
|
|
544
|
+
(outputMode(style) ?? defaultMode) === 'atomic' &&
|
|
545
|
+
options.names?.[style.name] === undefined
|
|
546
|
+
? flatten(style)
|
|
547
|
+
: undefined
|
|
548
|
+
// Grouped, explicit, and opaque rules may set any domain.
|
|
549
|
+
if (!items) {
|
|
550
|
+
last.clear()
|
|
551
|
+
continue
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
for (const [domain, list] of byDomain(items)) {
|
|
555
|
+
const key = JSON.stringify(
|
|
556
|
+
list.map((item) => [item.conditions, item.value]),
|
|
557
|
+
)
|
|
558
|
+
const previous = last.get(domain)
|
|
559
|
+
if (previous?.key !== key) {
|
|
560
|
+
last.set(domain, { key, owner: index })
|
|
561
|
+
continue
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
const reuse = runs.get(index) ?? new Map<string, number>()
|
|
565
|
+
reuse.set(domain, previous.owner)
|
|
566
|
+
runs.set(index, reuse)
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
447
571
|
for (const [styleIndex, style] of options.styles.styles.entries()) {
|
|
448
572
|
const mode = outputMode(style) ?? defaultMode
|
|
573
|
+
const independent =
|
|
574
|
+
compositionMode(style) === 'independent' && !options.development
|
|
449
575
|
if (!style.name || Object.hasOwn(classes, style.name)) {
|
|
450
576
|
diagnostics.push({
|
|
451
577
|
code: 'invalid_name',
|
|
@@ -473,9 +599,7 @@ export function compile<
|
|
|
473
599
|
}
|
|
474
600
|
|
|
475
601
|
const application =
|
|
476
|
-
explicit === undefined &&
|
|
477
|
-
options.composition === 'independent' &&
|
|
478
|
-
!options.development
|
|
602
|
+
explicit === undefined && independent
|
|
479
603
|
? `${mode}:${style.rules ? JSON.stringify(representation(style)) : ''}:${nested(canonicalStyles[styleIndex]!)}`
|
|
480
604
|
: undefined
|
|
481
605
|
if (application !== undefined && applications.has(application)) {
|
|
@@ -501,19 +625,16 @@ export function compile<
|
|
|
501
625
|
shared: boolean,
|
|
502
626
|
output = mode,
|
|
503
627
|
conditional?: ConditionalRule,
|
|
504
|
-
) {
|
|
505
|
-
if (!body) return
|
|
506
|
-
|
|
507
|
-
const
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
output === mode &&
|
|
511
|
-
options.composition === 'independent'
|
|
512
|
-
const reusable = explicit === undefined && (shared || independent)
|
|
628
|
+
): string | undefined {
|
|
629
|
+
if (!body) return undefined
|
|
630
|
+
|
|
631
|
+
const reusable =
|
|
632
|
+
explicit === undefined &&
|
|
633
|
+
(shared || (independent && mode === 'grouped' && output === mode))
|
|
513
634
|
const previous = reusable ? sharedRules![output].get(body) : undefined
|
|
514
635
|
if (previous && !names.includes(previous)) {
|
|
515
636
|
names.push(previous)
|
|
516
|
-
return
|
|
637
|
+
return previous
|
|
517
638
|
}
|
|
518
639
|
|
|
519
640
|
if (previous) shared = false
|
|
@@ -562,6 +683,8 @@ export function compile<
|
|
|
562
683
|
if (reusable) sharedRules![output].set(body, identity)
|
|
563
684
|
names.push(identity)
|
|
564
685
|
}
|
|
686
|
+
|
|
687
|
+
return identity
|
|
565
688
|
}
|
|
566
689
|
|
|
567
690
|
function atoms(
|
|
@@ -605,6 +728,16 @@ export function compile<
|
|
|
605
728
|
while (style.declarations[index]?.property === property)
|
|
606
729
|
values.push(style.declarations[index++]!)
|
|
607
730
|
|
|
731
|
+
const domain = conflict(property)
|
|
732
|
+
const owner = runs.get(styleIndex)?.get(domain)
|
|
733
|
+
const reused =
|
|
734
|
+
owner === undefined ? undefined : owned.get(owner)?.get(domain)
|
|
735
|
+
if (reused) {
|
|
736
|
+
for (const identity of reused)
|
|
737
|
+
if (!names.includes(identity)) names.push(identity)
|
|
738
|
+
continue
|
|
739
|
+
}
|
|
740
|
+
|
|
608
741
|
const value = values
|
|
609
742
|
.map(
|
|
610
743
|
({ important, value }) =>
|
|
@@ -618,8 +751,54 @@ export function compile<
|
|
|
618
751
|
const shared =
|
|
619
752
|
!nestedComposition &&
|
|
620
753
|
!conditions.length &&
|
|
621
|
-
|
|
622
|
-
emit(body, property, shared, mode, {
|
|
754
|
+
ordered.get(domain) !== false
|
|
755
|
+
const identity = emit(body, property, shared, mode, {
|
|
756
|
+
conditions,
|
|
757
|
+
value,
|
|
758
|
+
})
|
|
759
|
+
if (identity === undefined) continue
|
|
760
|
+
|
|
761
|
+
const domains = owned.get(styleIndex) ?? new Map<string, string[]>()
|
|
762
|
+
domains.set(domain, [...(domains.get(domain) ?? []), identity])
|
|
763
|
+
owned.set(styleIndex, domains)
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
// Independent class lists never combine, so only declarations within one
|
|
768
|
+
// conflict domain need relative order. Equal domain bodies share one rule.
|
|
769
|
+
function share(style: Style.NamedStyle) {
|
|
770
|
+
const items = flatten(style)
|
|
771
|
+
if (!items) return atoms(style)
|
|
772
|
+
|
|
773
|
+
for (const domain of byDomain(items).values()) {
|
|
774
|
+
// Conditional rules stay contextual to keep their order against base declarations.
|
|
775
|
+
if (domain.some((item) => item.conditions.length)) {
|
|
776
|
+
for (const item of domain)
|
|
777
|
+
emit(
|
|
778
|
+
item.conditions.reduceRight(
|
|
779
|
+
(body, condition) => `${condition}{${body}}`,
|
|
780
|
+
item.value,
|
|
781
|
+
),
|
|
782
|
+
item.property,
|
|
783
|
+
false,
|
|
784
|
+
mode,
|
|
785
|
+
item,
|
|
786
|
+
)
|
|
787
|
+
continue
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
const body = domain.map((item) => item.value).join('')
|
|
791
|
+
const identity = units.name(body, cssNamespace)
|
|
792
|
+
if (rules.has(identity) && rules.get(identity) !== body)
|
|
793
|
+
diagnostics.push({
|
|
794
|
+
code: 'identity_collision',
|
|
795
|
+
message:
|
|
796
|
+
'Generated class names conflict. Supply distinct config or style ids.',
|
|
797
|
+
path: [style.name],
|
|
798
|
+
})
|
|
799
|
+
|
|
800
|
+
rules.set(identity, body)
|
|
801
|
+
names.push(identity)
|
|
623
802
|
}
|
|
624
803
|
}
|
|
625
804
|
|
|
@@ -631,7 +810,7 @@ export function compile<
|
|
|
631
810
|
const key = conflict(declaration.property)
|
|
632
811
|
const reusable =
|
|
633
812
|
!style.rules &&
|
|
634
|
-
|
|
813
|
+
independent &&
|
|
635
814
|
groups.get(key) !== false &&
|
|
636
815
|
(occurrences.get(key) ?? 0) > 1
|
|
637
816
|
if (reusable) shared.push(declaration)
|
|
@@ -641,7 +820,8 @@ export function compile<
|
|
|
641
820
|
emit(shared.map(declarationBody).join(''), 'shared', false)
|
|
642
821
|
emit(local.map(declarationBody).join(''), 'style', false)
|
|
643
822
|
} else emit(nested(style), 'style', false)
|
|
644
|
-
} else
|
|
823
|
+
} else if (explicit === undefined && independent) share(style)
|
|
824
|
+
else atoms(style)
|
|
645
825
|
} catch (error) {
|
|
646
826
|
diagnostics.push({
|
|
647
827
|
code: 'invalid_declaration',
|
|
@@ -730,6 +910,7 @@ export function compile<
|
|
|
730
910
|
const result = {
|
|
731
911
|
...(scopes.resources.length ? { [Themes.shared]: scopes.resources } : {}),
|
|
732
912
|
...(contributionCss ? { contributionCss, scopedCss } : {}),
|
|
913
|
+
[ClassName.units]: units.used,
|
|
733
914
|
[ClassName.rules]: Object.freeze({
|
|
734
915
|
...Object.fromEntries(rules),
|
|
735
916
|
...Object.fromEntries(
|
|
@@ -744,6 +925,7 @@ export function compile<
|
|
|
744
925
|
vars: scopes.classes as Readonly<Record<themeName, string>>,
|
|
745
926
|
}
|
|
746
927
|
Object.defineProperty(result, ClassName.rules, { enumerable: false })
|
|
928
|
+
Object.defineProperty(result, ClassName.units, { enumerable: false })
|
|
747
929
|
return Object.freeze(result)
|
|
748
930
|
}
|
|
749
931
|
|
|
@@ -761,18 +943,20 @@ export declare namespace compile {
|
|
|
761
943
|
readonly [Themes.shared]?: 'all' | 'defaults' | undefined
|
|
762
944
|
/** Readable source bindings and consumer-owned configuration namespaces. */
|
|
763
945
|
readonly [ClassName.labels]?: ClassName.Labels | undefined
|
|
946
|
+
/** Names shared independent atomic rules, with one name table across a graph's modules. */
|
|
947
|
+
readonly [ClassName.units]?: ClassName.Units | undefined
|
|
764
948
|
/** Fixed class identities used by CSS-only consumers. */
|
|
765
949
|
readonly names?: Readonly<Record<string, string>> | undefined
|
|
766
950
|
/**
|
|
767
|
-
*
|
|
768
|
-
* Independent
|
|
769
|
-
*
|
|
951
|
+
* Ordered by default, preserving precedence across combined class lists.
|
|
952
|
+
* Independent shares applications and atomic declarations, so resolve
|
|
953
|
+
* composition first and never combine its lists.
|
|
770
954
|
*/
|
|
955
|
+
readonly composition?: 'independent' | 'ordered' | undefined
|
|
771
956
|
/** Eager module-level stylesheet contributions, supplied as static data. */
|
|
772
957
|
readonly contributions?: readonly Contribution[] | undefined
|
|
773
958
|
/** CSS representation; atomic declarations are the default. */
|
|
774
959
|
readonly cssOutput?: 'atomic' | 'grouped' | undefined
|
|
775
|
-
readonly composition?: 'independent' | 'ordered' | undefined
|
|
776
960
|
/** Retains live definitions for development updates. */
|
|
777
961
|
readonly development?: boolean | undefined
|
|
778
962
|
/**
|
|
@@ -797,6 +981,8 @@ export declare namespace compile {
|
|
|
797
981
|
> = {
|
|
798
982
|
/** Exact emitted rule bodies retained for graph-wide collision diagnostics. */
|
|
799
983
|
readonly [ClassName.rules]: Readonly<Record<string, string>>
|
|
984
|
+
/** Shared rule names chosen by this compilation, for graph cache validation. */
|
|
985
|
+
readonly [ClassName.units]: ClassName.Units['used']
|
|
800
986
|
/** Shared token resources omitted from this module's stylesheet. */
|
|
801
987
|
readonly [Themes.shared]?: readonly Themes.Resource[] | undefined
|
|
802
988
|
/** Contribution text separated for graph-wide hoisting. */
|