@bamboocss/generator 1.33.0 → 1.34.1
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/dist/index.cjs +127 -10
- package/dist/index.d.cts +60 -5
- package/dist/index.d.mts +60 -5
- package/dist/index.mjs +128 -11
- package/package.json +7 -7
package/dist/index.cjs
CHANGED
|
@@ -111,6 +111,7 @@ function generateConditions(ctx) {
|
|
|
111
111
|
function generateCssFn(ctx) {
|
|
112
112
|
const { utility, hash, prefix } = ctx;
|
|
113
113
|
const { separator } = utility;
|
|
114
|
+
const leafFallback = ctx.config.leafFallback ?? true;
|
|
114
115
|
return {
|
|
115
116
|
dts: outdent.outdent`
|
|
116
117
|
${ctx.file.importType("SystemStyleObject, ViewTransitionFn", "../types/index")}
|
|
@@ -204,12 +205,22 @@ function generateCssFn(ctx) {
|
|
|
204
205
|
|
|
205
206
|
// Emitted for the source transform, which rewrites a single dynamic style leaf into a
|
|
206
207
|
// call to this rather than leaving a \`css()\` behind. \`prefix\` is the class up to the
|
|
207
|
-
// value, resolved at build time; \`prop\`
|
|
208
|
-
// declines
|
|
209
|
-
export const cssLeaf = (prefix, prop, value) => {
|
|
208
|
+
// value, resolved at build time; \`prop\` names the property for the shapes \`leafClass\`
|
|
209
|
+
// declines.
|
|
210
|
+
${leafFallback ? `export const cssLeaf = (prefix, prop, value) => {
|
|
210
211
|
const className = leafClass(prefix, value)
|
|
211
212
|
return className === undefined ? css({ [prop]: value }) : className
|
|
212
|
-
}
|
|
213
|
+
}` : `export const cssLeaf = (prefix, prop, value) => {
|
|
214
|
+
const className = leafClass(prefix, value)
|
|
215
|
+
if (className === undefined) {
|
|
216
|
+
throw new Error(
|
|
217
|
+
\`[bamboocss] \\\`\${prop}\\\` got a \${Array.isArray(value) ? 'responsive array' : 'condition object'} from a \` +
|
|
218
|
+
\`runtime value, and \\\`leafFallback\\\` is off. Write the conditions as a literal at the call site, so \` +
|
|
219
|
+
\`the build resolves each branch, or set \\\`leafFallback: true\\\` to keep the runtime.\`,
|
|
220
|
+
)
|
|
221
|
+
}
|
|
222
|
+
return className
|
|
223
|
+
}`}
|
|
213
224
|
|
|
214
225
|
// Sugar for the string form, so the feature has an import to discover, a signature to
|
|
215
226
|
// hover and a name the editor can complete. The extractor evaluates the call, so the
|
|
@@ -2353,13 +2364,36 @@ function generatePropTypes(ctx) {
|
|
|
2353
2364
|
return outdent.outdent`
|
|
2354
2365
|
${result.join("\n")}
|
|
2355
2366
|
|
|
2356
|
-
type WithColorOpacityModifier<T> = [T] extends [string] ? \`$\{T}/\${string}\` & { __colorOpacityModifier?: true } : never
|
|
2357
|
-
|
|
2358
2367
|
type ImportantMark = "!" | "!important"
|
|
2359
2368
|
type WhitespaceImportant = \` \${ImportantMark}\`
|
|
2360
2369
|
type Important = ImportantMark | WhitespaceImportant
|
|
2361
2370
|
type WithImportant<T> = [T] extends [string] ? \`\${T}\${Important}\` & { __important?: true } : never
|
|
2362
2371
|
|
|
2372
|
+
/**
|
|
2373
|
+
* The modifiers a token path may carry, as one open-ended tail rather than one closed
|
|
2374
|
+
* template per form.
|
|
2375
|
+
*
|
|
2376
|
+
* ⚠️ The \`& { __modifier?: true }\` is load-bearing, and nothing reads it. Deleting it as
|
|
2377
|
+
* dead weight costs **12.8x** on \`tsc\` — measured at 87.2s against 6.8s over 4,000 call
|
|
2378
|
+
* sites — because it is what stops TypeScript attempting subtype reduction across the
|
|
2379
|
+
* union these expand into. The same applies to \`__important\` above.
|
|
2380
|
+
*
|
|
2381
|
+
* A template literal distributes over a union in any placeholder, so \`\${T}\` against a
|
|
2382
|
+
* 258-token colour palette is 258 members, and the old \`\${T}\${Important}\` was four times
|
|
2383
|
+
* that. Between them the two modifier forms were 5N of a ~1,560-member union for \`color\`
|
|
2384
|
+
* alone, and half the cost of type-checking a \`css()\` call under \`strictTokens\`. Folding
|
|
2385
|
+
* them into one 3N tail is 14.5% off that — 7.09s against 8.29s over the same 4,000 call
|
|
2386
|
+
* sites, with a control repeat agreeing to 3.5%.
|
|
2387
|
+
*
|
|
2388
|
+
* What it gives up is the tail: \`red.300!nonsense\` type-checks now, where five exact
|
|
2389
|
+
* templates would have rejected it. \`unresolvedToken\` strips the mark and resolves the
|
|
2390
|
+
* path underneath, so the build still reports it — warning by default, failing under
|
|
2391
|
+
* \`'error'\`. The diagnostic moves rather than disappears, and only for a value nobody
|
|
2392
|
+
* writes on purpose.
|
|
2393
|
+
*/
|
|
2394
|
+
type Modifier = "/" | "!" | " !"
|
|
2395
|
+
type WithModifier<T> = [T] extends [string] ? \`\${T}\${Modifier}\${string}\` & { __modifier?: true } : never
|
|
2396
|
+
|
|
2363
2397
|
/**
|
|
2364
2398
|
* A list of candidate values, most-preferred first, emitted as repeated declarations so the
|
|
2365
2399
|
* browser keeps the last one it understands.
|
|
@@ -2391,8 +2425,7 @@ function generatePropTypes(ctx) {
|
|
|
2391
2425
|
| \`[\${string}]\`
|
|
2392
2426
|
| FallbackValue
|
|
2393
2427
|
| WithImportant<FallbackValue>
|
|
2394
|
-
|
|
|
2395
|
-
| WithImportant<T>
|
|
2428
|
+
| WithModifier<T>
|
|
2396
2429
|
|
|
2397
2430
|
/**
|
|
2398
2431
|
* Will restrict the value of properties that have predefined values to those values only.
|
|
@@ -3934,8 +3967,21 @@ var Generator = class extends _bamboocss_core.Context {
|
|
|
3934
3967
|
* unused for want of a utility to reference it.
|
|
3935
3968
|
*
|
|
3936
3969
|
* `keep` carries names this cannot see for itself; see `collectKeyframeReferences`.
|
|
3970
|
+
*
|
|
3971
|
+
* `reachableVars` is `pruneTokens`' answer about custom properties, which has to be handed
|
|
3972
|
+
* over rather than re-derived here. A token kept by a reader outside the stylesheet — a
|
|
3973
|
+
* `token()` call, a `prune.keepTokens` pattern, a theme, a `globalCss` export — is
|
|
3974
|
+
* reachable to that pass and invisible to this one, so deriving it again from the css
|
|
3975
|
+
* deletes the `@keyframes` out from under a declaration that ships. Every caller that
|
|
3976
|
+
* prunes both hands it over.
|
|
3977
|
+
*
|
|
3978
|
+
* Omitting it falls back to what `prune.tokens` implies. Under `off` that is `'all'`: no
|
|
3979
|
+
* token declaration is removable, so each one ships and keeps the keyframe it names —
|
|
3980
|
+
* and `off` is precisely the setting chosen because something outside the stylesheet
|
|
3981
|
+
* reads them. Otherwise it is the css alone, which is what a caller running this pass
|
|
3982
|
+
* without the other one is asking for.
|
|
3937
3983
|
*/
|
|
3938
|
-
pruneKeyframes = (sheet, keep) => {
|
|
3984
|
+
pruneKeyframes = (sheet, keep, reachableVars) => {
|
|
3939
3985
|
if (!this.config.prune?.keyframes) return;
|
|
3940
3986
|
const layers = sheet.layers;
|
|
3941
3987
|
const keyframeNames = new Set(Object.keys(this.config.theme?.keyframes ?? {}));
|
|
@@ -3953,7 +3999,8 @@ var Generator = class extends _bamboocss_core.Context {
|
|
|
3953
3999
|
],
|
|
3954
4000
|
target: layers.tokens,
|
|
3955
4001
|
keyframeNames,
|
|
3956
|
-
keep: new Set([...this.getThemeKeyframeNames(keyframeNames), ...keep ?? []])
|
|
4002
|
+
keep: new Set([...this.getThemeKeyframeNames(keyframeNames), ...keep ?? []]),
|
|
4003
|
+
reachableVars: reachableVars ?? ((this.config.prune?.tokens ?? "reachable") === "off" ? "all" : void 0)
|
|
3957
4004
|
});
|
|
3958
4005
|
_bamboocss_logger.logger.debug("prune:keyframes", `Removed ${result.removed} unused keyframe(s)`);
|
|
3959
4006
|
return result;
|
|
@@ -4041,9 +4088,79 @@ var Generator = class extends _bamboocss_core.Context {
|
|
|
4041
4088
|
artifact: "styles.css",
|
|
4042
4089
|
content: css
|
|
4043
4090
|
}) ?? css;
|
|
4091
|
+
this.assertNoUnresolvedTokens();
|
|
4044
4092
|
return css;
|
|
4045
4093
|
};
|
|
4046
4094
|
/**
|
|
4095
|
+
* Fail on a style value shaped like a token path that names no token.
|
|
4096
|
+
*
|
|
4097
|
+
* Only under `unresolvedToken: 'error'` — see that option for why this one is graded and a
|
|
4098
|
+
* dead binding is not.
|
|
4099
|
+
*
|
|
4100
|
+
* Two sources, because neither sees the whole build.
|
|
4101
|
+
*
|
|
4102
|
+
* **Atomic styles are read off the decoded sheet** rather than accumulated as `transform`
|
|
4103
|
+
* runs, and that is the load-bearing part for them. A `Context` outlives rebuilds while the
|
|
4104
|
+
* decoder memoizes each atom by hash, so on the second build of the same source `transform`
|
|
4105
|
+
* is never re-entered: an accumulating record either keeps a finding past the edit that
|
|
4106
|
+
* fixed it — wedging a dev server — or is cleared and then never refilled, which passes a
|
|
4107
|
+
* build whose source is still broken. That second one is the worse failure and is what an
|
|
4108
|
+
* earlier version of this did.
|
|
4109
|
+
*
|
|
4110
|
+
* `decoder.atomic` has neither problem, because it is not a record of what happened — it is
|
|
4111
|
+
* what the sheet is built from, and each result keeps the `prop` and `value` it was written
|
|
4112
|
+
* with. So the question asked is the one that matters: does the stylesheet *being emitted*
|
|
4113
|
+
* contain a declaration the browser will drop.
|
|
4114
|
+
*
|
|
4115
|
+
* Within a watch process that set is cumulative, and so is the css: extraction is additive,
|
|
4116
|
+
* so the rule for a style deleted from source is still in the sheet until the process
|
|
4117
|
+
* restarts. This reports the same way for the same reason — the declaration really is still
|
|
4118
|
+
* in the file being written, and saying otherwise would be a check that disagreed with its
|
|
4119
|
+
* own output. A production build is a fresh process and sees only what its source asked
|
|
4120
|
+
* for.
|
|
4121
|
+
*
|
|
4122
|
+
* **Config-derived styles are not in that set at all**, which is the gap this used to have.
|
|
4123
|
+
* `globalCss`, the reset, config recipes and compositions serialize through
|
|
4124
|
+
* `transformStyles`, and that clones the decoder — so their atoms land in a throwaway and
|
|
4125
|
+
* `decoder.atomic` never hears about them. Reading only the sheet made `'error'` *quieter*
|
|
4126
|
+
* than the default on exactly those styles: the warning was suppressed in favour of a check
|
|
4127
|
+
* that could not see them, so a bad token in `globalCss` warned with the option unset and
|
|
4128
|
+
* then passed silently with it set to `'error'`. `utility.unresolvedTokens` is the record of
|
|
4129
|
+
* what only `transform` can see; see it for why accumulating is right for that half.
|
|
4130
|
+
*
|
|
4131
|
+
* Both halves key on `property:path` with shorthands resolved, so a value that does reach
|
|
4132
|
+
* both — every atomic style is transformed once before it is memoized — is one finding.
|
|
4133
|
+
*
|
|
4134
|
+
* Here rather than beside the asserts in `BambooContext` because this is where the sheet
|
|
4135
|
+
* exists: those all run during extraction, before anything has been decoded. Every path
|
|
4136
|
+
* that emits css comes through `getCss`.
|
|
4137
|
+
*/
|
|
4138
|
+
assertNoUnresolvedTokens = () => {
|
|
4139
|
+
if (this.config.unresolvedToken !== "error") return;
|
|
4140
|
+
const found = new Map(this.utility.unresolvedTokens);
|
|
4141
|
+
for (const atom of this.decoder.atomic) {
|
|
4142
|
+
const { prop, value } = atom.entry;
|
|
4143
|
+
if (typeof value !== "string" || typeof prop !== "string") continue;
|
|
4144
|
+
if (!this.utility.isUnresolvedTokenValue(prop, value)) continue;
|
|
4145
|
+
const key = this.utility.resolveShorthand(prop);
|
|
4146
|
+
const bare = this.utility.bareTokenPath(key, value);
|
|
4147
|
+
found.set(`${key}:${bare}`, {
|
|
4148
|
+
prop: key,
|
|
4149
|
+
value: bare,
|
|
4150
|
+
category: this.utility.getTokenCategory(key)
|
|
4151
|
+
});
|
|
4152
|
+
}
|
|
4153
|
+
if (!found.size) return;
|
|
4154
|
+
const detail = (0, _bamboocss_shared.truncateList)(Array.from(found.values(), ({ prop, value, category }) => {
|
|
4155
|
+
return `- \`${prop}: ${value}\`.${category ? ` Check the path against your \`${category}\` tokens.` : ""}`;
|
|
4156
|
+
}), {
|
|
4157
|
+
limit: 25,
|
|
4158
|
+
unit: "value",
|
|
4159
|
+
separator: "\n"
|
|
4160
|
+
});
|
|
4161
|
+
throw new _bamboocss_shared.BambooError("UNRESOLVED_TOKEN", `${found.size} style value(s) name a token that does not exist:\n\n${detail}\n\nEach is emitted as written, which parses — so the stylesheet is valid and nothing downstream objects. The browser drops the declaration at compute time and the style is simply absent from the element, which surfaces as "this never applied" a long way from the typo that caused it. Write \`[value]\` to mark one as a literal, or set \`unresolvedToken: 'warn'\` to report these without failing.`);
|
|
4162
|
+
};
|
|
4163
|
+
/**
|
|
4047
4164
|
* Get CSS for a specific layer from the stylesheet
|
|
4048
4165
|
*/
|
|
4049
4166
|
getLayerCss = (sheet, layer) => {
|
package/dist/index.d.cts
CHANGED
|
@@ -37,10 +37,7 @@ declare class Generator extends Context {
|
|
|
37
37
|
* `keep` carries references this cannot see for itself; see `collectTokenReferences`.
|
|
38
38
|
*/
|
|
39
39
|
pruneTokens: (sheet: Stylesheet, keep?: Set<string>, tokensReachableFromJs?: boolean) => {
|
|
40
|
-
|
|
41
|
-
kept: number;
|
|
42
|
-
removedProperties?: undefined;
|
|
43
|
-
} | {
|
|
40
|
+
reachable: Set<string> | undefined;
|
|
44
41
|
removed: number;
|
|
45
42
|
removedProperties: number;
|
|
46
43
|
kept: number;
|
|
@@ -63,8 +60,21 @@ declare class Generator extends Context {
|
|
|
63
60
|
* unused for want of a utility to reference it.
|
|
64
61
|
*
|
|
65
62
|
* `keep` carries names this cannot see for itself; see `collectKeyframeReferences`.
|
|
63
|
+
*
|
|
64
|
+
* `reachableVars` is `pruneTokens`' answer about custom properties, which has to be handed
|
|
65
|
+
* over rather than re-derived here. A token kept by a reader outside the stylesheet — a
|
|
66
|
+
* `token()` call, a `prune.keepTokens` pattern, a theme, a `globalCss` export — is
|
|
67
|
+
* reachable to that pass and invisible to this one, so deriving it again from the css
|
|
68
|
+
* deletes the `@keyframes` out from under a declaration that ships. Every caller that
|
|
69
|
+
* prunes both hands it over.
|
|
70
|
+
*
|
|
71
|
+
* Omitting it falls back to what `prune.tokens` implies. Under `off` that is `'all'`: no
|
|
72
|
+
* token declaration is removable, so each one ships and keeps the keyframe it names —
|
|
73
|
+
* and `off` is precisely the setting chosen because something outside the stylesheet
|
|
74
|
+
* reads them. Otherwise it is the css alone, which is what a caller running this pass
|
|
75
|
+
* without the other one is asking for.
|
|
66
76
|
*/
|
|
67
|
-
pruneKeyframes: (sheet: Stylesheet, keep?: Set<string>) => {
|
|
77
|
+
pruneKeyframes: (sheet: Stylesheet, keep?: Set<string>, reachableVars?: Set<string> | "all") => {
|
|
68
78
|
removed: number;
|
|
69
79
|
kept: number;
|
|
70
80
|
} | undefined;
|
|
@@ -119,6 +129,51 @@ declare class Generator extends Context {
|
|
|
119
129
|
private getAlwaysKeptTokenVars;
|
|
120
130
|
getParserCss: (decoder: StyleDecoder) => string;
|
|
121
131
|
getCss: (stylesheet?: Stylesheet) => string;
|
|
132
|
+
/**
|
|
133
|
+
* Fail on a style value shaped like a token path that names no token.
|
|
134
|
+
*
|
|
135
|
+
* Only under `unresolvedToken: 'error'` — see that option for why this one is graded and a
|
|
136
|
+
* dead binding is not.
|
|
137
|
+
*
|
|
138
|
+
* Two sources, because neither sees the whole build.
|
|
139
|
+
*
|
|
140
|
+
* **Atomic styles are read off the decoded sheet** rather than accumulated as `transform`
|
|
141
|
+
* runs, and that is the load-bearing part for them. A `Context` outlives rebuilds while the
|
|
142
|
+
* decoder memoizes each atom by hash, so on the second build of the same source `transform`
|
|
143
|
+
* is never re-entered: an accumulating record either keeps a finding past the edit that
|
|
144
|
+
* fixed it — wedging a dev server — or is cleared and then never refilled, which passes a
|
|
145
|
+
* build whose source is still broken. That second one is the worse failure and is what an
|
|
146
|
+
* earlier version of this did.
|
|
147
|
+
*
|
|
148
|
+
* `decoder.atomic` has neither problem, because it is not a record of what happened — it is
|
|
149
|
+
* what the sheet is built from, and each result keeps the `prop` and `value` it was written
|
|
150
|
+
* with. So the question asked is the one that matters: does the stylesheet *being emitted*
|
|
151
|
+
* contain a declaration the browser will drop.
|
|
152
|
+
*
|
|
153
|
+
* Within a watch process that set is cumulative, and so is the css: extraction is additive,
|
|
154
|
+
* so the rule for a style deleted from source is still in the sheet until the process
|
|
155
|
+
* restarts. This reports the same way for the same reason — the declaration really is still
|
|
156
|
+
* in the file being written, and saying otherwise would be a check that disagreed with its
|
|
157
|
+
* own output. A production build is a fresh process and sees only what its source asked
|
|
158
|
+
* for.
|
|
159
|
+
*
|
|
160
|
+
* **Config-derived styles are not in that set at all**, which is the gap this used to have.
|
|
161
|
+
* `globalCss`, the reset, config recipes and compositions serialize through
|
|
162
|
+
* `transformStyles`, and that clones the decoder — so their atoms land in a throwaway and
|
|
163
|
+
* `decoder.atomic` never hears about them. Reading only the sheet made `'error'` *quieter*
|
|
164
|
+
* than the default on exactly those styles: the warning was suppressed in favour of a check
|
|
165
|
+
* that could not see them, so a bad token in `globalCss` warned with the option unset and
|
|
166
|
+
* then passed silently with it set to `'error'`. `utility.unresolvedTokens` is the record of
|
|
167
|
+
* what only `transform` can see; see it for why accumulating is right for that half.
|
|
168
|
+
*
|
|
169
|
+
* Both halves key on `property:path` with shorthands resolved, so a value that does reach
|
|
170
|
+
* both — every atomic style is transformed once before it is memoized — is one finding.
|
|
171
|
+
*
|
|
172
|
+
* Here rather than beside the asserts in `BambooContext` because this is where the sheet
|
|
173
|
+
* exists: those all run during extraction, before anything has been decoded. Every path
|
|
174
|
+
* that emits css comes through `getCss`.
|
|
175
|
+
*/
|
|
176
|
+
assertNoUnresolvedTokens: () => void;
|
|
122
177
|
/**
|
|
123
178
|
* Get CSS for a specific layer from the stylesheet
|
|
124
179
|
*/
|
package/dist/index.d.mts
CHANGED
|
@@ -37,10 +37,7 @@ declare class Generator extends Context {
|
|
|
37
37
|
* `keep` carries references this cannot see for itself; see `collectTokenReferences`.
|
|
38
38
|
*/
|
|
39
39
|
pruneTokens: (sheet: Stylesheet, keep?: Set<string>, tokensReachableFromJs?: boolean) => {
|
|
40
|
-
|
|
41
|
-
kept: number;
|
|
42
|
-
removedProperties?: undefined;
|
|
43
|
-
} | {
|
|
40
|
+
reachable: Set<string> | undefined;
|
|
44
41
|
removed: number;
|
|
45
42
|
removedProperties: number;
|
|
46
43
|
kept: number;
|
|
@@ -63,8 +60,21 @@ declare class Generator extends Context {
|
|
|
63
60
|
* unused for want of a utility to reference it.
|
|
64
61
|
*
|
|
65
62
|
* `keep` carries names this cannot see for itself; see `collectKeyframeReferences`.
|
|
63
|
+
*
|
|
64
|
+
* `reachableVars` is `pruneTokens`' answer about custom properties, which has to be handed
|
|
65
|
+
* over rather than re-derived here. A token kept by a reader outside the stylesheet — a
|
|
66
|
+
* `token()` call, a `prune.keepTokens` pattern, a theme, a `globalCss` export — is
|
|
67
|
+
* reachable to that pass and invisible to this one, so deriving it again from the css
|
|
68
|
+
* deletes the `@keyframes` out from under a declaration that ships. Every caller that
|
|
69
|
+
* prunes both hands it over.
|
|
70
|
+
*
|
|
71
|
+
* Omitting it falls back to what `prune.tokens` implies. Under `off` that is `'all'`: no
|
|
72
|
+
* token declaration is removable, so each one ships and keeps the keyframe it names —
|
|
73
|
+
* and `off` is precisely the setting chosen because something outside the stylesheet
|
|
74
|
+
* reads them. Otherwise it is the css alone, which is what a caller running this pass
|
|
75
|
+
* without the other one is asking for.
|
|
66
76
|
*/
|
|
67
|
-
pruneKeyframes: (sheet: Stylesheet, keep?: Set<string>) => {
|
|
77
|
+
pruneKeyframes: (sheet: Stylesheet, keep?: Set<string>, reachableVars?: Set<string> | "all") => {
|
|
68
78
|
removed: number;
|
|
69
79
|
kept: number;
|
|
70
80
|
} | undefined;
|
|
@@ -119,6 +129,51 @@ declare class Generator extends Context {
|
|
|
119
129
|
private getAlwaysKeptTokenVars;
|
|
120
130
|
getParserCss: (decoder: StyleDecoder) => string;
|
|
121
131
|
getCss: (stylesheet?: Stylesheet) => string;
|
|
132
|
+
/**
|
|
133
|
+
* Fail on a style value shaped like a token path that names no token.
|
|
134
|
+
*
|
|
135
|
+
* Only under `unresolvedToken: 'error'` — see that option for why this one is graded and a
|
|
136
|
+
* dead binding is not.
|
|
137
|
+
*
|
|
138
|
+
* Two sources, because neither sees the whole build.
|
|
139
|
+
*
|
|
140
|
+
* **Atomic styles are read off the decoded sheet** rather than accumulated as `transform`
|
|
141
|
+
* runs, and that is the load-bearing part for them. A `Context` outlives rebuilds while the
|
|
142
|
+
* decoder memoizes each atom by hash, so on the second build of the same source `transform`
|
|
143
|
+
* is never re-entered: an accumulating record either keeps a finding past the edit that
|
|
144
|
+
* fixed it — wedging a dev server — or is cleared and then never refilled, which passes a
|
|
145
|
+
* build whose source is still broken. That second one is the worse failure and is what an
|
|
146
|
+
* earlier version of this did.
|
|
147
|
+
*
|
|
148
|
+
* `decoder.atomic` has neither problem, because it is not a record of what happened — it is
|
|
149
|
+
* what the sheet is built from, and each result keeps the `prop` and `value` it was written
|
|
150
|
+
* with. So the question asked is the one that matters: does the stylesheet *being emitted*
|
|
151
|
+
* contain a declaration the browser will drop.
|
|
152
|
+
*
|
|
153
|
+
* Within a watch process that set is cumulative, and so is the css: extraction is additive,
|
|
154
|
+
* so the rule for a style deleted from source is still in the sheet until the process
|
|
155
|
+
* restarts. This reports the same way for the same reason — the declaration really is still
|
|
156
|
+
* in the file being written, and saying otherwise would be a check that disagreed with its
|
|
157
|
+
* own output. A production build is a fresh process and sees only what its source asked
|
|
158
|
+
* for.
|
|
159
|
+
*
|
|
160
|
+
* **Config-derived styles are not in that set at all**, which is the gap this used to have.
|
|
161
|
+
* `globalCss`, the reset, config recipes and compositions serialize through
|
|
162
|
+
* `transformStyles`, and that clones the decoder — so their atoms land in a throwaway and
|
|
163
|
+
* `decoder.atomic` never hears about them. Reading only the sheet made `'error'` *quieter*
|
|
164
|
+
* than the default on exactly those styles: the warning was suppressed in favour of a check
|
|
165
|
+
* that could not see them, so a bad token in `globalCss` warned with the option unset and
|
|
166
|
+
* then passed silently with it set to `'error'`. `utility.unresolvedTokens` is the record of
|
|
167
|
+
* what only `transform` can see; see it for why accumulating is right for that half.
|
|
168
|
+
*
|
|
169
|
+
* Both halves key on `property:path` with shorthands resolved, so a value that does reach
|
|
170
|
+
* both — every atomic style is transformed once before it is memoized — is one finding.
|
|
171
|
+
*
|
|
172
|
+
* Here rather than beside the asserts in `BambooContext` because this is where the sheet
|
|
173
|
+
* exists: those all run during extraction, before anything has been decoded. Every path
|
|
174
|
+
* that emits css comes through `getCss`.
|
|
175
|
+
*/
|
|
176
|
+
assertNoUnresolvedTokens: () => void;
|
|
122
177
|
/**
|
|
123
178
|
* Get CSS for a specific layer from the stylesheet
|
|
124
179
|
*/
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Context, Recipes, expandNestedCss, extractParentSelectors, extractTrailingPseudos, pruneKeyframes, prunePreflight, pruneTokenVars, prunesPreflight, stringify } from "@bamboocss/core";
|
|
2
2
|
import { logger } from "@bamboocss/logger";
|
|
3
|
-
import { BambooError, capitalize, compact, cssVarRefs, dashCase, isBoolean, isObject, mapEntries, unionType, walkObject } from "@bamboocss/shared";
|
|
3
|
+
import { BambooError, capitalize, compact, cssVarRefs, dashCase, isBoolean, isObject, mapEntries, truncateList, unionType, walkObject } from "@bamboocss/shared";
|
|
4
4
|
import { match } from "ts-pattern";
|
|
5
5
|
import outdent$1, { outdent } from "outdent";
|
|
6
6
|
import { stringify as stringify$1 } from "javascript-stringify";
|
|
@@ -85,6 +85,7 @@ function generateConditions(ctx) {
|
|
|
85
85
|
function generateCssFn(ctx) {
|
|
86
86
|
const { utility, hash, prefix } = ctx;
|
|
87
87
|
const { separator } = utility;
|
|
88
|
+
const leafFallback = ctx.config.leafFallback ?? true;
|
|
88
89
|
return {
|
|
89
90
|
dts: outdent`
|
|
90
91
|
${ctx.file.importType("SystemStyleObject, ViewTransitionFn", "../types/index")}
|
|
@@ -178,12 +179,22 @@ function generateCssFn(ctx) {
|
|
|
178
179
|
|
|
179
180
|
// Emitted for the source transform, which rewrites a single dynamic style leaf into a
|
|
180
181
|
// call to this rather than leaving a \`css()\` behind. \`prefix\` is the class up to the
|
|
181
|
-
// value, resolved at build time; \`prop\`
|
|
182
|
-
// declines
|
|
183
|
-
export const cssLeaf = (prefix, prop, value) => {
|
|
182
|
+
// value, resolved at build time; \`prop\` names the property for the shapes \`leafClass\`
|
|
183
|
+
// declines.
|
|
184
|
+
${leafFallback ? `export const cssLeaf = (prefix, prop, value) => {
|
|
184
185
|
const className = leafClass(prefix, value)
|
|
185
186
|
return className === undefined ? css({ [prop]: value }) : className
|
|
186
|
-
}
|
|
187
|
+
}` : `export const cssLeaf = (prefix, prop, value) => {
|
|
188
|
+
const className = leafClass(prefix, value)
|
|
189
|
+
if (className === undefined) {
|
|
190
|
+
throw new Error(
|
|
191
|
+
\`[bamboocss] \\\`\${prop}\\\` got a \${Array.isArray(value) ? 'responsive array' : 'condition object'} from a \` +
|
|
192
|
+
\`runtime value, and \\\`leafFallback\\\` is off. Write the conditions as a literal at the call site, so \` +
|
|
193
|
+
\`the build resolves each branch, or set \\\`leafFallback: true\\\` to keep the runtime.\`,
|
|
194
|
+
)
|
|
195
|
+
}
|
|
196
|
+
return className
|
|
197
|
+
}`}
|
|
187
198
|
|
|
188
199
|
// Sugar for the string form, so the feature has an import to discover, a signature to
|
|
189
200
|
// hover and a name the editor can complete. The extractor evaluates the call, so the
|
|
@@ -2327,13 +2338,36 @@ function generatePropTypes(ctx) {
|
|
|
2327
2338
|
return outdent`
|
|
2328
2339
|
${result.join("\n")}
|
|
2329
2340
|
|
|
2330
|
-
type WithColorOpacityModifier<T> = [T] extends [string] ? \`$\{T}/\${string}\` & { __colorOpacityModifier?: true } : never
|
|
2331
|
-
|
|
2332
2341
|
type ImportantMark = "!" | "!important"
|
|
2333
2342
|
type WhitespaceImportant = \` \${ImportantMark}\`
|
|
2334
2343
|
type Important = ImportantMark | WhitespaceImportant
|
|
2335
2344
|
type WithImportant<T> = [T] extends [string] ? \`\${T}\${Important}\` & { __important?: true } : never
|
|
2336
2345
|
|
|
2346
|
+
/**
|
|
2347
|
+
* The modifiers a token path may carry, as one open-ended tail rather than one closed
|
|
2348
|
+
* template per form.
|
|
2349
|
+
*
|
|
2350
|
+
* ⚠️ The \`& { __modifier?: true }\` is load-bearing, and nothing reads it. Deleting it as
|
|
2351
|
+
* dead weight costs **12.8x** on \`tsc\` — measured at 87.2s against 6.8s over 4,000 call
|
|
2352
|
+
* sites — because it is what stops TypeScript attempting subtype reduction across the
|
|
2353
|
+
* union these expand into. The same applies to \`__important\` above.
|
|
2354
|
+
*
|
|
2355
|
+
* A template literal distributes over a union in any placeholder, so \`\${T}\` against a
|
|
2356
|
+
* 258-token colour palette is 258 members, and the old \`\${T}\${Important}\` was four times
|
|
2357
|
+
* that. Between them the two modifier forms were 5N of a ~1,560-member union for \`color\`
|
|
2358
|
+
* alone, and half the cost of type-checking a \`css()\` call under \`strictTokens\`. Folding
|
|
2359
|
+
* them into one 3N tail is 14.5% off that — 7.09s against 8.29s over the same 4,000 call
|
|
2360
|
+
* sites, with a control repeat agreeing to 3.5%.
|
|
2361
|
+
*
|
|
2362
|
+
* What it gives up is the tail: \`red.300!nonsense\` type-checks now, where five exact
|
|
2363
|
+
* templates would have rejected it. \`unresolvedToken\` strips the mark and resolves the
|
|
2364
|
+
* path underneath, so the build still reports it — warning by default, failing under
|
|
2365
|
+
* \`'error'\`. The diagnostic moves rather than disappears, and only for a value nobody
|
|
2366
|
+
* writes on purpose.
|
|
2367
|
+
*/
|
|
2368
|
+
type Modifier = "/" | "!" | " !"
|
|
2369
|
+
type WithModifier<T> = [T] extends [string] ? \`\${T}\${Modifier}\${string}\` & { __modifier?: true } : never
|
|
2370
|
+
|
|
2337
2371
|
/**
|
|
2338
2372
|
* A list of candidate values, most-preferred first, emitted as repeated declarations so the
|
|
2339
2373
|
* browser keeps the last one it understands.
|
|
@@ -2365,8 +2399,7 @@ function generatePropTypes(ctx) {
|
|
|
2365
2399
|
| \`[\${string}]\`
|
|
2366
2400
|
| FallbackValue
|
|
2367
2401
|
| WithImportant<FallbackValue>
|
|
2368
|
-
|
|
|
2369
|
-
| WithImportant<T>
|
|
2402
|
+
| WithModifier<T>
|
|
2370
2403
|
|
|
2371
2404
|
/**
|
|
2372
2405
|
* Will restrict the value of properties that have predefined values to those values only.
|
|
@@ -3908,8 +3941,21 @@ var Generator = class extends Context {
|
|
|
3908
3941
|
* unused for want of a utility to reference it.
|
|
3909
3942
|
*
|
|
3910
3943
|
* `keep` carries names this cannot see for itself; see `collectKeyframeReferences`.
|
|
3944
|
+
*
|
|
3945
|
+
* `reachableVars` is `pruneTokens`' answer about custom properties, which has to be handed
|
|
3946
|
+
* over rather than re-derived here. A token kept by a reader outside the stylesheet — a
|
|
3947
|
+
* `token()` call, a `prune.keepTokens` pattern, a theme, a `globalCss` export — is
|
|
3948
|
+
* reachable to that pass and invisible to this one, so deriving it again from the css
|
|
3949
|
+
* deletes the `@keyframes` out from under a declaration that ships. Every caller that
|
|
3950
|
+
* prunes both hands it over.
|
|
3951
|
+
*
|
|
3952
|
+
* Omitting it falls back to what `prune.tokens` implies. Under `off` that is `'all'`: no
|
|
3953
|
+
* token declaration is removable, so each one ships and keeps the keyframe it names —
|
|
3954
|
+
* and `off` is precisely the setting chosen because something outside the stylesheet
|
|
3955
|
+
* reads them. Otherwise it is the css alone, which is what a caller running this pass
|
|
3956
|
+
* without the other one is asking for.
|
|
3911
3957
|
*/
|
|
3912
|
-
pruneKeyframes = (sheet, keep) => {
|
|
3958
|
+
pruneKeyframes = (sheet, keep, reachableVars) => {
|
|
3913
3959
|
if (!this.config.prune?.keyframes) return;
|
|
3914
3960
|
const layers = sheet.layers;
|
|
3915
3961
|
const keyframeNames = new Set(Object.keys(this.config.theme?.keyframes ?? {}));
|
|
@@ -3927,7 +3973,8 @@ var Generator = class extends Context {
|
|
|
3927
3973
|
],
|
|
3928
3974
|
target: layers.tokens,
|
|
3929
3975
|
keyframeNames,
|
|
3930
|
-
keep: new Set([...this.getThemeKeyframeNames(keyframeNames), ...keep ?? []])
|
|
3976
|
+
keep: new Set([...this.getThemeKeyframeNames(keyframeNames), ...keep ?? []]),
|
|
3977
|
+
reachableVars: reachableVars ?? ((this.config.prune?.tokens ?? "reachable") === "off" ? "all" : void 0)
|
|
3931
3978
|
});
|
|
3932
3979
|
logger.debug("prune:keyframes", `Removed ${result.removed} unused keyframe(s)`);
|
|
3933
3980
|
return result;
|
|
@@ -4015,9 +4062,79 @@ var Generator = class extends Context {
|
|
|
4015
4062
|
artifact: "styles.css",
|
|
4016
4063
|
content: css
|
|
4017
4064
|
}) ?? css;
|
|
4065
|
+
this.assertNoUnresolvedTokens();
|
|
4018
4066
|
return css;
|
|
4019
4067
|
};
|
|
4020
4068
|
/**
|
|
4069
|
+
* Fail on a style value shaped like a token path that names no token.
|
|
4070
|
+
*
|
|
4071
|
+
* Only under `unresolvedToken: 'error'` — see that option for why this one is graded and a
|
|
4072
|
+
* dead binding is not.
|
|
4073
|
+
*
|
|
4074
|
+
* Two sources, because neither sees the whole build.
|
|
4075
|
+
*
|
|
4076
|
+
* **Atomic styles are read off the decoded sheet** rather than accumulated as `transform`
|
|
4077
|
+
* runs, and that is the load-bearing part for them. A `Context` outlives rebuilds while the
|
|
4078
|
+
* decoder memoizes each atom by hash, so on the second build of the same source `transform`
|
|
4079
|
+
* is never re-entered: an accumulating record either keeps a finding past the edit that
|
|
4080
|
+
* fixed it — wedging a dev server — or is cleared and then never refilled, which passes a
|
|
4081
|
+
* build whose source is still broken. That second one is the worse failure and is what an
|
|
4082
|
+
* earlier version of this did.
|
|
4083
|
+
*
|
|
4084
|
+
* `decoder.atomic` has neither problem, because it is not a record of what happened — it is
|
|
4085
|
+
* what the sheet is built from, and each result keeps the `prop` and `value` it was written
|
|
4086
|
+
* with. So the question asked is the one that matters: does the stylesheet *being emitted*
|
|
4087
|
+
* contain a declaration the browser will drop.
|
|
4088
|
+
*
|
|
4089
|
+
* Within a watch process that set is cumulative, and so is the css: extraction is additive,
|
|
4090
|
+
* so the rule for a style deleted from source is still in the sheet until the process
|
|
4091
|
+
* restarts. This reports the same way for the same reason — the declaration really is still
|
|
4092
|
+
* in the file being written, and saying otherwise would be a check that disagreed with its
|
|
4093
|
+
* own output. A production build is a fresh process and sees only what its source asked
|
|
4094
|
+
* for.
|
|
4095
|
+
*
|
|
4096
|
+
* **Config-derived styles are not in that set at all**, which is the gap this used to have.
|
|
4097
|
+
* `globalCss`, the reset, config recipes and compositions serialize through
|
|
4098
|
+
* `transformStyles`, and that clones the decoder — so their atoms land in a throwaway and
|
|
4099
|
+
* `decoder.atomic` never hears about them. Reading only the sheet made `'error'` *quieter*
|
|
4100
|
+
* than the default on exactly those styles: the warning was suppressed in favour of a check
|
|
4101
|
+
* that could not see them, so a bad token in `globalCss` warned with the option unset and
|
|
4102
|
+
* then passed silently with it set to `'error'`. `utility.unresolvedTokens` is the record of
|
|
4103
|
+
* what only `transform` can see; see it for why accumulating is right for that half.
|
|
4104
|
+
*
|
|
4105
|
+
* Both halves key on `property:path` with shorthands resolved, so a value that does reach
|
|
4106
|
+
* both — every atomic style is transformed once before it is memoized — is one finding.
|
|
4107
|
+
*
|
|
4108
|
+
* Here rather than beside the asserts in `BambooContext` because this is where the sheet
|
|
4109
|
+
* exists: those all run during extraction, before anything has been decoded. Every path
|
|
4110
|
+
* that emits css comes through `getCss`.
|
|
4111
|
+
*/
|
|
4112
|
+
assertNoUnresolvedTokens = () => {
|
|
4113
|
+
if (this.config.unresolvedToken !== "error") return;
|
|
4114
|
+
const found = new Map(this.utility.unresolvedTokens);
|
|
4115
|
+
for (const atom of this.decoder.atomic) {
|
|
4116
|
+
const { prop, value } = atom.entry;
|
|
4117
|
+
if (typeof value !== "string" || typeof prop !== "string") continue;
|
|
4118
|
+
if (!this.utility.isUnresolvedTokenValue(prop, value)) continue;
|
|
4119
|
+
const key = this.utility.resolveShorthand(prop);
|
|
4120
|
+
const bare = this.utility.bareTokenPath(key, value);
|
|
4121
|
+
found.set(`${key}:${bare}`, {
|
|
4122
|
+
prop: key,
|
|
4123
|
+
value: bare,
|
|
4124
|
+
category: this.utility.getTokenCategory(key)
|
|
4125
|
+
});
|
|
4126
|
+
}
|
|
4127
|
+
if (!found.size) return;
|
|
4128
|
+
const detail = truncateList(Array.from(found.values(), ({ prop, value, category }) => {
|
|
4129
|
+
return `- \`${prop}: ${value}\`.${category ? ` Check the path against your \`${category}\` tokens.` : ""}`;
|
|
4130
|
+
}), {
|
|
4131
|
+
limit: 25,
|
|
4132
|
+
unit: "value",
|
|
4133
|
+
separator: "\n"
|
|
4134
|
+
});
|
|
4135
|
+
throw new BambooError("UNRESOLVED_TOKEN", `${found.size} style value(s) name a token that does not exist:\n\n${detail}\n\nEach is emitted as written, which parses — so the stylesheet is valid and nothing downstream objects. The browser drops the declaration at compute time and the style is simply absent from the element, which surfaces as "this never applied" a long way from the typo that caused it. Write \`[value]\` to mark one as a literal, or set \`unresolvedToken: 'warn'\` to report these without failing.`);
|
|
4136
|
+
};
|
|
4137
|
+
/**
|
|
4021
4138
|
* Get CSS for a specific layer from the stylesheet
|
|
4022
4139
|
*/
|
|
4023
4140
|
getLayerCss = (sheet, layer) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bamboocss/generator",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.34.1",
|
|
4
4
|
"description": "The css generator for css bamboo",
|
|
5
5
|
"homepage": "https://bamboocss.com",
|
|
6
6
|
"license": "MIT",
|
|
@@ -38,12 +38,12 @@
|
|
|
38
38
|
"pluralize": "8.0.0",
|
|
39
39
|
"postcss": "8.5.26",
|
|
40
40
|
"ts-pattern": "5.9.0",
|
|
41
|
-
"@bamboocss/core": "1.
|
|
42
|
-
"@bamboocss/is-valid-prop": "^1.
|
|
43
|
-
"@bamboocss/logger": "1.
|
|
44
|
-
"@bamboocss/shared": "1.
|
|
45
|
-
"@bamboocss/token-dictionary": "1.
|
|
46
|
-
"@bamboocss/types": "1.
|
|
41
|
+
"@bamboocss/core": "1.34.1",
|
|
42
|
+
"@bamboocss/is-valid-prop": "^1.34.1",
|
|
43
|
+
"@bamboocss/logger": "1.34.1",
|
|
44
|
+
"@bamboocss/shared": "1.34.1",
|
|
45
|
+
"@bamboocss/token-dictionary": "1.34.1",
|
|
46
|
+
"@bamboocss/types": "1.34.1"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@types/pluralize": "0.0.33"
|