@barocss/browser 0.5.0 → 0.6.0
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 +2 -0
- package/dist/cdn/barocss.js +270 -7
- package/dist/cdn/barocss.js.map +1 -1
- package/dist/cdn/barocss.umd.cjs +1 -1
- package/dist/cdn/barocss.umd.cjs.map +1 -1
- package/dist/index.d.ts +106 -19
- package/dist/index.es.js +245 -143
- package/dist/index.umd.js +7 -5
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -33,6 +33,8 @@ renderJsonUi(spec); // mount your json-render Renderer
|
|
|
33
33
|
|
|
34
34
|
The five settings: `skipExisting: true`, `cssVarPrefix: 'tw'`, `theme: { extend: shadcnTheme }`, `preloadJsonRenderClasses(spec, runtime)` before mount, and the default layered preflight (don't set `preflight: false`).
|
|
35
35
|
|
|
36
|
+
**Non-shadcn site theme:** put the site's own tokens in `theme.extend` (e.g. `colors: { brand: { 600: '#2563eb' } }`). Literal values are safe. Pointing a token at the build's own var name (`brand: { 600: 'var(--color-brand-600)' }`) is also fine: BaroCSS skips that self-referencing `:root` var, so the build's value wins and `bg-brand-600` still uses it.
|
|
37
|
+
|
|
36
38
|
**Verify it rendered** (DevTools console, after mount):
|
|
37
39
|
|
|
38
40
|
```js
|
package/dist/cdn/barocss.js
CHANGED
|
@@ -275,6 +275,11 @@ function staticUtility(name, decls, opts, ctx) {
|
|
|
275
275
|
priority: opts?.priority
|
|
276
276
|
});
|
|
277
277
|
}
|
|
278
|
+
function spacingKeyValue(ctx, key, negative) {
|
|
279
|
+
if (key === "px" || !/^[a-zA-Z][\w-]*$/.test(key) || ctx.theme("spacing", key) == null) return null;
|
|
280
|
+
const ref = `var(--spacing-${key})`;
|
|
281
|
+
return negative ? `calc(${ref} * -1)` : ref;
|
|
282
|
+
}
|
|
278
283
|
function functionalUtility(opts, ctx) {
|
|
279
284
|
registerUtility({
|
|
280
285
|
name: opts.name,
|
|
@@ -343,14 +348,17 @@ function functionalUtility(opts, ctx) {
|
|
|
343
348
|
if (opts.supportsFraction && /^-?\d+\/\d+$/.test(value)) {
|
|
344
349
|
finalValue = value;
|
|
345
350
|
}
|
|
351
|
+
const spacingKey = opts.spacingKeys ? spacingKeyValue(ctx2, String(finalValue).replace(/^-/, ""), !!parsedUtility.negative) : null;
|
|
346
352
|
if (parsedUtility.negative && opts.supportsNegative && opts.handleNegativeBareValue) {
|
|
347
|
-
const bare = opts.handleNegativeBareValue({ value: String(finalValue).replace(/^-/, ""), ctx: ctx2, token, extra });
|
|
353
|
+
const bare = opts.handleNegativeBareValue({ value: String(finalValue).replace(/^-/, ""), ctx: ctx2, token, extra }) ?? spacingKey;
|
|
348
354
|
if (bare == null) return [];
|
|
349
355
|
finalValue = bare;
|
|
350
356
|
} else if (opts.handleBareValue) {
|
|
351
|
-
const bare = opts.handleBareValue({ value: finalValue, ctx: ctx2, token, extra });
|
|
357
|
+
const bare = opts.handleBareValue({ value: finalValue, ctx: ctx2, token, extra }) ?? spacingKey;
|
|
352
358
|
if (bare == null) return [];
|
|
353
359
|
finalValue = bare;
|
|
360
|
+
} else if (spacingKey) {
|
|
361
|
+
finalValue = spacingKey;
|
|
354
362
|
} else if (!/^-?(\d|\.\d)/.test(String(finalValue))) {
|
|
355
363
|
return [];
|
|
356
364
|
}
|
|
@@ -561,6 +569,18 @@ function isSafeVariantToken(value) {
|
|
|
561
569
|
function hasCommentToken(value) {
|
|
562
570
|
return value.includes("/*") || value.includes("*/");
|
|
563
571
|
}
|
|
572
|
+
function hasCommentDelimiter(text) {
|
|
573
|
+
for (let i = 0; i < text.length - 1; i++) {
|
|
574
|
+
const c = text[i];
|
|
575
|
+
if (c === "\\") {
|
|
576
|
+
i++;
|
|
577
|
+
continue;
|
|
578
|
+
}
|
|
579
|
+
const n = text[i + 1];
|
|
580
|
+
if (c === "/" && n === "*" || c === "*" && n === "/") return true;
|
|
581
|
+
}
|
|
582
|
+
return false;
|
|
583
|
+
}
|
|
564
584
|
function isStructureSafeValue(value) {
|
|
565
585
|
if (hasCommentToken(value)) return false;
|
|
566
586
|
return isSafeVariantValue(value, true);
|
|
@@ -708,6 +728,7 @@ function parseUtility(value, ctx) {
|
|
|
708
728
|
priority
|
|
709
729
|
};
|
|
710
730
|
}
|
|
731
|
+
const isSafePrelude = (text) => !hasCommentDelimiter(String(text ?? ""));
|
|
711
732
|
const isSafeDecl = (prop, value) => isStructureSafeValue(String(prop)) && isStructureSafeValue(String(value ?? ""));
|
|
712
733
|
const importantPrefix = "!important";
|
|
713
734
|
function astToCss(ast, baseSelector, opts, _indent = "") {
|
|
@@ -771,6 +792,7 @@ function astToCss(ast, baseSelector, opts, _indent = "") {
|
|
|
771
792
|
}).join(", ");
|
|
772
793
|
}
|
|
773
794
|
}
|
|
795
|
+
if (!isSafePrelude(selector)) return "";
|
|
774
796
|
if (minify) {
|
|
775
797
|
const css = `${indent}${selector}{${astToCss(
|
|
776
798
|
node.nodes,
|
|
@@ -795,6 +817,7 @@ ${astToCss(
|
|
|
795
817
|
}
|
|
796
818
|
}
|
|
797
819
|
case "style-rule": {
|
|
820
|
+
if (!isSafePrelude(node.selector)) return "";
|
|
798
821
|
if (minify) {
|
|
799
822
|
const css = `${indent}${node.selector} {${astToCss(
|
|
800
823
|
node.nodes,
|
|
@@ -819,6 +842,7 @@ ${astToCss(
|
|
|
819
842
|
}
|
|
820
843
|
}
|
|
821
844
|
case "at-rule": {
|
|
845
|
+
if (!isSafePrelude(node.name) || !isSafePrelude(node.params)) return "";
|
|
822
846
|
if (minify) {
|
|
823
847
|
const css = `${indent}@${node.name} ${node.params}{${astToCss(
|
|
824
848
|
node.nodes,
|
|
@@ -871,7 +895,7 @@ function rootToCss(nodes, opts) {
|
|
|
871
895
|
if (isSafeDecl(node.prop, node.value)) {
|
|
872
896
|
list.push(`${node.prop}: ${node.value};`);
|
|
873
897
|
}
|
|
874
|
-
} else if (node.type === "at-rule") {
|
|
898
|
+
} else if (node.type === "at-rule" && isSafePrelude(node.name) && isSafePrelude(node.params)) {
|
|
875
899
|
{
|
|
876
900
|
list.push(
|
|
877
901
|
`@${node.name} ${node.params} {
|
|
@@ -1099,8 +1123,13 @@ function themeToCssVarsAll(theme) {
|
|
|
1099
1123
|
// keyframes handled separately
|
|
1100
1124
|
};
|
|
1101
1125
|
}
|
|
1126
|
+
function isSelfReferencingVar(name, value) {
|
|
1127
|
+
if (typeof value !== "string") return false;
|
|
1128
|
+
const m = /^var\(\s*(--[\w-]+)\s*(?:,[\s\S]*)?\)$/.exec(value.trim());
|
|
1129
|
+
return !!m && m[1] === name.trim();
|
|
1130
|
+
}
|
|
1102
1131
|
function toCssVarsBlock(vars, extra = "") {
|
|
1103
|
-
return ":root,:host {\n" + Object.entries(vars).map(([k, v2]) => ` ${k}: ${v2};`).join("\n") + "\n}\n" + extra + "\n";
|
|
1132
|
+
return ":root,:host {\n" + Object.entries(vars).filter(([k, v2]) => !isSelfReferencingVar(k, v2)).map(([k, v2]) => ` ${k}: ${v2};`).join("\n") + "\n}\n" + extra + "\n";
|
|
1104
1133
|
}
|
|
1105
1134
|
const BARO_VAR = /--baro-/g;
|
|
1106
1135
|
const PREFIXED_KEYS = /* @__PURE__ */ new Set(["prop", "value", "params", "selector", "nodes", "items"]);
|
|
@@ -1713,6 +1742,15 @@ class IncrementalParser {
|
|
|
1713
1742
|
markProcessed(cls) {
|
|
1714
1743
|
this.processedClasses.add(cls);
|
|
1715
1744
|
}
|
|
1745
|
+
/**
|
|
1746
|
+
* Forgets that a class was processed, so a later request generates it again
|
|
1747
|
+
* (used when the browser runtime reclaims an unused class's rules, #269).
|
|
1748
|
+
*
|
|
1749
|
+
* @param cls - The CSS class name to forget
|
|
1750
|
+
*/
|
|
1751
|
+
unmarkProcessed(cls) {
|
|
1752
|
+
this.processedClasses.delete(cls);
|
|
1753
|
+
}
|
|
1716
1754
|
/**
|
|
1717
1755
|
* Process classes synchronously and update BrowserRuntime cache
|
|
1718
1756
|
* This method is used by ChangeDetector for scan operations
|
|
@@ -3682,6 +3720,7 @@ staticUtility("snap-proximity", [["--baro-scroll-snap-strictness", "proximity"]]
|
|
|
3682
3720
|
].forEach(([name, prop]) => {
|
|
3683
3721
|
functionalUtility({
|
|
3684
3722
|
name: `scroll-${name}`,
|
|
3723
|
+
spacingKeys: true,
|
|
3685
3724
|
prop,
|
|
3686
3725
|
supportsArbitrary: true,
|
|
3687
3726
|
supportsCustomProperty: true,
|
|
@@ -3709,6 +3748,7 @@ staticUtility("snap-proximity", [["--baro-scroll-snap-strictness", "proximity"]]
|
|
|
3709
3748
|
].forEach(([name, prop]) => {
|
|
3710
3749
|
functionalUtility({
|
|
3711
3750
|
name: `scroll-${name}`,
|
|
3751
|
+
spacingKeys: true,
|
|
3712
3752
|
prop,
|
|
3713
3753
|
supportsArbitrary: true,
|
|
3714
3754
|
supportsCustomProperty: true,
|
|
@@ -5242,6 +5282,7 @@ staticUtility("sticky", [["position", "sticky"]], { category: "layout" });
|
|
|
5242
5282
|
staticUtility(`-${name}-px`, [[prop, "-1px"]], { category: "layout" });
|
|
5243
5283
|
functionalUtility({
|
|
5244
5284
|
name,
|
|
5285
|
+
spacingKeys: true,
|
|
5245
5286
|
prop,
|
|
5246
5287
|
supportsNegative: true,
|
|
5247
5288
|
supportsFraction: true,
|
|
@@ -5272,6 +5313,7 @@ staticUtility("invisible", [["visibility", "hidden"]], { category: "layout" });
|
|
|
5272
5313
|
staticUtility("collapse", [["visibility", "collapse"]], { category: "layout" });
|
|
5273
5314
|
functionalUtility({
|
|
5274
5315
|
name: "gap-x",
|
|
5316
|
+
spacingKeys: true,
|
|
5275
5317
|
prop: "column-gap",
|
|
5276
5318
|
supportsArbitrary: true,
|
|
5277
5319
|
// gap-x-[10vw]
|
|
@@ -5288,6 +5330,7 @@ functionalUtility({
|
|
|
5288
5330
|
});
|
|
5289
5331
|
functionalUtility({
|
|
5290
5332
|
name: "gap-y",
|
|
5333
|
+
spacingKeys: true,
|
|
5291
5334
|
prop: "row-gap",
|
|
5292
5335
|
supportsArbitrary: true,
|
|
5293
5336
|
// gap-y-[10vw]
|
|
@@ -5304,6 +5347,7 @@ functionalUtility({
|
|
|
5304
5347
|
});
|
|
5305
5348
|
functionalUtility({
|
|
5306
5349
|
name: "gap",
|
|
5350
|
+
spacingKeys: true,
|
|
5307
5351
|
prop: "gap",
|
|
5308
5352
|
supportsArbitrary: true,
|
|
5309
5353
|
// gap-[10vw]
|
|
@@ -5821,6 +5865,7 @@ functionalUtility({
|
|
|
5821
5865
|
].forEach(([name, prop]) => {
|
|
5822
5866
|
staticUtility(`${name}-px`, [[prop, "1px"]], { category: "spacing" });
|
|
5823
5867
|
functionalUtility({
|
|
5868
|
+
spacingKeys: true,
|
|
5824
5869
|
name,
|
|
5825
5870
|
prop,
|
|
5826
5871
|
supportsArbitrary: true,
|
|
@@ -5845,6 +5890,7 @@ functionalUtility({
|
|
|
5845
5890
|
staticUtility(`${name}-px`, [[prop, "1px"]], { category: "spacing" });
|
|
5846
5891
|
staticUtility(`-${name}-px`, [[prop, "-1px"]], { category: "spacing" });
|
|
5847
5892
|
functionalUtility({
|
|
5893
|
+
spacingKeys: true,
|
|
5848
5894
|
name,
|
|
5849
5895
|
prop,
|
|
5850
5896
|
supportsNegative: true,
|
|
@@ -5875,6 +5921,7 @@ const SPACE_SELECTOR = ":where(& > :not(:last-child))";
|
|
|
5875
5921
|
() => rule(SPACE_SELECTOR, [decl(rev, "1")])
|
|
5876
5922
|
], { category: "spacing" });
|
|
5877
5923
|
functionalUtility({
|
|
5924
|
+
spacingKeys: true,
|
|
5878
5925
|
name,
|
|
5879
5926
|
supportsNegative: true,
|
|
5880
5927
|
supportsArbitrary: true,
|
|
@@ -5924,6 +5971,7 @@ const SPACE_SELECTOR = ":where(& > :not(:last-child))";
|
|
|
5924
5971
|
staticUtility(name, [["width", value]]);
|
|
5925
5972
|
});
|
|
5926
5973
|
functionalUtility({
|
|
5974
|
+
spacingKeys: true,
|
|
5927
5975
|
name: "w",
|
|
5928
5976
|
prop: "width",
|
|
5929
5977
|
supportsArbitrary: true,
|
|
@@ -5956,6 +6004,7 @@ functionalUtility({
|
|
|
5956
6004
|
staticUtility(name, [["width", w], ["height", h]]);
|
|
5957
6005
|
});
|
|
5958
6006
|
functionalUtility({
|
|
6007
|
+
spacingKeys: true,
|
|
5959
6008
|
name: "size",
|
|
5960
6009
|
supportsArbitrary: true,
|
|
5961
6010
|
supportsCustomProperty: true,
|
|
@@ -5995,6 +6044,7 @@ functionalUtility({
|
|
|
5995
6044
|
staticUtility(name, [["height", value]]);
|
|
5996
6045
|
});
|
|
5997
6046
|
functionalUtility({
|
|
6047
|
+
spacingKeys: true,
|
|
5998
6048
|
name: "h",
|
|
5999
6049
|
prop: "height",
|
|
6000
6050
|
supportsArbitrary: true,
|
|
@@ -6025,6 +6075,7 @@ functionalUtility({
|
|
|
6025
6075
|
staticUtility(name, [["min-height", value]], { category: "sizing" });
|
|
6026
6076
|
});
|
|
6027
6077
|
functionalUtility({
|
|
6078
|
+
spacingKeys: true,
|
|
6028
6079
|
name: "min-h",
|
|
6029
6080
|
prop: "min-height",
|
|
6030
6081
|
supportsArbitrary: true,
|
|
@@ -6055,6 +6106,7 @@ functionalUtility({
|
|
|
6055
6106
|
staticUtility(name, [["max-height", value]], { category: "sizing" });
|
|
6056
6107
|
});
|
|
6057
6108
|
functionalUtility({
|
|
6109
|
+
spacingKeys: true,
|
|
6058
6110
|
name: "max-h",
|
|
6059
6111
|
prop: "max-height",
|
|
6060
6112
|
supportsArbitrary: true,
|
|
@@ -6101,6 +6153,7 @@ functionalUtility({
|
|
|
6101
6153
|
staticUtility(name, [["min-width", value]], { category: "sizing" });
|
|
6102
6154
|
});
|
|
6103
6155
|
functionalUtility({
|
|
6156
|
+
spacingKeys: true,
|
|
6104
6157
|
name: "min-w",
|
|
6105
6158
|
prop: "min-width",
|
|
6106
6159
|
supportsArbitrary: true,
|
|
@@ -6136,6 +6189,7 @@ functionalUtility({
|
|
|
6136
6189
|
staticUtility(name, [["max-width", value]], { category: "sizing" });
|
|
6137
6190
|
});
|
|
6138
6191
|
functionalUtility({
|
|
6192
|
+
spacingKeys: true,
|
|
6139
6193
|
name: "max-w",
|
|
6140
6194
|
prop: "max-width",
|
|
6141
6195
|
supportsArbitrary: true,
|
|
@@ -8795,6 +8849,41 @@ class StylePartitionManager {
|
|
|
8795
8849
|
}
|
|
8796
8850
|
return { success, failed };
|
|
8797
8851
|
}
|
|
8852
|
+
/**
|
|
8853
|
+
* Remove one generated rule (#269 GC). Keeps `styles`, the #254 `keys` and the
|
|
8854
|
+
* sheet's cssRules parallel: one deleteRule at the rule's index, or a text
|
|
8855
|
+
* rebuild when the sheet isn't solely ours / has no CSSOM. Returns whether
|
|
8856
|
+
* the rule was found.
|
|
8857
|
+
*/
|
|
8858
|
+
removeRule(rule2, category) {
|
|
8859
|
+
let partition;
|
|
8860
|
+
if (category) {
|
|
8861
|
+
if (this.classToCategoryPartitionMap.get(rule2) !== category) return false;
|
|
8862
|
+
partition = this.categoryPartitions.get(category);
|
|
8863
|
+
} else {
|
|
8864
|
+
const partitionIndex = this.classToPartitionMap.get(rule2);
|
|
8865
|
+
partition = partitionIndex === void 0 ? void 0 : this.partitions[partitionIndex];
|
|
8866
|
+
}
|
|
8867
|
+
if (!partition) return false;
|
|
8868
|
+
const index = partition.styles.indexOf(rule2);
|
|
8869
|
+
if (index === -1) return false;
|
|
8870
|
+
const sheet = partition.styleElement.sheet;
|
|
8871
|
+
const inSync = !!sheet && sheet.cssRules.length === partition.styles.length;
|
|
8872
|
+
partition.styles.splice(index, 1);
|
|
8873
|
+
partition.keys?.splice(index, 1);
|
|
8874
|
+
if (inSync && sheet) {
|
|
8875
|
+
sheet.deleteRule(index);
|
|
8876
|
+
} else {
|
|
8877
|
+
partition.styleElement.textContent = partition.styles.length ? partition.styles.join("\n") + "\n" : "";
|
|
8878
|
+
}
|
|
8879
|
+
if (category) this.classToCategoryPartitionMap.delete(rule2);
|
|
8880
|
+
else this.classToPartitionMap.delete(rule2);
|
|
8881
|
+
return true;
|
|
8882
|
+
}
|
|
8883
|
+
/** Number of generated (non-root, non-preflight) rules currently held. */
|
|
8884
|
+
get ruleCount() {
|
|
8885
|
+
return this.classToPartitionMap.size + this.classToCategoryPartitionMap.size;
|
|
8886
|
+
}
|
|
8798
8887
|
/**
|
|
8799
8888
|
* 특정 규칙이 어느 파티션에 있는지 찾기
|
|
8800
8889
|
*/
|
|
@@ -8860,10 +8949,14 @@ class ChangeDetector {
|
|
|
8860
8949
|
*/
|
|
8861
8950
|
constructor(incrementalParser, BrowserRuntime2, getCategory = (cls) => parseClassName(cls).utility?.category) {
|
|
8862
8951
|
this.observer = null;
|
|
8952
|
+
this.gc = null;
|
|
8863
8953
|
this.incrementalParser = incrementalParser;
|
|
8864
8954
|
this.BrowserRuntime = BrowserRuntime2;
|
|
8865
8955
|
this.getCategory = getCategory;
|
|
8866
8956
|
}
|
|
8957
|
+
setGc(gc) {
|
|
8958
|
+
this.gc = gc;
|
|
8959
|
+
}
|
|
8867
8960
|
setParser(parser) {
|
|
8868
8961
|
this.incrementalParser = parser;
|
|
8869
8962
|
}
|
|
@@ -8890,9 +8983,19 @@ class ChangeDetector {
|
|
|
8890
8983
|
if (this.observer) {
|
|
8891
8984
|
this.observer.disconnect();
|
|
8892
8985
|
}
|
|
8986
|
+
this.gc?.setRoot(root);
|
|
8893
8987
|
this.observer = new MutationObserver((mutations) => {
|
|
8894
8988
|
const newClasses = /* @__PURE__ */ new Set();
|
|
8989
|
+
const gc = this.gc;
|
|
8895
8990
|
mutations.forEach((mutation) => {
|
|
8991
|
+
if (gc) {
|
|
8992
|
+
if (mutation.type === "attributes") {
|
|
8993
|
+
gc.reconcile(mutation.target);
|
|
8994
|
+
} else if (mutation.type === "childList") {
|
|
8995
|
+
mutation.removedNodes.forEach((node) => gc.reconcileTree(node));
|
|
8996
|
+
mutation.addedNodes.forEach((node) => gc.reconcileTree(node));
|
|
8997
|
+
}
|
|
8998
|
+
}
|
|
8896
8999
|
if (mutation.type === "attributes" && mutation.attributeName === "class" && root.contains(mutation.target)) {
|
|
8897
9000
|
const target = mutation.target;
|
|
8898
9001
|
if (target.className) {
|
|
@@ -8923,6 +9026,7 @@ class ChangeDetector {
|
|
|
8923
9026
|
} else {
|
|
8924
9027
|
this.BrowserRuntime?.applyParseResults([]);
|
|
8925
9028
|
}
|
|
9029
|
+
gc?.afterBatch();
|
|
8926
9030
|
});
|
|
8927
9031
|
this.observer.observe(root, {
|
|
8928
9032
|
attributes: true,
|
|
@@ -9041,6 +9145,115 @@ function collectLeadingClasses(rules, out = /* @__PURE__ */ new Set()) {
|
|
|
9041
9145
|
}
|
|
9042
9146
|
return out;
|
|
9043
9147
|
}
|
|
9148
|
+
class ClassGc {
|
|
9149
|
+
constructor(host, graceMs, maxRules, now = () => Date.now()) {
|
|
9150
|
+
this.host = host;
|
|
9151
|
+
this.graceMs = graceMs;
|
|
9152
|
+
this.maxRules = maxRules;
|
|
9153
|
+
this.now = now;
|
|
9154
|
+
this.counts = /* @__PURE__ */ new Map();
|
|
9155
|
+
this.counted = /* @__PURE__ */ new WeakMap();
|
|
9156
|
+
this.candidates = /* @__PURE__ */ new Map();
|
|
9157
|
+
this.timer = null;
|
|
9158
|
+
this.root = null;
|
|
9159
|
+
}
|
|
9160
|
+
/** Start counting for a new root: count every element currently inside it. */
|
|
9161
|
+
setRoot(root) {
|
|
9162
|
+
this.counts.clear();
|
|
9163
|
+
this.counted = /* @__PURE__ */ new WeakMap();
|
|
9164
|
+
this.candidates.clear();
|
|
9165
|
+
this.cancel();
|
|
9166
|
+
this.root = root;
|
|
9167
|
+
this.reconcileTree(root);
|
|
9168
|
+
}
|
|
9169
|
+
count(cls) {
|
|
9170
|
+
return this.counts.get(cls) ?? 0;
|
|
9171
|
+
}
|
|
9172
|
+
/** Re-count `el` and (optionally) all its descendants from their current state. */
|
|
9173
|
+
reconcileTree(node) {
|
|
9174
|
+
if (node.nodeType !== 1) return;
|
|
9175
|
+
const el = node;
|
|
9176
|
+
this.reconcile(el);
|
|
9177
|
+
el.querySelectorAll("[class]").forEach((child) => this.reconcile(child));
|
|
9178
|
+
}
|
|
9179
|
+
reconcile(el) {
|
|
9180
|
+
const root = this.root;
|
|
9181
|
+
const live = !!root && root.contains(el);
|
|
9182
|
+
const next = live ? Array.from(new Set(normalizeClassNameList(el.getAttribute("class")))) : [];
|
|
9183
|
+
const prev = this.counted.get(el);
|
|
9184
|
+
if (!prev && next.length === 0) return;
|
|
9185
|
+
const prevSet = new Set(prev ?? []);
|
|
9186
|
+
const nextSet = new Set(next);
|
|
9187
|
+
for (const cls of nextSet) {
|
|
9188
|
+
if (prevSet.has(cls)) continue;
|
|
9189
|
+
const c = (this.counts.get(cls) ?? 0) + 1;
|
|
9190
|
+
this.counts.set(cls, c);
|
|
9191
|
+
this.candidates.delete(cls);
|
|
9192
|
+
}
|
|
9193
|
+
for (const cls of prevSet) {
|
|
9194
|
+
if (nextSet.has(cls)) continue;
|
|
9195
|
+
const c = (this.counts.get(cls) ?? 0) - 1;
|
|
9196
|
+
if (c > 0) {
|
|
9197
|
+
this.counts.set(cls, c);
|
|
9198
|
+
} else {
|
|
9199
|
+
this.counts.delete(cls);
|
|
9200
|
+
this.candidates.delete(cls);
|
|
9201
|
+
this.candidates.set(cls, this.now());
|
|
9202
|
+
}
|
|
9203
|
+
}
|
|
9204
|
+
if (next.length) this.counted.set(el, next);
|
|
9205
|
+
else this.counted.delete(el);
|
|
9206
|
+
}
|
|
9207
|
+
/** Call after a mutation batch has been counted and its classes inserted. */
|
|
9208
|
+
afterBatch() {
|
|
9209
|
+
if (this.candidates.size === 0) return;
|
|
9210
|
+
if (this.host.cachedCount() > this.maxRules) {
|
|
9211
|
+
this.schedule(0);
|
|
9212
|
+
} else {
|
|
9213
|
+
this.schedule(this.graceMs);
|
|
9214
|
+
}
|
|
9215
|
+
}
|
|
9216
|
+
schedule(delay) {
|
|
9217
|
+
if (this.timer !== null) {
|
|
9218
|
+
if (delay > 0) return;
|
|
9219
|
+
clearTimeout(this.timer);
|
|
9220
|
+
}
|
|
9221
|
+
this.timer = setTimeout(() => {
|
|
9222
|
+
this.timer = null;
|
|
9223
|
+
this.sweep();
|
|
9224
|
+
}, delay);
|
|
9225
|
+
}
|
|
9226
|
+
/** Reclaim candidates whose grace period elapsed (plus LRU overflow). Public for tests. */
|
|
9227
|
+
sweep() {
|
|
9228
|
+
const now = this.now();
|
|
9229
|
+
const overflow = Math.max(0, this.host.cachedCount() - this.maxRules);
|
|
9230
|
+
const doomed = [];
|
|
9231
|
+
let evicted = 0;
|
|
9232
|
+
for (const [cls, since] of this.candidates) {
|
|
9233
|
+
const expired = now - since >= this.graceMs;
|
|
9234
|
+
if (!expired && evicted >= overflow) continue;
|
|
9235
|
+
this.candidates.delete(cls);
|
|
9236
|
+
if (this.count(cls) > 0 || this.inDom(cls) || this.host.isPermanent(cls)) continue;
|
|
9237
|
+
doomed.push(cls);
|
|
9238
|
+
if (!expired) evicted++;
|
|
9239
|
+
}
|
|
9240
|
+
if (doomed.length) this.host.reclaim(doomed);
|
|
9241
|
+
if (this.candidates.size) this.schedule(this.graceMs);
|
|
9242
|
+
}
|
|
9243
|
+
inDom(cls) {
|
|
9244
|
+
const root = this.root;
|
|
9245
|
+
if (!root) return false;
|
|
9246
|
+
const doc = root.ownerDocument ?? document;
|
|
9247
|
+
return root.classList.contains(cls) || doc.documentElement.classList.contains(cls) || doc.getElementsByClassName(cls).length > 0;
|
|
9248
|
+
}
|
|
9249
|
+
cancel() {
|
|
9250
|
+
if (this.timer !== null) clearTimeout(this.timer);
|
|
9251
|
+
this.timer = null;
|
|
9252
|
+
}
|
|
9253
|
+
stats() {
|
|
9254
|
+
return { trackedClasses: this.counts.size, candidates: this.candidates.size };
|
|
9255
|
+
}
|
|
9256
|
+
}
|
|
9044
9257
|
const LAYER_ORDER = "@layer theme, base, components, utilities;";
|
|
9045
9258
|
class BrowserRuntime {
|
|
9046
9259
|
constructor(options = {}) {
|
|
@@ -9049,6 +9262,9 @@ class BrowserRuntime {
|
|
|
9049
9262
|
this.isDestroyed = false;
|
|
9050
9263
|
this.existing = null;
|
|
9051
9264
|
this.existingSheetCount = -1;
|
|
9265
|
+
this.pinned = /* @__PURE__ */ new Set();
|
|
9266
|
+
this.gc = null;
|
|
9267
|
+
this.reclaimedCount = 0;
|
|
9052
9268
|
this.getCategory = (cls) => parseClassName(cls, this.context).utility?.category;
|
|
9053
9269
|
const defaultConfig = {};
|
|
9054
9270
|
this.options = {
|
|
@@ -9056,12 +9272,23 @@ class BrowserRuntime {
|
|
|
9056
9272
|
styleId: options.styleId || "barocss-runtime",
|
|
9057
9273
|
insertionPoint: options.insertionPoint || "head",
|
|
9058
9274
|
maxRulesPerPartition: options.maxRulesPerPartition || 50,
|
|
9059
|
-
skipExisting: options.skipExisting ?? false
|
|
9275
|
+
skipExisting: options.skipExisting ?? false,
|
|
9276
|
+
gc: options.gc ?? true,
|
|
9277
|
+
gcGraceMs: options.gcGraceMs ?? 3e3,
|
|
9278
|
+
maxRules: options.maxRules ?? Infinity
|
|
9060
9279
|
};
|
|
9061
9280
|
this.context = createContext(this.options.config);
|
|
9062
9281
|
this.incrementalParser = new IncrementalParser(this.context);
|
|
9063
9282
|
this.changeDetector = new ChangeDetector(this.incrementalParser, this, this.getCategory);
|
|
9064
9283
|
this.stylePartitionManager = new StylePartitionManager(this.getInsertionPoint(), this.options.maxRulesPerPartition, `${this.options.styleId}-partition`, this.getCategory);
|
|
9284
|
+
if (this.options.gc) {
|
|
9285
|
+
this.gc = new ClassGc({
|
|
9286
|
+
reclaim: (classes) => this.reclaim(classes),
|
|
9287
|
+
isPermanent: (cls) => this.isPermanent(cls),
|
|
9288
|
+
cachedCount: () => this.cache.size
|
|
9289
|
+
}, this.options.gcGraceMs, this.options.maxRules);
|
|
9290
|
+
this.changeDetector.setGc(this.gc);
|
|
9291
|
+
}
|
|
9065
9292
|
this.init();
|
|
9066
9293
|
}
|
|
9067
9294
|
// Debugging and logging helpers
|
|
@@ -9113,7 +9340,8 @@ ${preflightCSS}
|
|
|
9113
9340
|
*/
|
|
9114
9341
|
addClass(classes) {
|
|
9115
9342
|
if (this.isDestroyed) return;
|
|
9116
|
-
const classList = this.normalizeClasses(classes);
|
|
9343
|
+
const classList = this.normalizeClasses(classes).filter(Boolean);
|
|
9344
|
+
classList.forEach((cls) => this.pinned.add(cls));
|
|
9117
9345
|
this.processClasses(classList);
|
|
9118
9346
|
}
|
|
9119
9347
|
/**
|
|
@@ -9174,9 +9402,40 @@ ${preflightCSS}
|
|
|
9174
9402
|
rootCssCount: rootCssRules.length
|
|
9175
9403
|
});
|
|
9176
9404
|
}
|
|
9405
|
+
/** #269: a class that must never be reclaimed. */
|
|
9406
|
+
isPermanent(cls) {
|
|
9407
|
+
if (this.pinned.has(cls)) return true;
|
|
9408
|
+
if (typeof document === "undefined") return true;
|
|
9409
|
+
return this.getExistingClasses().has(cls);
|
|
9410
|
+
}
|
|
9411
|
+
/**
|
|
9412
|
+
* #269: delete the generated rules of classes no live element uses. Root/@property rules stay
|
|
9413
|
+
* (they are shared and harmless); a rule text another cached class still emits is kept.
|
|
9414
|
+
*/
|
|
9415
|
+
reclaim(classes) {
|
|
9416
|
+
if (this.isDestroyed) return;
|
|
9417
|
+
const victims = classes.filter((cls) => this.cache.has(cls));
|
|
9418
|
+
if (victims.length === 0) return;
|
|
9419
|
+
const results = victims.map((cls) => this.cache.get(cls));
|
|
9420
|
+
victims.forEach((cls) => {
|
|
9421
|
+
this.cache.delete(cls);
|
|
9422
|
+
this.incrementalParser.unmarkProcessed(cls);
|
|
9423
|
+
});
|
|
9424
|
+
const stillUsed = /* @__PURE__ */ new Set();
|
|
9425
|
+
for (const result of this.cache.values()) result.cssList.forEach((css) => stillUsed.add(css));
|
|
9426
|
+
for (const result of results) {
|
|
9427
|
+
const category = this.getCategory(result.cls);
|
|
9428
|
+
for (const css of result.cssList) {
|
|
9429
|
+
if (!stillUsed.has(css)) this.stylePartitionManager.removeRule(css, category);
|
|
9430
|
+
}
|
|
9431
|
+
}
|
|
9432
|
+
this.reclaimedCount += victims.length;
|
|
9433
|
+
}
|
|
9177
9434
|
/** Class names defined by the page's own stylesheets (BaroCSS's sheets and cross-origin sheets excluded). */
|
|
9178
9435
|
getExistingClasses() {
|
|
9436
|
+
const own = new Set(Array.from(document.querySelectorAll("style[data-barocss]"), (s) => s.sheet));
|
|
9179
9437
|
const sheets = Array.from(document.styleSheets).filter((sheet) => {
|
|
9438
|
+
if (own.has(sheet)) return false;
|
|
9180
9439
|
const owner = sheet.ownerNode;
|
|
9181
9440
|
return !(owner && typeof owner.hasAttribute === "function" && (owner.hasAttribute("data-barocss") || (owner.id || "").startsWith(this.options.styleId)));
|
|
9182
9441
|
});
|
|
@@ -9228,7 +9487,10 @@ ${preflightCSS}
|
|
|
9228
9487
|
return {
|
|
9229
9488
|
runtime: {
|
|
9230
9489
|
cachedClasses: this.cache.size,
|
|
9231
|
-
rootCacheSize: this.rootCache.size
|
|
9490
|
+
rootCacheSize: this.rootCache.size,
|
|
9491
|
+
ruleCount: this.stylePartitionManager.ruleCount,
|
|
9492
|
+
reclaimedClasses: this.reclaimedCount,
|
|
9493
|
+
gc: this.gc?.stats() ?? null
|
|
9232
9494
|
},
|
|
9233
9495
|
ast: incremental.cacheStats.ast,
|
|
9234
9496
|
incremental
|
|
@@ -9282,6 +9544,7 @@ ${preflightCSS}
|
|
|
9282
9544
|
destroy() {
|
|
9283
9545
|
if (this.isDestroyed) return;
|
|
9284
9546
|
this.changeDetector.disconnect();
|
|
9547
|
+
this.gc?.cancel();
|
|
9285
9548
|
this.stylePartitionManager.cleanup();
|
|
9286
9549
|
this.cache.clear();
|
|
9287
9550
|
this.rootCache.clear();
|