zyzz 0.0.24 → 0.0.26
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/Vars.d.ts +18 -2
- package/dist/Vars.d.ts.map +1 -1
- package/dist/Vars.js +21 -3
- package/dist/Vars.js.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 +3 -0
- package/dist/compiler/internal/Configurations.d.ts.map +1 -1
- package/dist/compiler/internal/Configurations.js +6 -1
- 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 +123 -35
- 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 +13 -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/internal/VariableSets.d.ts +20 -4
- package/dist/internal/VariableSets.d.ts.map +1 -1
- package/dist/internal/VariableSets.js +70 -19
- package/dist/internal/VariableSets.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/react-native/internal/Tokens.d.ts.map +1 -1
- package/dist/react-native/internal/Tokens.js +3 -6
- package/dist/react-native/internal/Tokens.js.map +1 -1
- 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 +33 -44
- 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/dist/web/internal/Themes.d.ts.map +1 -1
- package/dist/web/internal/Themes.js +4 -2
- package/dist/web/internal/Themes.js.map +1 -1
- package/package.json +1 -1
- package/src/Style.ts +2 -0
- package/src/Vars.ts +104 -12
- 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 +8 -1
- package/src/compiler/internal/Contract.ts +59 -7
- package/src/compiler/internal/PackedStyles.ts +9 -0
- package/src/compiler/internal/Themes.ts +174 -52
- package/src/internal/Configuration.ts +26 -4
- package/src/internal/Token.ts +2 -0
- package/src/internal/VariableSets.ts +104 -24
- 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/react-native/internal/Tokens.ts +5 -6
- package/src/unplugin/index.ts +35 -13
- package/src/vite/index.ts +38 -45
- package/src/web/Css.ts +228 -42
- package/src/web/internal/ClassName.ts +114 -0
- package/src/web/internal/Themes.ts +12 -11
|
@@ -494,13 +494,31 @@ export function collect(program: Ast.Program, options: collect.Options) {
|
|
|
494
494
|
root = root.object
|
|
495
495
|
}
|
|
496
496
|
if (root.type !== 'Identifier') return undefined
|
|
497
|
-
const
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
if (
|
|
503
|
-
|
|
497
|
+
const name = root.name
|
|
498
|
+
let value: unknown = (() => {
|
|
499
|
+
if (derived?.name === name) return derived.vars
|
|
500
|
+
|
|
501
|
+
const call = names.get(name)
|
|
502
|
+
if (call) {
|
|
503
|
+
if (
|
|
504
|
+
!call.variableSet &&
|
|
505
|
+
!call.directVariables &&
|
|
506
|
+
!['vars', 'tokens'].includes(path.shift()!)
|
|
507
|
+
)
|
|
508
|
+
return undefined
|
|
509
|
+
return themes[call.name]?.tokens
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// Config-bound vars read the configured variables, so they follow set scoping.
|
|
513
|
+
const config = configs.get(name)
|
|
514
|
+
if (
|
|
515
|
+
!config?.call.variableConfig ||
|
|
516
|
+
(!config.call.selection && path.shift() !== 'vars')
|
|
517
|
+
)
|
|
518
|
+
return undefined
|
|
519
|
+
return themes[config.call.name]?.tokens
|
|
520
|
+
})()
|
|
521
|
+
if (value === undefined) return undefined
|
|
504
522
|
for (const key of path)
|
|
505
523
|
value =
|
|
506
524
|
value && typeof value === 'object'
|
|
@@ -615,6 +633,68 @@ export function collect(program: Ast.Program, options: collect.Options) {
|
|
|
615
633
|
return result
|
|
616
634
|
}
|
|
617
635
|
|
|
636
|
+
/** Reads a derived-variables callback as one parameter name and a literal body. */
|
|
637
|
+
function derivation(argument: Ast.Node | undefined) {
|
|
638
|
+
const callback = argument && Expression.unwrap(argument)
|
|
639
|
+
if (
|
|
640
|
+
callback?.type !== 'ArrowFunctionExpression' &&
|
|
641
|
+
callback?.type !== 'FunctionExpression'
|
|
642
|
+
)
|
|
643
|
+
return undefined
|
|
644
|
+
|
|
645
|
+
const parameter = callback.params[0]
|
|
646
|
+
const body = (() => {
|
|
647
|
+
if (callback.body?.type !== 'BlockStatement') return callback.body
|
|
648
|
+
if (
|
|
649
|
+
callback.body.body.length === 1 &&
|
|
650
|
+
callback.body.body[0]?.type === 'ReturnStatement'
|
|
651
|
+
)
|
|
652
|
+
return callback.body.body[0].argument
|
|
653
|
+
return undefined
|
|
654
|
+
})()
|
|
655
|
+
if (
|
|
656
|
+
callback.async ||
|
|
657
|
+
callback.generator ||
|
|
658
|
+
callback.params.length !== 1 ||
|
|
659
|
+
parameter?.type !== 'Identifier' ||
|
|
660
|
+
!body
|
|
661
|
+
)
|
|
662
|
+
fail(
|
|
663
|
+
'Derived variables require one named parameter and a literal return value.',
|
|
664
|
+
callback,
|
|
665
|
+
)
|
|
666
|
+
|
|
667
|
+
return { body, name: parameter.name }
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/** Authored values of an extension, keeping base values at existing paths as `Vars.extend` types do. */
|
|
671
|
+
function extendedValues(base: Theme.Definition, extension: Theme.Definition) {
|
|
672
|
+
const metadata = extension[Token.definition]
|
|
673
|
+
const known = base[Token.definition].values
|
|
674
|
+
const tree: Record<string, unknown> = Object.create(null)
|
|
675
|
+
|
|
676
|
+
for (const path of Object.keys(metadata.values)) {
|
|
677
|
+
const parts = metadata.paths?.[path] ?? path.split('.')
|
|
678
|
+
const source = Object.hasOwn(known, path) ? base : extension
|
|
679
|
+
let target = tree
|
|
680
|
+
let token: unknown = source.tokens
|
|
681
|
+
|
|
682
|
+
for (const part of parts.slice(0, -1)) {
|
|
683
|
+
target = (target[part] ??= Object.create(null)) as Record<
|
|
684
|
+
string,
|
|
685
|
+
unknown
|
|
686
|
+
>
|
|
687
|
+
token = (token as Record<string, unknown>)[part]
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
const name = parts.at(-1)!
|
|
691
|
+
// Every metadata path has a reference in its own token tree.
|
|
692
|
+
target[name] = (token as Record<string, Token.Reference>)[name]!.value
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
return tree
|
|
696
|
+
}
|
|
697
|
+
|
|
618
698
|
function fail(message: string, node: Pick<Ast.Node, 'end' | 'start'>): never {
|
|
619
699
|
throw new InvalidError(message, node)
|
|
620
700
|
}
|
|
@@ -750,6 +830,18 @@ export function collect(program: Ast.Program, options: collect.Options) {
|
|
|
750
830
|
)
|
|
751
831
|
if (id !== undefined) name = Identity.requireId(id, 'Vars.define')
|
|
752
832
|
}
|
|
833
|
+
if (!config && method === 'extend') {
|
|
834
|
+
const argument = expression.arguments[2]
|
|
835
|
+
const callback = argument && Expression.unwrap(argument)
|
|
836
|
+
const id = Identifiers.explicit(
|
|
837
|
+
expression,
|
|
838
|
+
callback?.type === 'ArrowFunctionExpression' ||
|
|
839
|
+
callback?.type === 'FunctionExpression'
|
|
840
|
+
? 3
|
|
841
|
+
: 2,
|
|
842
|
+
)
|
|
843
|
+
if (id !== undefined) name = Identity.requireId(id, 'Vars.extend')
|
|
844
|
+
}
|
|
753
845
|
|
|
754
846
|
if (config) {
|
|
755
847
|
const nativeProvider =
|
|
@@ -931,42 +1023,17 @@ export function collect(program: Ast.Program, options: collect.Options) {
|
|
|
931
1023
|
|
|
932
1024
|
tokenType = type(expression.arguments[0]!)
|
|
933
1025
|
|
|
934
|
-
const
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
(callback?.type === 'ArrowFunctionExpression' ||
|
|
939
|
-
callback?.type === 'FunctionExpression')
|
|
940
|
-
) {
|
|
941
|
-
const parameter = callback.params[0]
|
|
942
|
-
const body = (() => {
|
|
943
|
-
if (callback.body?.type !== 'BlockStatement') return callback.body
|
|
944
|
-
if (
|
|
945
|
-
callback.body.body.length === 1 &&
|
|
946
|
-
callback.body.body[0]?.type === 'ReturnStatement'
|
|
947
|
-
)
|
|
948
|
-
return callback.body.body[0].argument
|
|
949
|
-
return undefined
|
|
950
|
-
})()
|
|
951
|
-
if (
|
|
952
|
-
callback.async ||
|
|
953
|
-
callback.generator ||
|
|
954
|
-
callback.params.length !== 1 ||
|
|
955
|
-
parameter?.type !== 'Identifier' ||
|
|
956
|
-
!body
|
|
957
|
-
)
|
|
958
|
-
fail(
|
|
959
|
-
'Derived variables require one named parameter and a literal return value.',
|
|
960
|
-
callback,
|
|
961
|
-
)
|
|
962
|
-
|
|
1026
|
+
const derive = variableSet
|
|
1027
|
+
? derivation(expression.arguments[1])
|
|
1028
|
+
: undefined
|
|
1029
|
+
if (derive) {
|
|
963
1030
|
const base = VariableSets.build(
|
|
964
1031
|
input,
|
|
965
1032
|
Object.freeze({ variableSet: true, [Token.identity]: name }),
|
|
966
1033
|
)
|
|
967
1034
|
input = VariableSets.merge(
|
|
968
1035
|
input,
|
|
969
|
-
data(body, { name:
|
|
1036
|
+
data(derive.body, { name: derive.name, vars: base }),
|
|
970
1037
|
)
|
|
971
1038
|
tokenType = Configurations.type(input)
|
|
972
1039
|
} else if (expression.arguments.length > 2)
|
|
@@ -994,33 +1061,88 @@ export function collect(program: Ast.Program, options: collect.Options) {
|
|
|
994
1061
|
} else {
|
|
995
1062
|
const base = expression.arguments[0]
|
|
996
1063
|
const parent = base ? resolve(base)?.call : undefined
|
|
1064
|
+
const derive = variableSet
|
|
1065
|
+
? derivation(expression.arguments[2])
|
|
1066
|
+
: undefined
|
|
997
1067
|
|
|
998
|
-
|
|
1068
|
+
const arity = derive ? 3 : 2
|
|
1069
|
+
// A trailing options object names a new set over the base's variables.
|
|
1070
|
+
const id =
|
|
1071
|
+
expression.arguments.length === arity + 1
|
|
1072
|
+
? Identifiers.explicit(expression, arity)
|
|
1073
|
+
: undefined
|
|
1074
|
+
const named = id !== undefined
|
|
1075
|
+
if (
|
|
1076
|
+
(expression.arguments.length !== arity && !named) ||
|
|
1077
|
+
!parent
|
|
1078
|
+
)
|
|
999
1079
|
fail(
|
|
1000
1080
|
'Vars.extend requires a preceding variable set and literal overrides.',
|
|
1001
1081
|
expression,
|
|
1002
1082
|
)
|
|
1003
1083
|
|
|
1004
1084
|
factoryReferences.add(base!.start)
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1085
|
+
tokenType = parent.tokenType
|
|
1086
|
+
output = parent.output
|
|
1087
|
+
|
|
1088
|
+
if (variableSet) {
|
|
1089
|
+
const theme = themes[parent.name]!
|
|
1090
|
+
const original = VariableSets.from(theme[Token.definition])
|
|
1091
|
+
const extend = Vars.extend as (
|
|
1092
|
+
variables: Vars.Definition,
|
|
1093
|
+
values: Vars.Values,
|
|
1094
|
+
derive: (vars: Vars.References<Vars.Values>) => Vars.Values,
|
|
1095
|
+
options: { readonly id?: string | undefined },
|
|
1096
|
+
) => Vars.Definition
|
|
1097
|
+
const extended = extend(
|
|
1098
|
+
original,
|
|
1099
|
+
data(expression.arguments[1]!) as Vars.Values,
|
|
1100
|
+
(vars) =>
|
|
1101
|
+
derive
|
|
1102
|
+
? (data(derive.body, {
|
|
1103
|
+
name: derive.name,
|
|
1104
|
+
vars: vars as Vars.Definition,
|
|
1105
|
+
}) as Vars.Values)
|
|
1106
|
+
: {},
|
|
1107
|
+
{ id },
|
|
1108
|
+
)
|
|
1109
|
+
definition = named
|
|
1110
|
+
? Token.bind(
|
|
1111
|
+
VariableSets.theme(extended),
|
|
1112
|
+
Object.freeze({
|
|
1113
|
+
...extended[Token.definition].contract,
|
|
1114
|
+
[Token.identity]: name,
|
|
1115
|
+
}),
|
|
1116
|
+
)
|
|
1117
|
+
: VariableSets.theme(extended)
|
|
1118
|
+
|
|
1119
|
+
const metadata = definition[Token.definition]
|
|
1120
|
+
// Additions grow the token contract; overridden leaves keep their base types, as in `Vars.extend`.
|
|
1121
|
+
if (
|
|
1122
|
+
named ||
|
|
1123
|
+
Object.keys(metadata.values).length !==
|
|
1124
|
+
Object.keys(theme[Token.definition].values).length ||
|
|
1125
|
+
JSON.stringify(metadata.queries) !==
|
|
1126
|
+
JSON.stringify(theme[Token.definition].queries)
|
|
1127
|
+
)
|
|
1128
|
+
tokenType = Configurations.type({
|
|
1129
|
+
...extendedValues(theme, definition),
|
|
1130
|
+
...metadata.queries,
|
|
1131
|
+
})
|
|
1132
|
+
} else
|
|
1133
|
+
definition = Theme.extend(
|
|
1134
|
+
themes[parent.name]!,
|
|
1135
|
+
data(expression.arguments[1]!) as Theme.Overrides<Theme.Tokens>,
|
|
1136
|
+
)
|
|
1137
|
+
|
|
1138
|
+
// Extensions share their base identity, so the binding keeps sibling sets distinct.
|
|
1016
1139
|
if (
|
|
1140
|
+
!named &&
|
|
1017
1141
|
definition[Token.definition].contract[Token.identity]?.startsWith(
|
|
1018
1142
|
'id-',
|
|
1019
1143
|
)
|
|
1020
1144
|
)
|
|
1021
|
-
name = definition.className.slice('z-theme-'.length)
|
|
1022
|
-
tokenType = parent.tokenType
|
|
1023
|
-
output = parent.output
|
|
1145
|
+
name = `${definition.className.slice('z-theme-'.length)}-${binding}`
|
|
1024
1146
|
}
|
|
1025
1147
|
} catch (error) {
|
|
1026
1148
|
if (error instanceof InvalidError) throw error
|
|
@@ -137,11 +137,12 @@ export function create(
|
|
|
137
137
|
appearance: result.appearance,
|
|
138
138
|
script: result.script,
|
|
139
139
|
style: result.style,
|
|
140
|
+
// The default set's metadata lets Vars.extend grow configured vars.
|
|
140
141
|
vars: Object.freeze(
|
|
141
|
-
Object.defineProperties(
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
),
|
|
142
|
+
Object.defineProperties(vars, {
|
|
143
|
+
...Object.getOwnPropertyDescriptors(theme.tokens),
|
|
144
|
+
[Token.definition]: { value: theme[Token.definition] },
|
|
145
|
+
}),
|
|
145
146
|
),
|
|
146
147
|
variants: result.variants,
|
|
147
148
|
})
|
|
@@ -150,6 +151,7 @@ export function create(
|
|
|
150
151
|
for (const key of Object.keys(input))
|
|
151
152
|
if (
|
|
152
153
|
![
|
|
154
|
+
'composition',
|
|
153
155
|
'cssOutput',
|
|
154
156
|
'id',
|
|
155
157
|
'defaultLayer',
|
|
@@ -190,6 +192,13 @@ export function create(
|
|
|
190
192
|
)
|
|
191
193
|
throw new InvalidError('cssOutput must be atomic or grouped.')
|
|
192
194
|
|
|
195
|
+
if (
|
|
196
|
+
input.composition !== undefined &&
|
|
197
|
+
input.composition !== 'independent' &&
|
|
198
|
+
input.composition !== 'ordered'
|
|
199
|
+
)
|
|
200
|
+
throw new InvalidError('composition must be independent or ordered.')
|
|
201
|
+
|
|
193
202
|
if (input.theme !== undefined && input.themes !== undefined)
|
|
194
203
|
throw new InvalidError('Use either theme or themes, not both.')
|
|
195
204
|
|
|
@@ -253,6 +262,9 @@ export function create(
|
|
|
253
262
|
|
|
254
263
|
const contract = Object.freeze({
|
|
255
264
|
...(variableMode ? { variableSet: true, mappings: variableMappings } : {}),
|
|
265
|
+
...(input.composition !== undefined
|
|
266
|
+
? { composition: input.composition as 'independent' | 'ordered' }
|
|
267
|
+
: {}),
|
|
256
268
|
...(input.defaultLayer !== undefined
|
|
257
269
|
? { defaultLayer: input.defaultLayer as string }
|
|
258
270
|
: {}),
|
|
@@ -452,6 +464,11 @@ export function create(
|
|
|
452
464
|
export declare namespace create {
|
|
453
465
|
/** Optional layer names and mutually exclusive theme modes. */
|
|
454
466
|
type Options = {
|
|
467
|
+
/**
|
|
468
|
+
* Whether bound styles' class lists may combine. Independent atomic styles
|
|
469
|
+
* share rules for repeated declarations, so combine them only with `cx`.
|
|
470
|
+
*/
|
|
471
|
+
readonly composition?: 'independent' | 'ordered' | undefined
|
|
455
472
|
/** CSS representation inherited by bound helpers; atomic by default. */
|
|
456
473
|
readonly cssOutput?: 'atomic' | 'grouped' | undefined
|
|
457
474
|
/** Fallback CSS layer for bound styles and variants; unlayered when omitted. */
|
|
@@ -837,6 +854,11 @@ type Validated<options> = Record<
|
|
|
837
854
|
|
|
838
855
|
/** Vars, optional named alternatives, and category-to-property mappings. */
|
|
839
856
|
export type VariableOptions = {
|
|
857
|
+
/**
|
|
858
|
+
* Whether bound styles' class lists may combine. Independent atomic styles
|
|
859
|
+
* share rules for repeated declarations, so combine them only with `cx`.
|
|
860
|
+
*/
|
|
861
|
+
readonly composition?: 'independent' | 'ordered' | undefined
|
|
840
862
|
/** CSS representation inherited by bound helpers; atomic by default. */
|
|
841
863
|
readonly cssOutput?: 'atomic' | 'grouped' | undefined
|
|
842
864
|
/** Fallback CSS layer for bound styles and variants; unlayered when omitted. */
|
package/src/internal/Token.ts
CHANGED
|
@@ -393,6 +393,8 @@ export type Contract = {
|
|
|
393
393
|
readonly variableSet?: boolean | undefined
|
|
394
394
|
/** Configuration-local category-to-property mappings. */
|
|
395
395
|
readonly mappings?: VariableSets.Mappings | false | undefined
|
|
396
|
+
/** Whether bound styles' class lists combine, retained by configuration-bound handles. */
|
|
397
|
+
readonly composition?: 'independent' | 'ordered' | undefined
|
|
396
398
|
/** Web emission mode retained by configuration-bound theme handles. */
|
|
397
399
|
readonly cssOutput?: 'atomic' | 'grouped' | undefined
|
|
398
400
|
/** Fallback layer for declarations without an explicit layer. */
|
|
@@ -55,19 +55,30 @@ export function from(metadata: Token.Metadata): Vars.Definition {
|
|
|
55
55
|
) as Vars.Definition
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
/**
|
|
58
|
+
/**
|
|
59
|
+
* Validates and freezes a complete set, or grows a base set with compatible
|
|
60
|
+
* overrides and new leaves.
|
|
61
|
+
*/
|
|
59
62
|
export function build(
|
|
60
63
|
input: unknown,
|
|
61
64
|
contract: Token.Contract,
|
|
62
|
-
|
|
63
|
-
baseQueries?: Query.Metadata,
|
|
64
|
-
basePaths?: Token.Metadata['paths'],
|
|
65
|
+
options: build.Options = {},
|
|
65
66
|
): Vars.Definition {
|
|
67
|
+
const base = options.base
|
|
66
68
|
const values: Record<string, Token.Value> = Object.assign(
|
|
67
69
|
Object.create(null),
|
|
68
|
-
base,
|
|
70
|
+
base?.values,
|
|
69
71
|
)
|
|
70
|
-
const paths = { ...
|
|
72
|
+
const paths = { ...base?.paths }
|
|
73
|
+
// Segment keys keep dotted typography conditions distinct from nested paths.
|
|
74
|
+
const leaves = new Set<string>()
|
|
75
|
+
const categories = new Set<string>()
|
|
76
|
+
for (const path of Object.keys(base?.values ?? {})) {
|
|
77
|
+
const parts = base!.paths?.[path] ?? path.split('.')
|
|
78
|
+
leaves.add(JSON.stringify(parts))
|
|
79
|
+
for (let index = 1; index < parts.length; index++)
|
|
80
|
+
categories.add(JSON.stringify(parts.slice(0, index)))
|
|
81
|
+
}
|
|
71
82
|
const conditions: string[][] = []
|
|
72
83
|
const active = new Set<object>()
|
|
73
84
|
function visit(input: unknown, path: string[]) {
|
|
@@ -86,14 +97,31 @@ export function build(
|
|
|
86
97
|
) {
|
|
87
98
|
if (!path.length)
|
|
88
99
|
throw new Vars.InvalidError(path, 'Expected a variable record.')
|
|
89
|
-
|
|
100
|
+
if (categories.has(JSON.stringify(path)))
|
|
101
|
+
throw new Vars.InvalidError(
|
|
102
|
+
path,
|
|
103
|
+
'Extensions cannot replace a variable category with a leaf.',
|
|
104
|
+
)
|
|
90
105
|
const name = path.join('.')
|
|
91
|
-
|
|
106
|
+
const previous =
|
|
107
|
+
base && Object.hasOwn(base.values, name) ? base.values[name] : undefined
|
|
108
|
+
if (previous !== undefined && options.derived)
|
|
92
109
|
throw new Vars.InvalidError(
|
|
93
110
|
path,
|
|
94
|
-
'
|
|
111
|
+
'Derived variables cannot replace existing paths.',
|
|
95
112
|
)
|
|
96
|
-
|
|
113
|
+
// Restating the base reference would make the variable reference itself.
|
|
114
|
+
if (
|
|
115
|
+
previous !== undefined &&
|
|
116
|
+
Token.is(input) &&
|
|
117
|
+
input.path === name &&
|
|
118
|
+
(input.contract === contract ||
|
|
119
|
+
(contract[Token.identity] !== undefined &&
|
|
120
|
+
input.contract[Token.identity] === contract[Token.identity]))
|
|
121
|
+
)
|
|
122
|
+
return
|
|
123
|
+
const value = read(input, path, queryData)
|
|
124
|
+
if (previous !== undefined && domain(previous) !== domain(value))
|
|
97
125
|
throw new Vars.InvalidError(
|
|
98
126
|
path,
|
|
99
127
|
'Variable overrides must preserve their domain.',
|
|
@@ -103,11 +131,19 @@ export function build(
|
|
|
103
131
|
values[name] = value
|
|
104
132
|
return
|
|
105
133
|
}
|
|
134
|
+
if (leaves.has(JSON.stringify(path)))
|
|
135
|
+
throw new Vars.InvalidError(
|
|
136
|
+
path,
|
|
137
|
+
'Extensions cannot replace a variable leaf with a category.',
|
|
138
|
+
)
|
|
106
139
|
const entries = record(input, path)
|
|
107
140
|
if (active.has(input as object))
|
|
108
141
|
throw new Vars.InvalidError(path, 'Cyclic variables are not supported.')
|
|
109
142
|
active.add(input as object)
|
|
110
|
-
if (
|
|
143
|
+
if (
|
|
144
|
+
!entries.length &&
|
|
145
|
+
(!base || (path.length > 0 && !categories.has(JSON.stringify(path))))
|
|
146
|
+
)
|
|
111
147
|
throw new Vars.InvalidError(path, 'Variable records cannot be empty.')
|
|
112
148
|
for (const [key, value] of entries) {
|
|
113
149
|
const query = path[0] === 'typography' && Typography.condition(key)
|
|
@@ -122,34 +158,29 @@ export function build(
|
|
|
122
158
|
active.delete(input as object)
|
|
123
159
|
}
|
|
124
160
|
const fields = Object.fromEntries(record(input, []))
|
|
125
|
-
const queries = { ...
|
|
161
|
+
const queries = { ...base?.queries }
|
|
126
162
|
for (const key of ['breakpoint', 'container', 'containerNames'] as const) {
|
|
127
163
|
if (Object.hasOwn(fields, key)) {
|
|
128
164
|
const value = fields[key]
|
|
129
165
|
if (base) {
|
|
130
166
|
if (key === 'containerNames') {
|
|
131
167
|
if (
|
|
132
|
-
|
|
133
|
-
|
|
168
|
+
!Array.isArray(value) ||
|
|
169
|
+
(base.queries?.containerNames ?? []).some(
|
|
170
|
+
(name) => !value.includes(name),
|
|
171
|
+
)
|
|
134
172
|
)
|
|
135
173
|
throw new Vars.InvalidError(
|
|
136
174
|
[key],
|
|
137
|
-
'Extensions cannot
|
|
175
|
+
'Extensions cannot remove container identities.',
|
|
138
176
|
)
|
|
139
|
-
} else
|
|
140
|
-
for (const [name] of record(value, [key]))
|
|
141
|
-
if (!Object.hasOwn(baseQueries?.[key] ?? {}, name))
|
|
142
|
-
throw new Vars.InvalidError(
|
|
143
|
-
[key, name],
|
|
144
|
-
'Extensions cannot add query thresholds.',
|
|
145
|
-
)
|
|
146
|
-
}
|
|
177
|
+
} else record(value, [key])
|
|
147
178
|
}
|
|
148
179
|
Object.assign(queries, {
|
|
149
180
|
[key]:
|
|
150
181
|
key === 'containerNames'
|
|
151
182
|
? value
|
|
152
|
-
: { ...
|
|
183
|
+
: { ...base?.queries?.[key], ...value },
|
|
153
184
|
})
|
|
154
185
|
if (key !== 'container' || Object.keys(value as object).length === 0)
|
|
155
186
|
delete fields[key]
|
|
@@ -178,6 +209,55 @@ export function build(
|
|
|
178
209
|
) as Vars.Definition
|
|
179
210
|
}
|
|
180
211
|
|
|
212
|
+
/** Set construction inputs. */
|
|
213
|
+
export declare namespace build {
|
|
214
|
+
/** Base set metadata and how its existing paths may change. */
|
|
215
|
+
type Options = {
|
|
216
|
+
/** Set whose leaves the input overrides or extends. */
|
|
217
|
+
readonly base?:
|
|
218
|
+
| Pick<Token.Metadata, 'paths' | 'queries' | 'values'>
|
|
219
|
+
| undefined
|
|
220
|
+
/** Rejects existing paths, as derived leaves only add to the set. */
|
|
221
|
+
readonly derived?: boolean | undefined
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Reads a set's references as values for a named extension. Typography presets
|
|
227
|
+
* keep their base values, since presets expand at each use.
|
|
228
|
+
*/
|
|
229
|
+
export function references(
|
|
230
|
+
variables: object,
|
|
231
|
+
metadata: Pick<Token.Metadata, 'paths' | 'queries' | 'values'>,
|
|
232
|
+
): Vars.Values {
|
|
233
|
+
const tree: Record<string, unknown> = Object.create(null)
|
|
234
|
+
|
|
235
|
+
for (const [path, value] of Object.entries(metadata.values)) {
|
|
236
|
+
const parts = metadata.paths?.[path] ?? path.split('.')
|
|
237
|
+
let target = tree
|
|
238
|
+
let source: unknown = variables
|
|
239
|
+
|
|
240
|
+
for (const part of parts.slice(0, -1)) {
|
|
241
|
+
target = (target[part] ??= Object.create(null)) as Record<string, unknown>
|
|
242
|
+
source = (source as Record<string, unknown>)[part]
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const name = parts.at(-1)!
|
|
246
|
+
target[name] =
|
|
247
|
+
parts[0] === 'typography'
|
|
248
|
+
? value
|
|
249
|
+
: (source as Record<string, unknown>)[name]
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
for (const key of ['breakpoint', 'container'] as const)
|
|
253
|
+
if (Object.keys(metadata.queries?.[key] ?? {}).length)
|
|
254
|
+
tree[key] = metadata.queries![key]
|
|
255
|
+
if (metadata.queries?.containerNames?.length)
|
|
256
|
+
tree.containerNames = metadata.queries.containerNames
|
|
257
|
+
|
|
258
|
+
return tree as Vars.Values
|
|
259
|
+
}
|
|
260
|
+
|
|
181
261
|
function buildTree(
|
|
182
262
|
values: Token.Metadata['values'],
|
|
183
263
|
contract: Token.Contract,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** Retains filesystem snapshots and an incremental graph within a Next.js loader worker. @module */
|
|
2
2
|
import * as Contract from '../../compiler/internal/Contract.js'
|
|
3
|
+
import * as Contracts from '../../node/internal/Contracts.js'
|
|
3
4
|
import * as Fs from 'node:fs/promises'
|
|
4
5
|
import * as Graph from '../../compiler/Graph.js'
|
|
5
6
|
import * as Path from 'node:path'
|
|
@@ -26,6 +27,7 @@ export type Context = {
|
|
|
26
27
|
/** Creates project state owned by a loader configuration in one worker process. */
|
|
27
28
|
export function create(options: create.Options) {
|
|
28
29
|
let compiler = Graph.create()
|
|
30
|
+
const packages = Contracts.create()
|
|
29
31
|
const files = new Map<string, { source: string; version: string }>()
|
|
30
32
|
const libraries = new Map<
|
|
31
33
|
string,
|
|
@@ -122,6 +124,20 @@ export function create(options: create.Options) {
|
|
|
122
124
|
}
|
|
123
125
|
return pending
|
|
124
126
|
}
|
|
127
|
+
// Files behind package contracts stay dependencies even when the module is unchanged.
|
|
128
|
+
const inputs = new Set<string>()
|
|
129
|
+
const host: Contracts.read.Options = {
|
|
130
|
+
read: async (file) => {
|
|
131
|
+
const source = await read(file)
|
|
132
|
+
// Next ignores node_modules edits, and tracked manifests stalled webpack rebuilds after a failed compile.
|
|
133
|
+
if (Path.basename(file) === 'package.json') return source
|
|
134
|
+
track(file)
|
|
135
|
+
inputs.add(file)
|
|
136
|
+
return source
|
|
137
|
+
},
|
|
138
|
+
resolve: async (specifier, importer) =>
|
|
139
|
+
(await resolve(Path.dirname(importer), specifier)) || undefined,
|
|
140
|
+
}
|
|
125
141
|
const id = (file: string) =>
|
|
126
142
|
`app/${Path.relative(root, file).split(Path.sep).join('/')}`
|
|
127
143
|
const eligible = (file: string) =>
|
|
@@ -322,14 +338,10 @@ export function create(options: create.Options) {
|
|
|
322
338
|
await visit(resolved)
|
|
323
339
|
continue
|
|
324
340
|
}
|
|
325
|
-
const
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
links[specifier] = resolved
|
|
330
|
-
} catch (error) {
|
|
331
|
-
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error
|
|
332
|
-
}
|
|
341
|
+
const contract = await packages.read(resolved, host)
|
|
342
|
+
if (contract === undefined) continue
|
|
343
|
+
contracts[resolved] = contract
|
|
344
|
+
links[specifier] = resolved
|
|
333
345
|
}
|
|
334
346
|
}
|
|
335
347
|
|
|
@@ -360,11 +372,10 @@ export function create(options: create.Options) {
|
|
|
360
372
|
|
|
361
373
|
const links = (imports[owner] ??= Object.create(null))
|
|
362
374
|
links[specifier] = target
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
}
|
|
375
|
+
contracts[target] ??= (await packages.read(target, {
|
|
376
|
+
...host,
|
|
377
|
+
required: true,
|
|
378
|
+
}))!
|
|
368
379
|
await dependencies(target)
|
|
369
380
|
owner = target
|
|
370
381
|
}
|
|
@@ -388,16 +399,10 @@ export function create(options: create.Options) {
|
|
|
388
399
|
if (output.code === source && !output.css) {
|
|
389
400
|
context.addDependency(context.resourcePath)
|
|
390
401
|
for (const file of runtime) context.addDependency(file)
|
|
391
|
-
for (const file of
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
context.addDependency(
|
|
396
|
-
target.startsWith('app/')
|
|
397
|
-
? Path.join(root, target.slice(4))
|
|
398
|
-
: `${target}.zyzz.json`,
|
|
399
|
-
)
|
|
400
|
-
}
|
|
402
|
+
for (const file of inputs) context.addDependency(file)
|
|
403
|
+
for (const target of Object.values(imports[entry] ?? {}))
|
|
404
|
+
if (target?.startsWith('app/'))
|
|
405
|
+
context.addDependency(Path.join(root, target.slice(4)))
|
|
401
406
|
} else {
|
|
402
407
|
for (const file of tracked) context.addDependency(file)
|
|
403
408
|
}
|
package/src/next/loader.ts
CHANGED
|
@@ -161,7 +161,8 @@ async function compile(context: Context, source: string) {
|
|
|
161
161
|
placeholder,
|
|
162
162
|
Path.relative(directory, file)
|
|
163
163
|
.split(Path.sep)
|
|
164
|
-
|
|
164
|
+
// Bundlers resolve url() paths without decoding, so escape only characters that change URL parsing.
|
|
165
|
+
.map((part) => part.replace(/[%#?]/g, encodeURIComponent))
|
|
165
166
|
.join('/') + target.slice(raw.length),
|
|
166
167
|
)
|
|
167
168
|
}
|