zyzz 0.0.23 → 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 +10 -1
- 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 +64 -27
- 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/react-native/internal/Device.js +90 -37
- package/dist/react-native/internal/Device.js.map +3 -3
- package/dist/react-native/internal/NativeZyzz.js.map +2 -2
- package/dist/react-native/internal/Store.d.ts +4 -0
- package/dist/react-native/internal/Store.d.ts.map +1 -1
- package/dist/react-native/internal/Store.js.map +1 -1
- package/dist/react-native/internal/Viewport.d.ts +6 -0
- package/dist/react-native/internal/Viewport.d.ts.map +1 -1
- package/dist/react-native/internal/Viewport.js +2 -0
- package/dist/react-native/internal/Viewport.js.map +1 -1
- package/dist/react-native/react.d.ts +5 -2
- package/dist/react-native/react.d.ts.map +1 -1
- package/dist/react-native/react.js +60 -8
- package/dist/react-native/react.js.map +1 -1
- package/dist/runtime/NativeContext.d.ts +6 -0
- package/dist/runtime/NativeContext.d.ts.map +1 -1
- package/dist/runtime/NativeContext.js +62 -9
- package/dist/runtime/NativeContext.js.map +1 -1
- package/dist/unplugin/index.d.ts.map +1 -1
- package/dist/unplugin/index.js +35 -14
- package/dist/unplugin/index.js.map +1 -1
- package/dist/variants.d.ts +3 -2
- package/dist/variants.d.ts.map +1 -1
- package/dist/variants.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/native/NativeZyzz.cpp +52 -65
- package/native/NativeZyzz.h +0 -4
- package/native/ZyzzSpecJSI.h +0 -10
- package/native/android/ZyzzSpec-generated.cpp +0 -6
- 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 +23 -1
- 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 +90 -36
- package/src/node/internal/Contracts.ts +384 -0
- package/src/react-native/internal/Device.ts +129 -48
- package/src/react-native/internal/NativeZyzz.ts +4 -6
- package/src/react-native/internal/Store.ts +4 -0
- package/src/react-native/internal/Viewport.ts +7 -0
- package/src/react-native/react.ts +81 -11
- package/src/runtime/NativeContext.ts +83 -9
- package/src/unplugin/index.ts +39 -14
- package/src/variants.ts +3 -2
- package/src/vite/index.ts +35 -36
- package/src/web/Css.ts +228 -42
- package/src/web/internal/ClassName.ts +114 -0
|
@@ -9,6 +9,7 @@ import type * as Ast from '@oxc-project/types'
|
|
|
9
9
|
import * as Css from '../web/Css.js'
|
|
10
10
|
import * as ClassName from '../web/internal/ClassName.js'
|
|
11
11
|
import * as Expression from './internal/Expression.js'
|
|
12
|
+
import * as Identity from '../internal/Identity.js'
|
|
12
13
|
import MagicString from 'magic-string'
|
|
13
14
|
import * as Mapping from '@jridgewell/gen-mapping'
|
|
14
15
|
import * as Namespaces from './internal/Namespaces.js'
|
|
@@ -183,6 +184,47 @@ export function compile(options: compile.Options): compile.ReturnType {
|
|
|
183
184
|
},
|
|
184
185
|
})
|
|
185
186
|
|
|
187
|
+
const found = localApplications?.find() ?? []
|
|
188
|
+
// Compositions supersede their inputs' classes, so those reads stay unfolded.
|
|
189
|
+
const composing = (application: Span) =>
|
|
190
|
+
extracted.calls.some(
|
|
191
|
+
(call) =>
|
|
192
|
+
(call.composition || call.runtimeComposition) &&
|
|
193
|
+
call.start < application.start &&
|
|
194
|
+
application.end <= call.end,
|
|
195
|
+
)
|
|
196
|
+
const folded = new Set(
|
|
197
|
+
found.filter(
|
|
198
|
+
(application) =>
|
|
199
|
+
!composing(application) &&
|
|
200
|
+
!extracted.calls.some(
|
|
201
|
+
(call) =>
|
|
202
|
+
call.output === 'html' &&
|
|
203
|
+
call.runtimeComposition?.some(
|
|
204
|
+
(input) => input.applicationStart === application.start,
|
|
205
|
+
),
|
|
206
|
+
),
|
|
207
|
+
),
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
const local =
|
|
211
|
+
portable || options.development
|
|
212
|
+
? undefined
|
|
213
|
+
: localApplications?.definitions()
|
|
214
|
+
|
|
215
|
+
// Styles applied only alone to DOM elements, or inside cx, never meet another
|
|
216
|
+
// style's classes. Atomic ones share rules like independent styles.
|
|
217
|
+
const standalone = new Set(
|
|
218
|
+
[...(local?.values.keys() ?? [])].filter((name) =>
|
|
219
|
+
found.every(
|
|
220
|
+
(application) =>
|
|
221
|
+
application.name !== name ||
|
|
222
|
+
application.terminal ||
|
|
223
|
+
composing(application),
|
|
224
|
+
),
|
|
225
|
+
),
|
|
226
|
+
)
|
|
227
|
+
|
|
186
228
|
// Scheme rules accompany modules whose runtime helpers can apply a scheme class.
|
|
187
229
|
const schemes =
|
|
188
230
|
options.schemes ??
|
|
@@ -196,6 +238,12 @@ export function compile(options: compile.Options): compile.ReturnType {
|
|
|
196
238
|
[ThemeRules.shared]: options[ThemeRules.shared],
|
|
197
239
|
development: options.development,
|
|
198
240
|
[ClassName.labels]: extracted.styles[ClassName.labels],
|
|
241
|
+
[ClassName.units]:
|
|
242
|
+
options[ClassName.units] ??
|
|
243
|
+
ClassName.registry().scope({
|
|
244
|
+
owner: options.moduleId,
|
|
245
|
+
qualifier: Identity.compact(options.moduleId).slice(-6),
|
|
246
|
+
}),
|
|
199
247
|
composition: options.composition,
|
|
200
248
|
cssOutput: options.cssOutput,
|
|
201
249
|
names: portable ? portableNames : undefined,
|
|
@@ -206,14 +254,23 @@ export function compile(options: compile.Options): compile.ReturnType {
|
|
|
206
254
|
if (portable || options.development) return extracted.styles
|
|
207
255
|
|
|
208
256
|
const dead = localApplications?.dead()
|
|
209
|
-
if (!dead?.size) return extracted.styles
|
|
257
|
+
if (!dead?.size && !standalone.size) return extracted.styles
|
|
210
258
|
|
|
211
259
|
return {
|
|
212
|
-
styles: extracted.styles.styles.map((style) =>
|
|
213
|
-
dead
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
260
|
+
styles: extracted.styles.styles.map((style) => {
|
|
261
|
+
if (dead?.has(style.name))
|
|
262
|
+
return {
|
|
263
|
+
cssOutput: style.cssOutput,
|
|
264
|
+
declarations: [],
|
|
265
|
+
name: style.name,
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Grouped output keeps one class per style.
|
|
269
|
+
return standalone.has(style.name) &&
|
|
270
|
+
(style.cssOutput ?? options.cssOutput ?? 'atomic') === 'atomic'
|
|
271
|
+
? { ...style, composition: 'independent' as const }
|
|
272
|
+
: style
|
|
273
|
+
}),
|
|
217
274
|
}
|
|
218
275
|
})(),
|
|
219
276
|
contributions: extracted.contributions,
|
|
@@ -319,6 +376,35 @@ export function compile(options: compile.Options): compile.ReturnType {
|
|
|
319
376
|
),
|
|
320
377
|
)
|
|
321
378
|
|
|
379
|
+
// Definitions read only through folded applications need no class list of
|
|
380
|
+
// their own. True marks definitions whose every read follows initialization.
|
|
381
|
+
const unread = new Map(
|
|
382
|
+
[...(local?.values ?? [])].filter(([name]) =>
|
|
383
|
+
found.every(
|
|
384
|
+
(application) => application.name !== name || folded.has(application),
|
|
385
|
+
),
|
|
386
|
+
),
|
|
387
|
+
)
|
|
388
|
+
const namespaces = (local?.namespaces ?? []).filter((namespace) =>
|
|
389
|
+
[...namespace.ends.keys()].every((name) => unread.get(name)),
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
const erased = new Map(namespaces.flatMap((namespace) => [...namespace.ends]))
|
|
393
|
+
|
|
394
|
+
// An ambient namespace keeps member types without runtime initialization.
|
|
395
|
+
for (const namespace of namespaces)
|
|
396
|
+
module.prependRight(namespace.start, 'declare ')
|
|
397
|
+
|
|
398
|
+
for (const call of extracted.calls) {
|
|
399
|
+
const end = erased.get(call.name)
|
|
400
|
+
if (end !== undefined)
|
|
401
|
+
module.overwrite(
|
|
402
|
+
end,
|
|
403
|
+
call.start,
|
|
404
|
+
`: import('zyzz').style.ReturnType${call.output === 'html' ? "<'html'>" : ''}`,
|
|
405
|
+
)
|
|
406
|
+
}
|
|
407
|
+
|
|
322
408
|
let callable = false
|
|
323
409
|
|
|
324
410
|
const composed = (node: Span) =>
|
|
@@ -438,6 +524,17 @@ export function compile(options: compile.Options): compile.ReturnType {
|
|
|
438
524
|
return typed ? `(${result} as ${type})` : result
|
|
439
525
|
}
|
|
440
526
|
|
|
527
|
+
const initialized = unread.get(call.name)
|
|
528
|
+
if (initialized !== undefined) {
|
|
529
|
+
if (erased.has(call.name)) return ''
|
|
530
|
+
|
|
531
|
+
// Guards read only truthiness, and failures before initialization still call it.
|
|
532
|
+
const value = initialized ? 'void 0' : '1'
|
|
533
|
+
return /\.[cm]?tsx?$/.test(options.moduleId)
|
|
534
|
+
? `(${value} as unknown as import('zyzz').style.ReturnType${call.output === 'html' ? "<'html'>" : ''})`
|
|
535
|
+
: value
|
|
536
|
+
}
|
|
537
|
+
|
|
441
538
|
if (application.folded)
|
|
442
539
|
return composeHtml
|
|
443
540
|
? `${compositionHtml}.from({className:${JSON.stringify(classes[call.name])}})`
|
|
@@ -463,31 +560,13 @@ export function compile(options: compile.Options): compile.ReturnType {
|
|
|
463
560
|
!call.slots &&
|
|
464
561
|
!call.runtimeComposition &&
|
|
465
562
|
!application.folded &&
|
|
563
|
+
!unread.has(call.name) &&
|
|
466
564
|
call.output !== 'html'
|
|
467
565
|
)
|
|
468
566
|
callable = true
|
|
469
567
|
}
|
|
470
568
|
|
|
471
|
-
for (const application of
|
|
472
|
-
if (
|
|
473
|
-
extracted.calls.some(
|
|
474
|
-
(call) =>
|
|
475
|
-
call.output === 'html' &&
|
|
476
|
-
call.runtimeComposition?.some(
|
|
477
|
-
(input) => input.applicationStart === application.start,
|
|
478
|
-
),
|
|
479
|
-
)
|
|
480
|
-
)
|
|
481
|
-
continue
|
|
482
|
-
if (
|
|
483
|
-
extracted.calls.some(
|
|
484
|
-
(call) =>
|
|
485
|
-
(call.composition || call.runtimeComposition) &&
|
|
486
|
-
call.start < application.start &&
|
|
487
|
-
application.end <= call.end,
|
|
488
|
-
)
|
|
489
|
-
)
|
|
490
|
-
continue
|
|
569
|
+
for (const application of folded) {
|
|
491
570
|
const className = JSON.stringify(classes[application.name])
|
|
492
571
|
|
|
493
572
|
const key =
|
|
@@ -495,12 +574,18 @@ export function compile(options: compile.Options): compile.ReturnType {
|
|
|
495
574
|
'html'
|
|
496
575
|
? 'class'
|
|
497
576
|
: 'className'
|
|
577
|
+
const callee = options.source.slice(
|
|
578
|
+
application.start,
|
|
579
|
+
application.calleeEnd,
|
|
580
|
+
)
|
|
498
581
|
|
|
499
|
-
// Keep a callable guard so bundlers also retain failures before initialization.
|
|
500
582
|
module.overwrite(
|
|
501
583
|
application.start,
|
|
502
584
|
application.end,
|
|
503
|
-
|
|
585
|
+
application.initialized
|
|
586
|
+
? `({${key}:${className}})`
|
|
587
|
+
: // Keep a callable guard so bundlers also retain failures before initialization.
|
|
588
|
+
`(${callee}?{${key}:${className}}:${callee}())`,
|
|
504
589
|
)
|
|
505
590
|
}
|
|
506
591
|
|
|
@@ -688,7 +773,17 @@ export function compile(options: compile.Options): compile.ReturnType {
|
|
|
688
773
|
specifier.importKind === 'type' ||
|
|
689
774
|
!(
|
|
690
775
|
node.source.value === 'zyzz'
|
|
691
|
-
? [
|
|
776
|
+
? [
|
|
777
|
+
'Config',
|
|
778
|
+
'cx',
|
|
779
|
+
'defineConfig',
|
|
780
|
+
'defineVars',
|
|
781
|
+
'extendVars',
|
|
782
|
+
'style',
|
|
783
|
+
'Vars',
|
|
784
|
+
'variable',
|
|
785
|
+
'variants',
|
|
786
|
+
]
|
|
692
787
|
: [
|
|
693
788
|
'Css',
|
|
694
789
|
'cssFunction',
|
|
@@ -1135,6 +1230,7 @@ export function compile(options: compile.Options): compile.ReturnType {
|
|
|
1135
1230
|
? { [ThemeRules.shared]: emitted[ThemeRules.shared] }
|
|
1136
1231
|
: {}),
|
|
1137
1232
|
[ClassName.rules]: emitted[ClassName.rules],
|
|
1233
|
+
[ClassName.units]: emitted[ClassName.units],
|
|
1138
1234
|
classes,
|
|
1139
1235
|
code: portable ? options.source : module.toString(),
|
|
1140
1236
|
css: namespaced.css,
|
|
@@ -1150,6 +1246,7 @@ export function compile(options: compile.Options): compile.ReturnType {
|
|
|
1150
1246
|
vars: emitted.vars,
|
|
1151
1247
|
}
|
|
1152
1248
|
Object.defineProperty(result, ClassName.rules, { enumerable: false })
|
|
1249
|
+
Object.defineProperty(result, ClassName.units, { enumerable: false })
|
|
1153
1250
|
return Object.freeze(result)
|
|
1154
1251
|
}
|
|
1155
1252
|
|
|
@@ -1161,6 +1258,8 @@ export declare namespace compile {
|
|
|
1161
1258
|
type Options = Source.extract.Options & {
|
|
1162
1259
|
/** Separates generated token definitions for independently loaded modules. */
|
|
1163
1260
|
readonly [ThemeRules.shared]?: 'all' | 'defaults' | undefined
|
|
1261
|
+
/** Names shared independent atomic rules, with one name table across a graph's modules. */
|
|
1262
|
+
readonly [ClassName.units]?: ClassName.Units | undefined
|
|
1164
1263
|
/** Disable source rewriting while emitting CSS for runtime authoring. Defaults to true. */
|
|
1165
1264
|
readonly compiler?: boolean | undefined
|
|
1166
1265
|
/** Whether compiled applications can be combined with one another. */
|
|
@@ -1188,6 +1287,8 @@ export declare namespace compile {
|
|
|
1188
1287
|
readonly code: string
|
|
1189
1288
|
/** Exact emitted rules retained for collision diagnostics. */
|
|
1190
1289
|
readonly [ClassName.rules]: Readonly<Record<string, string>>
|
|
1290
|
+
/** Shared rule names chosen by this module, for graph cache validation. */
|
|
1291
|
+
readonly [ClassName.units]?: ClassName.Units['used'] | undefined
|
|
1191
1292
|
/** Ordered, unminified stylesheet text. */
|
|
1192
1293
|
readonly css: string
|
|
1193
1294
|
/** Standard stylesheet map with authored selector/declaration locations. */
|
|
@@ -34,6 +34,10 @@ export function create(
|
|
|
34
34
|
}
|
|
35
35
|
direct: Source.Call | undefined
|
|
36
36
|
members: Map<string, Source.Call>
|
|
37
|
+
/** Namespace statement start and member identifier ends by extracted name. */
|
|
38
|
+
namespace?: Namespace | undefined
|
|
39
|
+
/** Offset after which top-level reads observe an initialized binding. */
|
|
40
|
+
ready: number
|
|
37
41
|
}[] = []
|
|
38
42
|
|
|
39
43
|
for (const statement of program.body) {
|
|
@@ -43,6 +47,7 @@ export function create(
|
|
|
43
47
|
statement.body?.type === 'TSModuleBlock'
|
|
44
48
|
) {
|
|
45
49
|
const members = new Map<string, Source.Call>()
|
|
50
|
+
const ends = new Map<string, number>()
|
|
46
51
|
let valid = true
|
|
47
52
|
|
|
48
53
|
for (const member of statement.body.body) {
|
|
@@ -71,6 +76,7 @@ export function create(
|
|
|
71
76
|
}
|
|
72
77
|
|
|
73
78
|
members.set(declaration.id.name, call)
|
|
79
|
+
ends.set(call.name, declaration.id.end)
|
|
74
80
|
memberBindings.set(declaration.id.start, call.name)
|
|
75
81
|
}
|
|
76
82
|
}
|
|
@@ -80,6 +86,8 @@ export function create(
|
|
|
80
86
|
declaration: { id: statement.id },
|
|
81
87
|
direct: undefined,
|
|
82
88
|
members,
|
|
89
|
+
namespace: { ends, start: statement.start },
|
|
90
|
+
ready: statement.end,
|
|
83
91
|
})
|
|
84
92
|
|
|
85
93
|
continue
|
|
@@ -142,6 +150,7 @@ export function create(
|
|
|
142
150
|
declaration: { ...declaration, id: declaration.id },
|
|
143
151
|
direct,
|
|
144
152
|
members,
|
|
153
|
+
ready: declaration.end,
|
|
145
154
|
})
|
|
146
155
|
}
|
|
147
156
|
}
|
|
@@ -149,6 +158,9 @@ export function create(
|
|
|
149
158
|
if (!candidates.length) return undefined
|
|
150
159
|
|
|
151
160
|
const lexical = new Set<Ast.Node>()
|
|
161
|
+
// Reads inside functions, classes, or namespaces may run before initialization.
|
|
162
|
+
const deferred = new Set<Ast.Node>()
|
|
163
|
+
const terminals = new Set<Ast.Node>()
|
|
152
164
|
const scope = new Scope.Tracker({ preserveExitedScopes: true })
|
|
153
165
|
Walker.walk(program, { scopeTracker: scope })
|
|
154
166
|
scope.freeze()
|
|
@@ -185,8 +197,23 @@ export function create(
|
|
|
185
197
|
candidates.some(
|
|
186
198
|
(candidate) => candidate.declaration.id.start === id.start,
|
|
187
199
|
)
|
|
188
|
-
)
|
|
200
|
+
) {
|
|
189
201
|
lexical.add(node)
|
|
202
|
+
if (terminal(ancestors)) terminals.add(node)
|
|
203
|
+
if (
|
|
204
|
+
ancestors.some((node) =>
|
|
205
|
+
[
|
|
206
|
+
'ArrowFunctionExpression',
|
|
207
|
+
'ClassDeclaration',
|
|
208
|
+
'ClassExpression',
|
|
209
|
+
'FunctionDeclaration',
|
|
210
|
+
'FunctionExpression',
|
|
211
|
+
'TSModuleDeclaration',
|
|
212
|
+
].includes(node.type),
|
|
213
|
+
)
|
|
214
|
+
)
|
|
215
|
+
deferred.add(node)
|
|
216
|
+
}
|
|
190
217
|
},
|
|
191
218
|
leave() {
|
|
192
219
|
ancestors.pop()
|
|
@@ -200,6 +227,80 @@ export function create(
|
|
|
200
227
|
>(candidates.map(({ declaration }) => [declaration.id.name, []]))
|
|
201
228
|
let evaluation = false
|
|
202
229
|
|
|
230
|
+
// Returns undefined when any read escapes. Recipes, dynamic styles, and
|
|
231
|
+
// applications with overrides keep their runtime calls.
|
|
232
|
+
function collect(candidate: (typeof candidates)[number]):
|
|
233
|
+
| {
|
|
234
|
+
readonly applications: readonly Application[]
|
|
235
|
+
readonly called: ReadonlySet<string>
|
|
236
|
+
}
|
|
237
|
+
| undefined {
|
|
238
|
+
const { declaration, direct, members } = candidate
|
|
239
|
+
const applications: Application[] = []
|
|
240
|
+
const called = new Set<string>()
|
|
241
|
+
|
|
242
|
+
for (const reference of references.get(declaration.id.name) ?? []) {
|
|
243
|
+
if (reference === declaration.id) continue
|
|
244
|
+
|
|
245
|
+
let callee: Ast.Node = reference
|
|
246
|
+
let call = direct
|
|
247
|
+
|
|
248
|
+
if (!direct) {
|
|
249
|
+
const member = parents.get(reference)
|
|
250
|
+
|
|
251
|
+
if (
|
|
252
|
+
member?.type !== 'MemberExpression' ||
|
|
253
|
+
member.object !== reference ||
|
|
254
|
+
member.computed ||
|
|
255
|
+
member.optional ||
|
|
256
|
+
member.property.type !== 'Identifier'
|
|
257
|
+
)
|
|
258
|
+
return undefined
|
|
259
|
+
|
|
260
|
+
call = members.get(member.property.name)
|
|
261
|
+
callee = member
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (
|
|
265
|
+
call?.composition ||
|
|
266
|
+
call?.runtimeComposition ||
|
|
267
|
+
(!options.dynamic && (call?.slots || call?.recipe))
|
|
268
|
+
)
|
|
269
|
+
continue
|
|
270
|
+
|
|
271
|
+
const application = parents.get(callee)
|
|
272
|
+
|
|
273
|
+
if (
|
|
274
|
+
!call ||
|
|
275
|
+
application?.type !== 'CallExpression' ||
|
|
276
|
+
application.callee !== callee ||
|
|
277
|
+
application.optional
|
|
278
|
+
)
|
|
279
|
+
return undefined
|
|
280
|
+
|
|
281
|
+
// An override needs the runtime callable, but exposes no other definition.
|
|
282
|
+
if (!options.dynamic && application.arguments.length) {
|
|
283
|
+
called.add(call.name)
|
|
284
|
+
continue
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
applications.push({
|
|
288
|
+
calleeEnd: callee.end,
|
|
289
|
+
end: application.end,
|
|
290
|
+
initialized:
|
|
291
|
+
!deferred.has(reference) &&
|
|
292
|
+
reference.start >= candidate.ready &&
|
|
293
|
+
(!candidate.namespace ||
|
|
294
|
+
namespaceCounts.get(declaration.id.name) === 1),
|
|
295
|
+
name: call.name,
|
|
296
|
+
start: application.start,
|
|
297
|
+
terminal: terminals.has(reference),
|
|
298
|
+
})
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return { applications, called }
|
|
302
|
+
}
|
|
303
|
+
|
|
203
304
|
return {
|
|
204
305
|
dead() {
|
|
205
306
|
if (evaluation) return new Set<string>()
|
|
@@ -255,6 +356,58 @@ export function create(
|
|
|
255
356
|
|
|
256
357
|
return result
|
|
257
358
|
},
|
|
359
|
+
definitions() {
|
|
360
|
+
const namespaces: Namespace[] = []
|
|
361
|
+
const values = new Map<string, boolean>()
|
|
362
|
+
|
|
363
|
+
if (evaluation) return { namespaces, values }
|
|
364
|
+
|
|
365
|
+
const counts = new Map<string, number>()
|
|
366
|
+
|
|
367
|
+
for (const { declaration } of candidates)
|
|
368
|
+
counts.set(
|
|
369
|
+
declaration.id.name,
|
|
370
|
+
(counts.get(declaration.id.name) ?? 0) + 1,
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
for (const candidate of candidates) {
|
|
374
|
+
const name = candidate.declaration.id.name
|
|
375
|
+
if (counts.get(name)! > 1 || (namespaceCounts.get(name) ?? 0) > 1)
|
|
376
|
+
continue
|
|
377
|
+
|
|
378
|
+
const result = collect(candidate)
|
|
379
|
+
if (!result) continue
|
|
380
|
+
|
|
381
|
+
let erased = !!candidate.namespace
|
|
382
|
+
|
|
383
|
+
for (const call of candidate.direct
|
|
384
|
+
? [candidate.direct]
|
|
385
|
+
: candidate.members.values()) {
|
|
386
|
+
if (
|
|
387
|
+
call.composition ||
|
|
388
|
+
call.runtimeComposition ||
|
|
389
|
+
call.slots ||
|
|
390
|
+
call.recipe ||
|
|
391
|
+
result.called.has(call.name)
|
|
392
|
+
) {
|
|
393
|
+
erased = false
|
|
394
|
+
continue
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const initialized = result.applications.every(
|
|
398
|
+
(application) =>
|
|
399
|
+
application.name !== call.name || application.initialized,
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
values.set(call.name, initialized)
|
|
403
|
+
erased &&= initialized
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if (erased) namespaces.push(candidate.namespace!)
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
return { namespaces, values }
|
|
410
|
+
},
|
|
258
411
|
enter(node, parent) {
|
|
259
412
|
if (
|
|
260
413
|
node.type === 'MemberExpression' &&
|
|
@@ -280,68 +433,9 @@ export function create(
|
|
|
280
433
|
find() {
|
|
281
434
|
if (evaluation) return []
|
|
282
435
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
const applications: Application[] = []
|
|
287
|
-
let valid = true
|
|
288
|
-
|
|
289
|
-
for (const reference of references.get(declaration.id.name) ?? []) {
|
|
290
|
-
if (reference === declaration.id) continue
|
|
291
|
-
|
|
292
|
-
let callee: Ast.Node = reference
|
|
293
|
-
let call = direct
|
|
294
|
-
|
|
295
|
-
if (!direct) {
|
|
296
|
-
const member = parents.get(reference)
|
|
297
|
-
|
|
298
|
-
if (
|
|
299
|
-
member?.type !== 'MemberExpression' ||
|
|
300
|
-
member.object !== reference ||
|
|
301
|
-
member.computed ||
|
|
302
|
-
member.optional ||
|
|
303
|
-
member.property.type !== 'Identifier'
|
|
304
|
-
) {
|
|
305
|
-
valid = false
|
|
306
|
-
break
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
call = members.get(member.property.name)
|
|
310
|
-
callee = member
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
if (
|
|
314
|
-
call?.composition ||
|
|
315
|
-
call?.runtimeComposition ||
|
|
316
|
-
(!options.dynamic && (call?.slots || call?.recipe))
|
|
317
|
-
)
|
|
318
|
-
continue
|
|
319
|
-
|
|
320
|
-
const application = parents.get(callee)
|
|
321
|
-
|
|
322
|
-
if (
|
|
323
|
-
!call ||
|
|
324
|
-
application?.type !== 'CallExpression' ||
|
|
325
|
-
application.callee !== callee ||
|
|
326
|
-
application.optional ||
|
|
327
|
-
(!options.dynamic && application.arguments.length)
|
|
328
|
-
) {
|
|
329
|
-
valid = false
|
|
330
|
-
break
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
applications.push({
|
|
334
|
-
calleeEnd: callee.end,
|
|
335
|
-
end: application.end,
|
|
336
|
-
name: call.name,
|
|
337
|
-
start: application.start,
|
|
338
|
-
})
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
if (valid) result.push(...applications)
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
return result
|
|
436
|
+
return candidates.flatMap(
|
|
437
|
+
(candidate) => collect(candidate)?.applications ?? [],
|
|
438
|
+
)
|
|
345
439
|
},
|
|
346
440
|
}
|
|
347
441
|
}
|
|
@@ -352,16 +446,37 @@ type Application = {
|
|
|
352
446
|
readonly calleeEnd: number
|
|
353
447
|
/** End of the application. */
|
|
354
448
|
readonly end: number
|
|
449
|
+
/** Whether the read runs at top level after its definition initializes. */
|
|
450
|
+
readonly initialized: boolean
|
|
355
451
|
/** Extracted class identity. */
|
|
356
452
|
readonly name: string
|
|
357
453
|
/** Start of the application. */
|
|
358
454
|
readonly start: number
|
|
455
|
+
/** Whether a DOM element receives the props alone, so its classes meet no other style's. */
|
|
456
|
+
readonly terminal: boolean
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/** A top-level namespace whose members are all static definitions. */
|
|
460
|
+
type Namespace = {
|
|
461
|
+
/** Member identifier ends keyed by extracted name. */
|
|
462
|
+
readonly ends: ReadonlyMap<string, number>
|
|
463
|
+
/** Start of the namespace statement. */
|
|
464
|
+
readonly start: number
|
|
359
465
|
}
|
|
360
466
|
|
|
361
467
|
/** Binding references gathered during the transform's existing walk. */
|
|
362
468
|
type Collector = {
|
|
363
469
|
/** Definitions with no observable value reads, retaining escaped containers. */
|
|
364
470
|
dead: () => ReadonlySet<string>
|
|
471
|
+
/**
|
|
472
|
+
* Static definitions whose every read is a found application, keyed by
|
|
473
|
+
* extracted name with whether every read is initialized. Namespaces list
|
|
474
|
+
* those whose definitions all qualify with initialized reads.
|
|
475
|
+
*/
|
|
476
|
+
definitions: () => {
|
|
477
|
+
readonly namespaces: readonly Namespace[]
|
|
478
|
+
readonly values: ReadonlyMap<string, boolean>
|
|
479
|
+
}
|
|
365
480
|
/** Records relevant reads and their immediate parents. */
|
|
366
481
|
enter: (node: Ast.Node, parent: Ast.Node | null | undefined) => void
|
|
367
482
|
/** Resolves applications after traversal completes. */
|
|
@@ -376,3 +491,64 @@ export declare namespace create {
|
|
|
376
491
|
readonly dynamic?: boolean | undefined
|
|
377
492
|
}
|
|
378
493
|
}
|
|
494
|
+
|
|
495
|
+
// Matches a no-argument application spread onto, or read as the class of, a DOM element.
|
|
496
|
+
function terminal(path: readonly Ast.Node[]): boolean {
|
|
497
|
+
let index = path.length - 1
|
|
498
|
+
let node = path[index]
|
|
499
|
+
const member = path[index - 1]
|
|
500
|
+
if (member?.type === 'MemberExpression' && member.object === node)
|
|
501
|
+
node = path[--index]
|
|
502
|
+
|
|
503
|
+
const call = path[--index]
|
|
504
|
+
if (
|
|
505
|
+
call?.type !== 'CallExpression' ||
|
|
506
|
+
call.callee !== node ||
|
|
507
|
+
call.arguments.length
|
|
508
|
+
)
|
|
509
|
+
return false
|
|
510
|
+
|
|
511
|
+
const parent = path[--index]
|
|
512
|
+
if (parent?.type === 'JSXSpreadAttribute')
|
|
513
|
+
return parent.argument === call && alone(path[index - 1], parent)
|
|
514
|
+
|
|
515
|
+
if (
|
|
516
|
+
parent?.type !== 'MemberExpression' ||
|
|
517
|
+
parent.object !== call ||
|
|
518
|
+
parent.computed ||
|
|
519
|
+
parent.property.type !== 'Identifier' ||
|
|
520
|
+
!/^class(?:Name)?$/.test(parent.property.name)
|
|
521
|
+
)
|
|
522
|
+
return false
|
|
523
|
+
|
|
524
|
+
const container = path[--index]
|
|
525
|
+
const attribute = path[--index]
|
|
526
|
+
return (
|
|
527
|
+
container?.type === 'JSXExpressionContainer' &&
|
|
528
|
+
container.expression === parent &&
|
|
529
|
+
attribute?.type === 'JSXAttribute' &&
|
|
530
|
+
attribute.value === container &&
|
|
531
|
+
attribute.name.type === 'JSXIdentifier' &&
|
|
532
|
+
/^class(?:Name)?$/.test(attribute.name.name) &&
|
|
533
|
+
alone(path[index - 1], attribute)
|
|
534
|
+
)
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// Other spreads or class attributes could add another style's classes.
|
|
538
|
+
function alone(element: Ast.Node | undefined, own: Ast.Node): boolean {
|
|
539
|
+
return (
|
|
540
|
+
element?.type === 'JSXOpeningElement' &&
|
|
541
|
+
element.name.type === 'JSXIdentifier' &&
|
|
542
|
+
/^[a-z]/.test(element.name.name) &&
|
|
543
|
+
element.attributes.every(
|
|
544
|
+
(attribute) =>
|
|
545
|
+
attribute === own ||
|
|
546
|
+
(attribute.type === 'JSXAttribute' &&
|
|
547
|
+
!(
|
|
548
|
+
attribute.name.type === 'JSXIdentifier'
|
|
549
|
+
? attribute.name.name
|
|
550
|
+
: attribute.name.namespace.name
|
|
551
|
+
).startsWith('class')),
|
|
552
|
+
)
|
|
553
|
+
)
|
|
554
|
+
}
|
|
@@ -405,7 +405,15 @@ export function collect(options: collect.Options) {
|
|
|
405
405
|
node,
|
|
406
406
|
)
|
|
407
407
|
const name = `composition-${options.identity}-${node.start}`
|
|
408
|
+
// A composition of independent styles keeps their sharing contract.
|
|
409
|
+
const independent = (calls: readonly Source.Call[]) =>
|
|
410
|
+
calls.every(
|
|
411
|
+
(call) => styles.get(call.name)!.composition === 'independent',
|
|
412
|
+
)
|
|
413
|
+
? ({ composition: 'independent' } as const)
|
|
414
|
+
: {}
|
|
408
415
|
const style: Style.NamedStyle = {
|
|
416
|
+
...independent(selected),
|
|
409
417
|
cssOutput: 'atomic',
|
|
410
418
|
name,
|
|
411
419
|
declarations: [],
|
|
@@ -580,6 +588,7 @@ export function collect(options: collect.Options) {
|
|
|
580
588
|
.join(' '),
|
|
581
589
|
},
|
|
582
590
|
style: {
|
|
591
|
+
...independent(included),
|
|
583
592
|
cssOutput: 'atomic',
|
|
584
593
|
name,
|
|
585
594
|
declarations: [],
|
|
@@ -149,6 +149,9 @@ export function collect(options: collect.Options): Themes.Link {
|
|
|
149
149
|
: {}),
|
|
150
150
|
[Token.identity]: identity,
|
|
151
151
|
...(input.id !== undefined ? { [Token.complete]: true } : {}),
|
|
152
|
+
...(input.composition !== undefined
|
|
153
|
+
? { composition: input.composition }
|
|
154
|
+
: {}),
|
|
152
155
|
cssOutput: input.cssOutput ?? 'atomic',
|
|
153
156
|
...(input.defaultLayer !== undefined
|
|
154
157
|
? { defaultLayer: input.defaultLayer }
|
|
@@ -243,6 +246,7 @@ export function collect(options: collect.Options): Themes.Link {
|
|
|
243
246
|
return {}
|
|
244
247
|
})(),
|
|
245
248
|
...(input.id !== undefined ? { id: input.id } : {}),
|
|
249
|
+
...(input.composition ? { composition: input.composition } : {}),
|
|
246
250
|
...(input.cssOutput ? { cssOutput: input.cssOutput } : {}),
|
|
247
251
|
...(input.defaultLayer !== undefined
|
|
248
252
|
? { defaultLayer: input.defaultLayer }
|