domflax 0.1.2 → 0.2.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 +66 -31
- package/dist/chunk-EYQXQQQH.js +336 -0
- package/dist/chunk-EYQXQQQH.js.map +1 -0
- package/dist/{chunk-DNHOGPYV.js → chunk-FPT4EJ6Q.js} +1100 -1551
- package/dist/chunk-FPT4EJ6Q.js.map +1 -0
- package/dist/chunk-JBM3MJRM.js +382 -0
- package/dist/chunk-JBM3MJRM.js.map +1 -0
- package/dist/{chunk-DWLB7FRR.js → chunk-TTJEXWAC.js} +322 -9
- package/dist/chunk-TTJEXWAC.js.map +1 -0
- package/dist/{chunk-6WVVF6AD.js → chunk-U5GOONKV.js} +5 -2
- package/dist/{chunk-6WVVF6AD.js.map → chunk-U5GOONKV.js.map} +1 -1
- package/dist/cli.cjs +3010 -2789
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +268 -232
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +1684 -1649
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +255 -498
- package/dist/index.d.ts +255 -498
- package/dist/index.js +17 -37
- package/dist/{pattern-F5xBtIE-.d.cts → pattern-DotR_dHs.d.cts} +1 -1
- package/dist/pattern-kit.cjs +60 -1
- package/dist/pattern-kit.cjs.map +1 -1
- package/dist/pattern-kit.d.cts +2 -2
- package/dist/pattern-kit.d.ts +2 -2
- package/dist/pattern-kit.js +2 -2
- package/dist/{pattern-CV607P87.d.ts → pattern-urm5uuwj.d.ts} +1 -1
- package/dist/{resolve-ops-DIwEelH-.d.ts → resolve-ops-D8aQina5.d.cts} +20 -0
- package/dist/{resolve-ops-DIwEelH-.d.cts → resolve-ops-D8aQina5.d.ts} +20 -0
- package/dist/verify.d.cts +1 -1
- package/dist/verify.d.ts +1 -1
- package/dist/verify.js +1 -1
- package/dist/webpack-loader.cjs +1615 -1633
- package/dist/webpack-loader.cjs.map +1 -1
- package/dist/webpack-loader.d.cts +8 -2
- package/dist/webpack-loader.d.ts +8 -2
- package/dist/webpack-loader.js +8 -5
- package/dist/webpack-loader.js.map +1 -1
- package/dist/worker.cjs +5337 -0
- package/dist/worker.cjs.map +1 -0
- package/dist/worker.d.cts +2 -0
- package/dist/worker.d.ts +2 -0
- package/dist/worker.js +72 -0
- package/dist/worker.js.map +1 -0
- package/package.json +4 -2
- package/dist/chunk-DNHOGPYV.js.map +0 -1
- package/dist/chunk-DOQEBGWB.js +0 -188
- package/dist/chunk-DOQEBGWB.js.map +0 -1
- package/dist/chunk-DWLB7FRR.js.map +0 -1
package/dist/webpack-loader.cjs
CHANGED
|
@@ -96,6 +96,7 @@ function defaultMeta(safetyFloor = 0) {
|
|
|
96
96
|
hasDynamicChildren: false,
|
|
97
97
|
isComponent: false,
|
|
98
98
|
hasDangerousHtml: false,
|
|
99
|
+
hasUnresolvedClasses: false,
|
|
99
100
|
targetedByCombinator: false,
|
|
100
101
|
targetedByStructuralPseudo: false,
|
|
101
102
|
selectorDependents: 0,
|
|
@@ -107,6 +108,7 @@ function defaultMeta(safetyFloor = 0) {
|
|
|
107
108
|
declaresCustomProperties: false,
|
|
108
109
|
whitespaceSensitive: false,
|
|
109
110
|
touched: false,
|
|
111
|
+
styleDirty: false,
|
|
110
112
|
synthetic: false,
|
|
111
113
|
safetyFloor
|
|
112
114
|
};
|
|
@@ -287,6 +289,14 @@ function markTouched(state, id) {
|
|
|
287
289
|
state.touched.add(id);
|
|
288
290
|
}
|
|
289
291
|
}
|
|
292
|
+
function markStyleDirty(state, id) {
|
|
293
|
+
const n = state.doc.nodes.get(id);
|
|
294
|
+
if (n) {
|
|
295
|
+
n.meta.touched = true;
|
|
296
|
+
n.meta.styleDirty = true;
|
|
297
|
+
state.touched.add(id);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
290
300
|
function removeSubtree(state, id) {
|
|
291
301
|
const node = state.doc.nodes.get(id);
|
|
292
302
|
if (!node) return;
|
|
@@ -578,7 +588,7 @@ function applyOne(state, op) {
|
|
|
578
588
|
return [precond(op, op.target, "setClassList target is not an element")];
|
|
579
589
|
}
|
|
580
590
|
el.computed = cloneStyleMap(op.style);
|
|
581
|
-
|
|
591
|
+
markStyleDirty(state, op.target);
|
|
582
592
|
return [];
|
|
583
593
|
}
|
|
584
594
|
case "mergeStyle": {
|
|
@@ -601,7 +611,7 @@ function applyOne(state, op) {
|
|
|
601
611
|
const src = doc.nodes.get(op.source);
|
|
602
612
|
if (src) markTouched(state, op.source);
|
|
603
613
|
}
|
|
604
|
-
|
|
614
|
+
markStyleDirty(state, op.target);
|
|
605
615
|
return [];
|
|
606
616
|
}
|
|
607
617
|
case "foldInheritedStyles":
|
|
@@ -652,7 +662,7 @@ function applyFold(state, op) {
|
|
|
652
662
|
}
|
|
653
663
|
if (folded) {
|
|
654
664
|
into.computed = { blocks: nextBlocks };
|
|
655
|
-
|
|
665
|
+
markStyleDirty(state, intoId);
|
|
656
666
|
}
|
|
657
667
|
}
|
|
658
668
|
for (const d of issues) state.diagnostics.push(d);
|
|
@@ -912,8 +922,123 @@ function buildMatchContext(doc, elementId, resolver, selectors, safety, phase, i
|
|
|
912
922
|
var DISPLAY = "display";
|
|
913
923
|
var POSITION = "position";
|
|
914
924
|
var TRANSFORM = "transform";
|
|
925
|
+
var ALIGN_ITEMS = "align-items";
|
|
926
|
+
var JUSTIFY_CONTENT = "justify-content";
|
|
927
|
+
var JUSTIFY_ITEMS = "justify-items";
|
|
928
|
+
var PLACE_ITEMS = "place-items";
|
|
929
|
+
var PLACE_SELF = "place-self";
|
|
915
930
|
var CONTEXT_SAFE_DISPLAYS = /* @__PURE__ */ new Set(["block", "contents", ""]);
|
|
916
931
|
var STATIC_POSITIONS = /* @__PURE__ */ new Set(["static", ""]);
|
|
932
|
+
var CENTERING_DISPLAYS = /* @__PURE__ */ new Set(["flex", "grid"]);
|
|
933
|
+
var GRID_PARENT_DISPLAYS = /* @__PURE__ */ new Set(["grid"]);
|
|
934
|
+
var STRETCHY_ITEM_ALIGN = /* @__PURE__ */ new Set(["normal", "stretch"]);
|
|
935
|
+
var PARENT_ITEMS_ALIGN_PROPS = [ALIGN_ITEMS, JUSTIFY_ITEMS, PLACE_ITEMS];
|
|
936
|
+
function isBaseCondition(block) {
|
|
937
|
+
const c = block.condition;
|
|
938
|
+
return c.media === "" && c.states.length === 0 && c.pseudoElement === "";
|
|
939
|
+
}
|
|
940
|
+
function baseValue(sm, prop) {
|
|
941
|
+
for (const block of sm.blocks.values()) {
|
|
942
|
+
if (!isBaseCondition(block)) continue;
|
|
943
|
+
const d = block.decls.get(prop);
|
|
944
|
+
return d ? String(d.value) : null;
|
|
945
|
+
}
|
|
946
|
+
return null;
|
|
947
|
+
}
|
|
948
|
+
function parentIsFillingGrid(before, wrapper, norm) {
|
|
949
|
+
if (wrapper.parent == null) return false;
|
|
950
|
+
const p = before.nodes.get(wrapper.parent);
|
|
951
|
+
if (!p || p.kind !== "element") return false;
|
|
952
|
+
const pc2 = norm.normalizeStyleMap(p.computed);
|
|
953
|
+
let baseIsGrid = false;
|
|
954
|
+
for (const block of pc2.blocks.values()) {
|
|
955
|
+
const disp = block.decls.get(DISPLAY);
|
|
956
|
+
if (disp) {
|
|
957
|
+
if (!GRID_PARENT_DISPLAYS.has(String(disp.value))) return false;
|
|
958
|
+
if (isBaseCondition(block)) baseIsGrid = true;
|
|
959
|
+
}
|
|
960
|
+
for (const prop of PARENT_ITEMS_ALIGN_PROPS) {
|
|
961
|
+
const d = block.decls.get(prop);
|
|
962
|
+
if (d && !STRETCHY_ITEM_ALIGN.has(String(d.value))) return false;
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
return baseIsGrid;
|
|
966
|
+
}
|
|
967
|
+
function wrapperHasOnlyCenteringStyle(wrapperComputed, childComputed, norm) {
|
|
968
|
+
for (const block of wrapperComputed.blocks.values()) {
|
|
969
|
+
const base = isBaseCondition(block);
|
|
970
|
+
const ck = conditionKey(block.condition);
|
|
971
|
+
for (const [prop, decl] of block.decls) {
|
|
972
|
+
const val = String(decl.value);
|
|
973
|
+
if (prop === DISPLAY) {
|
|
974
|
+
if (base && CENTERING_DISPLAYS.has(val)) continue;
|
|
975
|
+
return false;
|
|
976
|
+
}
|
|
977
|
+
if (prop === ALIGN_ITEMS) {
|
|
978
|
+
if (base && val === "center") continue;
|
|
979
|
+
return false;
|
|
980
|
+
}
|
|
981
|
+
if (prop === JUSTIFY_CONTENT) {
|
|
982
|
+
if (base && val === "center") continue;
|
|
983
|
+
return false;
|
|
984
|
+
}
|
|
985
|
+
if (prop === POSITION) {
|
|
986
|
+
if (STATIC_POSITIONS.has(val)) continue;
|
|
987
|
+
return false;
|
|
988
|
+
}
|
|
989
|
+
if (prop === TRANSFORM) {
|
|
990
|
+
if (val === "none") continue;
|
|
991
|
+
return false;
|
|
992
|
+
}
|
|
993
|
+
if (isInherited2(decl, norm)) continue;
|
|
994
|
+
if (childReproduces(childComputed, ck, prop, val)) continue;
|
|
995
|
+
return false;
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
return true;
|
|
999
|
+
}
|
|
1000
|
+
function wrapperCentersSingleElement(before, wrapper) {
|
|
1001
|
+
let elements = 0;
|
|
1002
|
+
for (const cid of wrapper.children) {
|
|
1003
|
+
const n = before.nodes.get(cid);
|
|
1004
|
+
if (!n) continue;
|
|
1005
|
+
if (n.kind === "element") {
|
|
1006
|
+
elements += 1;
|
|
1007
|
+
continue;
|
|
1008
|
+
}
|
|
1009
|
+
if (n.kind === "comment") continue;
|
|
1010
|
+
if (n.kind === "text" && n.value.trim() === "") continue;
|
|
1011
|
+
return false;
|
|
1012
|
+
}
|
|
1013
|
+
return elements === 1;
|
|
1014
|
+
}
|
|
1015
|
+
function childHasSelfAlign(childBefore, norm) {
|
|
1016
|
+
if (!childBefore) return false;
|
|
1017
|
+
const sm = norm.normalizeStyleMap(childBefore);
|
|
1018
|
+
for (const block of sm.blocks.values()) {
|
|
1019
|
+
for (const prop of SELF_ALIGN_PROPS) {
|
|
1020
|
+
const d = block.decls.get(prop);
|
|
1021
|
+
if (d && !NEUTRAL_ALIGN.has(String(d.value))) return true;
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
return false;
|
|
1025
|
+
}
|
|
1026
|
+
function childGainsPlaceSelfCenter(childAfter) {
|
|
1027
|
+
if (baseValue(childAfter, PLACE_SELF) === "center") return true;
|
|
1028
|
+
return baseValue(childAfter, "align-self") === "center" && baseValue(childAfter, "justify-self") === "center";
|
|
1029
|
+
}
|
|
1030
|
+
function isContextCompensatedCentering(before, wrapper, wrapperComputed, childBefore, childAfter, norm) {
|
|
1031
|
+
if (!childAfter) return false;
|
|
1032
|
+
if (!CENTERING_DISPLAYS.has(baseValue(wrapperComputed, DISPLAY) ?? "")) return false;
|
|
1033
|
+
if (baseValue(wrapperComputed, ALIGN_ITEMS) !== "center") return false;
|
|
1034
|
+
if (baseValue(wrapperComputed, JUSTIFY_CONTENT) !== "center") return false;
|
|
1035
|
+
const childAfterNorm = norm.normalizeStyleMap(childAfter);
|
|
1036
|
+
if (!childGainsPlaceSelfCenter(childAfterNorm)) return false;
|
|
1037
|
+
if (!wrapperHasOnlyCenteringStyle(wrapperComputed, childAfterNorm, norm)) return false;
|
|
1038
|
+
if (!wrapperCentersSingleElement(before, wrapper)) return false;
|
|
1039
|
+
if (childHasSelfAlign(childBefore, norm)) return false;
|
|
1040
|
+
return parentIsFillingGrid(before, wrapper, norm);
|
|
1041
|
+
}
|
|
917
1042
|
var SELF_ALIGN_PROPS = [
|
|
918
1043
|
"place-self",
|
|
919
1044
|
"align-self",
|
|
@@ -1009,10 +1134,16 @@ function classifyFlattenOps(before, after, ops, norm) {
|
|
|
1009
1134
|
if (!wrapper || wrapper.kind !== "element") {
|
|
1010
1135
|
return { kind: "provably-safe", wrapperId: null, childId: null };
|
|
1011
1136
|
}
|
|
1137
|
+
if (wrapper.meta.hasUnresolvedClasses) {
|
|
1138
|
+
return { kind: "needs-verification", wrapperId, childId: survivingChildOf(ops, wrapper, before) };
|
|
1139
|
+
}
|
|
1012
1140
|
const childId = survivingChildOf(ops, wrapper, before);
|
|
1013
1141
|
const wrapperComputed = norm.normalizeStyleMap(wrapper.computed);
|
|
1014
1142
|
const childAfter = childId != null ? getElement(after, childId)?.computed ?? null : null;
|
|
1015
1143
|
const childBefore = childId != null ? getElement(before, childId)?.computed ?? null : null;
|
|
1144
|
+
if (isContextCompensatedCentering(before, wrapper, wrapperComputed, childBefore, childAfter, norm)) {
|
|
1145
|
+
return { kind: "provably-safe", wrapperId, childId };
|
|
1146
|
+
}
|
|
1016
1147
|
if (establishesChildContext(wrapperComputed)) {
|
|
1017
1148
|
return { kind: "needs-verification", wrapperId, childId };
|
|
1018
1149
|
}
|
|
@@ -1441,22 +1572,52 @@ function sameTokens(a, b) {
|
|
|
1441
1572
|
for (let i = 0; i < a.length; i += 1) if (a[i] !== b[i]) return false;
|
|
1442
1573
|
return true;
|
|
1443
1574
|
}
|
|
1575
|
+
function residualStyle(computed2, covered, norm) {
|
|
1576
|
+
const cov = norm.normalizeStyleMap(covered);
|
|
1577
|
+
const blocks = /* @__PURE__ */ new Map();
|
|
1578
|
+
for (const [key, block] of norm.normalizeStyleMap(computed2).blocks) {
|
|
1579
|
+
const covBlock = cov.blocks.get(key);
|
|
1580
|
+
const decls = /* @__PURE__ */ new Map();
|
|
1581
|
+
for (const [prop, decl] of block.decls) {
|
|
1582
|
+
const covDecl = covBlock?.decls.get(prop);
|
|
1583
|
+
if (covDecl && covDecl.value === decl.value && covDecl.important === decl.important) continue;
|
|
1584
|
+
decls.set(prop, decl);
|
|
1585
|
+
}
|
|
1586
|
+
if (decls.size > 0) blocks.set(key, { condition: block.condition, decls });
|
|
1587
|
+
}
|
|
1588
|
+
return { blocks };
|
|
1589
|
+
}
|
|
1590
|
+
function joinedLength(tokens) {
|
|
1591
|
+
if (tokens.length === 0) return 0;
|
|
1592
|
+
let len = tokens.length - 1;
|
|
1593
|
+
for (const t of tokens) len += t.length;
|
|
1594
|
+
return len;
|
|
1595
|
+
}
|
|
1596
|
+
var COMPRESS_FLOOR = 1;
|
|
1444
1597
|
function syncClassesFromComputed(doc, resolver, norm) {
|
|
1445
1598
|
const sink = createSyntheticSink();
|
|
1599
|
+
const isDroppable = (t) => resolver.owns(t) && resolver.selectorUsage(t).droppable;
|
|
1446
1600
|
for (const id of elementIds(doc)) {
|
|
1447
1601
|
const el = getElement(doc, id);
|
|
1448
|
-
if (!el
|
|
1602
|
+
if (!el) continue;
|
|
1449
1603
|
if (el.classes.opaque || el.classes.hasDynamic) continue;
|
|
1604
|
+
const compressOnly = !el.meta.styleDirty;
|
|
1605
|
+
if (compressOnly && el.meta.safetyFloor < COMPRESS_FLOOR) continue;
|
|
1450
1606
|
const tokens = staticTokensOf(el.classes);
|
|
1607
|
+
if (tokens.length === 0) continue;
|
|
1608
|
+
const retained = tokens.filter((t) => !isDroppable(t));
|
|
1609
|
+
if (compressOnly && retained.length === tokens.length) continue;
|
|
1610
|
+
const covered = retained.length > 0 ? resolver.resolve({ classes: retained }).styles : null;
|
|
1611
|
+
const target = covered ? residualStyle(el.computed, covered, norm) : el.computed;
|
|
1451
1612
|
const ctx = { normalizer: norm, sink };
|
|
1452
|
-
const emitted = resolver.emit(
|
|
1613
|
+
const emitted = resolver.emit(target, ctx).classes;
|
|
1453
1614
|
if (emitted.length === 0) continue;
|
|
1454
1615
|
const emittedSet = new Set(emitted);
|
|
1455
1616
|
const next = [];
|
|
1456
1617
|
const seen = /* @__PURE__ */ new Set();
|
|
1457
1618
|
for (const t of tokens) {
|
|
1458
1619
|
if (seen.has(t)) continue;
|
|
1459
|
-
const keep = emittedSet.has(t) || !
|
|
1620
|
+
const keep = emittedSet.has(t) || !isDroppable(t);
|
|
1460
1621
|
if (keep) {
|
|
1461
1622
|
next.push(t);
|
|
1462
1623
|
seen.add(t);
|
|
@@ -1468,10 +1629,98 @@ function syncClassesFromComputed(doc, resolver, norm) {
|
|
|
1468
1629
|
seen.add(c);
|
|
1469
1630
|
}
|
|
1470
1631
|
if (sameTokens(next, tokens)) continue;
|
|
1632
|
+
if (compressOnly) {
|
|
1633
|
+
if (!norm.equals(resolver.resolve({ classes: next }).styles, el.computed)) continue;
|
|
1634
|
+
if (joinedLength(next) > joinedLength(tokens)) continue;
|
|
1635
|
+
}
|
|
1471
1636
|
el.classes = staticClassList(el.classes, next);
|
|
1472
1637
|
}
|
|
1473
1638
|
}
|
|
1474
1639
|
|
|
1640
|
+
// ../core/src/compress-engine.ts
|
|
1641
|
+
var SEP = "";
|
|
1642
|
+
function tupleKey(condition, property, value, important) {
|
|
1643
|
+
return `${condition}${SEP}${property}${SEP}${value}${SEP}${important ? "1" : "0"}`;
|
|
1644
|
+
}
|
|
1645
|
+
function styleMapTuples(map, norm) {
|
|
1646
|
+
const out = [];
|
|
1647
|
+
const normalized = norm.normalizeStyleMap(map);
|
|
1648
|
+
for (const [ck, block] of normalized.blocks) {
|
|
1649
|
+
for (const [prop, decl] of block.decls) {
|
|
1650
|
+
out.push(tupleKey(String(ck), String(prop), String(decl.value), decl.important));
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
return out;
|
|
1654
|
+
}
|
|
1655
|
+
var DEFAULT_MAX_UNIVERSE = 20;
|
|
1656
|
+
function minStringCover(universe, vocabulary, options = {}) {
|
|
1657
|
+
const uniq = [...new Set(universe)];
|
|
1658
|
+
if (uniq.length === 0) return [];
|
|
1659
|
+
const n = uniq.length;
|
|
1660
|
+
const max = options.maxUniverse ?? DEFAULT_MAX_UNIVERSE;
|
|
1661
|
+
if (n > max) return null;
|
|
1662
|
+
const bitOf = /* @__PURE__ */ new Map();
|
|
1663
|
+
uniq.forEach((t, i) => bitOf.set(t, i));
|
|
1664
|
+
const byMask = /* @__PURE__ */ new Map();
|
|
1665
|
+
for (const entry of vocabulary) {
|
|
1666
|
+
if (entry.tuples.length === 0) continue;
|
|
1667
|
+
let mask = 0;
|
|
1668
|
+
let ok = true;
|
|
1669
|
+
for (const t of entry.tuples) {
|
|
1670
|
+
const b = bitOf.get(t);
|
|
1671
|
+
if (b === void 0) {
|
|
1672
|
+
ok = false;
|
|
1673
|
+
break;
|
|
1674
|
+
}
|
|
1675
|
+
mask |= 1 << b;
|
|
1676
|
+
}
|
|
1677
|
+
if (!ok || mask === 0) continue;
|
|
1678
|
+
const cost = entry.token.length + 1;
|
|
1679
|
+
const prev = byMask.get(mask);
|
|
1680
|
+
if (!prev || cost < prev.cost || cost === prev.cost && entry.token < prev.token) {
|
|
1681
|
+
byMask.set(mask, { token: entry.token, mask, cost });
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
const cands = [...byMask.values()];
|
|
1685
|
+
if (cands.length === 0) return null;
|
|
1686
|
+
const full = (1 << n) - 1;
|
|
1687
|
+
const byBit = Array.from({ length: n }, () => []);
|
|
1688
|
+
cands.forEach((c, ci) => {
|
|
1689
|
+
for (let b = 0; b < n; b += 1) if (c.mask & 1 << b) byBit[b].push(ci);
|
|
1690
|
+
});
|
|
1691
|
+
const size = full + 1;
|
|
1692
|
+
const dp = new Float64Array(size).fill(Infinity);
|
|
1693
|
+
const fromCand = new Int32Array(size).fill(-1);
|
|
1694
|
+
const fromMask = new Int32Array(size).fill(-1);
|
|
1695
|
+
dp[0] = 0;
|
|
1696
|
+
for (let mask = 0; mask < full; mask += 1) {
|
|
1697
|
+
const cur = dp[mask];
|
|
1698
|
+
if (!Number.isFinite(cur)) continue;
|
|
1699
|
+
let b = 0;
|
|
1700
|
+
while (b < n && mask & 1 << b) b += 1;
|
|
1701
|
+
for (const ci of byBit[b]) {
|
|
1702
|
+
const c = cands[ci];
|
|
1703
|
+
const nm = mask | c.mask;
|
|
1704
|
+
const cost = cur + c.cost;
|
|
1705
|
+
if (cost < dp[nm]) {
|
|
1706
|
+
dp[nm] = cost;
|
|
1707
|
+
fromCand[nm] = ci;
|
|
1708
|
+
fromMask[nm] = mask;
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
if (!Number.isFinite(dp[full])) return null;
|
|
1713
|
+
const chosen = [];
|
|
1714
|
+
let m = full;
|
|
1715
|
+
while (m !== 0) {
|
|
1716
|
+
const ci = fromCand[m];
|
|
1717
|
+
if (ci < 0) return null;
|
|
1718
|
+
chosen.push(cands[ci].token);
|
|
1719
|
+
m = fromMask[m];
|
|
1720
|
+
}
|
|
1721
|
+
return [...new Set(chosen)].sort();
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1475
1724
|
// ../pattern-kit/src/normalize.ts
|
|
1476
1725
|
var INHERITED_PROPERTIES = [
|
|
1477
1726
|
"azimuth",
|
|
@@ -1556,6 +1805,18 @@ var BOX_SIDES = {
|
|
|
1556
1805
|
padding: ["padding-top", "padding-right", "padding-bottom", "padding-left"],
|
|
1557
1806
|
margin: ["margin-top", "margin-right", "margin-bottom", "margin-left"],
|
|
1558
1807
|
inset: ["top", "right", "bottom", "left"],
|
|
1808
|
+
"scroll-margin": [
|
|
1809
|
+
"scroll-margin-top",
|
|
1810
|
+
"scroll-margin-right",
|
|
1811
|
+
"scroll-margin-bottom",
|
|
1812
|
+
"scroll-margin-left"
|
|
1813
|
+
],
|
|
1814
|
+
"scroll-padding": [
|
|
1815
|
+
"scroll-padding-top",
|
|
1816
|
+
"scroll-padding-right",
|
|
1817
|
+
"scroll-padding-bottom",
|
|
1818
|
+
"scroll-padding-left"
|
|
1819
|
+
],
|
|
1559
1820
|
"border-width": [
|
|
1560
1821
|
"border-top-width",
|
|
1561
1822
|
"border-right-width",
|
|
@@ -1573,8 +1834,35 @@ var BOX_SIDES = {
|
|
|
1573
1834
|
"border-right-color",
|
|
1574
1835
|
"border-bottom-color",
|
|
1575
1836
|
"border-left-color"
|
|
1837
|
+
],
|
|
1838
|
+
// `border-radius` 1–4 value form maps to the four CORNERS (TL, TR, BR, BL) — the same positional
|
|
1839
|
+
// pattern boxFourSides implements. Only the slash-free form is expanded (see expandShorthand).
|
|
1840
|
+
"border-radius": [
|
|
1841
|
+
"border-top-left-radius",
|
|
1842
|
+
"border-top-right-radius",
|
|
1843
|
+
"border-bottom-right-radius",
|
|
1844
|
+
"border-bottom-left-radius"
|
|
1576
1845
|
]
|
|
1577
1846
|
};
|
|
1847
|
+
var AXIS_PAIRS = {
|
|
1848
|
+
overflow: ["overflow-x", "overflow-y"],
|
|
1849
|
+
"overscroll-behavior": ["overscroll-behavior-x", "overscroll-behavior-y"],
|
|
1850
|
+
"place-items": ["align-items", "justify-items"],
|
|
1851
|
+
"place-content": ["align-content", "justify-content"],
|
|
1852
|
+
"place-self": ["align-self", "justify-self"]
|
|
1853
|
+
};
|
|
1854
|
+
var LOGICAL_PAIRS = {
|
|
1855
|
+
"padding-inline": ["padding-left", "padding-right"],
|
|
1856
|
+
"padding-block": ["padding-top", "padding-bottom"],
|
|
1857
|
+
"margin-inline": ["margin-left", "margin-right"],
|
|
1858
|
+
"margin-block": ["margin-top", "margin-bottom"],
|
|
1859
|
+
"inset-inline": ["left", "right"],
|
|
1860
|
+
"inset-block": ["top", "bottom"],
|
|
1861
|
+
"scroll-padding-inline": ["scroll-padding-left", "scroll-padding-right"],
|
|
1862
|
+
"scroll-padding-block": ["scroll-padding-top", "scroll-padding-bottom"],
|
|
1863
|
+
"scroll-margin-inline": ["scroll-margin-left", "scroll-margin-right"],
|
|
1864
|
+
"scroll-margin-block": ["scroll-margin-top", "scroll-margin-bottom"]
|
|
1865
|
+
};
|
|
1578
1866
|
function splitTopLevel(value) {
|
|
1579
1867
|
const out = [];
|
|
1580
1868
|
let depth = 0;
|
|
@@ -1608,6 +1896,7 @@ function boxFourSides(values) {
|
|
|
1608
1896
|
}
|
|
1609
1897
|
}
|
|
1610
1898
|
function expandShorthand(prop, value) {
|
|
1899
|
+
if (prop === "border-radius" && value.includes("/")) return [[prop, value]];
|
|
1611
1900
|
const box = BOX_SIDES[prop];
|
|
1612
1901
|
if (box) {
|
|
1613
1902
|
const parts = splitTopLevel(value);
|
|
@@ -1617,6 +1906,19 @@ function expandShorthand(prop, value) {
|
|
|
1617
1906
|
}
|
|
1618
1907
|
return [[prop, value]];
|
|
1619
1908
|
}
|
|
1909
|
+
const axis = AXIS_PAIRS[prop];
|
|
1910
|
+
if (axis) {
|
|
1911
|
+
const parts = splitTopLevel(value);
|
|
1912
|
+
if (parts.length === 1) return [[axis[0], parts[0]], [axis[1], parts[0]]];
|
|
1913
|
+
if (parts.length === 2) return [[axis[0], parts[0]], [axis[1], parts[1]]];
|
|
1914
|
+
return [[prop, value]];
|
|
1915
|
+
}
|
|
1916
|
+
const logical = LOGICAL_PAIRS[prop];
|
|
1917
|
+
if (logical) {
|
|
1918
|
+
const parts = splitTopLevel(value);
|
|
1919
|
+
if (parts.length === 1) return [[logical[0], parts[0]], [logical[1], parts[0]]];
|
|
1920
|
+
return [[prop, value]];
|
|
1921
|
+
}
|
|
1620
1922
|
if (prop === "gap" || prop === "grid-gap") {
|
|
1621
1923
|
const parts = splitTopLevel(value);
|
|
1622
1924
|
if (parts.length === 1) {
|
|
@@ -1774,7 +2076,12 @@ var VISUAL_PROPERTIES = /* @__PURE__ */ new Set([
|
|
|
1774
2076
|
"border-right-color",
|
|
1775
2077
|
"border-bottom-color",
|
|
1776
2078
|
"border-left-color",
|
|
1777
|
-
|
|
2079
|
+
// `border-radius` is expanded to its four corner longhands by the shared normalizer, so the
|
|
2080
|
+
// paint-establishing check must match those (a rounded wrapper still clips its background).
|
|
2081
|
+
"border-top-left-radius",
|
|
2082
|
+
"border-top-right-radius",
|
|
2083
|
+
"border-bottom-right-radius",
|
|
2084
|
+
"border-bottom-left-radius",
|
|
1778
2085
|
"box-shadow",
|
|
1779
2086
|
"outline",
|
|
1780
2087
|
"outline-width",
|
|
@@ -1800,6 +2107,7 @@ var hasOwnVisualStyle = (node, ctx) => {
|
|
|
1800
2107
|
const el = asElement(node);
|
|
1801
2108
|
if (!el) return false;
|
|
1802
2109
|
if (el.meta.hasOwnVisualStyle) return true;
|
|
2110
|
+
if (el.meta.hasUnresolvedClasses) return true;
|
|
1803
2111
|
const computedMap = ctx.computedOf(el) ?? el.computed;
|
|
1804
2112
|
const norm = normalizer.normalizeStyleMap(computedMap);
|
|
1805
2113
|
for (const block of norm.blocks.values()) {
|
|
@@ -2004,7 +2312,160 @@ function definePattern(config) {
|
|
|
2004
2312
|
return validatePattern(spec);
|
|
2005
2313
|
}
|
|
2006
2314
|
|
|
2007
|
-
// ../patterns/src/library/
|
|
2315
|
+
// ../patterns/src/library/flex/flex-center-wrapper.pattern.ts
|
|
2316
|
+
var flexCenterWrapper = definePattern({
|
|
2317
|
+
name: "flex-center-wrapper",
|
|
2318
|
+
category: "flatten/flex/flex-center-wrapper",
|
|
2319
|
+
safety: 2,
|
|
2320
|
+
doc: {
|
|
2321
|
+
title: "Flatten flex-centering wrapper",
|
|
2322
|
+
summary: "A div that only centers a single child (display:flex; align-items:center; justify-content:center) is removed; the child gains place-self:center.",
|
|
2323
|
+
before: '<div style="display:flex;align-items:center;justify-content:center"><Child/></div>',
|
|
2324
|
+
after: '<Child style="place-self:center"/>',
|
|
2325
|
+
safetyRationale: "Wrapper paints nothing, carries no ref/handlers/dynamic children, and is not a combinator subject; inheritable styles are folded onto the child before removal."
|
|
2326
|
+
},
|
|
2327
|
+
match: {
|
|
2328
|
+
tag: "div",
|
|
2329
|
+
style: { display: "flex", alignItems: "center", justifyContent: "center" },
|
|
2330
|
+
onlyChild: "element",
|
|
2331
|
+
paintsNothing: true
|
|
2332
|
+
},
|
|
2333
|
+
rewrite: {
|
|
2334
|
+
flattenInto: "child",
|
|
2335
|
+
childGains: { placeSelf: "center" }
|
|
2336
|
+
},
|
|
2337
|
+
// Collapsing a flex-centering wrapper to `place-self:center` on the child is render-identical ONLY
|
|
2338
|
+
// when the child's NEW parent is a statically-known GRID that lets the wrapper fill its area (there
|
|
2339
|
+
// `place-self`'s align-self AND justify-self both take effect). Under that ONE context the flatten is
|
|
2340
|
+
// classified `provably-safe` and commits; under a flex/block/unknown parent — or when the wrapper
|
|
2341
|
+
// drops any own style — it stays `needs-verification` and the conservative production gate PRESERVES
|
|
2342
|
+
// it. Op-level correctness (purity, id-preserving unwrap, opacity-barrier safety) is additionally
|
|
2343
|
+
// asserted by the invariant suite over every pattern.
|
|
2344
|
+
test: {
|
|
2345
|
+
cases: [
|
|
2346
|
+
{
|
|
2347
|
+
name: "grid parent \u2192 flattened (child gains place-self-center)",
|
|
2348
|
+
before: '<div className="grid"><div className="flex items-center justify-center"><span className="bg-red-200">x</span></div></div>',
|
|
2349
|
+
after: '<div className="grid"><span className="bg-red-200 place-self-center">x</span></div>'
|
|
2350
|
+
}
|
|
2351
|
+
],
|
|
2352
|
+
noMatch: [
|
|
2353
|
+
// Non-grid (flex) parent (document root): `justify-self` is ignored in flex → not provably safe.
|
|
2354
|
+
'<div className="flex justify-center items-center"><div className="bg-red-200">Hello</div></div>',
|
|
2355
|
+
// Grid parent, but the wrapper drops padding when removed → not layout-neutral (rule 3).
|
|
2356
|
+
'<div className="grid"><div className="p-4 flex items-center justify-center"><span className="bg-red-200">x</span></div></div>',
|
|
2357
|
+
// Grid parent forcing place-items-center: the wrapper would not fill its area → fill guard skips.
|
|
2358
|
+
'<div className="grid place-items-center"><div className="flex items-center justify-center"><span className="bg-red-200">x</span></div></div>',
|
|
2359
|
+
// onClick is a hard opacity barrier → the wrapper is load-bearing regardless of the gate.
|
|
2360
|
+
'<div className="flex justify-center items-center" onClick={handleClick}><div className="bg-red-200">Hello</div></div>'
|
|
2361
|
+
]
|
|
2362
|
+
}
|
|
2363
|
+
});
|
|
2364
|
+
|
|
2365
|
+
// ../patterns/src/library/fragment/redundant-fragment.pattern.ts
|
|
2366
|
+
function parentIsRedundantFragment(node, ctx) {
|
|
2367
|
+
const el = node;
|
|
2368
|
+
if (el.kind !== "element") return false;
|
|
2369
|
+
const parentId = el.parent;
|
|
2370
|
+
if (parentId == null) return false;
|
|
2371
|
+
const parent = ctx.doc.nodes.get(parentId);
|
|
2372
|
+
if (!parent || parent.kind !== "fragment") return false;
|
|
2373
|
+
if (parent.parent == null) return false;
|
|
2374
|
+
if (parent.children.length !== 1) return false;
|
|
2375
|
+
const m = parent.meta;
|
|
2376
|
+
if (m.hasKey || m.hasRef || m.hasEventHandlers || m.hasDynamicChildren || m.hasDangerousHtml || m.hasSpreadAttrs || m.isComponent) {
|
|
2377
|
+
return false;
|
|
2378
|
+
}
|
|
2379
|
+
if (m.targetedByCombinator || m.targetedByStructuralPseudo) return false;
|
|
2380
|
+
const fid = parentId;
|
|
2381
|
+
if (ctx.selectors.targetedByCombinator(fid) || ctx.selectors.targetedByStructuralPseudo(fid)) {
|
|
2382
|
+
return false;
|
|
2383
|
+
}
|
|
2384
|
+
if (ctx.selectors.reparentImpact(fid).size > 0) return false;
|
|
2385
|
+
return true;
|
|
2386
|
+
}
|
|
2387
|
+
var redundantFragment = definePattern({
|
|
2388
|
+
name: "redundant-fragment",
|
|
2389
|
+
category: "flatten/fragment/redundant-fragment",
|
|
2390
|
+
safety: 1,
|
|
2391
|
+
doc: {
|
|
2392
|
+
title: "Flatten redundant single-child fragment",
|
|
2393
|
+
summary: "A fragment whose only child is a single node is removed; the child is spliced up into the fragment's slot, preserving its IRNodeId, siblings, attributes and the CSS cascade.",
|
|
2394
|
+
before: "<><Child/></>",
|
|
2395
|
+
after: "<Child/>",
|
|
2396
|
+
safetyRationale: "A fragment paints nothing and renders no box; with exactly one child its removal changes no sibling/structural-pseudo match-set. Keyed fragments and fragments carrying ref/handlers/dynamic-children/raw-html/spread are excluded as opacity barriers."
|
|
2397
|
+
},
|
|
2398
|
+
match: parentIsRedundantFragment,
|
|
2399
|
+
rewrite: (ctx, rw) => {
|
|
2400
|
+
const parentId = ctx.node.parent;
|
|
2401
|
+
if (parentId == null) return null;
|
|
2402
|
+
const fragment = ctx.doc.nodes.get(parentId);
|
|
2403
|
+
if (!fragment || fragment.kind !== "fragment") return null;
|
|
2404
|
+
return [rw.unwrap(fragment)];
|
|
2405
|
+
},
|
|
2406
|
+
test: {
|
|
2407
|
+
cases: [
|
|
2408
|
+
{
|
|
2409
|
+
// A fragment renders no box, so unwrapping a single-child fragment is always layout-identical
|
|
2410
|
+
// → a provably-safe flatten: the child is spliced up into the fragment's slot.
|
|
2411
|
+
before: '<><span className="bg-red-200">Hi</span></>',
|
|
2412
|
+
after: '<span className="bg-red-200">Hi</span>'
|
|
2413
|
+
}
|
|
2414
|
+
],
|
|
2415
|
+
noMatch: [
|
|
2416
|
+
// Two children ⇒ not a single-child fragment, so the fragment is load-bearing and stays.
|
|
2417
|
+
'<><span className="bg-red-200">A</span><span className="bg-green-200">B</span></>'
|
|
2418
|
+
]
|
|
2419
|
+
}
|
|
2420
|
+
});
|
|
2421
|
+
|
|
2422
|
+
// ../patterns/src/library/grid/grid-center-wrapper.pattern.ts
|
|
2423
|
+
var gridCenterWrapper = definePattern({
|
|
2424
|
+
name: "grid-center-wrapper",
|
|
2425
|
+
category: "flatten/grid/grid-center-wrapper",
|
|
2426
|
+
safety: 2,
|
|
2427
|
+
doc: {
|
|
2428
|
+
title: "Flatten grid-centering wrapper",
|
|
2429
|
+
summary: "A div that only centers a single child (display:grid; align-items:center; justify-content:center) is removed; the child gains place-self:center.",
|
|
2430
|
+
before: '<div style="display:grid;align-items:center;justify-content:center"><Child/></div>',
|
|
2431
|
+
after: '<Child style="place-self:center"/>',
|
|
2432
|
+
safetyRationale: "Wrapper paints nothing, carries no ref/handlers/dynamic children, and is not a combinator subject; inheritable styles are folded onto the child before removal. The place-self:center collapse is committed by the gate only under a statically-known filling grid parent."
|
|
2433
|
+
},
|
|
2434
|
+
match: {
|
|
2435
|
+
tag: "div",
|
|
2436
|
+
style: { display: "grid", alignItems: "center", justifyContent: "center" },
|
|
2437
|
+
onlyChild: "element",
|
|
2438
|
+
paintsNothing: true
|
|
2439
|
+
},
|
|
2440
|
+
rewrite: {
|
|
2441
|
+
flattenInto: "child",
|
|
2442
|
+
childGains: { placeSelf: "center" }
|
|
2443
|
+
},
|
|
2444
|
+
// Like `flex-center-wrapper`, collapsing to `place-self:center` is render-identical ONLY when the
|
|
2445
|
+
// child's NEW parent is a statically-known GRID that lets the wrapper fill its area (there both halves
|
|
2446
|
+
// of place-self take effect). Under that ONE context the flatten is `provably-safe` and commits; under
|
|
2447
|
+
// a flex/block/unknown parent — or when the wrapper drops any own style — it stays `needs-verification`
|
|
2448
|
+
// and the conservative production gate PRESERVES it. Op-level correctness is asserted by the invariant suite.
|
|
2449
|
+
test: {
|
|
2450
|
+
cases: [
|
|
2451
|
+
{
|
|
2452
|
+
name: "grid parent \u2192 flattened (child gains place-self-center)",
|
|
2453
|
+
before: '<div className="grid"><div className="grid items-center justify-center"><span className="bg-red-200">x</span></div></div>',
|
|
2454
|
+
after: '<div className="grid"><span className="bg-red-200 place-self-center">x</span></div>'
|
|
2455
|
+
}
|
|
2456
|
+
],
|
|
2457
|
+
noMatch: [
|
|
2458
|
+
// Non-grid (document-root) parent: justify-self is ignored outside a grid → not provably safe.
|
|
2459
|
+
'<div className="grid justify-center items-center"><div className="bg-red-200">Hello</div></div>',
|
|
2460
|
+
// Grid parent, but the wrapper drops padding when removed → not layout-neutral, preserved.
|
|
2461
|
+
'<div className="grid"><div className="p-4 grid items-center justify-center"><span className="bg-red-200">x</span></div></div>',
|
|
2462
|
+
// onClick is a hard opacity barrier → the wrapper is load-bearing regardless of the gate.
|
|
2463
|
+
'<div className="grid justify-center items-center" onClick={handleClick}><div className="bg-red-200">Hello</div></div>'
|
|
2464
|
+
]
|
|
2465
|
+
}
|
|
2466
|
+
});
|
|
2467
|
+
|
|
2468
|
+
// ../patterns/src/library/wrapper/display-contents-wrapper.pattern.ts
|
|
2008
2469
|
function asEl(node) {
|
|
2009
2470
|
const n = node;
|
|
2010
2471
|
return n.kind === "element" ? n : null;
|
|
@@ -2028,7 +2489,7 @@ var targetedByStructuralPseudo = (node, ctx) => {
|
|
|
2028
2489
|
};
|
|
2029
2490
|
var displayContentsWrapper = definePattern({
|
|
2030
2491
|
name: "display-contents-wrapper",
|
|
2031
|
-
category: "flatten/display-contents-wrapper",
|
|
2492
|
+
category: "flatten/wrapper/display-contents-wrapper",
|
|
2032
2493
|
safety: 2,
|
|
2033
2494
|
doc: {
|
|
2034
2495
|
title: "Flatten display:contents wrapper",
|
|
@@ -2068,7 +2529,7 @@ var displayContentsWrapper = definePattern({
|
|
|
2068
2529
|
}
|
|
2069
2530
|
});
|
|
2070
2531
|
|
|
2071
|
-
// ../patterns/src/library/
|
|
2532
|
+
// ../patterns/src/library/wrapper/empty-style-div.pattern.ts
|
|
2072
2533
|
function asEl2(node) {
|
|
2073
2534
|
const n = node;
|
|
2074
2535
|
return n.kind === "element" ? n : null;
|
|
@@ -2100,7 +2561,7 @@ var hasNonBlockDisplay = (node, ctx) => {
|
|
|
2100
2561
|
};
|
|
2101
2562
|
var emptyStyleDiv = definePattern({
|
|
2102
2563
|
name: "empty-style-div",
|
|
2103
|
-
category: "flatten/empty-style-div",
|
|
2564
|
+
category: "flatten/wrapper/empty-style-div",
|
|
2104
2565
|
safety: 1,
|
|
2105
2566
|
doc: {
|
|
2106
2567
|
title: "Flatten empty-style div wrapper",
|
|
@@ -2139,1595 +2600,229 @@ var emptyStyleDiv = definePattern({
|
|
|
2139
2600
|
}
|
|
2140
2601
|
});
|
|
2141
2602
|
|
|
2142
|
-
// ../patterns/src/library/
|
|
2143
|
-
var
|
|
2144
|
-
|
|
2145
|
-
|
|
2603
|
+
// ../patterns/src/library/wrapper/inherited-only-wrapper.pattern.ts
|
|
2604
|
+
var INERT_HOST_TAGS = /* @__PURE__ */ new Set(["div", "span"]);
|
|
2605
|
+
var isInertHostTag = (node) => {
|
|
2606
|
+
const n = node;
|
|
2607
|
+
if (n.kind !== "element") return false;
|
|
2608
|
+
return INERT_HOST_TAGS.has(String(n.tag).toLowerCase());
|
|
2609
|
+
};
|
|
2610
|
+
var isComponentNode2 = (node) => {
|
|
2611
|
+
const n = node;
|
|
2612
|
+
return n.kind === "element" ? n.meta.isComponent : false;
|
|
2613
|
+
};
|
|
2614
|
+
var hasOnlyInheritedStyle = (node, ctx) => {
|
|
2615
|
+
const sm = normalizer.normalizeStyleMap(ctx.computed());
|
|
2616
|
+
let sawAny = false;
|
|
2617
|
+
for (const block of sm.blocks.values()) {
|
|
2618
|
+
for (const decl of block.decls.values()) {
|
|
2619
|
+
sawAny = true;
|
|
2620
|
+
const inherited = decl.inherited || normalizer.inherited.isInherited(decl.property);
|
|
2621
|
+
if (!inherited) return false;
|
|
2622
|
+
}
|
|
2623
|
+
}
|
|
2624
|
+
return sawAny;
|
|
2625
|
+
};
|
|
2626
|
+
var inheritedOnlyWrapper = definePattern({
|
|
2627
|
+
name: "inherited-only-wrapper",
|
|
2628
|
+
category: "flatten/wrapper/inherited-only-wrapper",
|
|
2146
2629
|
safety: 2,
|
|
2147
2630
|
doc: {
|
|
2148
|
-
title: "Flatten
|
|
2149
|
-
summary: "A
|
|
2150
|
-
before: '<div style="
|
|
2151
|
-
after: '<Child style="
|
|
2152
|
-
safetyRationale: "
|
|
2631
|
+
title: "Flatten inherited-only styling wrapper",
|
|
2632
|
+
summary: "A paint-free wrapper whose only own declarations are inherited properties (text-align, color, font-*, \u2026) is removed; its inherited style is folded onto the sole child, which keeps the same inherited values for the whole subtree.",
|
|
2633
|
+
before: '<div style="text-align:center"><Child/></div>',
|
|
2634
|
+
after: '<Child style="text-align:center"/>',
|
|
2635
|
+
safetyRationale: "Inherited properties reach descendants purely through inheritance, so folding them onto the child and removing the box is render-identical. The wrapper carries nothing non-inherited, establishes no box/formatting/stacking context, and is guarded by the auto-applied opacity-barrier + selector-safety set."
|
|
2153
2636
|
},
|
|
2154
2637
|
match: {
|
|
2155
|
-
tag: "div",
|
|
2156
|
-
style: { display: "flex", alignItems: "center", justifyContent: "center" },
|
|
2157
2638
|
onlyChild: "element",
|
|
2158
|
-
paintsNothing: true
|
|
2159
|
-
|
|
2160
|
-
rewrite: {
|
|
2161
|
-
flattenInto: "child",
|
|
2162
|
-
childGains: { placeSelf: "center" }
|
|
2639
|
+
paintsNothing: true,
|
|
2640
|
+
where: [isInertHostTag, not(isComponentNode2), hasOnlyInheritedStyle]
|
|
2163
2641
|
},
|
|
2164
|
-
|
|
2165
|
-
// the child's NEW parent is flex/grid; moreover the wrapper's own `display:flex` establishes a
|
|
2166
|
-
// formatting context. Both make this a `needs-verification` flatten, which the conservative
|
|
2167
|
-
// production gate (`'provably-safe'`, used by the harness) intentionally REVERTS — so every case
|
|
2168
|
-
// here is a no-match: the wrapper is preserved. Op-level rewrite correctness (purity, id-preserving
|
|
2169
|
-
// unwrap, opacity-barrier safety) is still asserted by the invariant suite over every pattern.
|
|
2642
|
+
rewrite: { flattenInto: "child" },
|
|
2170
2643
|
test: {
|
|
2644
|
+
cases: [
|
|
2645
|
+
{
|
|
2646
|
+
// `text-align:center` is inherited → folded onto the child; the paint-free wrapper is removed.
|
|
2647
|
+
before: '<div className="text-center"><p className="bg-red-200">x</p></div>',
|
|
2648
|
+
after: '<p className="bg-red-200 text-center">x</p>'
|
|
2649
|
+
}
|
|
2650
|
+
],
|
|
2171
2651
|
noMatch: [
|
|
2172
|
-
//
|
|
2173
|
-
//
|
|
2174
|
-
'<div className="
|
|
2175
|
-
//
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
'<div className="flex justify-center items-center" onClick={handleClick}><div className="bg-red-200">Hello</div></div>'
|
|
2652
|
+
// `p-4` is a NON-inherited padding: removing the box would drop it, so the flatten-safety gate
|
|
2653
|
+
// reverts the unwrap and the wrapper is left unchanged.
|
|
2654
|
+
'<div className="p-4"><p className="bg-red-200">x</p></div>',
|
|
2655
|
+
// A `<p>` wrapper is NOT an inert host box: its UA default display/margins are not captured in the
|
|
2656
|
+
// class-derived computed style, so removing it is not provably layout-neutral → left unchanged.
|
|
2657
|
+
'<p className="text-center"><span className="bg-red-200">x</span></p>'
|
|
2179
2658
|
]
|
|
2180
2659
|
}
|
|
2181
2660
|
});
|
|
2182
2661
|
|
|
2183
|
-
// ../patterns/src/library/
|
|
2184
|
-
var inlineFlexCenterWrapper = definePattern({
|
|
2185
|
-
name: "inline-flex-center-wrapper",
|
|
2186
|
-
category: "flatten/inline-flex-center-wrapper",
|
|
2187
|
-
safety: 2,
|
|
2188
|
-
doc: {
|
|
2189
|
-
title: "Flatten inline-flex-centering wrapper",
|
|
2190
|
-
summary: "A div that only centers a single child (display:inline-flex; align-items:center; justify-content:center) is removed; the child gains place-self:center.",
|
|
2191
|
-
before: '<div style="display:inline-flex;align-items:center;justify-content:center"><Child/></div>',
|
|
2192
|
-
after: '<Child style="place-self:center"/>',
|
|
2193
|
-
safetyRationale: "Wrapper paints nothing, carries no ref/handlers/dynamic children, and is not a combinator subject; inheritable styles are folded onto the child before removal."
|
|
2194
|
-
},
|
|
2195
|
-
match: {
|
|
2196
|
-
tag: "div",
|
|
2197
|
-
style: { display: "inline-flex", alignItems: "center", justifyContent: "center" },
|
|
2198
|
-
onlyChild: "element",
|
|
2199
|
-
paintsNothing: true
|
|
2200
|
-
},
|
|
2201
|
-
rewrite: {
|
|
2202
|
-
flattenInto: "child",
|
|
2203
|
-
childGains: { placeSelf: "center" }
|
|
2204
|
-
},
|
|
2205
|
-
// Like its block-level sibling, this centering flatten is `needs-verification` (the wrapper's own
|
|
2206
|
-
// `display:inline-flex` establishes a formatting context, and place-self centering only holds under
|
|
2207
|
-
// a flex/grid parent), so the conservative production gate (`'provably-safe'`) REVERTS it — every
|
|
2208
|
-
// case here is a no-match. Op-level correctness is covered by the invariant suite.
|
|
2209
|
-
test: {
|
|
2210
|
-
noMatch: [
|
|
2211
|
-
// Even under a static flex/grid parent the centering flatten is not provably layout-neutral.
|
|
2212
|
-
'<div className="grid"><div className="inline-flex items-center justify-center"><span className="bg-red-200">x</span></div></div>',
|
|
2213
|
-
// Non-flex/grid parent (document root) → left unchanged.
|
|
2214
|
-
'<div className="inline-flex justify-center items-center"><div className="bg-red-200">Hello</div></div>',
|
|
2215
|
-
// onClick is a hard opacity barrier → the wrapper is load-bearing regardless of the gate.
|
|
2216
|
-
'<div className="inline-flex justify-center items-center" onClick={handleClick}><div className="bg-red-200">Hello</div></div>'
|
|
2217
|
-
]
|
|
2218
|
-
}
|
|
2219
|
-
});
|
|
2220
|
-
|
|
2221
|
-
// ../patterns/src/library/flatten/nested-flex-merge.pattern.ts
|
|
2222
|
-
function baseConditionStyleMap(decls) {
|
|
2223
|
-
const map = /* @__PURE__ */ new Map();
|
|
2224
|
-
for (const [prop, value] of decls) {
|
|
2225
|
-
for (const decl of normalizer.normalizeDeclaration(prop, value, false)) {
|
|
2226
|
-
map.set(decl.property, decl);
|
|
2227
|
-
}
|
|
2228
|
-
}
|
|
2229
|
-
const block = { condition: BASE_CONDITION, decls: map };
|
|
2230
|
-
const blocks = /* @__PURE__ */ new Map([[conditionKey(BASE_CONDITION), block]]);
|
|
2231
|
-
return { blocks };
|
|
2232
|
-
}
|
|
2233
|
-
var DISPLAY_FLEX = baseConditionStyleMap([["display", "flex"]]);
|
|
2234
|
-
var FLEX_CONTAINER_PROPERTIES = /* @__PURE__ */ new Set([
|
|
2235
|
-
"display",
|
|
2236
|
-
"flex-direction",
|
|
2237
|
-
"flex-wrap",
|
|
2238
|
-
"justify-content",
|
|
2239
|
-
"align-items",
|
|
2240
|
-
"align-content",
|
|
2241
|
-
"place-content",
|
|
2242
|
-
"place-items",
|
|
2243
|
-
"row-gap",
|
|
2244
|
-
"column-gap"
|
|
2245
|
-
]);
|
|
2246
|
-
function outerMergeSafe(sm) {
|
|
2247
|
-
const norm = normalizer.normalizeStyleMap(sm);
|
|
2248
|
-
for (const block of norm.blocks.values()) {
|
|
2249
|
-
for (const decl of block.decls.values()) {
|
|
2250
|
-
if (FLEX_CONTAINER_PROPERTIES.has(String(decl.property))) continue;
|
|
2251
|
-
if (decl.inherited) continue;
|
|
2252
|
-
return false;
|
|
2253
|
-
}
|
|
2254
|
-
}
|
|
2255
|
-
return true;
|
|
2256
|
-
}
|
|
2257
|
-
function flexConflict(outer, inner) {
|
|
2258
|
-
const a = normalizer.normalizeStyleMap(outer);
|
|
2259
|
-
const b = normalizer.normalizeStyleMap(inner);
|
|
2260
|
-
for (const [key, blockA] of a.blocks) {
|
|
2261
|
-
const blockB = b.blocks.get(key);
|
|
2262
|
-
if (!blockB) continue;
|
|
2263
|
-
for (const [prop, declA] of blockA.decls) {
|
|
2264
|
-
if (!FLEX_CONTAINER_PROPERTIES.has(String(prop))) continue;
|
|
2265
|
-
const declB = blockB.decls.get(prop);
|
|
2266
|
-
if (declB && declB.value !== declA.value) return true;
|
|
2267
|
-
}
|
|
2268
|
-
}
|
|
2269
|
-
return false;
|
|
2270
|
-
}
|
|
2271
|
-
function extractFlexStyle(sm) {
|
|
2272
|
-
const blocks = /* @__PURE__ */ new Map();
|
|
2273
|
-
for (const [key, block] of sm.blocks) {
|
|
2274
|
-
const decls = /* @__PURE__ */ new Map();
|
|
2275
|
-
for (const [prop, decl] of block.decls) {
|
|
2276
|
-
if (FLEX_CONTAINER_PROPERTIES.has(String(prop))) decls.set(prop, decl);
|
|
2277
|
-
}
|
|
2278
|
-
if (decls.size > 0) blocks.set(key, { condition: block.condition, decls });
|
|
2279
|
-
}
|
|
2280
|
-
return { blocks };
|
|
2281
|
-
}
|
|
2282
|
-
var isInnerFlex = and(
|
|
2283
|
-
isElement("div"),
|
|
2284
|
-
computed(DISPLAY_FLEX),
|
|
2285
|
-
not(targetedByCombinator)
|
|
2286
|
-
);
|
|
2287
|
-
var nestedFlexMerge = definePattern({
|
|
2288
|
-
name: "nested-flex-merge",
|
|
2289
|
-
category: "flatten/nested-flex-merge",
|
|
2290
|
-
safety: 2,
|
|
2291
|
-
doc: {
|
|
2292
|
-
title: "Merge nested flex containers",
|
|
2293
|
-
summary: "A flex container whose only child is itself a flex container with non-conflicting flex properties is collapsed into one; the wrapper is removed and its flex declarations merge onto the surviving child.",
|
|
2294
|
-
before: '<div style="display:flex;align-items:center;gap:8px"><div style="display:flex;flex-direction:column"/></div>',
|
|
2295
|
-
after: '<div style="display:flex;flex-direction:column;align-items:center;gap:8px"/>',
|
|
2296
|
-
safetyRationale: "The wrapper paints nothing, declares only flex-container/inheritable properties, carries no ref/handlers/dynamic children, and is not a combinator subject; the two containers do not conflict on any flex property, so the union is unambiguous and lossless."
|
|
2297
|
-
},
|
|
2298
|
-
match: {
|
|
2299
|
-
tag: "div",
|
|
2300
|
-
style: { display: "flex" },
|
|
2301
|
-
onlyChild: "element",
|
|
2302
|
-
paintsNothing: true
|
|
2303
|
-
},
|
|
2304
|
-
rewrite: (ctx, rw) => {
|
|
2305
|
-
const outer = ctx.node;
|
|
2306
|
-
const inner = ctx.onlyElementChild();
|
|
2307
|
-
if (!inner) return null;
|
|
2308
|
-
if (!isInnerFlex(inner, ctx)) return null;
|
|
2309
|
-
const outerStyle = ctx.computed();
|
|
2310
|
-
const innerStyle = ctx.computedOf(inner);
|
|
2311
|
-
if (!outerMergeSafe(outerStyle)) return null;
|
|
2312
|
-
if (flexConflict(outerStyle, innerStyle)) return null;
|
|
2313
|
-
return [
|
|
2314
|
-
// 1. Preserve inheritable values (color/font/…) by folding them onto the child first.
|
|
2315
|
-
rw.foldInheritedStyles(outer, inner, { conditions: "all" }),
|
|
2316
|
-
// 2. Transfer the wrapper's flex-container declarations onto the child (target-wins keeps the
|
|
2317
|
-
// child's value for any shared property — identical anyway, we proved non-conflict).
|
|
2318
|
-
rw.mergeStyle(inner, null, extractFlexStyle(outerStyle), "target-wins"),
|
|
2319
|
-
// 3. Remove the wrapper (structural-safe; hoists the child and preserves its IRNodeId).
|
|
2320
|
-
rw.unwrap(outer)
|
|
2321
|
-
];
|
|
2322
|
-
},
|
|
2323
|
-
// Merging the outer flex container into the inner removes the outer's box, but a `display:flex`
|
|
2324
|
-
// wrapper establishes a formatting context, so this is a `needs-verification` flatten that the
|
|
2325
|
-
// conservative production gate (`'provably-safe'`) REVERTS — every case here is a no-match. The
|
|
2326
|
-
// merge's op-level correctness (purity, id-preserving unwrap, opacity-barrier safety) is asserted
|
|
2327
|
-
// by the invariant suite over every pattern.
|
|
2328
|
-
test: {
|
|
2329
|
-
noMatch: [
|
|
2330
|
-
// The merge is real but not provably layout-neutral (the wrapper establishes a flex context),
|
|
2331
|
-
// so under the conservative gate the nested containers are left in place.
|
|
2332
|
-
'<div className="flex items-center gap-2" data-x="1"><div className="flex flex-col">X</div></div>',
|
|
2333
|
-
// A non-flex wrapper does not match the flex-container signature → left unchanged anyway.
|
|
2334
|
-
'<div className="block bg-blue-500"><div className="flex flex-col">X</div></div>'
|
|
2335
|
-
]
|
|
2336
|
-
}
|
|
2337
|
-
});
|
|
2338
|
-
|
|
2339
|
-
// ../patterns/src/library/flatten/nested-grid-merge.pattern.ts
|
|
2340
|
-
function baseConditionStyleMap2(decls) {
|
|
2341
|
-
const map = /* @__PURE__ */ new Map();
|
|
2342
|
-
for (const [prop, value] of decls) {
|
|
2343
|
-
for (const decl of normalizer.normalizeDeclaration(prop, value, false)) {
|
|
2344
|
-
map.set(decl.property, decl);
|
|
2345
|
-
}
|
|
2346
|
-
}
|
|
2347
|
-
const block = { condition: BASE_CONDITION, decls: map };
|
|
2348
|
-
const blocks = /* @__PURE__ */ new Map([[conditionKey(BASE_CONDITION), block]]);
|
|
2349
|
-
return { blocks };
|
|
2350
|
-
}
|
|
2351
|
-
var DISPLAY_GRID = baseConditionStyleMap2([["display", "grid"]]);
|
|
2352
|
-
var GRID_CONTAINER_PROPERTIES = /* @__PURE__ */ new Set([
|
|
2353
|
-
"display",
|
|
2354
|
-
"grid-template-columns",
|
|
2355
|
-
"grid-template-rows",
|
|
2356
|
-
"grid-template-areas",
|
|
2357
|
-
"grid-auto-columns",
|
|
2358
|
-
"grid-auto-rows",
|
|
2359
|
-
"grid-auto-flow",
|
|
2360
|
-
"justify-content",
|
|
2361
|
-
"align-content",
|
|
2362
|
-
"place-content",
|
|
2363
|
-
"justify-items",
|
|
2364
|
-
"align-items",
|
|
2365
|
-
"place-items",
|
|
2366
|
-
"row-gap",
|
|
2367
|
-
"column-gap"
|
|
2368
|
-
]);
|
|
2369
|
-
function outerMergeSafe2(sm) {
|
|
2370
|
-
const norm = normalizer.normalizeStyleMap(sm);
|
|
2371
|
-
for (const block of norm.blocks.values()) {
|
|
2372
|
-
for (const decl of block.decls.values()) {
|
|
2373
|
-
if (GRID_CONTAINER_PROPERTIES.has(String(decl.property))) continue;
|
|
2374
|
-
if (decl.inherited) continue;
|
|
2375
|
-
return false;
|
|
2376
|
-
}
|
|
2377
|
-
}
|
|
2378
|
-
return true;
|
|
2379
|
-
}
|
|
2380
|
-
function gridConflict(outer, inner) {
|
|
2381
|
-
const a = normalizer.normalizeStyleMap(outer);
|
|
2382
|
-
const b = normalizer.normalizeStyleMap(inner);
|
|
2383
|
-
for (const [key, blockA] of a.blocks) {
|
|
2384
|
-
const blockB = b.blocks.get(key);
|
|
2385
|
-
if (!blockB) continue;
|
|
2386
|
-
for (const [prop, declA] of blockA.decls) {
|
|
2387
|
-
if (!GRID_CONTAINER_PROPERTIES.has(String(prop))) continue;
|
|
2388
|
-
const declB = blockB.decls.get(prop);
|
|
2389
|
-
if (declB && declB.value !== declA.value) return true;
|
|
2390
|
-
}
|
|
2391
|
-
}
|
|
2392
|
-
return false;
|
|
2393
|
-
}
|
|
2394
|
-
function extractGridStyle(sm) {
|
|
2395
|
-
const blocks = /* @__PURE__ */ new Map();
|
|
2396
|
-
for (const [key, block] of sm.blocks) {
|
|
2397
|
-
const decls = /* @__PURE__ */ new Map();
|
|
2398
|
-
for (const [prop, decl] of block.decls) {
|
|
2399
|
-
if (GRID_CONTAINER_PROPERTIES.has(String(prop))) decls.set(prop, decl);
|
|
2400
|
-
}
|
|
2401
|
-
if (decls.size > 0) blocks.set(key, { condition: block.condition, decls });
|
|
2402
|
-
}
|
|
2403
|
-
return { blocks };
|
|
2404
|
-
}
|
|
2405
|
-
var isInnerGrid = and(
|
|
2406
|
-
isElement("div"),
|
|
2407
|
-
computed(DISPLAY_GRID),
|
|
2408
|
-
not(targetedByCombinator)
|
|
2409
|
-
);
|
|
2410
|
-
var nestedGridMerge = definePattern({
|
|
2411
|
-
name: "nested-grid-merge",
|
|
2412
|
-
category: "flatten/nested-grid-merge",
|
|
2413
|
-
safety: 2,
|
|
2414
|
-
doc: {
|
|
2415
|
-
title: "Merge nested grid containers",
|
|
2416
|
-
summary: "A grid container whose only child is itself a grid container with non-conflicting grid properties is collapsed into one; the wrapper is removed and its grid declarations merge onto the surviving child.",
|
|
2417
|
-
before: '<div style="display:grid;gap:8px"><div style="display:grid;grid-template-columns:1fr 1fr"/></div>',
|
|
2418
|
-
after: '<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px"/>',
|
|
2419
|
-
safetyRationale: "The wrapper paints nothing, declares only grid-container/inheritable properties, carries no ref/handlers/dynamic children, and is not a combinator subject; the two containers do not conflict on any grid property, so the union is unambiguous and lossless."
|
|
2420
|
-
},
|
|
2421
|
-
match: {
|
|
2422
|
-
tag: "div",
|
|
2423
|
-
style: { display: "grid" },
|
|
2424
|
-
onlyChild: "element",
|
|
2425
|
-
paintsNothing: true
|
|
2426
|
-
},
|
|
2427
|
-
rewrite: (ctx, rw) => {
|
|
2428
|
-
const outer = ctx.node;
|
|
2429
|
-
const inner = ctx.onlyElementChild();
|
|
2430
|
-
if (!inner) return null;
|
|
2431
|
-
if (!isInnerGrid(inner, ctx)) return null;
|
|
2432
|
-
const outerStyle = ctx.computed();
|
|
2433
|
-
const innerStyle = ctx.computedOf(inner);
|
|
2434
|
-
if (!outerMergeSafe2(outerStyle)) return null;
|
|
2435
|
-
if (gridConflict(outerStyle, innerStyle)) return null;
|
|
2436
|
-
return [
|
|
2437
|
-
// 1. Preserve inheritable values (color/font/…) by folding them onto the child first.
|
|
2438
|
-
rw.foldInheritedStyles(outer, inner, { conditions: "all" }),
|
|
2439
|
-
// 2. Transfer the wrapper's grid-container declarations onto the child (target-wins keeps the
|
|
2440
|
-
// child's value for any shared property — identical anyway, we proved non-conflict).
|
|
2441
|
-
rw.mergeStyle(inner, null, extractGridStyle(outerStyle), "target-wins"),
|
|
2442
|
-
// 3. Remove the wrapper (structural-safe; hoists the child and preserves its IRNodeId).
|
|
2443
|
-
rw.unwrap(outer)
|
|
2444
|
-
];
|
|
2445
|
-
},
|
|
2446
|
-
// Like its flex sibling, this merge removes the outer container's box, but a `display:grid` wrapper
|
|
2447
|
-
// establishes a formatting context, so it is a `needs-verification` flatten that the conservative
|
|
2448
|
-
// production gate (`'provably-safe'`) REVERTS — every case here is a no-match. Op-level correctness
|
|
2449
|
-
// is asserted by the invariant suite over every pattern.
|
|
2450
|
-
test: {
|
|
2451
|
-
noMatch: [
|
|
2452
|
-
// The merge is real but not provably layout-neutral (the wrapper establishes a grid context),
|
|
2453
|
-
// so under the conservative gate the nested containers are left in place.
|
|
2454
|
-
'<div className="grid gap-2" data-x="1"><div className="grid grid-cols-2">X</div></div>',
|
|
2455
|
-
// A non-grid wrapper does not match the grid-container signature → left unchanged anyway.
|
|
2456
|
-
'<div className="block bg-blue-500"><div className="grid grid-cols-2">X</div></div>'
|
|
2457
|
-
]
|
|
2458
|
-
}
|
|
2459
|
-
});
|
|
2460
|
-
|
|
2461
|
-
// ../patterns/src/library/flatten/passthrough-wrapper.pattern.ts
|
|
2662
|
+
// ../patterns/src/library/wrapper/passthrough-wrapper.pattern.ts
|
|
2462
2663
|
function metaOf2(node) {
|
|
2463
2664
|
const n = node;
|
|
2464
2665
|
return n.kind === "element" ? n.meta : null;
|
|
2465
2666
|
}
|
|
2466
|
-
function elementOf(node) {
|
|
2467
|
-
const n = node;
|
|
2468
|
-
return n.kind === "element" ? n : null;
|
|
2469
|
-
}
|
|
2470
|
-
var establishesContext = (node) => {
|
|
2471
|
-
const m = metaOf2(node);
|
|
2472
|
-
if (!m) return false;
|
|
2473
|
-
return m.establishesBox || m.establishesFormattingContext || m.establishesStackingContext || m.isContainingBlock || m.declaresCustomProperties;
|
|
2474
|
-
};
|
|
2475
|
-
var hasSpreadAttrs2 = (node) => metaOf2(node)?.hasSpreadAttrs ?? false;
|
|
2476
|
-
var isComponentNode2 = (node) => metaOf2(node)?.isComponent ?? false;
|
|
2477
|
-
var hasOwnAttrs2 = (node) => {
|
|
2478
|
-
const el = elementOf(node);
|
|
2479
|
-
if (!el) return false;
|
|
2480
|
-
return el.attrs.entries.size > 0 || el.attrs.spreads.length > 0;
|
|
2481
|
-
};
|
|
2482
|
-
var targetedByStructuralPseudo3 = (node, ctx) => {
|
|
2483
|
-
const el = elementOf(node);
|
|
2484
|
-
if (!el) return false;
|
|
2485
|
-
if (el.meta.targetedByStructuralPseudo) return true;
|
|
2486
|
-
return ctx.selectors.targetedByStructuralPseudo(el.id);
|
|
2487
|
-
};
|
|
2488
|
-
var passthroughWrapper = definePattern({
|
|
2489
|
-
name: "passthrough-wrapper",
|
|
2490
|
-
category: "flatten/passthrough-wrapper",
|
|
2491
|
-
safety: 2,
|
|
2492
|
-
doc: {
|
|
2493
|
-
title: "Flatten passthrough wrapper",
|
|
2494
|
-
summary: "A div with no own visual/box style, no attributes beyond an inert class, exactly one element child, and no opacity barriers is removed; its sole child is hoisted in its place.",
|
|
2495
|
-
before: "<div><Child/></div>",
|
|
2496
|
-
after: "<Child/>",
|
|
2497
|
-
safetyRationale: "Wrapper paints nothing and establishes no layout/paint/var context, carries no ref/handlers/dynamic-children/html/spread/component identity, owns no targetable attrs, and is not a combinator/structural-pseudo subject (reparenting changes no match-set); inheritable styles are folded onto the child before removal."
|
|
2498
|
-
},
|
|
2499
|
-
match: {
|
|
2500
|
-
tag: "div",
|
|
2501
|
-
onlyChild: "element",
|
|
2502
|
-
paintsNothing: true,
|
|
2503
|
-
where: [
|
|
2504
|
-
not(establishesContext),
|
|
2505
|
-
not(hasOwnAttrs2),
|
|
2506
|
-
not(hasDynamicClasses),
|
|
2507
|
-
not(hasSpreadAttrs2),
|
|
2508
|
-
not(isComponentNode2),
|
|
2509
|
-
not(targetedByStructuralPseudo3)
|
|
2510
|
-
]
|
|
2511
|
-
},
|
|
2512
|
-
rewrite: { flattenInto: "child" },
|
|
2513
|
-
test: {
|
|
2514
|
-
cases: [
|
|
2515
|
-
{
|
|
2516
|
-
// A plain, style-free wrapper paints nothing and establishes no context → a provably-safe
|
|
2517
|
-
// flatten under the conservative gate: the wrapper is removed and its sole child hoisted.
|
|
2518
|
-
before: '<div><a className="bg-red-200">Link</a></div>',
|
|
2519
|
-
after: '<a className="bg-red-200">Link</a>'
|
|
2520
|
-
}
|
|
2521
|
-
],
|
|
2522
|
-
noMatch: [
|
|
2523
|
-
// A ref pins the wrapper's element identity (a hard opacity barrier) → not a passthrough.
|
|
2524
|
-
'<div ref={rootRef}><a className="bg-red-200">Link</a></div>',
|
|
2525
|
-
// A `display:flex` wrapper establishes a formatting context, so removing its box is NOT
|
|
2526
|
-
// provably layout-neutral → the conservative gate leaves it in place.
|
|
2527
|
-
'<div className="flex"><a className="bg-red-200">Link</a></div>'
|
|
2528
|
-
]
|
|
2529
|
-
}
|
|
2530
|
-
});
|
|
2531
|
-
|
|
2532
|
-
// ../patterns/src/library/flatten/redundant-fragment.pattern.ts
|
|
2533
|
-
function parentIsRedundantFragment(node, ctx) {
|
|
2534
|
-
const el = node;
|
|
2535
|
-
if (el.kind !== "element") return false;
|
|
2536
|
-
const parentId = el.parent;
|
|
2537
|
-
if (parentId == null) return false;
|
|
2538
|
-
const parent = ctx.doc.nodes.get(parentId);
|
|
2539
|
-
if (!parent || parent.kind !== "fragment") return false;
|
|
2540
|
-
if (parent.parent == null) return false;
|
|
2541
|
-
if (parent.children.length !== 1) return false;
|
|
2542
|
-
const m = parent.meta;
|
|
2543
|
-
if (m.hasKey || m.hasRef || m.hasEventHandlers || m.hasDynamicChildren || m.hasDangerousHtml || m.hasSpreadAttrs || m.isComponent) {
|
|
2544
|
-
return false;
|
|
2545
|
-
}
|
|
2546
|
-
if (m.targetedByCombinator || m.targetedByStructuralPseudo) return false;
|
|
2547
|
-
const fid = parentId;
|
|
2548
|
-
if (ctx.selectors.targetedByCombinator(fid) || ctx.selectors.targetedByStructuralPseudo(fid)) {
|
|
2549
|
-
return false;
|
|
2550
|
-
}
|
|
2551
|
-
if (ctx.selectors.reparentImpact(fid).size > 0) return false;
|
|
2552
|
-
return true;
|
|
2553
|
-
}
|
|
2554
|
-
var redundantFragment = definePattern({
|
|
2555
|
-
name: "redundant-fragment",
|
|
2556
|
-
category: "flatten/redundant-fragment",
|
|
2557
|
-
safety: 1,
|
|
2558
|
-
doc: {
|
|
2559
|
-
title: "Flatten redundant single-child fragment",
|
|
2560
|
-
summary: "A fragment whose only child is a single node is removed; the child is spliced up into the fragment's slot, preserving its IRNodeId, siblings, attributes and the CSS cascade.",
|
|
2561
|
-
before: "<><Child/></>",
|
|
2562
|
-
after: "<Child/>",
|
|
2563
|
-
safetyRationale: "A fragment paints nothing and renders no box; with exactly one child its removal changes no sibling/structural-pseudo match-set. Keyed fragments and fragments carrying ref/handlers/dynamic-children/raw-html/spread are excluded as opacity barriers."
|
|
2564
|
-
},
|
|
2565
|
-
match: parentIsRedundantFragment,
|
|
2566
|
-
rewrite: (ctx, rw) => {
|
|
2567
|
-
const parentId = ctx.node.parent;
|
|
2568
|
-
if (parentId == null) return null;
|
|
2569
|
-
const fragment = ctx.doc.nodes.get(parentId);
|
|
2570
|
-
if (!fragment || fragment.kind !== "fragment") return null;
|
|
2571
|
-
return [rw.unwrap(fragment)];
|
|
2572
|
-
},
|
|
2573
|
-
test: {
|
|
2574
|
-
cases: [
|
|
2575
|
-
{
|
|
2576
|
-
// A fragment renders no box, so unwrapping a single-child fragment is always layout-identical
|
|
2577
|
-
// → a provably-safe flatten: the child is spliced up into the fragment's slot.
|
|
2578
|
-
before: '<><span className="bg-red-200">Hi</span></>',
|
|
2579
|
-
after: '<span className="bg-red-200">Hi</span>'
|
|
2580
|
-
}
|
|
2581
|
-
],
|
|
2582
|
-
noMatch: [
|
|
2583
|
-
// Two children ⇒ not a single-child fragment, so the fragment is load-bearing and stays.
|
|
2584
|
-
'<><span className="bg-red-200">A</span><span className="bg-green-200">B</span></>'
|
|
2585
|
-
]
|
|
2586
|
-
}
|
|
2587
|
-
});
|
|
2588
|
-
|
|
2589
|
-
// ../patterns/src/library/flatten/redundant-inline-wrapper.pattern.ts
|
|
2590
|
-
function asEl3(node) {
|
|
2591
|
-
const n = node;
|
|
2592
|
-
return n.kind === "element" ? n : null;
|
|
2593
|
-
}
|
|
2594
|
-
function metaOf3(node) {
|
|
2595
|
-
return asEl3(node)?.meta ?? null;
|
|
2596
|
-
}
|
|
2597
|
-
var establishesContext2 = (node) => {
|
|
2598
|
-
const m = metaOf3(node);
|
|
2599
|
-
if (!m) return false;
|
|
2600
|
-
return m.establishesBox || m.establishesFormattingContext || m.establishesStackingContext || m.isContainingBlock || m.declaresCustomProperties;
|
|
2601
|
-
};
|
|
2602
|
-
var hasSpreadAttrs3 = (node) => metaOf3(node)?.hasSpreadAttrs ?? false;
|
|
2603
|
-
var isComponentNode3 = (node) => metaOf3(node)?.isComponent ?? false;
|
|
2604
|
-
var hasOwnAttrs3 = (node) => {
|
|
2605
|
-
const el = asEl3(node);
|
|
2606
|
-
if (!el) return false;
|
|
2607
|
-
return el.attrs.entries.size > 0 || el.attrs.spreads.length > 0;
|
|
2608
|
-
};
|
|
2609
|
-
var targetedByStructuralPseudo4 = (node, ctx) => {
|
|
2610
|
-
const el = asEl3(node);
|
|
2611
|
-
if (!el) return false;
|
|
2612
|
-
if (el.meta.targetedByStructuralPseudo) return true;
|
|
2613
|
-
return ctx.selectors.targetedByStructuralPseudo(el.id);
|
|
2614
|
-
};
|
|
2615
|
-
var DISPLAY3 = "display";
|
|
2616
|
-
var hasNonInlineDisplay = (node, ctx) => {
|
|
2617
|
-
const el = asEl3(node);
|
|
2618
|
-
if (!el) return false;
|
|
2619
|
-
const sm = ctx.computedOf(el) ?? el.computed;
|
|
2620
|
-
for (const block of sm.blocks.values()) {
|
|
2621
|
-
const decl = block.decls.get(DISPLAY3);
|
|
2622
|
-
if (decl && String(decl.value) !== "inline") return true;
|
|
2623
|
-
}
|
|
2624
|
-
return false;
|
|
2625
|
-
};
|
|
2626
|
-
var redundantInlineWrapper = definePattern({
|
|
2627
|
-
name: "redundant-inline-wrapper",
|
|
2628
|
-
category: "flatten/redundant-inline-wrapper",
|
|
2629
|
-
safety: 2,
|
|
2630
|
-
doc: {
|
|
2631
|
-
title: "Flatten redundant inline wrapper",
|
|
2632
|
-
summary: "An inline span with no own visual/box style, no attributes beyond an inert class, exactly one element child, and no opacity barriers is removed; its sole child is hoisted in its place.",
|
|
2633
|
-
before: "<span><Child/></span>",
|
|
2634
|
-
after: "<Child/>",
|
|
2635
|
-
safetyRationale: "An empty inline box paints nothing and establishes no layout/paint/var context; with the inline default display and a single element child its removal changes no paint and no flow. The span carries no ref/handlers/dynamic-children/html/spread/component identity, owns no targetable attrs, and is not a combinator/structural-pseudo subject; inheritable styles are folded onto the child before removal."
|
|
2636
|
-
},
|
|
2637
|
-
match: {
|
|
2638
|
-
tag: "span",
|
|
2639
|
-
onlyChild: "element",
|
|
2640
|
-
paintsNothing: true,
|
|
2641
|
-
where: [
|
|
2642
|
-
not(hasNonInlineDisplay),
|
|
2643
|
-
not(establishesContext2),
|
|
2644
|
-
not(hasOwnAttrs3),
|
|
2645
|
-
not(hasDynamicClasses),
|
|
2646
|
-
not(hasSpreadAttrs3),
|
|
2647
|
-
not(isComponentNode3),
|
|
2648
|
-
not(targetedByStructuralPseudo4)
|
|
2649
|
-
]
|
|
2650
|
-
},
|
|
2651
|
-
rewrite: { flattenInto: "child" },
|
|
2652
|
-
test: {
|
|
2653
|
-
cases: [
|
|
2654
|
-
{
|
|
2655
|
-
// An empty inline span paints nothing and establishes no context → a provably-safe flatten:
|
|
2656
|
-
// the span is removed and its sole child hoisted in place.
|
|
2657
|
-
before: '<span><a className="text-blue-500">Link</a></span>',
|
|
2658
|
-
after: '<a className="text-blue-500">Link</a>'
|
|
2659
|
-
}
|
|
2660
|
-
],
|
|
2661
|
-
noMatch: [
|
|
2662
|
-
// A ref pins the span's element identity (a hard opacity barrier) → not a passthrough.
|
|
2663
|
-
'<span ref={spanRef}><a className="text-blue-500">Link</a></span>',
|
|
2664
|
-
// The span paints its own background (own visual style) → kept.
|
|
2665
|
-
'<span className="bg-green-200"><a className="text-blue-500">Link</a></span>',
|
|
2666
|
-
// Non-inline display (inline-block) participates in layout differently → kept.
|
|
2667
|
-
'<span className="inline-block"><a className="text-blue-500">Link</a></span>'
|
|
2668
|
-
]
|
|
2669
|
-
}
|
|
2670
|
-
});
|
|
2671
|
-
|
|
2672
|
-
// ../patterns/src/library/compress/border-radius-shorthand.pattern.ts
|
|
2673
|
-
var CORNERS = [
|
|
2674
|
-
"border-top-left-radius",
|
|
2675
|
-
"border-top-right-radius",
|
|
2676
|
-
"border-bottom-right-radius",
|
|
2677
|
-
"border-bottom-left-radius"
|
|
2678
|
-
];
|
|
2679
|
-
var CORNER_SET = new Set(CORNERS);
|
|
2680
|
-
var BASE_KEY = conditionKey(BASE_CONDITION);
|
|
2681
|
-
var RADIUS = "border-radius";
|
|
2682
|
-
var NON_COLLAPSIBLE_VALUES = /* @__PURE__ */ new Set([
|
|
2683
|
-
"initial",
|
|
2684
|
-
"inherit",
|
|
2685
|
-
"unset",
|
|
2686
|
-
"revert",
|
|
2687
|
-
"revert-layer"
|
|
2688
|
-
]);
|
|
2689
|
-
function analyzeRadius(sm) {
|
|
2690
|
-
const block = sm.blocks.get(BASE_KEY);
|
|
2691
|
-
if (!block) return null;
|
|
2692
|
-
const corners = [];
|
|
2693
|
-
for (const corner of CORNERS) {
|
|
2694
|
-
const decl = block.decls.get(corner);
|
|
2695
|
-
if (!decl) return null;
|
|
2696
|
-
corners.push(decl);
|
|
2697
|
-
}
|
|
2698
|
-
const important = corners[0].important;
|
|
2699
|
-
if (!corners.every((d) => d.important === important)) return null;
|
|
2700
|
-
const value = String(corners[0].value);
|
|
2701
|
-
if (NON_COLLAPSIBLE_VALUES.has(value)) return null;
|
|
2702
|
-
if (!corners.every((d) => String(d.value) === value)) return null;
|
|
2703
|
-
const relative = corners.some((d) => d.relativeToParent);
|
|
2704
|
-
return { value, important, relative };
|
|
2705
|
-
}
|
|
2706
|
-
function withFoldedRadius(sm, fold) {
|
|
2707
|
-
const blocks = /* @__PURE__ */ new Map();
|
|
2708
|
-
for (const [key, block] of sm.blocks) {
|
|
2709
|
-
if (key !== BASE_KEY) {
|
|
2710
|
-
blocks.set(key, block);
|
|
2711
|
-
continue;
|
|
2712
|
-
}
|
|
2713
|
-
const decls = /* @__PURE__ */ new Map();
|
|
2714
|
-
for (const [prop, decl] of block.decls) {
|
|
2715
|
-
if (CORNER_SET.has(String(prop))) continue;
|
|
2716
|
-
decls.set(prop, decl);
|
|
2717
|
-
}
|
|
2718
|
-
const shorthand = {
|
|
2719
|
-
property: RADIUS,
|
|
2720
|
-
value: fold.value,
|
|
2721
|
-
important: fold.important,
|
|
2722
|
-
relativeToParent: fold.relative,
|
|
2723
|
-
inherited: false
|
|
2724
|
-
// border-radius is never inherited
|
|
2725
|
-
};
|
|
2726
|
-
decls.set(shorthand.property, shorthand);
|
|
2727
|
-
blocks.set(key, { condition: block.condition, decls });
|
|
2728
|
-
}
|
|
2729
|
-
return { blocks };
|
|
2730
|
-
}
|
|
2731
|
-
var borderRadiusShorthand = definePattern({
|
|
2732
|
-
name: "border-radius-shorthand",
|
|
2733
|
-
category: "compress/border-radius-shorthand",
|
|
2734
|
-
safety: 1,
|
|
2735
|
-
doc: {
|
|
2736
|
-
title: "Collapse equal corner radii into border-radius",
|
|
2737
|
-
summary: "An element whose four corner radii (border-*-radius longhands) are all equal is rewritten to the single Tailwind rounded-* utility (border-radius === the four equal corners).",
|
|
2738
|
-
before: '<div class="rounded-tl-lg rounded-tr-lg rounded-br-lg rounded-bl-lg"/>',
|
|
2739
|
-
after: '<div class="rounded-lg"/>',
|
|
2740
|
-
safetyRationale: "`border-radius` is value-identical to four equal corner radii \u2014 a class-only change. It is safe even on an element with a ref, event handler, dynamic child, or dangerouslySetInnerHTML \u2014 a className rewrite touches none of them; only a dynamic/opaque class list or a combinator-subject class is excluded, so no behaviour or project selector is disturbed."
|
|
2741
|
-
},
|
|
2742
|
-
rewrite: {
|
|
2743
|
-
rewriteClasses(computed2) {
|
|
2744
|
-
const fold = analyzeRadius(computed2);
|
|
2745
|
-
return fold ? withFoldedRadius(computed2, fold) : null;
|
|
2746
|
-
}
|
|
2747
|
-
},
|
|
2748
|
-
test: {
|
|
2749
|
-
cases: [
|
|
2750
|
-
{
|
|
2751
|
-
// The four equal corner longhands collapse to a `border-radius` decl at the IR level; the
|
|
2752
|
-
// minimizing reverse-emit then picks the single shortest utility (`rounded-lg`) that reproduces
|
|
2753
|
-
// it, replacing the four `rounded-{tl,tr,br,bl}-lg` tokens. `bg-red-200` is preserved.
|
|
2754
|
-
before: '<div className="rounded-tl-lg rounded-tr-lg rounded-br-lg rounded-bl-lg bg-red-200">box</div>',
|
|
2755
|
-
after: '<div className="bg-red-200 rounded-lg">box</div>'
|
|
2756
|
-
}
|
|
2757
|
-
],
|
|
2758
|
-
// Corners differ (top corners vs bottom corners) → no all-equal collapse.
|
|
2759
|
-
noMatch: ['<div className="rounded-t-lg rounded-b-sm bg-red-200">box</div>']
|
|
2760
|
-
}
|
|
2761
|
-
});
|
|
2762
|
-
|
|
2763
|
-
// ../patterns/src/library/compress/border-shorthand.pattern.ts
|
|
2764
|
-
var WIDTH_SIDES = [
|
|
2765
|
-
"border-top-width",
|
|
2766
|
-
"border-right-width",
|
|
2767
|
-
"border-bottom-width",
|
|
2768
|
-
"border-left-width"
|
|
2769
|
-
];
|
|
2770
|
-
var WIDTH_SIDE_SET = new Set(WIDTH_SIDES);
|
|
2771
|
-
var BASE_KEY2 = conditionKey(BASE_CONDITION);
|
|
2772
|
-
var BORDER_WIDTH = "border-width";
|
|
2773
|
-
function analyzeWidth(sm) {
|
|
2774
|
-
const block = sm.blocks.get(BASE_KEY2);
|
|
2775
|
-
if (!block) return null;
|
|
2776
|
-
const sides = [];
|
|
2777
|
-
for (const side of WIDTH_SIDES) {
|
|
2778
|
-
const decl = block.decls.get(side);
|
|
2779
|
-
if (!decl) return null;
|
|
2780
|
-
sides.push(decl);
|
|
2781
|
-
}
|
|
2782
|
-
const [top, right, bottom, left] = sides;
|
|
2783
|
-
if (!(top.important === right.important && right.important === bottom.important && bottom.important === left.important)) {
|
|
2784
|
-
return null;
|
|
2785
|
-
}
|
|
2786
|
-
const tv = String(top.value);
|
|
2787
|
-
const rv = String(right.value);
|
|
2788
|
-
const bv = String(bottom.value);
|
|
2789
|
-
const lv = String(left.value);
|
|
2790
|
-
if (tv !== bv || lv !== rv) return null;
|
|
2791
|
-
const value = tv === lv ? tv : `${tv} ${lv}`;
|
|
2792
|
-
const relative = sides.some((d) => d.relativeToParent);
|
|
2793
|
-
return { value, important: top.important, relative };
|
|
2794
|
-
}
|
|
2795
|
-
function withFoldedWidth(sm, fold) {
|
|
2796
|
-
const blocks = /* @__PURE__ */ new Map();
|
|
2797
|
-
for (const [key, block] of sm.blocks) {
|
|
2798
|
-
if (key !== BASE_KEY2) {
|
|
2799
|
-
blocks.set(key, block);
|
|
2800
|
-
continue;
|
|
2801
|
-
}
|
|
2802
|
-
const decls = /* @__PURE__ */ new Map();
|
|
2803
|
-
for (const [prop, decl] of block.decls) {
|
|
2804
|
-
if (WIDTH_SIDE_SET.has(String(prop))) continue;
|
|
2805
|
-
decls.set(prop, decl);
|
|
2806
|
-
}
|
|
2807
|
-
const shorthand = {
|
|
2808
|
-
property: BORDER_WIDTH,
|
|
2809
|
-
value: fold.value,
|
|
2810
|
-
important: fold.important,
|
|
2811
|
-
relativeToParent: fold.relative,
|
|
2812
|
-
inherited: false
|
|
2813
|
-
// border-width is never inherited
|
|
2814
|
-
};
|
|
2815
|
-
decls.set(shorthand.property, shorthand);
|
|
2816
|
-
blocks.set(key, { condition: block.condition, decls });
|
|
2817
|
-
}
|
|
2818
|
-
return { blocks };
|
|
2819
|
-
}
|
|
2820
|
-
var borderShorthand = definePattern({
|
|
2821
|
-
name: "border-shorthand",
|
|
2822
|
-
category: "compress/border-shorthand",
|
|
2823
|
-
safety: 1,
|
|
2824
|
-
doc: {
|
|
2825
|
-
title: "Collapse border-width longhands to shorthand",
|
|
2826
|
-
summary: "Equal border width on all four sides (or matching x/y pairs) expressed as separate longhand declarations is collapsed to the shortest equivalent border-width shorthand (border-* / border-x-* border-y-*).",
|
|
2827
|
-
before: '<div class="border-t-2 border-r-2 border-b-2 border-l-2"/>',
|
|
2828
|
-
after: '<div class="border-2"/>',
|
|
2829
|
-
safetyRationale: "A value-preserving re-serialization of the same computed border widths (style/color longhands untouched) \u2014 a class-only change. It is safe even on an element with a ref, event handler, dynamic child, or dangerouslySetInnerHTML \u2014 a className rewrite touches none of them; only a dynamic/opaque class list or a combinator-subject class is excluded, so no behaviour or project selector is disturbed."
|
|
2830
|
-
},
|
|
2831
|
-
rewrite: {
|
|
2832
|
-
rewriteClasses(computed2) {
|
|
2833
|
-
const fold = analyzeWidth(computed2);
|
|
2834
|
-
return fold ? withFoldedWidth(computed2, fold) : null;
|
|
2835
|
-
}
|
|
2836
|
-
},
|
|
2837
|
-
test: {
|
|
2838
|
-
cases: [
|
|
2839
|
-
{
|
|
2840
|
-
// The four equal width longhands collapse to a `border-width` shorthand at the IR level, and the
|
|
2841
|
-
// minimizing reverse-emit picks the single shortest utility (`border-2`) that reproduces it,
|
|
2842
|
-
// replacing the four `border-{t,r,b,l}-2` tokens. `bg-red-200` is preserved.
|
|
2843
|
-
before: '<div className="border-t-2 border-r-2 border-b-2 border-l-2 bg-red-200">box</div>',
|
|
2844
|
-
after: '<div className="bg-red-200 border-2">box</div>'
|
|
2845
|
-
}
|
|
2846
|
-
],
|
|
2847
|
-
// Asymmetric widths (top != bottom) cannot fold into a shorthand.
|
|
2848
|
-
noMatch: ['<div className="border-t-2 border-r-4 border-b-8 border-l-4 bg-red-200">box</div>']
|
|
2849
|
-
}
|
|
2850
|
-
});
|
|
2851
|
-
|
|
2852
|
-
// ../patterns/src/library/compress/dedupe-classes.pattern.ts
|
|
2853
|
-
function findRedundantClasses(computed2) {
|
|
2854
|
-
const winners = /* @__PURE__ */ new Set();
|
|
2855
|
-
const shadowed = /* @__PURE__ */ new Set();
|
|
2856
|
-
for (const block of computed2.blocks.values()) {
|
|
2857
|
-
for (const decl of block.decls.values()) {
|
|
2858
|
-
if (decl.origin && decl.origin.kind === "class") winners.add(decl.origin.className);
|
|
2859
|
-
for (const o of decl.shadowed ?? []) {
|
|
2860
|
-
if (o.kind === "class") shadowed.add(o.className);
|
|
2861
|
-
}
|
|
2862
|
-
}
|
|
2863
|
-
}
|
|
2864
|
-
return { winners, shadowed };
|
|
2865
|
-
}
|
|
2866
|
-
var dedupeClasses = definePattern({
|
|
2867
|
-
name: "dedupe-classes",
|
|
2868
|
-
category: "compress/dedupe-classes",
|
|
2869
|
-
safety: 1,
|
|
2870
|
-
doc: {
|
|
2871
|
-
title: "Dedupe fully-overridden class tokens",
|
|
2872
|
-
summary: "Drops class tokens whose every declaration is overridden by a later token resolving to the same property; the surviving token set produces a byte-for-byte identical computed style.",
|
|
2873
|
-
before: '<p class="text-sm text-lg" />',
|
|
2874
|
-
after: '<p class="text-lg" />',
|
|
2875
|
-
safetyRationale: "A fully-overridden token contributes nothing to the computed style in any condition, so removing it changes no pixels \u2014 a class-only change. It is safe even on an element with a ref, event handler, dynamic child, or dangerouslySetInnerHTML \u2014 a className rewrite touches none of them; only a dynamic/opaque class list or a combinator-subject class is excluded, so no behaviour or project selector is disturbed."
|
|
2876
|
-
},
|
|
2877
|
-
rewrite: {
|
|
2878
|
-
dropClasses(computed2, ctx) {
|
|
2879
|
-
const { winners, shadowed } = findRedundantClasses(computed2);
|
|
2880
|
-
const drop = /* @__PURE__ */ new Set();
|
|
2881
|
-
for (const cls of shadowed) {
|
|
2882
|
-
if (winners.has(cls)) continue;
|
|
2883
|
-
if (!ctx.resolver.selectorUsage(cls).droppable) continue;
|
|
2884
|
-
drop.add(cls);
|
|
2885
|
-
}
|
|
2886
|
-
return drop;
|
|
2887
|
-
}
|
|
2888
|
-
},
|
|
2889
|
-
test: {
|
|
2890
|
-
cases: [
|
|
2891
|
-
{
|
|
2892
|
-
// `text-sm` is fully overridden by `text-lg` (both set font-size + line-height). The resolver
|
|
2893
|
-
// records that shadowing in provenance and reports the Tailwind utility as droppable, so the
|
|
2894
|
-
// pattern drops `text-sm`; the reverse-emit then re-derives the minimal set (`text-lg`).
|
|
2895
|
-
before: '<p className="text-sm text-lg">Hi</p>',
|
|
2896
|
-
after: '<p className="text-lg">Hi</p>'
|
|
2897
|
-
}
|
|
2898
|
-
],
|
|
2899
|
-
// Both tokens win a distinct property (no full override) → nothing to dedupe.
|
|
2900
|
-
noMatch: ['<p className="text-lg font-bold">Hi</p>']
|
|
2901
|
-
}
|
|
2902
|
-
});
|
|
2903
|
-
|
|
2904
|
-
// ../patterns/src/library/compress/gap-shorthand.pattern.ts
|
|
2905
|
-
var ROW_GAP = "row-gap";
|
|
2906
|
-
var COLUMN_GAP = "column-gap";
|
|
2907
|
-
var GAP = "gap";
|
|
2908
|
-
var BASE_KEY3 = conditionKey(BASE_CONDITION);
|
|
2909
|
-
function withGapShorthand(sm, gapDecl) {
|
|
2910
|
-
const blocks = /* @__PURE__ */ new Map();
|
|
2911
|
-
for (const [key, block] of sm.blocks) {
|
|
2912
|
-
if (key !== BASE_KEY3) {
|
|
2913
|
-
blocks.set(key, block);
|
|
2914
|
-
continue;
|
|
2915
|
-
}
|
|
2916
|
-
const decls = /* @__PURE__ */ new Map();
|
|
2917
|
-
for (const [prop, decl] of block.decls) {
|
|
2918
|
-
if (prop === ROW_GAP || prop === COLUMN_GAP) continue;
|
|
2919
|
-
decls.set(prop, decl);
|
|
2920
|
-
}
|
|
2921
|
-
decls.set(gapDecl.property, gapDecl);
|
|
2922
|
-
blocks.set(key, { condition: block.condition, decls });
|
|
2923
|
-
}
|
|
2924
|
-
return { blocks };
|
|
2925
|
-
}
|
|
2926
|
-
var gapShorthand = definePattern({
|
|
2927
|
-
name: "gap-shorthand",
|
|
2928
|
-
category: "compress/gap-shorthand",
|
|
2929
|
-
safety: 1,
|
|
2930
|
-
doc: {
|
|
2931
|
-
title: "Collapse equal row/column gap into the `gap` shorthand",
|
|
2932
|
-
summary: "An element whose computed row-gap and column-gap are equal has the two axis longhands collapsed into a single-value `gap` shorthand (Tailwind gap-x-* gap-y-* \u2192 gap-*).",
|
|
2933
|
-
before: '<div style="row-gap:16px;column-gap:16px"/>',
|
|
2934
|
-
after: '<div style="gap:16px"/>',
|
|
2935
|
-
safetyRationale: "A single-value `gap` is value-identical to an equal row-gap+column-gap pair \u2014 a class-only change. It is safe even on an element with a ref, event handler, dynamic child, or dangerouslySetInnerHTML \u2014 a className rewrite touches none of them; only a dynamic/opaque class list or a combinator-subject class is excluded, so no behaviour or project selector is disturbed."
|
|
2936
|
-
},
|
|
2937
|
-
rewrite: {
|
|
2938
|
-
rewriteClasses(computed2) {
|
|
2939
|
-
const base = computed2.blocks.get(BASE_KEY3);
|
|
2940
|
-
if (!base) return null;
|
|
2941
|
-
const rowGap = base.decls.get(ROW_GAP);
|
|
2942
|
-
const colGap = base.decls.get(COLUMN_GAP);
|
|
2943
|
-
if (!rowGap || !colGap) return null;
|
|
2944
|
-
if (rowGap.important !== colGap.important) return null;
|
|
2945
|
-
if (rowGap.value !== colGap.value) return null;
|
|
2946
|
-
const gapDecl = {
|
|
2947
|
-
property: GAP,
|
|
2948
|
-
value: rowGap.value,
|
|
2949
|
-
important: rowGap.important,
|
|
2950
|
-
relativeToParent: rowGap.relativeToParent || colGap.relativeToParent,
|
|
2951
|
-
inherited: false
|
|
2952
|
-
// gap is not an inherited property
|
|
2953
|
-
};
|
|
2954
|
-
return withGapShorthand(computed2, gapDecl);
|
|
2955
|
-
}
|
|
2956
|
-
},
|
|
2957
|
-
test: {
|
|
2958
|
-
cases: [
|
|
2959
|
-
{
|
|
2960
|
-
// Equal row/column gap collapse to a `gap` decl at the IR level; the minimizing reverse-emit
|
|
2961
|
-
// re-expands `gap` to row-gap+column-gap and picks the single utility covering both (`gap-4`),
|
|
2962
|
-
// replacing the `gap-x-4`+`gap-y-4` pair. `bg-red-200` is preserved.
|
|
2963
|
-
before: '<div className="gap-x-4 gap-y-4 bg-red-200">box</div>',
|
|
2964
|
-
after: '<div className="bg-red-200 gap-4">box</div>'
|
|
2965
|
-
}
|
|
2966
|
-
],
|
|
2967
|
-
// Unequal axes (row-gap != column-gap) have no single-value `gap` equivalent → not collapsed.
|
|
2968
|
-
noMatch: ['<div className="gap-x-2 gap-y-4 bg-red-200">box</div>']
|
|
2969
|
-
}
|
|
2970
|
-
});
|
|
2971
|
-
|
|
2972
|
-
// ../patterns/src/library/compress/inset-shorthand.pattern.ts
|
|
2973
|
-
var TOP = "top";
|
|
2974
|
-
var RIGHT = "right";
|
|
2975
|
-
var BOTTOM = "bottom";
|
|
2976
|
-
var LEFT = "left";
|
|
2977
|
-
var INSET = "inset";
|
|
2978
|
-
var INSET_BLOCK = "inset-block";
|
|
2979
|
-
var INSET_INLINE = "inset-inline";
|
|
2980
|
-
function sameSide(a, b) {
|
|
2981
|
-
return a !== void 0 && b !== void 0 && a.value === b.value && a.important === b.important;
|
|
2982
|
-
}
|
|
2983
|
-
function asProperty(src, property) {
|
|
2984
|
-
return { ...src, property, inherited: normalizer.inherited.isInherited(property) };
|
|
2985
|
-
}
|
|
2986
|
-
function withBaseDecls(src, baseDecls) {
|
|
2987
|
-
const blocks = /* @__PURE__ */ new Map();
|
|
2988
|
-
for (const [key, block] of src.blocks) {
|
|
2989
|
-
const decls = key === BASE_CONDITION_KEY ? baseDecls : new Map(block.decls);
|
|
2990
|
-
blocks.set(key, { condition: block.condition, decls });
|
|
2991
|
-
}
|
|
2992
|
-
return { blocks };
|
|
2993
|
-
}
|
|
2994
|
-
var insetShorthand = definePattern({
|
|
2995
|
-
name: "inset-shorthand",
|
|
2996
|
-
category: "compress/inset-shorthand",
|
|
2997
|
-
safety: 2,
|
|
2998
|
-
doc: {
|
|
2999
|
-
title: "Compress inset longhands into a shorthand",
|
|
3000
|
-
summary: "top/right/bottom/left set to one value collapse to `inset`; a matching top/bottom or left/right pair collapses to `inset-block` / `inset-inline` (Tailwind inset-y / inset-x).",
|
|
3001
|
-
before: '<div style="top:10px;right:10px;bottom:10px;left:10px"/>',
|
|
3002
|
-
after: '<div style="inset:10px"/>',
|
|
3003
|
-
safetyRationale: "Meaning-preserving inset shorthand compaction \u2014 a class-only change. It is safe even on an element with a ref, event handler, dynamic child, or dangerouslySetInnerHTML \u2014 a className rewrite touches none of them; only a dynamic/opaque class list or a combinator-subject class is excluded, so no behaviour or project selector is disturbed."
|
|
3004
|
-
},
|
|
3005
|
-
rewrite: {
|
|
3006
|
-
rewriteClasses(computed2) {
|
|
3007
|
-
const base = computed2.blocks.get(BASE_CONDITION_KEY);
|
|
3008
|
-
if (!base) return null;
|
|
3009
|
-
const top = base.decls.get(TOP);
|
|
3010
|
-
const right = base.decls.get(RIGHT);
|
|
3011
|
-
const bottom = base.decls.get(BOTTOM);
|
|
3012
|
-
const left = base.decls.get(LEFT);
|
|
3013
|
-
const next = new Map(base.decls);
|
|
3014
|
-
if (top && sameSide(top, right) && sameSide(top, bottom) && sameSide(top, left)) {
|
|
3015
|
-
next.delete(TOP);
|
|
3016
|
-
next.delete(RIGHT);
|
|
3017
|
-
next.delete(BOTTOM);
|
|
3018
|
-
next.delete(LEFT);
|
|
3019
|
-
next.set(INSET, asProperty(top, INSET));
|
|
3020
|
-
} else {
|
|
3021
|
-
let collapsed = false;
|
|
3022
|
-
if (sameSide(top, bottom)) {
|
|
3023
|
-
next.delete(TOP);
|
|
3024
|
-
next.delete(BOTTOM);
|
|
3025
|
-
next.set(INSET_BLOCK, asProperty(top, INSET_BLOCK));
|
|
3026
|
-
collapsed = true;
|
|
3027
|
-
}
|
|
3028
|
-
if (sameSide(left, right)) {
|
|
3029
|
-
next.delete(LEFT);
|
|
3030
|
-
next.delete(RIGHT);
|
|
3031
|
-
next.set(INSET_INLINE, asProperty(left, INSET_INLINE));
|
|
3032
|
-
collapsed = true;
|
|
3033
|
-
}
|
|
3034
|
-
if (!collapsed) return null;
|
|
3035
|
-
}
|
|
3036
|
-
return withBaseDecls(computed2, next);
|
|
3037
|
-
}
|
|
3038
|
-
},
|
|
3039
|
-
test: {
|
|
3040
|
-
cases: [
|
|
3041
|
-
{
|
|
3042
|
-
// The four equal inset longhands collapse to an `inset` shorthand at the IR level; the
|
|
3043
|
-
// minimizing reverse-emit expands it back to top/right/bottom/left and picks the single utility
|
|
3044
|
-
// covering all four (`inset-0`), replacing the four physical-side tokens. `bg-red-200` survives.
|
|
3045
|
-
before: '<div className="top-0 right-0 bottom-0 left-0 bg-red-200">box</div>',
|
|
3046
|
-
after: '<div className="bg-red-200 inset-0">box</div>'
|
|
3047
|
-
}
|
|
3048
|
-
],
|
|
3049
|
-
// No matching inset pair (all four distinct) → nothing collapses.
|
|
3050
|
-
noMatch: ['<div className="top-0 right-1 bottom-2 left-3 bg-red-200">box</div>']
|
|
3051
|
-
}
|
|
3052
|
-
});
|
|
3053
|
-
|
|
3054
|
-
// ../patterns/src/library/compress/margin-shorthand.pattern.ts
|
|
3055
|
-
var MARGIN_SIDES = [
|
|
3056
|
-
"margin-top",
|
|
3057
|
-
"margin-right",
|
|
3058
|
-
"margin-bottom",
|
|
3059
|
-
"margin-left"
|
|
3060
|
-
];
|
|
3061
|
-
var MARGIN_SIDE_SET = new Set(MARGIN_SIDES);
|
|
3062
|
-
var BASE_KEY4 = conditionKey(BASE_CONDITION);
|
|
3063
|
-
function collapseMarginValue(top, right, bottom, left) {
|
|
3064
|
-
if (right === left) {
|
|
3065
|
-
if (top === bottom) {
|
|
3066
|
-
return top === right ? top : `${top} ${right}`;
|
|
3067
|
-
}
|
|
3068
|
-
return `${top} ${right} ${bottom}`;
|
|
3069
|
-
}
|
|
3070
|
-
return `${top} ${right} ${bottom} ${left}`;
|
|
3071
|
-
}
|
|
3072
|
-
function withFoldedMargin(sm, marginDecl) {
|
|
3073
|
-
const blocks = /* @__PURE__ */ new Map();
|
|
3074
|
-
for (const [key, block] of sm.blocks) {
|
|
3075
|
-
if (key !== BASE_KEY4) {
|
|
3076
|
-
blocks.set(key, block);
|
|
3077
|
-
continue;
|
|
3078
|
-
}
|
|
3079
|
-
const decls = /* @__PURE__ */ new Map();
|
|
3080
|
-
for (const [prop, decl] of block.decls) {
|
|
3081
|
-
if (!MARGIN_SIDE_SET.has(String(prop))) decls.set(prop, decl);
|
|
3082
|
-
}
|
|
3083
|
-
decls.set(marginDecl.property, marginDecl);
|
|
3084
|
-
blocks.set(key, { condition: block.condition, decls });
|
|
3085
|
-
}
|
|
3086
|
-
return { blocks };
|
|
3087
|
-
}
|
|
3088
|
-
var marginShorthand = definePattern({
|
|
3089
|
-
name: "margin-shorthand",
|
|
3090
|
-
category: "compress/margin-shorthand",
|
|
3091
|
-
safety: 2,
|
|
3092
|
-
doc: {
|
|
3093
|
-
title: "Compress margin longhands into the `margin` shorthand",
|
|
3094
|
-
summary: "An element with margin-top/right/bottom/left all set has them collapsed into the shortest legal `margin` shorthand (the m / mx / my forms); meaning is preserved, declaration count drops.",
|
|
3095
|
-
before: '<div style="margin-top:8px;margin-right:8px;margin-bottom:8px;margin-left:8px"/>',
|
|
3096
|
-
after: '<div style="margin:8px"/>',
|
|
3097
|
-
safetyRationale: "A pure representation change of the same computed margins (no pixels move) \u2014 a class-only change. It is safe even on an element with a ref, event handler, dynamic child, or dangerouslySetInnerHTML \u2014 a className rewrite touches none of them; only a dynamic/opaque class list or a combinator-subject class is excluded, so no behaviour or project selector is disturbed."
|
|
3098
|
-
},
|
|
3099
|
-
rewrite: {
|
|
3100
|
-
rewriteClasses(computed2) {
|
|
3101
|
-
const base = computed2.blocks.get(BASE_KEY4);
|
|
3102
|
-
if (!base) return null;
|
|
3103
|
-
const sides = MARGIN_SIDES.map((p) => base.decls.get(p));
|
|
3104
|
-
if (sides.some((d) => d === void 0)) return null;
|
|
3105
|
-
const [mt, mr, mb, ml] = sides;
|
|
3106
|
-
if (mt.important || mr.important || mb.important || ml.important) return null;
|
|
3107
|
-
const value = collapseMarginValue(
|
|
3108
|
-
String(mt.value),
|
|
3109
|
-
String(mr.value),
|
|
3110
|
-
String(mb.value),
|
|
3111
|
-
String(ml.value)
|
|
3112
|
-
);
|
|
3113
|
-
const marginDecl = {
|
|
3114
|
-
property: "margin",
|
|
3115
|
-
value,
|
|
3116
|
-
important: false,
|
|
3117
|
-
relativeToParent: mt.relativeToParent || mr.relativeToParent || mb.relativeToParent || ml.relativeToParent,
|
|
3118
|
-
inherited: false
|
|
3119
|
-
// margin is not an inherited property
|
|
3120
|
-
};
|
|
3121
|
-
return withFoldedMargin(computed2, marginDecl);
|
|
3122
|
-
}
|
|
3123
|
-
},
|
|
3124
|
-
test: {
|
|
3125
|
-
cases: [
|
|
3126
|
-
{
|
|
3127
|
-
// The four equal margin longhands collapse to a `margin` shorthand at the IR level, and the
|
|
3128
|
-
// minimizing reverse-emit picks the single shortest utility (`m-2`) reproducing it, replacing
|
|
3129
|
-
// the four `m{t,r,b,l}-2` tokens. `bg-red-200` is preserved.
|
|
3130
|
-
before: '<div className="mt-2 mr-2 mb-2 ml-2 bg-red-200">box</div>',
|
|
3131
|
-
after: '<div className="bg-red-200 m-2">box</div>'
|
|
3132
|
-
}
|
|
3133
|
-
],
|
|
3134
|
-
// Only two margin sides set → the four-longhand `margin` collapse does not apply.
|
|
3135
|
-
noMatch: ['<div className="mt-2 mb-2 bg-red-200">box</div>']
|
|
3136
|
-
}
|
|
3137
|
-
});
|
|
3138
|
-
|
|
3139
|
-
// ../patterns/src/library/compress/overflow-shorthand.pattern.ts
|
|
3140
|
-
var OVERFLOW_X = "overflow-x";
|
|
3141
|
-
var OVERFLOW_Y = "overflow-y";
|
|
3142
|
-
var OVERFLOW = "overflow";
|
|
3143
|
-
var BASE_KEY5 = conditionKey(BASE_CONDITION);
|
|
3144
|
-
function withOverflowShorthand(sm, overflowDecl) {
|
|
3145
|
-
const blocks = /* @__PURE__ */ new Map();
|
|
3146
|
-
for (const [key, block] of sm.blocks) {
|
|
3147
|
-
if (key !== BASE_KEY5) {
|
|
3148
|
-
blocks.set(key, block);
|
|
3149
|
-
continue;
|
|
3150
|
-
}
|
|
3151
|
-
const decls = /* @__PURE__ */ new Map();
|
|
3152
|
-
for (const [prop, decl] of block.decls) {
|
|
3153
|
-
if (prop === OVERFLOW_X || prop === OVERFLOW_Y) continue;
|
|
3154
|
-
decls.set(prop, decl);
|
|
3155
|
-
}
|
|
3156
|
-
decls.set(overflowDecl.property, overflowDecl);
|
|
3157
|
-
blocks.set(key, { condition: block.condition, decls });
|
|
3158
|
-
}
|
|
3159
|
-
return { blocks };
|
|
3160
|
-
}
|
|
3161
|
-
var overflowShorthand = definePattern({
|
|
3162
|
-
name: "overflow-shorthand",
|
|
3163
|
-
category: "compress/overflow-shorthand",
|
|
3164
|
-
safety: 1,
|
|
3165
|
-
doc: {
|
|
3166
|
-
title: "Collapse equal overflow axes into the `overflow` shorthand",
|
|
3167
|
-
summary: "An element whose computed overflow-x and overflow-y are equal has the two axis longhands collapsed into a single `overflow` shorthand (Tailwind overflow-x-* overflow-y-* \u2192 overflow-*).",
|
|
3168
|
-
before: '<div style="overflow-x:auto;overflow-y:auto"/>',
|
|
3169
|
-
after: '<div style="overflow:auto"/>',
|
|
3170
|
-
safetyRationale: "A single-keyword `overflow` is value-identical to equal overflow-x+overflow-y \u2014 a class-only change. It is safe even on an element with a ref, event handler, dynamic child, or dangerouslySetInnerHTML \u2014 a className rewrite touches none of them; only a dynamic/opaque class list or a combinator-subject class is excluded, so no behaviour or project selector is disturbed."
|
|
3171
|
-
},
|
|
3172
|
-
rewrite: {
|
|
3173
|
-
rewriteClasses(computed2) {
|
|
3174
|
-
const base = computed2.blocks.get(BASE_KEY5);
|
|
3175
|
-
if (!base) return null;
|
|
3176
|
-
const overflowX = base.decls.get(OVERFLOW_X);
|
|
3177
|
-
const overflowY = base.decls.get(OVERFLOW_Y);
|
|
3178
|
-
if (!overflowX || !overflowY) return null;
|
|
3179
|
-
if (overflowX.important !== overflowY.important) return null;
|
|
3180
|
-
if (overflowX.value !== overflowY.value) return null;
|
|
3181
|
-
const overflowDecl = {
|
|
3182
|
-
property: OVERFLOW,
|
|
3183
|
-
value: overflowX.value,
|
|
3184
|
-
important: overflowX.important,
|
|
3185
|
-
relativeToParent: overflowX.relativeToParent || overflowY.relativeToParent,
|
|
3186
|
-
inherited: false
|
|
3187
|
-
// overflow is not an inherited property
|
|
3188
|
-
};
|
|
3189
|
-
return withOverflowShorthand(computed2, overflowDecl);
|
|
3190
|
-
}
|
|
3191
|
-
},
|
|
3192
|
-
test: {
|
|
3193
|
-
cases: [
|
|
3194
|
-
{
|
|
3195
|
-
// Equal overflow axes collapse to an `overflow` decl at the IR level; the minimizing
|
|
3196
|
-
// reverse-emit picks the single utility covering both (`overflow-auto`), replacing the
|
|
3197
|
-
// `overflow-x-auto`+`overflow-y-auto` pair. `bg-red-200` is preserved.
|
|
3198
|
-
before: '<div className="overflow-x-auto overflow-y-auto bg-red-200">box</div>',
|
|
3199
|
-
after: '<div className="bg-red-200 overflow-auto">box</div>'
|
|
3200
|
-
}
|
|
3201
|
-
],
|
|
3202
|
-
// Mismatched axes (overflow-x != overflow-y) have no single-keyword equivalent → not collapsed.
|
|
3203
|
-
noMatch: ['<div className="overflow-x-auto overflow-y-hidden bg-red-200">box</div>']
|
|
3204
|
-
}
|
|
3205
|
-
});
|
|
3206
|
-
|
|
3207
|
-
// ../patterns/src/library/compress/overscroll-behavior-shorthand.pattern.ts
|
|
3208
|
-
var OVERSCROLL_X = "overscroll-behavior-x";
|
|
3209
|
-
var OVERSCROLL_Y = "overscroll-behavior-y";
|
|
3210
|
-
var OVERSCROLL = "overscroll-behavior";
|
|
3211
|
-
var BASE_KEY6 = conditionKey(BASE_CONDITION);
|
|
3212
|
-
var NON_COLLAPSIBLE_VALUES2 = /* @__PURE__ */ new Set([
|
|
3213
|
-
"initial",
|
|
3214
|
-
"inherit",
|
|
3215
|
-
"unset",
|
|
3216
|
-
"revert",
|
|
3217
|
-
"revert-layer"
|
|
3218
|
-
]);
|
|
3219
|
-
function withOverscrollShorthand(sm, shorthand) {
|
|
3220
|
-
const blocks = /* @__PURE__ */ new Map();
|
|
3221
|
-
for (const [key, block] of sm.blocks) {
|
|
3222
|
-
if (key !== BASE_KEY6) {
|
|
3223
|
-
blocks.set(key, block);
|
|
3224
|
-
continue;
|
|
3225
|
-
}
|
|
3226
|
-
const decls = /* @__PURE__ */ new Map();
|
|
3227
|
-
for (const [prop, decl] of block.decls) {
|
|
3228
|
-
if (prop === OVERSCROLL_X || prop === OVERSCROLL_Y) continue;
|
|
3229
|
-
decls.set(prop, decl);
|
|
3230
|
-
}
|
|
3231
|
-
decls.set(shorthand.property, shorthand);
|
|
3232
|
-
blocks.set(key, { condition: block.condition, decls });
|
|
3233
|
-
}
|
|
3234
|
-
return { blocks };
|
|
3235
|
-
}
|
|
3236
|
-
var overscrollBehaviorShorthand = definePattern({
|
|
3237
|
-
name: "overscroll-behavior-shorthand",
|
|
3238
|
-
category: "compress/overscroll-behavior-shorthand",
|
|
3239
|
-
safety: 1,
|
|
3240
|
-
doc: {
|
|
3241
|
-
title: "Collapse equal overscroll-behavior axes into overscroll-behavior",
|
|
3242
|
-
summary: "An element whose computed overscroll-behavior-x and overscroll-behavior-y are equal has the two axis longhands collapsed into a single `overscroll-behavior` shorthand (Tailwind overscroll-x-* overscroll-y-* \u2192 overscroll-*).",
|
|
3243
|
-
before: '<div style="overscroll-behavior-x:contain;overscroll-behavior-y:contain"/>',
|
|
3244
|
-
after: '<div class="overscroll-contain"/>',
|
|
3245
|
-
safetyRationale: "`overscroll-behavior` is value-identical to an equal x+y axis pair \u2014 a class-only change. It is safe even on an element with a ref, event handler, dynamic child, or dangerouslySetInnerHTML \u2014 a className rewrite touches none of them; only a dynamic/opaque class list or a combinator-subject class is excluded, so no behaviour or project selector is disturbed."
|
|
3246
|
-
},
|
|
3247
|
-
rewrite: {
|
|
3248
|
-
rewriteClasses(computed2) {
|
|
3249
|
-
const base = computed2.blocks.get(BASE_KEY6);
|
|
3250
|
-
if (!base) return null;
|
|
3251
|
-
const x = base.decls.get(OVERSCROLL_X);
|
|
3252
|
-
const y = base.decls.get(OVERSCROLL_Y);
|
|
3253
|
-
if (!x || !y) return null;
|
|
3254
|
-
if (x.important !== y.important) return null;
|
|
3255
|
-
const value = String(x.value);
|
|
3256
|
-
if (NON_COLLAPSIBLE_VALUES2.has(value)) return null;
|
|
3257
|
-
if (value !== String(y.value)) return null;
|
|
3258
|
-
const shorthand = {
|
|
3259
|
-
property: OVERSCROLL,
|
|
3260
|
-
value: x.value,
|
|
3261
|
-
important: x.important,
|
|
3262
|
-
relativeToParent: x.relativeToParent || y.relativeToParent,
|
|
3263
|
-
inherited: false
|
|
3264
|
-
// overscroll-behavior is not an inherited property
|
|
3265
|
-
};
|
|
3266
|
-
return withOverscrollShorthand(computed2, shorthand);
|
|
3267
|
-
}
|
|
3268
|
-
},
|
|
3269
|
-
test: {
|
|
3270
|
-
cases: [
|
|
3271
|
-
{
|
|
3272
|
-
// Equal x/y axes collapse to an `overscroll-behavior` decl at the IR level; the minimizing
|
|
3273
|
-
// reverse-emit picks the single utility covering both (`overscroll-contain`), replacing the
|
|
3274
|
-
// `overscroll-x-contain`+`overscroll-y-contain` pair. `bg-red-200` is preserved.
|
|
3275
|
-
before: '<div className="overscroll-x-contain overscroll-y-contain bg-red-200">box</div>',
|
|
3276
|
-
after: '<div className="bg-red-200 overscroll-contain">box</div>'
|
|
3277
|
-
}
|
|
3278
|
-
],
|
|
3279
|
-
// Axes differ (x != y) → no equal-axis collapse.
|
|
3280
|
-
noMatch: ['<div className="overscroll-x-contain overscroll-y-auto bg-red-200">box</div>']
|
|
3281
|
-
}
|
|
3282
|
-
});
|
|
3283
|
-
|
|
3284
|
-
// ../patterns/src/library/compress/padding-shorthand.pattern.ts
|
|
3285
|
-
var PADDING_SIDES = [
|
|
3286
|
-
"padding-top",
|
|
3287
|
-
"padding-right",
|
|
3288
|
-
"padding-bottom",
|
|
3289
|
-
"padding-left"
|
|
3290
|
-
];
|
|
3291
|
-
var PADDING_SIDE_SET = new Set(PADDING_SIDES);
|
|
3292
|
-
var BASE_KEY7 = conditionKey(BASE_CONDITION);
|
|
3293
|
-
function analyzePadding(sm) {
|
|
3294
|
-
const block = sm.blocks.get(BASE_KEY7);
|
|
3295
|
-
if (!block) return null;
|
|
3296
|
-
const sides = [];
|
|
3297
|
-
for (const side of PADDING_SIDES) {
|
|
3298
|
-
const decl = block.decls.get(side);
|
|
3299
|
-
if (!decl) return null;
|
|
3300
|
-
sides.push(decl);
|
|
3301
|
-
}
|
|
3302
|
-
const [top, right, bottom, left] = sides;
|
|
3303
|
-
if (!(top.important === right.important && right.important === bottom.important && bottom.important === left.important)) {
|
|
3304
|
-
return null;
|
|
3305
|
-
}
|
|
3306
|
-
const tv = String(top.value);
|
|
3307
|
-
const rv = String(right.value);
|
|
3308
|
-
const bv = String(bottom.value);
|
|
3309
|
-
const lv = String(left.value);
|
|
3310
|
-
if (tv !== bv || lv !== rv) return null;
|
|
3311
|
-
const value = tv === lv ? tv : `${tv} ${lv}`;
|
|
3312
|
-
const relative = sides.some((d) => d.relativeToParent);
|
|
3313
|
-
return { value, important: top.important, relative };
|
|
3314
|
-
}
|
|
3315
|
-
function withFoldedPadding(sm, fold) {
|
|
3316
|
-
const blocks = /* @__PURE__ */ new Map();
|
|
3317
|
-
for (const [key, block] of sm.blocks) {
|
|
3318
|
-
if (key !== BASE_KEY7) {
|
|
3319
|
-
blocks.set(key, block);
|
|
3320
|
-
continue;
|
|
3321
|
-
}
|
|
3322
|
-
const decls = /* @__PURE__ */ new Map();
|
|
3323
|
-
for (const [prop, decl] of block.decls) {
|
|
3324
|
-
if (PADDING_SIDE_SET.has(String(prop))) continue;
|
|
3325
|
-
decls.set(prop, decl);
|
|
3326
|
-
}
|
|
3327
|
-
const shorthand = {
|
|
3328
|
-
property: "padding",
|
|
3329
|
-
value: fold.value,
|
|
3330
|
-
important: fold.important,
|
|
3331
|
-
relativeToParent: fold.relative,
|
|
3332
|
-
inherited: false
|
|
3333
|
-
// padding is never inherited
|
|
3334
|
-
};
|
|
3335
|
-
decls.set(shorthand.property, shorthand);
|
|
3336
|
-
blocks.set(key, { condition: block.condition, decls });
|
|
3337
|
-
}
|
|
3338
|
-
return { blocks };
|
|
3339
|
-
}
|
|
3340
|
-
var paddingShorthand = definePattern({
|
|
3341
|
-
name: "padding-shorthand",
|
|
3342
|
-
category: "compress/padding-shorthand",
|
|
3343
|
-
safety: 1,
|
|
3344
|
-
doc: {
|
|
3345
|
-
title: "Collapse padding longhands to shorthand",
|
|
3346
|
-
summary: "Equal padding on all four sides (or matching x/y pairs) expressed as separate longhand declarations is collapsed to the shortest equivalent padding shorthand (p-* / px-* py-*).",
|
|
3347
|
-
before: '<div class="pt-4 pr-4 pb-4 pl-4"/>',
|
|
3348
|
-
after: '<div class="p-4"/>',
|
|
3349
|
-
safetyRationale: "A value-preserving re-serialization of the same computed padding on the same node \u2014 a class-only change. It is safe even on an element with a ref, event handler, dynamic child, or dangerouslySetInnerHTML \u2014 a className rewrite touches none of them; only a dynamic/opaque class list or a combinator-subject class is excluded, so no behaviour or project selector is disturbed."
|
|
3350
|
-
},
|
|
3351
|
-
rewrite: {
|
|
3352
|
-
rewriteClasses(computed2) {
|
|
3353
|
-
const fold = analyzePadding(computed2);
|
|
3354
|
-
return fold ? withFoldedPadding(computed2, fold) : null;
|
|
3355
|
-
}
|
|
3356
|
-
},
|
|
3357
|
-
test: {
|
|
3358
|
-
cases: [
|
|
3359
|
-
{
|
|
3360
|
-
// The four equal padding longhands collapse to a `padding` shorthand at the IR level, and the
|
|
3361
|
-
// minimizing reverse-emit picks the single shortest utility (`p-4`) that reproduces it,
|
|
3362
|
-
// replacing the four `p{t,r,b,l}-4` tokens. `bg-red-200` is preserved (its order is stable).
|
|
3363
|
-
before: '<div className="pt-4 pr-4 pb-4 pl-4 bg-red-200">box</div>',
|
|
3364
|
-
after: '<div className="bg-red-200 p-4">box</div>'
|
|
3365
|
-
},
|
|
3366
|
-
{
|
|
3367
|
-
// A dynamic `{x}` child no longer blocks compress: only the element's OWN class tokens are
|
|
3368
|
-
// rewritten (px-4 py-4 → p-4); the dynamic child is untouched by a class-only change. This is
|
|
3369
|
-
// the real-app common case (most elements have dynamic content).
|
|
3370
|
-
before: '<div className="px-4 py-4">{x}</div>',
|
|
3371
|
-
after: '<div className="p-4">{x}</div>'
|
|
3372
|
-
}
|
|
3373
|
-
],
|
|
3374
|
-
// Asymmetric padding (top != bottom) cannot fold into a shorthand → left unchanged.
|
|
3375
|
-
noMatch: ['<div className="pt-2 pr-4 pb-8 pl-4 bg-red-200">box</div>']
|
|
3376
|
-
}
|
|
3377
|
-
});
|
|
3378
|
-
|
|
3379
|
-
// ../patterns/src/library/compress/place-shorthand.pattern.ts
|
|
3380
|
-
var ALIGN_ITEMS = "align-items";
|
|
3381
|
-
var JUSTIFY_ITEMS = "justify-items";
|
|
3382
|
-
var PLACE_ITEMS = "place-items";
|
|
3383
|
-
var ALIGN_CONTENT = "align-content";
|
|
3384
|
-
var JUSTIFY_CONTENT = "justify-content";
|
|
3385
|
-
var PLACE_CONTENT = "place-content";
|
|
3386
|
-
var BASE_KEY8 = conditionKey(BASE_CONDITION);
|
|
3387
|
-
function samePair(a, b) {
|
|
3388
|
-
return a !== void 0 && b !== void 0 && a.value === b.value && a.important === b.important;
|
|
3389
|
-
}
|
|
3390
|
-
function placeDecl(property, align) {
|
|
3391
|
-
return {
|
|
3392
|
-
property,
|
|
3393
|
-
value: align.value,
|
|
3394
|
-
important: align.important,
|
|
3395
|
-
relativeToParent: false,
|
|
3396
|
-
// alignment keywords (center/start/stretch/…) are not length-relative
|
|
3397
|
-
inherited: false
|
|
3398
|
-
// none of the place-* alignment properties are inherited
|
|
3399
|
-
};
|
|
3400
|
-
}
|
|
3401
|
-
function withBaseDecls2(sm, baseDecls) {
|
|
3402
|
-
const blocks = /* @__PURE__ */ new Map();
|
|
3403
|
-
for (const [key, block] of sm.blocks) {
|
|
3404
|
-
const decls = key === BASE_KEY8 ? new Map(baseDecls) : block.decls;
|
|
3405
|
-
blocks.set(key, { condition: block.condition, decls });
|
|
3406
|
-
}
|
|
3407
|
-
return { blocks };
|
|
3408
|
-
}
|
|
3409
|
-
var placeShorthand = definePattern({
|
|
3410
|
-
name: "place-shorthand",
|
|
3411
|
-
category: "compress/place-shorthand",
|
|
3412
|
-
safety: 1,
|
|
3413
|
-
doc: {
|
|
3414
|
-
title: "Collapse matching alignment pairs into `place-*` shorthands",
|
|
3415
|
-
summary: "When align-items equals justify-items they collapse to `place-items`; when align-content equals justify-content they collapse to `place-content`. The two collapses are independent.",
|
|
3416
|
-
before: '<div style="align-items:center;justify-items:center"/>',
|
|
3417
|
-
after: '<div style="place-items:center"/>',
|
|
3418
|
-
safetyRationale: "A `place-*` shorthand is value-identical to its equal align/justify pair \u2014 a class-only change. It is safe even on an element with a ref, event handler, dynamic child, or dangerouslySetInnerHTML \u2014 a className rewrite touches none of them; only a dynamic/opaque class list or a combinator-subject class is excluded, so no behaviour or project selector is disturbed."
|
|
3419
|
-
},
|
|
3420
|
-
rewrite: {
|
|
3421
|
-
rewriteClasses(computed2) {
|
|
3422
|
-
const base = computed2.blocks.get(BASE_KEY8);
|
|
3423
|
-
if (!base) return null;
|
|
3424
|
-
const alignItems = base.decls.get(ALIGN_ITEMS);
|
|
3425
|
-
const justifyItems = base.decls.get(JUSTIFY_ITEMS);
|
|
3426
|
-
const alignContent = base.decls.get(ALIGN_CONTENT);
|
|
3427
|
-
const justifyContent = base.decls.get(JUSTIFY_CONTENT);
|
|
3428
|
-
const next = new Map(base.decls);
|
|
3429
|
-
let collapsed = false;
|
|
3430
|
-
if (samePair(alignItems, justifyItems)) {
|
|
3431
|
-
next.delete(ALIGN_ITEMS);
|
|
3432
|
-
next.delete(JUSTIFY_ITEMS);
|
|
3433
|
-
next.set(PLACE_ITEMS, placeDecl(PLACE_ITEMS, alignItems));
|
|
3434
|
-
collapsed = true;
|
|
3435
|
-
}
|
|
3436
|
-
if (samePair(alignContent, justifyContent)) {
|
|
3437
|
-
next.delete(ALIGN_CONTENT);
|
|
3438
|
-
next.delete(JUSTIFY_CONTENT);
|
|
3439
|
-
next.set(PLACE_CONTENT, placeDecl(PLACE_CONTENT, alignContent));
|
|
3440
|
-
collapsed = true;
|
|
3441
|
-
}
|
|
3442
|
-
if (!collapsed) return null;
|
|
3443
|
-
return withBaseDecls2(computed2, next);
|
|
3444
|
-
}
|
|
3445
|
-
},
|
|
3446
|
-
test: {
|
|
3447
|
-
cases: [
|
|
3448
|
-
{
|
|
3449
|
-
// The matching items pair collapses to a `place-items` decl at the IR level; the minimizing
|
|
3450
|
-
// reverse-emit picks the single utility covering both (`place-items-center`), replacing the
|
|
3451
|
-
// `items-center`+`justify-items-center` pair. `bg-red-200` is preserved.
|
|
3452
|
-
before: '<div className="items-center justify-items-center bg-red-200">box</div>',
|
|
3453
|
-
after: '<div className="bg-red-200 place-items-center">box</div>'
|
|
3454
|
-
}
|
|
3455
|
-
],
|
|
3456
|
-
// Mismatched alignment (align-items != justify-items, no content pair) → nothing collapses.
|
|
3457
|
-
noMatch: ['<div className="items-center justify-items-start bg-red-200">box</div>']
|
|
3458
|
-
}
|
|
3459
|
-
});
|
|
3460
|
-
|
|
3461
|
-
// ../patterns/src/library/compress/scroll-margin-shorthand.pattern.ts
|
|
3462
|
-
var SCROLL_MARGIN_SIDES = [
|
|
3463
|
-
"scroll-margin-top",
|
|
3464
|
-
"scroll-margin-right",
|
|
3465
|
-
"scroll-margin-bottom",
|
|
3466
|
-
"scroll-margin-left"
|
|
3467
|
-
];
|
|
3468
|
-
var SIDE_SET = new Set(SCROLL_MARGIN_SIDES);
|
|
3469
|
-
var BASE_KEY9 = conditionKey(BASE_CONDITION);
|
|
3470
|
-
var SCROLL_MARGIN = "scroll-margin";
|
|
3471
|
-
var NON_COLLAPSIBLE_VALUES3 = /* @__PURE__ */ new Set([
|
|
3472
|
-
"initial",
|
|
3473
|
-
"inherit",
|
|
3474
|
-
"unset",
|
|
3475
|
-
"revert",
|
|
3476
|
-
"revert-layer"
|
|
3477
|
-
]);
|
|
3478
|
-
function analyzeScrollMargin(sm) {
|
|
3479
|
-
const block = sm.blocks.get(BASE_KEY9);
|
|
3480
|
-
if (!block) return null;
|
|
3481
|
-
const sides = [];
|
|
3482
|
-
for (const side of SCROLL_MARGIN_SIDES) {
|
|
3483
|
-
const decl = block.decls.get(side);
|
|
3484
|
-
if (!decl) return null;
|
|
3485
|
-
sides.push(decl);
|
|
3486
|
-
}
|
|
3487
|
-
const important = sides[0].important;
|
|
3488
|
-
if (!sides.every((d) => d.important === important)) return null;
|
|
3489
|
-
const value = String(sides[0].value);
|
|
3490
|
-
if (NON_COLLAPSIBLE_VALUES3.has(value)) return null;
|
|
3491
|
-
if (!sides.every((d) => String(d.value) === value)) return null;
|
|
3492
|
-
const relative = sides.some((d) => d.relativeToParent);
|
|
3493
|
-
return { value, important, relative };
|
|
3494
|
-
}
|
|
3495
|
-
function withFoldedScrollMargin(sm, fold) {
|
|
3496
|
-
const blocks = /* @__PURE__ */ new Map();
|
|
3497
|
-
for (const [key, block] of sm.blocks) {
|
|
3498
|
-
if (key !== BASE_KEY9) {
|
|
3499
|
-
blocks.set(key, block);
|
|
3500
|
-
continue;
|
|
3501
|
-
}
|
|
3502
|
-
const decls = /* @__PURE__ */ new Map();
|
|
3503
|
-
for (const [prop, decl] of block.decls) {
|
|
3504
|
-
if (SIDE_SET.has(String(prop))) continue;
|
|
3505
|
-
decls.set(prop, decl);
|
|
3506
|
-
}
|
|
3507
|
-
const shorthand = {
|
|
3508
|
-
property: SCROLL_MARGIN,
|
|
3509
|
-
value: fold.value,
|
|
3510
|
-
important: fold.important,
|
|
3511
|
-
relativeToParent: fold.relative,
|
|
3512
|
-
inherited: false
|
|
3513
|
-
// scroll-margin is never inherited
|
|
3514
|
-
};
|
|
3515
|
-
decls.set(shorthand.property, shorthand);
|
|
3516
|
-
blocks.set(key, { condition: block.condition, decls });
|
|
3517
|
-
}
|
|
3518
|
-
return { blocks };
|
|
3519
|
-
}
|
|
3520
|
-
var scrollMarginShorthand = definePattern({
|
|
3521
|
-
name: "scroll-margin-shorthand",
|
|
3522
|
-
category: "compress/scroll-margin-shorthand",
|
|
3523
|
-
safety: 1,
|
|
3524
|
-
doc: {
|
|
3525
|
-
title: "Collapse equal scroll-margin sides into scroll-margin",
|
|
3526
|
-
summary: "An element whose four scroll-margin sides are all equal is rewritten to the single Tailwind scroll-m-* utility (scroll-margin === the four equal sides).",
|
|
3527
|
-
before: '<div class="scroll-mt-4 scroll-mr-4 scroll-mb-4 scroll-ml-4"/>',
|
|
3528
|
-
after: '<div class="scroll-m-4"/>',
|
|
3529
|
-
safetyRationale: "`scroll-margin` is value-identical to four equal scroll-margin sides \u2014 a class-only change. It is safe even on an element with a ref, event handler, dynamic child, or dangerouslySetInnerHTML \u2014 a className rewrite touches none of them; only a dynamic/opaque class list or a combinator-subject class is excluded, so no behaviour or project selector is disturbed."
|
|
3530
|
-
},
|
|
3531
|
-
rewrite: {
|
|
3532
|
-
rewriteClasses(computed2) {
|
|
3533
|
-
const fold = analyzeScrollMargin(computed2);
|
|
3534
|
-
return fold ? withFoldedScrollMargin(computed2, fold) : null;
|
|
3535
|
-
}
|
|
3536
|
-
},
|
|
3537
|
-
test: {
|
|
3538
|
-
cases: [
|
|
3539
|
-
{
|
|
3540
|
-
// The four equal scroll-margin longhands collapse to a `scroll-margin` decl at the IR level; the
|
|
3541
|
-
// minimizing reverse-emit then picks the single shortest utility (`scroll-m-4`) that reproduces
|
|
3542
|
-
// it, replacing the four `scroll-m{t,r,b,l}-4` tokens. `bg-red-200` is preserved.
|
|
3543
|
-
before: '<div className="scroll-mt-4 scroll-mr-4 scroll-mb-4 scroll-ml-4 bg-red-200">box</div>',
|
|
3544
|
-
after: '<div className="bg-red-200 scroll-m-4">box</div>'
|
|
3545
|
-
}
|
|
3546
|
-
],
|
|
3547
|
-
// Sides differ (top != bottom) → no all-equal collapse.
|
|
3548
|
-
noMatch: ['<div className="scroll-mt-2 scroll-mr-4 scroll-mb-8 scroll-ml-4 bg-red-200">box</div>']
|
|
3549
|
-
}
|
|
3550
|
-
});
|
|
3551
|
-
|
|
3552
|
-
// ../patterns/src/library/compress/scroll-padding-shorthand.pattern.ts
|
|
3553
|
-
var SCROLL_PADDING_SIDES = [
|
|
3554
|
-
"scroll-padding-top",
|
|
3555
|
-
"scroll-padding-right",
|
|
3556
|
-
"scroll-padding-bottom",
|
|
3557
|
-
"scroll-padding-left"
|
|
3558
|
-
];
|
|
3559
|
-
var SIDE_SET2 = new Set(SCROLL_PADDING_SIDES);
|
|
3560
|
-
var BASE_KEY10 = conditionKey(BASE_CONDITION);
|
|
3561
|
-
var SCROLL_PADDING = "scroll-padding";
|
|
3562
|
-
var NON_COLLAPSIBLE_VALUES4 = /* @__PURE__ */ new Set([
|
|
3563
|
-
"initial",
|
|
3564
|
-
"inherit",
|
|
3565
|
-
"unset",
|
|
3566
|
-
"revert",
|
|
3567
|
-
"revert-layer"
|
|
3568
|
-
]);
|
|
3569
|
-
function analyzeScrollPadding(sm) {
|
|
3570
|
-
const block = sm.blocks.get(BASE_KEY10);
|
|
3571
|
-
if (!block) return null;
|
|
3572
|
-
const sides = [];
|
|
3573
|
-
for (const side of SCROLL_PADDING_SIDES) {
|
|
3574
|
-
const decl = block.decls.get(side);
|
|
3575
|
-
if (!decl) return null;
|
|
3576
|
-
sides.push(decl);
|
|
3577
|
-
}
|
|
3578
|
-
const important = sides[0].important;
|
|
3579
|
-
if (!sides.every((d) => d.important === important)) return null;
|
|
3580
|
-
const value = String(sides[0].value);
|
|
3581
|
-
if (NON_COLLAPSIBLE_VALUES4.has(value)) return null;
|
|
3582
|
-
if (!sides.every((d) => String(d.value) === value)) return null;
|
|
3583
|
-
const relative = sides.some((d) => d.relativeToParent);
|
|
3584
|
-
return { value, important, relative };
|
|
3585
|
-
}
|
|
3586
|
-
function withFoldedScrollPadding(sm, fold) {
|
|
3587
|
-
const blocks = /* @__PURE__ */ new Map();
|
|
3588
|
-
for (const [key, block] of sm.blocks) {
|
|
3589
|
-
if (key !== BASE_KEY10) {
|
|
3590
|
-
blocks.set(key, block);
|
|
3591
|
-
continue;
|
|
3592
|
-
}
|
|
3593
|
-
const decls = /* @__PURE__ */ new Map();
|
|
3594
|
-
for (const [prop, decl] of block.decls) {
|
|
3595
|
-
if (SIDE_SET2.has(String(prop))) continue;
|
|
3596
|
-
decls.set(prop, decl);
|
|
3597
|
-
}
|
|
3598
|
-
const shorthand = {
|
|
3599
|
-
property: SCROLL_PADDING,
|
|
3600
|
-
value: fold.value,
|
|
3601
|
-
important: fold.important,
|
|
3602
|
-
relativeToParent: fold.relative,
|
|
3603
|
-
inherited: false
|
|
3604
|
-
// scroll-padding is never inherited
|
|
3605
|
-
};
|
|
3606
|
-
decls.set(shorthand.property, shorthand);
|
|
3607
|
-
blocks.set(key, { condition: block.condition, decls });
|
|
3608
|
-
}
|
|
3609
|
-
return { blocks };
|
|
2667
|
+
function elementOf(node) {
|
|
2668
|
+
const n = node;
|
|
2669
|
+
return n.kind === "element" ? n : null;
|
|
3610
2670
|
}
|
|
3611
|
-
var
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
2671
|
+
var establishesContext = (node) => {
|
|
2672
|
+
const m = metaOf2(node);
|
|
2673
|
+
if (!m) return false;
|
|
2674
|
+
return m.establishesBox || m.establishesFormattingContext || m.establishesStackingContext || m.isContainingBlock || m.declaresCustomProperties;
|
|
2675
|
+
};
|
|
2676
|
+
var hasSpreadAttrs2 = (node) => metaOf2(node)?.hasSpreadAttrs ?? false;
|
|
2677
|
+
var isComponentNode3 = (node) => metaOf2(node)?.isComponent ?? false;
|
|
2678
|
+
var hasOwnAttrs2 = (node) => {
|
|
2679
|
+
const el = elementOf(node);
|
|
2680
|
+
if (!el) return false;
|
|
2681
|
+
return el.attrs.entries.size > 0 || el.attrs.spreads.length > 0;
|
|
2682
|
+
};
|
|
2683
|
+
var targetedByStructuralPseudo3 = (node, ctx) => {
|
|
2684
|
+
const el = elementOf(node);
|
|
2685
|
+
if (!el) return false;
|
|
2686
|
+
if (el.meta.targetedByStructuralPseudo) return true;
|
|
2687
|
+
return ctx.selectors.targetedByStructuralPseudo(el.id);
|
|
2688
|
+
};
|
|
2689
|
+
var passthroughWrapper = definePattern({
|
|
2690
|
+
name: "passthrough-wrapper",
|
|
2691
|
+
category: "flatten/wrapper/passthrough-wrapper",
|
|
2692
|
+
safety: 2,
|
|
3615
2693
|
doc: {
|
|
3616
|
-
title: "
|
|
3617
|
-
summary: "
|
|
3618
|
-
before:
|
|
3619
|
-
after:
|
|
3620
|
-
safetyRationale: "
|
|
2694
|
+
title: "Flatten passthrough wrapper",
|
|
2695
|
+
summary: "A div with no own visual/box style, no attributes beyond an inert class, exactly one element child, and no opacity barriers is removed; its sole child is hoisted in its place.",
|
|
2696
|
+
before: "<div><Child/></div>",
|
|
2697
|
+
after: "<Child/>",
|
|
2698
|
+
safetyRationale: "Wrapper paints nothing and establishes no layout/paint/var context, carries no ref/handlers/dynamic-children/html/spread/component identity, owns no targetable attrs, and is not a combinator/structural-pseudo subject (reparenting changes no match-set); inheritable styles are folded onto the child before removal."
|
|
3621
2699
|
},
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
2700
|
+
match: {
|
|
2701
|
+
tag: "div",
|
|
2702
|
+
onlyChild: "element",
|
|
2703
|
+
paintsNothing: true,
|
|
2704
|
+
where: [
|
|
2705
|
+
not(establishesContext),
|
|
2706
|
+
not(hasOwnAttrs2),
|
|
2707
|
+
not(hasDynamicClasses),
|
|
2708
|
+
not(hasSpreadAttrs2),
|
|
2709
|
+
not(isComponentNode3),
|
|
2710
|
+
not(targetedByStructuralPseudo3)
|
|
2711
|
+
]
|
|
3627
2712
|
},
|
|
2713
|
+
rewrite: { flattenInto: "child" },
|
|
3628
2714
|
test: {
|
|
3629
2715
|
cases: [
|
|
3630
2716
|
{
|
|
3631
|
-
//
|
|
3632
|
-
//
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
after: '<div className="bg-red-200 scroll-p-4">box</div>'
|
|
2717
|
+
// A plain, style-free wrapper paints nothing and establishes no context → a provably-safe
|
|
2718
|
+
// flatten under the conservative gate: the wrapper is removed and its sole child hoisted.
|
|
2719
|
+
before: '<div><a className="bg-red-200">Link</a></div>',
|
|
2720
|
+
after: '<a className="bg-red-200">Link</a>'
|
|
3636
2721
|
}
|
|
3637
2722
|
],
|
|
3638
|
-
|
|
3639
|
-
|
|
2723
|
+
noMatch: [
|
|
2724
|
+
// A ref pins the wrapper's element identity (a hard opacity barrier) → not a passthrough.
|
|
2725
|
+
'<div ref={rootRef}><a className="bg-red-200">Link</a></div>',
|
|
2726
|
+
// A `display:flex` wrapper establishes a formatting context, so removing its box is NOT
|
|
2727
|
+
// provably layout-neutral → the conservative gate leaves it in place.
|
|
2728
|
+
'<div className="flex"><a className="bg-red-200">Link</a></div>'
|
|
2729
|
+
]
|
|
3640
2730
|
}
|
|
3641
2731
|
});
|
|
3642
2732
|
|
|
3643
|
-
// ../patterns/src/library/
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
function
|
|
3649
|
-
return
|
|
3650
|
-
}
|
|
3651
|
-
function withSizeShorthand(sm, value, important) {
|
|
3652
|
-
const baseKey = conditionKey(BASE_CONDITION);
|
|
3653
|
-
const blocks = /* @__PURE__ */ new Map();
|
|
3654
|
-
for (const [key, block] of sm.blocks) {
|
|
3655
|
-
if (key !== baseKey) {
|
|
3656
|
-
blocks.set(key, block);
|
|
3657
|
-
continue;
|
|
3658
|
-
}
|
|
3659
|
-
const decls = new Map(block.decls);
|
|
3660
|
-
decls.delete(WIDTH);
|
|
3661
|
-
decls.delete(HEIGHT);
|
|
3662
|
-
for (const decl of normalizer.normalizeDeclaration(String(SIZE), value, important)) {
|
|
3663
|
-
decls.set(decl.property, decl);
|
|
3664
|
-
}
|
|
3665
|
-
blocks.set(key, { condition: block.condition, decls });
|
|
3666
|
-
}
|
|
3667
|
-
return { blocks };
|
|
2733
|
+
// ../patterns/src/library/wrapper/redundant-inline-wrapper.pattern.ts
|
|
2734
|
+
function asEl3(node) {
|
|
2735
|
+
const n = node;
|
|
2736
|
+
return n.kind === "element" ? n : null;
|
|
2737
|
+
}
|
|
2738
|
+
function metaOf3(node) {
|
|
2739
|
+
return asEl3(node)?.meta ?? null;
|
|
3668
2740
|
}
|
|
3669
|
-
var
|
|
3670
|
-
|
|
3671
|
-
|
|
2741
|
+
var establishesContext2 = (node) => {
|
|
2742
|
+
const m = metaOf3(node);
|
|
2743
|
+
if (!m) return false;
|
|
2744
|
+
return m.establishesBox || m.establishesFormattingContext || m.establishesStackingContext || m.isContainingBlock || m.declaresCustomProperties;
|
|
2745
|
+
};
|
|
2746
|
+
var hasSpreadAttrs3 = (node) => metaOf3(node)?.hasSpreadAttrs ?? false;
|
|
2747
|
+
var isComponentNode4 = (node) => metaOf3(node)?.isComponent ?? false;
|
|
2748
|
+
var hasOwnAttrs3 = (node) => {
|
|
2749
|
+
const el = asEl3(node);
|
|
2750
|
+
if (!el) return false;
|
|
2751
|
+
return el.attrs.entries.size > 0 || el.attrs.spreads.length > 0;
|
|
2752
|
+
};
|
|
2753
|
+
var targetedByStructuralPseudo4 = (node, ctx) => {
|
|
2754
|
+
const el = asEl3(node);
|
|
2755
|
+
if (!el) return false;
|
|
2756
|
+
if (el.meta.targetedByStructuralPseudo) return true;
|
|
2757
|
+
return ctx.selectors.targetedByStructuralPseudo(el.id);
|
|
2758
|
+
};
|
|
2759
|
+
var DISPLAY3 = "display";
|
|
2760
|
+
var hasNonInlineDisplay = (node, ctx) => {
|
|
2761
|
+
const el = asEl3(node);
|
|
2762
|
+
if (!el) return false;
|
|
2763
|
+
const sm = ctx.computedOf(el) ?? el.computed;
|
|
2764
|
+
for (const block of sm.blocks.values()) {
|
|
2765
|
+
const decl = block.decls.get(DISPLAY3);
|
|
2766
|
+
if (decl && String(decl.value) !== "inline") return true;
|
|
2767
|
+
}
|
|
2768
|
+
return false;
|
|
2769
|
+
};
|
|
2770
|
+
var redundantInlineWrapper = definePattern({
|
|
2771
|
+
name: "redundant-inline-wrapper",
|
|
2772
|
+
category: "flatten/wrapper/redundant-inline-wrapper",
|
|
3672
2773
|
safety: 2,
|
|
3673
2774
|
doc: {
|
|
3674
|
-
title: "
|
|
3675
|
-
summary: "An
|
|
3676
|
-
before:
|
|
3677
|
-
after:
|
|
3678
|
-
safetyRationale: "
|
|
2775
|
+
title: "Flatten redundant inline wrapper",
|
|
2776
|
+
summary: "An inline span with no own visual/box style, no attributes beyond an inert class, exactly one element child, and no opacity barriers is removed; its sole child is hoisted in its place.",
|
|
2777
|
+
before: "<span><Child/></span>",
|
|
2778
|
+
after: "<Child/>",
|
|
2779
|
+
safetyRationale: "An empty inline box paints nothing and establishes no layout/paint/var context; with the inline default display and a single element child its removal changes no paint and no flow. The span carries no ref/handlers/dynamic-children/html/spread/component identity, owns no targetable attrs, and is not a combinator/structural-pseudo subject; inheritable styles are folded onto the child before removal."
|
|
3679
2780
|
},
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
2781
|
+
match: {
|
|
2782
|
+
tag: "span",
|
|
2783
|
+
onlyChild: "element",
|
|
2784
|
+
paintsNothing: true,
|
|
2785
|
+
where: [
|
|
2786
|
+
not(hasNonInlineDisplay),
|
|
2787
|
+
not(establishesContext2),
|
|
2788
|
+
not(hasOwnAttrs3),
|
|
2789
|
+
not(hasDynamicClasses),
|
|
2790
|
+
not(hasSpreadAttrs3),
|
|
2791
|
+
not(isComponentNode4),
|
|
2792
|
+
not(targetedByStructuralPseudo4)
|
|
2793
|
+
]
|
|
3691
2794
|
},
|
|
2795
|
+
rewrite: { flattenInto: "child" },
|
|
3692
2796
|
test: {
|
|
3693
2797
|
cases: [
|
|
3694
2798
|
{
|
|
3695
|
-
//
|
|
3696
|
-
//
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
after: '<div className="bg-red-200 size-10">box</div>'
|
|
2799
|
+
// An empty inline span paints nothing and establishes no context → a provably-safe flatten:
|
|
2800
|
+
// the span is removed and its sole child hoisted in place.
|
|
2801
|
+
before: '<span><a className="text-blue-500">Link</a></span>',
|
|
2802
|
+
after: '<a className="text-blue-500">Link</a>'
|
|
3700
2803
|
}
|
|
3701
2804
|
],
|
|
3702
|
-
|
|
3703
|
-
|
|
2805
|
+
noMatch: [
|
|
2806
|
+
// A ref pins the span's element identity (a hard opacity barrier) → not a passthrough.
|
|
2807
|
+
'<span ref={spanRef}><a className="text-blue-500">Link</a></span>',
|
|
2808
|
+
// The span paints its own background (own visual style) → kept.
|
|
2809
|
+
'<span className="bg-green-200"><a className="text-blue-500">Link</a></span>',
|
|
2810
|
+
// Non-inline display (inline-block) participates in layout differently → kept.
|
|
2811
|
+
'<span className="inline-block"><a className="text-blue-500">Link</a></span>'
|
|
2812
|
+
]
|
|
3704
2813
|
}
|
|
3705
2814
|
});
|
|
3706
2815
|
|
|
3707
2816
|
// ../patterns/src/_registry.generated.ts
|
|
3708
2817
|
var builtinPatterns = [
|
|
2818
|
+
flexCenterWrapper,
|
|
2819
|
+
redundantFragment,
|
|
2820
|
+
gridCenterWrapper,
|
|
3709
2821
|
displayContentsWrapper,
|
|
3710
2822
|
emptyStyleDiv,
|
|
3711
|
-
|
|
3712
|
-
inlineFlexCenterWrapper,
|
|
3713
|
-
nestedFlexMerge,
|
|
3714
|
-
nestedGridMerge,
|
|
2823
|
+
inheritedOnlyWrapper,
|
|
3715
2824
|
passthroughWrapper,
|
|
3716
|
-
|
|
3717
|
-
redundantInlineWrapper,
|
|
3718
|
-
borderRadiusShorthand,
|
|
3719
|
-
borderShorthand,
|
|
3720
|
-
dedupeClasses,
|
|
3721
|
-
gapShorthand,
|
|
3722
|
-
insetShorthand,
|
|
3723
|
-
marginShorthand,
|
|
3724
|
-
overflowShorthand,
|
|
3725
|
-
overscrollBehaviorShorthand,
|
|
3726
|
-
paddingShorthand,
|
|
3727
|
-
placeShorthand,
|
|
3728
|
-
scrollMarginShorthand,
|
|
3729
|
-
scrollPaddingShorthand,
|
|
3730
|
-
sizeShorthand
|
|
2825
|
+
redundantInlineWrapper
|
|
3731
2826
|
];
|
|
3732
2827
|
|
|
3733
2828
|
// ../resolver-tailwind/src/tailwind/fingerprint.ts
|
|
@@ -3778,14 +2873,312 @@ function synthesizeResidual(remaining, ctx) {
|
|
|
3778
2873
|
|
|
3779
2874
|
// ../resolver-tailwind/src/tailwind/engine.ts
|
|
3780
2875
|
var import_node_module = require("module");
|
|
2876
|
+
var path3 = __toESM(require("path"), 1);
|
|
2877
|
+
|
|
2878
|
+
// ../resolver-tailwind/src/tailwind/engine-v4.ts
|
|
2879
|
+
var import_node_fs2 = require("fs");
|
|
2880
|
+
var path2 = __toESM(require("path"), 1);
|
|
2881
|
+
|
|
2882
|
+
// ../resolver-tailwind/src/tailwind/v4-bridge.ts
|
|
2883
|
+
var import_node_child_process = require("child_process");
|
|
2884
|
+
var import_node_fs = require("fs");
|
|
2885
|
+
var import_node_os = require("os");
|
|
3781
2886
|
var path = __toESM(require("path"), 1);
|
|
2887
|
+
var CHILD_SOURCE = String.raw`
|
|
2888
|
+
import { createRequire } from 'node:module';
|
|
2889
|
+
import { pathToFileURL } from 'node:url';
|
|
2890
|
+
import * as fs from 'node:fs';
|
|
2891
|
+
import * as path from 'node:path';
|
|
2892
|
+
|
|
2893
|
+
function out(obj) { process.stdout.write(JSON.stringify(obj)); process.exit(0); }
|
|
2894
|
+
|
|
2895
|
+
let payload;
|
|
2896
|
+
try { payload = JSON.parse(fs.readFileSync(process.argv[2], 'utf8')); }
|
|
2897
|
+
catch { out({ ok: false }); }
|
|
2898
|
+
|
|
2899
|
+
const projectRoot = payload.projectRoot;
|
|
2900
|
+
const entries = payload.entries || [];
|
|
2901
|
+
const req = createRequire(path.join(projectRoot, '__domflax_tw4__.js'));
|
|
2902
|
+
|
|
2903
|
+
async function importFrom(id) {
|
|
2904
|
+
const resolved = req.resolve(id);
|
|
2905
|
+
return import(pathToFileURL(resolved).href);
|
|
2906
|
+
}
|
|
2907
|
+
|
|
2908
|
+
// Primary loader: @tailwindcss/node (the companion every v4 build tool installs). It resolves
|
|
2909
|
+
// '@import "tailwindcss"' and @theme against the project on disk.
|
|
2910
|
+
async function loadViaNode() {
|
|
2911
|
+
let mod;
|
|
2912
|
+
try { mod = await importFrom('@tailwindcss/node'); } catch { return null; }
|
|
2913
|
+
if (!mod || typeof mod.__unstable__loadDesignSystem !== 'function') return null;
|
|
2914
|
+
for (const e of entries) {
|
|
2915
|
+
try { return await mod.__unstable__loadDesignSystem(e.css, { base: e.base }); } catch {}
|
|
2916
|
+
}
|
|
2917
|
+
return null;
|
|
2918
|
+
}
|
|
2919
|
+
|
|
2920
|
+
// Secondary loader: bare 'tailwindcss' with a filesystem stylesheet resolver (best-effort).
|
|
2921
|
+
async function loadViaCore() {
|
|
2922
|
+
let tw;
|
|
2923
|
+
try { tw = await importFrom('tailwindcss'); } catch { return null; }
|
|
2924
|
+
if (!tw || typeof tw.__unstable__loadDesignSystem !== 'function') return null;
|
|
2925
|
+
const loadStylesheet = async (id, base) => {
|
|
2926
|
+
const r = createRequire(path.join(base, '__domflax_tw4__.js'));
|
|
2927
|
+
let p;
|
|
2928
|
+
const tries = id === 'tailwindcss' ? ['tailwindcss/index.css', 'tailwindcss'] : [id, id + '/index.css'];
|
|
2929
|
+
for (const t of tries) { try { p = r.resolve(t); break; } catch {} }
|
|
2930
|
+
if (!p) p = path.resolve(base, id);
|
|
2931
|
+
return { path: p, base: path.dirname(p), content: fs.readFileSync(p, 'utf8') };
|
|
2932
|
+
};
|
|
2933
|
+
const loadModule = async (id, base) => {
|
|
2934
|
+
const r = createRequire(path.join(base, '__domflax_tw4__.js'));
|
|
2935
|
+
const p = r.resolve(id);
|
|
2936
|
+
return { path: p, base: path.dirname(p), module: (await import(pathToFileURL(p).href)).default };
|
|
2937
|
+
};
|
|
2938
|
+
for (const e of entries) {
|
|
2939
|
+
try { return await tw.__unstable__loadDesignSystem(e.css, { base: e.base, loadStylesheet, loadModule }); } catch {}
|
|
2940
|
+
}
|
|
2941
|
+
return null;
|
|
2942
|
+
}
|
|
2943
|
+
|
|
2944
|
+
const ds = (await loadViaNode()) || (await loadViaCore());
|
|
2945
|
+
if (!ds) out({ ok: false });
|
|
2946
|
+
|
|
2947
|
+
let names = [];
|
|
2948
|
+
try {
|
|
2949
|
+
names = ds.getClassList().map((e) => (Array.isArray(e) ? e[0] : e)).filter((n) => typeof n === 'string');
|
|
2950
|
+
} catch { out({ ok: false }); }
|
|
2951
|
+
|
|
2952
|
+
let css = [];
|
|
2953
|
+
try { css = ds.candidatesToCss(names); } catch { out({ ok: false }); }
|
|
2954
|
+
|
|
2955
|
+
const result = [];
|
|
2956
|
+
for (let i = 0; i < names.length; i += 1) {
|
|
2957
|
+
const c = css[i];
|
|
2958
|
+
if (typeof c === 'string' && c.length > 0) result.push([names[i], c]);
|
|
2959
|
+
}
|
|
2960
|
+
out({ ok: true, entries: result });
|
|
2961
|
+
`;
|
|
2962
|
+
function runV4Bridge(payload) {
|
|
2963
|
+
let dir = null;
|
|
2964
|
+
try {
|
|
2965
|
+
dir = (0, import_node_fs.mkdtempSync)(path.join((0, import_node_os.tmpdir)(), "domflax-tw4-"));
|
|
2966
|
+
const scriptPath = path.join(dir, "bridge.mjs");
|
|
2967
|
+
const payloadPath = path.join(dir, "payload.json");
|
|
2968
|
+
(0, import_node_fs.writeFileSync)(scriptPath, CHILD_SOURCE, "utf8");
|
|
2969
|
+
(0, import_node_fs.writeFileSync)(payloadPath, JSON.stringify(payload), "utf8");
|
|
2970
|
+
const stdout = (0, import_node_child_process.execFileSync)(process.execPath, [scriptPath, payloadPath], {
|
|
2971
|
+
cwd: payload.projectRoot,
|
|
2972
|
+
encoding: "utf8",
|
|
2973
|
+
timeout: 9e4,
|
|
2974
|
+
maxBuffer: 256 * 1024 * 1024,
|
|
2975
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
2976
|
+
});
|
|
2977
|
+
const parsed = JSON.parse(stdout);
|
|
2978
|
+
if (!parsed.ok || !Array.isArray(parsed.entries) || parsed.entries.length === 0) return null;
|
|
2979
|
+
const entries = parsed.entries.filter(
|
|
2980
|
+
(e) => Array.isArray(e) && typeof e[0] === "string" && typeof e[1] === "string"
|
|
2981
|
+
);
|
|
2982
|
+
return entries.length > 0 ? { entries } : null;
|
|
2983
|
+
} catch {
|
|
2984
|
+
return null;
|
|
2985
|
+
} finally {
|
|
2986
|
+
if (dir) {
|
|
2987
|
+
try {
|
|
2988
|
+
(0, import_node_fs.rmSync)(dir, { recursive: true, force: true });
|
|
2989
|
+
} catch {
|
|
2990
|
+
}
|
|
2991
|
+
}
|
|
2992
|
+
}
|
|
2993
|
+
}
|
|
2994
|
+
|
|
2995
|
+
// ../resolver-tailwind/src/tailwind/v4-css.ts
|
|
2996
|
+
function stripComments(src) {
|
|
2997
|
+
return src.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
2998
|
+
}
|
|
2999
|
+
function toDecl(buffer) {
|
|
3000
|
+
const buf = buffer.trim();
|
|
3001
|
+
if (buf.length === 0 || buf[0] === "@") return null;
|
|
3002
|
+
const colon = buf.indexOf(":");
|
|
3003
|
+
if (colon <= 0) return null;
|
|
3004
|
+
const prop = buf.slice(0, colon).trim();
|
|
3005
|
+
let value = buf.slice(colon + 1).trim();
|
|
3006
|
+
if (prop.length === 0 || value.length === 0) return null;
|
|
3007
|
+
let important = false;
|
|
3008
|
+
const bang = /!\s*important\s*$/i.exec(value);
|
|
3009
|
+
if (bang) {
|
|
3010
|
+
important = true;
|
|
3011
|
+
value = value.slice(0, bang.index).trim();
|
|
3012
|
+
}
|
|
3013
|
+
return { type: "decl", prop, value, important };
|
|
3014
|
+
}
|
|
3015
|
+
function splitAtRule(prelude) {
|
|
3016
|
+
const m = /^@([A-Za-z-]+)\s*([\s\S]*)$/.exec(prelude);
|
|
3017
|
+
if (!m) return { name: prelude.slice(1).trim(), params: "" };
|
|
3018
|
+
return { name: m[1].toLowerCase(), params: m[2].trim() };
|
|
3019
|
+
}
|
|
3020
|
+
function parseBlock(src, start) {
|
|
3021
|
+
const nodes = [];
|
|
3022
|
+
let buf = "";
|
|
3023
|
+
let i = start;
|
|
3024
|
+
while (i < src.length) {
|
|
3025
|
+
const c = src[i];
|
|
3026
|
+
if (c === "{") {
|
|
3027
|
+
const prelude = buf.trim();
|
|
3028
|
+
buf = "";
|
|
3029
|
+
const inner = parseBlock(src, i + 1);
|
|
3030
|
+
i = inner.next;
|
|
3031
|
+
if (prelude.startsWith("@")) {
|
|
3032
|
+
const { name, params } = splitAtRule(prelude);
|
|
3033
|
+
nodes.push({ type: "atrule", name, params, nodes: inner.nodes });
|
|
3034
|
+
} else if (prelude.length > 0) {
|
|
3035
|
+
nodes.push({ type: "rule", selector: prelude, nodes: inner.nodes });
|
|
3036
|
+
}
|
|
3037
|
+
} else if (c === "}") {
|
|
3038
|
+
const d = toDecl(buf);
|
|
3039
|
+
if (d) nodes.push(d);
|
|
3040
|
+
return { nodes, next: i + 1 };
|
|
3041
|
+
} else if (c === ";") {
|
|
3042
|
+
const d = toDecl(buf);
|
|
3043
|
+
if (d) nodes.push(d);
|
|
3044
|
+
buf = "";
|
|
3045
|
+
i += 1;
|
|
3046
|
+
} else {
|
|
3047
|
+
buf += c;
|
|
3048
|
+
i += 1;
|
|
3049
|
+
}
|
|
3050
|
+
}
|
|
3051
|
+
const tail = toDecl(buf);
|
|
3052
|
+
if (tail) nodes.push(tail);
|
|
3053
|
+
return { nodes, next: i };
|
|
3054
|
+
}
|
|
3055
|
+
function resolveNesting(child, parent) {
|
|
3056
|
+
const c = child.trim();
|
|
3057
|
+
if (parent.length === 0) return c;
|
|
3058
|
+
if (c.includes("&")) return c.split("&").join(parent);
|
|
3059
|
+
return `${parent} ${c}`;
|
|
3060
|
+
}
|
|
3061
|
+
var DROP_ATRULES = /* @__PURE__ */ new Set(["property", "keyframes", "font-face", "charset", "import"]);
|
|
3062
|
+
function flattenNodes(nodes, selector, at, out) {
|
|
3063
|
+
const own = [];
|
|
3064
|
+
for (const n of nodes) if (n.type === "decl") own.push(n);
|
|
3065
|
+
if (own.length > 0 && selector.length > 0) out.push({ selector, at: [...at], decls: own });
|
|
3066
|
+
for (const n of nodes) {
|
|
3067
|
+
if (n.type === "rule") {
|
|
3068
|
+
flattenNodes(n.nodes, resolveNesting(n.selector, selector), at, out);
|
|
3069
|
+
} else if (n.type === "atrule") {
|
|
3070
|
+
if (n.name === "media") {
|
|
3071
|
+
flattenNodes(n.nodes, selector, [...at, { name: "media", params: n.params }], out);
|
|
3072
|
+
} else if (n.name === "layer") {
|
|
3073
|
+
flattenNodes(n.nodes, selector, at, out);
|
|
3074
|
+
} else if (!DROP_ATRULES.has(n.name)) {
|
|
3075
|
+
flattenNodes(n.nodes, selector, [...at, { name: n.name, params: n.params }], out);
|
|
3076
|
+
}
|
|
3077
|
+
}
|
|
3078
|
+
}
|
|
3079
|
+
}
|
|
3080
|
+
function leafToNode(leaf) {
|
|
3081
|
+
const declNodes = leaf.decls.map((d) => ({
|
|
3082
|
+
type: "decl",
|
|
3083
|
+
prop: d.prop,
|
|
3084
|
+
value: d.value,
|
|
3085
|
+
important: d.important
|
|
3086
|
+
}));
|
|
3087
|
+
let node = { type: "rule", selector: leaf.selector, nodes: declNodes };
|
|
3088
|
+
for (let i = leaf.at.length - 1; i >= 0; i -= 1) {
|
|
3089
|
+
node = { type: "atrule", name: leaf.at[i].name, params: leaf.at[i].params, nodes: [node] };
|
|
3090
|
+
}
|
|
3091
|
+
return node;
|
|
3092
|
+
}
|
|
3093
|
+
function parseUtilityCss(css) {
|
|
3094
|
+
try {
|
|
3095
|
+
const { nodes } = parseBlock(stripComments(css), 0);
|
|
3096
|
+
const leaves = [];
|
|
3097
|
+
flattenNodes(nodes, "", [], leaves);
|
|
3098
|
+
return leaves.map(leafToNode);
|
|
3099
|
+
} catch {
|
|
3100
|
+
return [];
|
|
3101
|
+
}
|
|
3102
|
+
}
|
|
3103
|
+
|
|
3104
|
+
// ../resolver-tailwind/src/tailwind/engine-v4.ts
|
|
3105
|
+
var SEARCH_DIRS = ["", "src", "app", "styles", "src/styles", "src/app", "app/styles", "assets/css", "css"];
|
|
3106
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", "out", ".next", "coverage"]);
|
|
3107
|
+
var ENTRY_RE = /@import\s+["']tailwindcss["']|@tailwind\b|@theme\b/;
|
|
3108
|
+
function scanDir(dir) {
|
|
3109
|
+
let names;
|
|
3110
|
+
try {
|
|
3111
|
+
names = (0, import_node_fs2.readdirSync)(dir);
|
|
3112
|
+
} catch {
|
|
3113
|
+
return null;
|
|
3114
|
+
}
|
|
3115
|
+
for (const name of names) {
|
|
3116
|
+
if (!name.toLowerCase().endsWith(".css")) continue;
|
|
3117
|
+
const file = path2.join(dir, name);
|
|
3118
|
+
try {
|
|
3119
|
+
if (!(0, import_node_fs2.statSync)(file).isFile()) continue;
|
|
3120
|
+
const css = (0, import_node_fs2.readFileSync)(file, "utf8");
|
|
3121
|
+
if (ENTRY_RE.test(css)) return { css, base: path2.dirname(file) };
|
|
3122
|
+
} catch {
|
|
3123
|
+
}
|
|
3124
|
+
}
|
|
3125
|
+
return null;
|
|
3126
|
+
}
|
|
3127
|
+
function findCssEntries(projectRoot) {
|
|
3128
|
+
const out = [];
|
|
3129
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3130
|
+
for (const rel of SEARCH_DIRS) {
|
|
3131
|
+
const dir = path2.resolve(projectRoot, rel);
|
|
3132
|
+
if (seen.has(dir) || [...SKIP_DIRS].some((s) => dir.includes(`${path2.sep}${s}`))) continue;
|
|
3133
|
+
seen.add(dir);
|
|
3134
|
+
const hit = scanDir(dir);
|
|
3135
|
+
if (hit) {
|
|
3136
|
+
out.push(hit);
|
|
3137
|
+
break;
|
|
3138
|
+
}
|
|
3139
|
+
}
|
|
3140
|
+
out.push({ css: '@import "tailwindcss";', base: projectRoot });
|
|
3141
|
+
return out;
|
|
3142
|
+
}
|
|
3143
|
+
function makeV4Engine(entries, version) {
|
|
3144
|
+
const cssByClass = new Map(entries.map(([name, css]) => [name, css]));
|
|
3145
|
+
const nodeCache = /* @__PURE__ */ new Map();
|
|
3146
|
+
const nodesFor = (token) => {
|
|
3147
|
+
let cached = nodeCache.get(token);
|
|
3148
|
+
if (!cached) {
|
|
3149
|
+
const css = cssByClass.get(token);
|
|
3150
|
+
cached = css ? parseUtilityCss(css) : [];
|
|
3151
|
+
nodeCache.set(token, cached);
|
|
3152
|
+
}
|
|
3153
|
+
return cached;
|
|
3154
|
+
};
|
|
3155
|
+
return {
|
|
3156
|
+
version,
|
|
3157
|
+
context: {
|
|
3158
|
+
// The resolver keeps only string entries; we hand it the concrete class names directly.
|
|
3159
|
+
getClassList: () => [...cssByClass.keys()]
|
|
3160
|
+
},
|
|
3161
|
+
generate(candidates) {
|
|
3162
|
+
const out = [];
|
|
3163
|
+
for (const c of candidates) for (const n of nodesFor(c)) out.push(n);
|
|
3164
|
+
return out;
|
|
3165
|
+
}
|
|
3166
|
+
};
|
|
3167
|
+
}
|
|
3168
|
+
function loadV4Engine(projectRoot, version) {
|
|
3169
|
+
const snapshot = runV4Bridge({ projectRoot, entries: findCssEntries(projectRoot) });
|
|
3170
|
+
if (!snapshot) return null;
|
|
3171
|
+
return makeV4Engine(snapshot.entries, version);
|
|
3172
|
+
}
|
|
3173
|
+
|
|
3174
|
+
// ../resolver-tailwind/src/tailwind/engine.ts
|
|
3782
3175
|
function moduleBase() {
|
|
3783
3176
|
return typeof __filename === "string" ? __filename : importMetaUrl;
|
|
3784
3177
|
}
|
|
3785
3178
|
function projectRequire(projectRoot) {
|
|
3786
3179
|
const bases = [];
|
|
3787
|
-
if (projectRoot) bases.push(
|
|
3788
|
-
bases.push(
|
|
3180
|
+
if (projectRoot) bases.push(path3.join(projectRoot, "__domflax__.js"));
|
|
3181
|
+
bases.push(path3.join(process.cwd(), "__domflax__.js"));
|
|
3789
3182
|
bases.push(moduleBase());
|
|
3790
3183
|
for (const base of bases) {
|
|
3791
3184
|
try {
|
|
@@ -3797,14 +3190,36 @@ function projectRequire(projectRoot) {
|
|
|
3797
3190
|
}
|
|
3798
3191
|
return null;
|
|
3799
3192
|
}
|
|
3193
|
+
var FIRST_UNSUPPORTED_MAJOR = 4;
|
|
3194
|
+
function majorOf(version) {
|
|
3195
|
+
const m = /^\s*(\d+)/.exec(version);
|
|
3196
|
+
return m ? Number(m[1]) : null;
|
|
3197
|
+
}
|
|
3800
3198
|
function loadEngine(options) {
|
|
3801
3199
|
const req = projectRequire(options.projectRoot);
|
|
3802
|
-
if (!req) return null;
|
|
3200
|
+
if (!req) return { engine: null, version: null, unsupportedMajor: null };
|
|
3201
|
+
let version = null;
|
|
3202
|
+
try {
|
|
3203
|
+
version = req("tailwindcss/package.json").version;
|
|
3204
|
+
} catch {
|
|
3205
|
+
return { engine: null, version: null, unsupportedMajor: null };
|
|
3206
|
+
}
|
|
3207
|
+
const major = majorOf(version);
|
|
3208
|
+
if (major !== null && major >= FIRST_UNSUPPORTED_MAJOR) {
|
|
3209
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
3210
|
+
let v4 = null;
|
|
3211
|
+
try {
|
|
3212
|
+
v4 = loadV4Engine(projectRoot, version);
|
|
3213
|
+
} catch {
|
|
3214
|
+
v4 = null;
|
|
3215
|
+
}
|
|
3216
|
+
if (v4) return { engine: v4, version, unsupportedMajor: null };
|
|
3217
|
+
return { engine: null, version, unsupportedMajor: major };
|
|
3218
|
+
}
|
|
3803
3219
|
try {
|
|
3804
3220
|
const resolveConfig = req("tailwindcss/resolveConfig.js");
|
|
3805
3221
|
const { createContext } = req("tailwindcss/lib/lib/setupContextUtils.js");
|
|
3806
3222
|
const { generateRules } = req("tailwindcss/lib/lib/generateRules.js");
|
|
3807
|
-
const pkg = req("tailwindcss/package.json");
|
|
3808
3223
|
let userConfig = options.config ?? { content: [{ raw: "" }] };
|
|
3809
3224
|
if (options.configPath !== void 0) {
|
|
3810
3225
|
const loadConfig = req("tailwindcss/loadConfig.js");
|
|
@@ -3813,15 +3228,19 @@ function loadEngine(options) {
|
|
|
3813
3228
|
const resolved = resolveConfig(userConfig);
|
|
3814
3229
|
const context = createContext(resolved);
|
|
3815
3230
|
return {
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3231
|
+
engine: {
|
|
3232
|
+
version,
|
|
3233
|
+
context,
|
|
3234
|
+
generate(candidates) {
|
|
3235
|
+
const rules = generateRules(new Set(candidates), context);
|
|
3236
|
+
return rules.map(([, node]) => node);
|
|
3237
|
+
}
|
|
3238
|
+
},
|
|
3239
|
+
version,
|
|
3240
|
+
unsupportedMajor: null
|
|
3822
3241
|
};
|
|
3823
3242
|
} catch {
|
|
3824
|
-
return null;
|
|
3243
|
+
return { engine: null, version, unsupportedMajor: null };
|
|
3825
3244
|
}
|
|
3826
3245
|
}
|
|
3827
3246
|
|
|
@@ -4000,22 +3419,39 @@ var DROPPABLE_USAGE = {
|
|
|
4000
3419
|
};
|
|
4001
3420
|
|
|
4002
3421
|
// ../resolver-tailwind/src/tailwind/resolver.ts
|
|
3422
|
+
var warnedUnsupported = /* @__PURE__ */ new Set();
|
|
4003
3423
|
var TailwindResolver = class {
|
|
4004
3424
|
id = "tailwind";
|
|
4005
3425
|
provider;
|
|
4006
3426
|
fingerprint;
|
|
3427
|
+
/**
|
|
3428
|
+
* SAFETY (Layer 1): the detected Tailwind MAJOR when the project's version is one this resolver
|
|
3429
|
+
* cannot drive (v4+), else `null`. When set, {@link resolve} reports every token as unknown, so
|
|
3430
|
+
* downstream files are left unchanged (never mis-optimized). Exposed for diagnostics/tests.
|
|
3431
|
+
*/
|
|
3432
|
+
unsupportedMajor;
|
|
4007
3433
|
#engine;
|
|
4008
3434
|
/** Per-token extraction cache (engine output is pure for a fixed config). */
|
|
4009
3435
|
#tokenCache = /* @__PURE__ */ new Map();
|
|
4010
3436
|
/** Per-class-set forward-resolution cache. */
|
|
4011
3437
|
#resolveCache = /* @__PURE__ */ new Map();
|
|
4012
|
-
/** Lazily built reverse index for {@link emit}. */
|
|
3438
|
+
/** Lazily built reverse index for the greedy {@link emit} fallback. */
|
|
4013
3439
|
#reverseIndex = null;
|
|
3440
|
+
/** Lazily built cover vocabulary (base-condition tuple sets) for the exact-cover engine. */
|
|
3441
|
+
#coverVocab = null;
|
|
4014
3442
|
constructor(config = {}) {
|
|
4015
|
-
|
|
4016
|
-
this
|
|
3443
|
+
const loaded = loadEngine(config);
|
|
3444
|
+
this.#engine = loaded.engine;
|
|
3445
|
+
this.unsupportedMajor = loaded.unsupportedMajor;
|
|
3446
|
+
this.provider = config.provider ?? (loaded.version ? `tailwindcss@${loaded.version}` : "tailwindcss");
|
|
4017
3447
|
const seed = JSON.stringify(config.config ?? {}) + (config.configPath ?? "");
|
|
4018
3448
|
this.fingerprint = config.fingerprint ?? `${this.provider}/${fnv1a(seed)}`;
|
|
3449
|
+
if (this.unsupportedMajor !== null && !warnedUnsupported.has(this.provider)) {
|
|
3450
|
+
warnedUnsupported.add(this.provider);
|
|
3451
|
+
console.warn(
|
|
3452
|
+
`domflax: detected Tailwind v${this.unsupportedMajor} (${this.provider}) but could not load its design system (is @tailwindcss/node installed?); classes cannot be resolved, so files are left unchanged to avoid unsafe edits.`
|
|
3453
|
+
);
|
|
3454
|
+
}
|
|
4019
3455
|
}
|
|
4020
3456
|
/** Engine-backed, cached single-token extraction. */
|
|
4021
3457
|
#extract(token) {
|
|
@@ -4124,9 +3560,75 @@ var TailwindResolver = class {
|
|
|
4124
3560
|
this.#reverseIndex = index;
|
|
4125
3561
|
return index;
|
|
4126
3562
|
}
|
|
3563
|
+
/**
|
|
3564
|
+
* The cover vocabulary: every base-condition, plain-subject utility mapped to the {@link tupleKey}s
|
|
3565
|
+
* of its full normalized-longhand declaration set. Built once from a SINGLE engine `generate` over
|
|
3566
|
+
* the enumerable class list (grouped by selector), so it is the same cost as {@link #buildReverseIndex}.
|
|
3567
|
+
* This is what the provider-uniform exact-cover engine searches; the element's own droppable tokens
|
|
3568
|
+
* are members of it, guaranteeing feasibility. Variant / combinator / pseudo utilities are excluded
|
|
3569
|
+
* (their effect is not the element's own base box), so a target carrying such conditions simply finds
|
|
3570
|
+
* no cover and falls back to the greedy emit.
|
|
3571
|
+
*/
|
|
3572
|
+
#buildCoverVocab() {
|
|
3573
|
+
if (this.#coverVocab) return this.#coverVocab;
|
|
3574
|
+
const baseCk = String(conditionKey(BASE_CONDITION));
|
|
3575
|
+
const out = [];
|
|
3576
|
+
if (this.#engine) {
|
|
3577
|
+
try {
|
|
3578
|
+
const classes = this.#engine.context.getClassList().filter((c) => typeof c === "string");
|
|
3579
|
+
const nodes = this.#engine.generate(classes);
|
|
3580
|
+
for (const node of nodes) {
|
|
3581
|
+
if (node.type !== "rule") continue;
|
|
3582
|
+
const rule = node;
|
|
3583
|
+
const parsed = parseSelector(rule.selector);
|
|
3584
|
+
if (parsed.kind !== "simple" || parsed.states.length > 0 || parsed.pseudoElement !== "") {
|
|
3585
|
+
continue;
|
|
3586
|
+
}
|
|
3587
|
+
const className = unescapeClass(rule.selector);
|
|
3588
|
+
if (className === null) continue;
|
|
3589
|
+
const tuples = [];
|
|
3590
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3591
|
+
for (const child of rule.nodes ?? []) {
|
|
3592
|
+
if (child.type !== "decl") continue;
|
|
3593
|
+
const d = child;
|
|
3594
|
+
if (typeof d.value !== "string") continue;
|
|
3595
|
+
for (const decl of normalizer.normalizeDeclaration(d.prop, d.value, d.important === true)) {
|
|
3596
|
+
const k = tupleKey(baseCk, String(decl.property), String(decl.value), decl.important);
|
|
3597
|
+
if (!seen.has(k)) {
|
|
3598
|
+
seen.add(k);
|
|
3599
|
+
tuples.push(k);
|
|
3600
|
+
}
|
|
3601
|
+
}
|
|
3602
|
+
}
|
|
3603
|
+
if (tuples.length > 0) out.push({ token: className, tuples });
|
|
3604
|
+
}
|
|
3605
|
+
} catch {
|
|
3606
|
+
}
|
|
3607
|
+
}
|
|
3608
|
+
this.#coverVocab = out;
|
|
3609
|
+
return out;
|
|
3610
|
+
}
|
|
3611
|
+
/**
|
|
3612
|
+
* Try the minimal-string exact-cover engine over the WHOLE utility vocabulary. On success the chosen
|
|
3613
|
+
* set is verified by the mandatory CORRECTNESS BACKSTOP — re-resolve it and assert it reproduces the
|
|
3614
|
+
* target's tuples EXACTLY — before it is returned; any mismatch (or no cover / oversize universe)
|
|
3615
|
+
* yields `null` so {@link emit} uses its greedy fallback. Never returns a set that misrepresents `U`.
|
|
3616
|
+
*/
|
|
3617
|
+
#tryCover(normalized, norm) {
|
|
3618
|
+
const universe = styleMapTuples(normalized, norm);
|
|
3619
|
+
if (universe.length === 0) return { classes: [], exact: true, warnings: [] };
|
|
3620
|
+
const chosen = minStringCover(universe, this.#buildCoverVocab());
|
|
3621
|
+
if (!chosen || chosen.length === 0) return null;
|
|
3622
|
+
const reTuples = new Set(styleMapTuples(this.resolve({ classes: chosen }).styles, norm));
|
|
3623
|
+
if (reTuples.size !== universe.length) return null;
|
|
3624
|
+
for (const t of universe) if (!reTuples.has(t)) return null;
|
|
3625
|
+
return { classes: chosen, exact: true, warnings: [] };
|
|
3626
|
+
}
|
|
4127
3627
|
emit(styles, ctx) {
|
|
4128
3628
|
const norm = ctx.normalizer ?? normalizer;
|
|
4129
3629
|
const normalized = norm.normalizeStyleMap(styles);
|
|
3630
|
+
const cover = this.#tryCover(normalized, norm);
|
|
3631
|
+
if (cover) return cover;
|
|
4130
3632
|
const base = normalized.blocks.get(conditionKey(BASE_CONDITION));
|
|
4131
3633
|
if (!base || base.decls.size === 0) return { classes: [], exact: true, warnings: [] };
|
|
4132
3634
|
const hasNonBase = normalized.blocks.size > 1;
|
|
@@ -4156,13 +3658,13 @@ var TailwindResolver = class {
|
|
|
4156
3658
|
let bestCover = 0;
|
|
4157
3659
|
for (const entry of candidates) {
|
|
4158
3660
|
const [token, declMap] = entry;
|
|
4159
|
-
let
|
|
4160
|
-
for (const prop of declMap.keys()) if (remaining.has(prop))
|
|
4161
|
-
if (
|
|
4162
|
-
const better = best === null ||
|
|
3661
|
+
let cover2 = 0;
|
|
3662
|
+
for (const prop of declMap.keys()) if (remaining.has(prop)) cover2 += 1;
|
|
3663
|
+
if (cover2 === 0) continue;
|
|
3664
|
+
const better = best === null || cover2 > bestCover || cover2 === bestCover && declMap.size < best[1].size || cover2 === bestCover && declMap.size === best[1].size && token < best[0];
|
|
4163
3665
|
if (better) {
|
|
4164
3666
|
best = entry;
|
|
4165
|
-
bestCover =
|
|
3667
|
+
bestCover = cover2;
|
|
4166
3668
|
}
|
|
4167
3669
|
}
|
|
4168
3670
|
if (!best) break;
|
|
@@ -4233,14 +3735,14 @@ var LEGACY_PSEUDO_ELEMENTS2 = /* @__PURE__ */ new Set([
|
|
|
4233
3735
|
|
|
4234
3736
|
// ../resolver-css/src/engine.ts
|
|
4235
3737
|
var import_node_module2 = require("module");
|
|
4236
|
-
var
|
|
3738
|
+
var path4 = __toESM(require("path"), 1);
|
|
4237
3739
|
function moduleBase2() {
|
|
4238
3740
|
return typeof __filename === "string" ? __filename : importMetaUrl;
|
|
4239
3741
|
}
|
|
4240
3742
|
function loadPostcssEngine(projectRoot) {
|
|
4241
3743
|
const bases = [];
|
|
4242
|
-
if (projectRoot) bases.push(
|
|
4243
|
-
bases.push(
|
|
3744
|
+
if (projectRoot) bases.push(path4.join(projectRoot, "__domflax__.js"));
|
|
3745
|
+
bases.push(path4.join(process.cwd(), "__domflax__.js"));
|
|
4244
3746
|
bases.push(moduleBase2());
|
|
4245
3747
|
for (const base of bases) {
|
|
4246
3748
|
try {
|
|
@@ -4293,15 +3795,15 @@ function collectDecls(rule) {
|
|
|
4293
3795
|
}
|
|
4294
3796
|
|
|
4295
3797
|
// ../resolver-css/src/misc-helpers.ts
|
|
4296
|
-
var
|
|
3798
|
+
var import_node_fs3 = require("fs");
|
|
4297
3799
|
function isPlainClassToken(token) {
|
|
4298
3800
|
return token.length > 0 && !/[\s.#>+~:[\]()]/.test(token);
|
|
4299
3801
|
}
|
|
4300
|
-
function readCssPath(
|
|
3802
|
+
function readCssPath(path5) {
|
|
4301
3803
|
try {
|
|
4302
|
-
return { id:
|
|
3804
|
+
return { id: path5, css: (0, import_node_fs3.readFileSync)(path5, "utf8") };
|
|
4303
3805
|
} catch (cause) {
|
|
4304
|
-
throw new Error(`resolver-css: cannot read CSS file "${
|
|
3806
|
+
throw new Error(`resolver-css: cannot read CSS file "${path5}"`, { cause });
|
|
4305
3807
|
}
|
|
4306
3808
|
}
|
|
4307
3809
|
function deriveFingerprint(provider, files) {
|
|
@@ -4356,6 +3858,8 @@ var CustomCSSResolver = class {
|
|
|
4356
3858
|
/** Distinct COMPLEX selectors (combinator or structural pseudo), sorted. */
|
|
4357
3859
|
#complex;
|
|
4358
3860
|
#reverse = null;
|
|
3861
|
+
/** Lazily built cover vocabulary (full condition-keyed tuple sets) for the exact-cover engine. */
|
|
3862
|
+
#coverVocab = null;
|
|
4359
3863
|
constructor(cssFiles = [], options = {}) {
|
|
4360
3864
|
ensurePostcss(options.projectRoot);
|
|
4361
3865
|
const fromDisk = (options.files ?? []).map(readCssPath);
|
|
@@ -4388,8 +3892,23 @@ var CustomCSSResolver = class {
|
|
|
4388
3892
|
}
|
|
4389
3893
|
emit(styles, ctx) {
|
|
4390
3894
|
const norm = ctx.normalizer ?? normalizer;
|
|
3895
|
+
const normalized = norm.normalizeStyleMap(styles);
|
|
3896
|
+
const universe = styleMapTuples(normalized, norm);
|
|
3897
|
+
if (universe.length === 0) return { classes: [], exact: true, warnings: [] };
|
|
3898
|
+
const chosen = minStringCover(universe, this.#buildCoverVocab());
|
|
3899
|
+
if (chosen && chosen.length > 0) {
|
|
3900
|
+
const reTuples = new Set(styleMapTuples(this.resolve({ classes: chosen }).styles, norm));
|
|
3901
|
+
let ok = reTuples.size === universe.length;
|
|
3902
|
+
if (ok) {
|
|
3903
|
+
for (const t of universe) if (!reTuples.has(t)) {
|
|
3904
|
+
ok = false;
|
|
3905
|
+
break;
|
|
3906
|
+
}
|
|
3907
|
+
}
|
|
3908
|
+
if (ok) return { classes: chosen, exact: true, warnings: [] };
|
|
3909
|
+
}
|
|
4391
3910
|
const remaining = /* @__PURE__ */ new Map();
|
|
4392
|
-
for (const [ck, block] of
|
|
3911
|
+
for (const [ck, block] of normalized.blocks) {
|
|
4393
3912
|
for (const [prop, decl] of block.decls) {
|
|
4394
3913
|
remaining.set(`${ck} ${prop}`, String(decl.value));
|
|
4395
3914
|
}
|
|
@@ -4599,7 +4118,23 @@ var CustomCSSResolver = class {
|
|
|
4599
4118
|
if (rawBlocks.size === 0) return emptyStyleMap();
|
|
4600
4119
|
return normalizer.normalizeStyleMap({ blocks: rawBlocks });
|
|
4601
4120
|
}
|
|
4602
|
-
/**
|
|
4121
|
+
/**
|
|
4122
|
+
* Build (once) the cover vocabulary for the exact-cover engine: every forward-resolvable class
|
|
4123
|
+
* mapped to the {@link styleMapTuples} of its full (condition-keyed, `!important`-aware) declaration
|
|
4124
|
+
* set. Unlike {@link #reverseIndex} this carries ALL style conditions and the important flag, so the
|
|
4125
|
+
* engine can pick a custom class covering hover/media declarations too.
|
|
4126
|
+
*/
|
|
4127
|
+
#buildCoverVocab() {
|
|
4128
|
+
if (this.#coverVocab) return this.#coverVocab;
|
|
4129
|
+
const out = [];
|
|
4130
|
+
for (const token of this.#classIndex.keys()) {
|
|
4131
|
+
const tuples = styleMapTuples(this.#resolveTokens([token], [token]), normalizer);
|
|
4132
|
+
if (tuples.length > 0) out.push({ token, tuples });
|
|
4133
|
+
}
|
|
4134
|
+
this.#coverVocab = out;
|
|
4135
|
+
return out;
|
|
4136
|
+
}
|
|
4137
|
+
/** Build (once) the reverse index used by the greedy {@link emit} fallback. */
|
|
4603
4138
|
#reverseIndex() {
|
|
4604
4139
|
if (this.#reverse) return this.#reverse;
|
|
4605
4140
|
const out = [];
|
|
@@ -4624,11 +4159,350 @@ function createCssResolver(cssFiles = [], options) {
|
|
|
4624
4159
|
var import_node_path = require("path");
|
|
4625
4160
|
var import_node_url = require("url");
|
|
4626
4161
|
|
|
4162
|
+
// ../frontend-html/src/backend.ts
|
|
4163
|
+
var import_magic_string = __toESM(require("magic-string"), 1);
|
|
4164
|
+
function staticTokensOf2(classes) {
|
|
4165
|
+
const out = [];
|
|
4166
|
+
for (const seg of classes.segments) {
|
|
4167
|
+
if (seg.kind === "static") for (const t of seg.tokens) out.push(t.value);
|
|
4168
|
+
}
|
|
4169
|
+
return out;
|
|
4170
|
+
}
|
|
4171
|
+
function sameTokens2(a, b) {
|
|
4172
|
+
if (a.length !== b.length) return false;
|
|
4173
|
+
for (let i = 0; i < a.length; i += 1) if (a[i] !== b[i]) return false;
|
|
4174
|
+
return true;
|
|
4175
|
+
}
|
|
4176
|
+
function primarySource(doc) {
|
|
4177
|
+
for (const sf of doc.sources.values()) {
|
|
4178
|
+
if (typeof sf.text === "string" && sf.text.length > 0) return sf;
|
|
4179
|
+
}
|
|
4180
|
+
return null;
|
|
4181
|
+
}
|
|
4182
|
+
function collectKept(doc) {
|
|
4183
|
+
const out = [];
|
|
4184
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4185
|
+
const visit = (id) => {
|
|
4186
|
+
if (seen.has(id)) return;
|
|
4187
|
+
seen.add(id);
|
|
4188
|
+
const n = doc.nodes.get(id);
|
|
4189
|
+
if (!n) return;
|
|
4190
|
+
out.push(n);
|
|
4191
|
+
if (n.kind === "element" || n.kind === "fragment") for (const c of n.children) visit(c);
|
|
4192
|
+
};
|
|
4193
|
+
visit(doc.root);
|
|
4194
|
+
return out;
|
|
4195
|
+
}
|
|
4196
|
+
function strictlyContains(a, b) {
|
|
4197
|
+
if (a.file !== b.file) return false;
|
|
4198
|
+
if (a.start <= b.start && b.end <= a.end) return !(a.start === b.start && a.end === b.end);
|
|
4199
|
+
return false;
|
|
4200
|
+
}
|
|
4201
|
+
function backrefIds(doc) {
|
|
4202
|
+
const out = [];
|
|
4203
|
+
const max = doc.alloc.peek;
|
|
4204
|
+
for (let i = 1; i < max; i += 1) {
|
|
4205
|
+
const id = i;
|
|
4206
|
+
if (doc.backref.get(id)) out.push(id);
|
|
4207
|
+
}
|
|
4208
|
+
return out;
|
|
4209
|
+
}
|
|
4210
|
+
function currentTokens(sf, valueSpan) {
|
|
4211
|
+
const raw = sf.text.slice(valueSpan.start, valueSpan.end).trim();
|
|
4212
|
+
const unquoted = raw.replace(/^['"]/, "").replace(/['"]$/, "");
|
|
4213
|
+
return unquoted.split(/\s+/).filter((t) => t.length > 0);
|
|
4214
|
+
}
|
|
4215
|
+
function editClasses(ms, doc, sf, el) {
|
|
4216
|
+
const classes = el.classes;
|
|
4217
|
+
if (classes.hasDynamic || classes.opaque) return false;
|
|
4218
|
+
const tokens = staticTokensOf2(classes);
|
|
4219
|
+
const valueSpan = classes.valueSpan;
|
|
4220
|
+
if (valueSpan && valueSpan.file === sf.id) {
|
|
4221
|
+
if (sameTokens2(currentTokens(sf, valueSpan), tokens)) return false;
|
|
4222
|
+
const current = sf.text.slice(valueSpan.start, valueSpan.end).trim();
|
|
4223
|
+
const quote = current.startsWith("'") ? "'" : '"';
|
|
4224
|
+
ms.overwrite(valueSpan.start, valueSpan.end, `${quote}${tokens.join(" ")}${quote}`);
|
|
4225
|
+
return true;
|
|
4226
|
+
}
|
|
4227
|
+
if (tokens.length === 0) return false;
|
|
4228
|
+
const openTag = doc.backref.get(el.id)?.openTagSpan;
|
|
4229
|
+
if (!openTag || openTag.file !== sf.id) return false;
|
|
4230
|
+
ms.appendLeft(openTag.start + 1 + el.tag.length, ` class="${tokens.join(" ")}"`);
|
|
4231
|
+
return true;
|
|
4232
|
+
}
|
|
4233
|
+
function surgicalPrint(doc) {
|
|
4234
|
+
const sf = primarySource(doc);
|
|
4235
|
+
if (!sf) return null;
|
|
4236
|
+
const ms = new import_magic_string.default(sf.text);
|
|
4237
|
+
const kept = collectKept(doc);
|
|
4238
|
+
const keptSpans = [];
|
|
4239
|
+
for (const n of kept) if (n.span && n.span.file === sf.id) keptSpans.push(n.span);
|
|
4240
|
+
const removed = [];
|
|
4241
|
+
for (const id of backrefIds(doc)) {
|
|
4242
|
+
if (doc.nodes.has(id)) continue;
|
|
4243
|
+
const back = doc.backref.get(id);
|
|
4244
|
+
if (!back || back.span.file !== sf.id) continue;
|
|
4245
|
+
const unwrapped = keptSpans.some((k) => strictlyContains(back.span, k));
|
|
4246
|
+
removed.push({ backref: back, unwrapped });
|
|
4247
|
+
}
|
|
4248
|
+
const fullRemovals = removed.filter((r) => !r.unwrapped).map((r) => r.backref.span);
|
|
4249
|
+
for (const r of removed) {
|
|
4250
|
+
const s = r.backref.span;
|
|
4251
|
+
if (fullRemovals.some((f) => f !== s && strictlyContains(f, s))) continue;
|
|
4252
|
+
if (r.unwrapped) {
|
|
4253
|
+
const open = r.backref.openTagSpan;
|
|
4254
|
+
const close = r.backref.closeTagSpan;
|
|
4255
|
+
if (open && open.file === sf.id && open.end > open.start) ms.remove(open.start, open.end);
|
|
4256
|
+
if (close && close.file === sf.id && close.end > close.start) ms.remove(close.start, close.end);
|
|
4257
|
+
} else {
|
|
4258
|
+
ms.remove(s.start, s.end);
|
|
4259
|
+
}
|
|
4260
|
+
}
|
|
4261
|
+
for (const n of kept) if (n.kind === "element") editClasses(ms, doc, sf, n);
|
|
4262
|
+
return ms.toString();
|
|
4263
|
+
}
|
|
4264
|
+
function doPrint(doc) {
|
|
4265
|
+
return surgicalPrint(doc) ?? "";
|
|
4266
|
+
}
|
|
4267
|
+
|
|
4268
|
+
// ../frontend-html/src/parse.ts
|
|
4269
|
+
var import_node_module3 = require("module");
|
|
4270
|
+
|
|
4271
|
+
// ../frontend-html/src/walk.ts
|
|
4272
|
+
var HTML_LANGS = ["html"];
|
|
4273
|
+
var FILE_ID = 1;
|
|
4274
|
+
function looksLikeHtml(id, code) {
|
|
4275
|
+
if (/\.html?$/i.test(id)) return true;
|
|
4276
|
+
const head = code.slice(0, 256).trimStart().toLowerCase();
|
|
4277
|
+
return head.startsWith("<!doctype html") || head.startsWith("<html") || head.startsWith("<");
|
|
4278
|
+
}
|
|
4279
|
+
var OPAQUE_SUBTREE_TAGS = /* @__PURE__ */ new Set([
|
|
4280
|
+
"script",
|
|
4281
|
+
"style",
|
|
4282
|
+
"template",
|
|
4283
|
+
"svg",
|
|
4284
|
+
"pre",
|
|
4285
|
+
"textarea"
|
|
4286
|
+
]);
|
|
4287
|
+
function isOpaqueSubtreeTag(tag) {
|
|
4288
|
+
return OPAQUE_SUBTREE_TAGS.has(tag);
|
|
4289
|
+
}
|
|
4290
|
+
function elementIsOpaque(attrs) {
|
|
4291
|
+
for (const a of attrs) {
|
|
4292
|
+
const n = a.name.toLowerCase();
|
|
4293
|
+
if (n === "id" || n === "contenteditable") return true;
|
|
4294
|
+
if (n.startsWith("on")) return true;
|
|
4295
|
+
}
|
|
4296
|
+
return false;
|
|
4297
|
+
}
|
|
4298
|
+
function hasEventHandler(attrs) {
|
|
4299
|
+
for (const a of attrs) if (/^on/i.test(a.name)) return true;
|
|
4300
|
+
return false;
|
|
4301
|
+
}
|
|
4302
|
+
function span(start, end) {
|
|
4303
|
+
return { file: FILE_ID, start, end };
|
|
4304
|
+
}
|
|
4305
|
+
function attrsLocOf(loc) {
|
|
4306
|
+
if (!loc) return void 0;
|
|
4307
|
+
return loc.startTag?.attrs ?? loc.attrs;
|
|
4308
|
+
}
|
|
4309
|
+
function classValueSpan(loc, source) {
|
|
4310
|
+
const attrsLoc = attrsLocOf(loc);
|
|
4311
|
+
const cl = attrsLoc?.["class"];
|
|
4312
|
+
if (!cl) return null;
|
|
4313
|
+
const text = source.slice(cl.startOffset, cl.endOffset);
|
|
4314
|
+
const eq = text.indexOf("=");
|
|
4315
|
+
if (eq === -1) return null;
|
|
4316
|
+
let i = eq + 1;
|
|
4317
|
+
while (i < text.length && /\s/.test(text[i])) i += 1;
|
|
4318
|
+
if (i >= text.length) return null;
|
|
4319
|
+
return span(cl.startOffset + i, cl.endOffset);
|
|
4320
|
+
}
|
|
4321
|
+
|
|
4322
|
+
// ../frontend-html/src/parse.ts
|
|
4323
|
+
var cachedParse5 = null;
|
|
4324
|
+
function loadParse5() {
|
|
4325
|
+
if (cachedParse5) return cachedParse5;
|
|
4326
|
+
const req = (0, import_node_module3.createRequire)(importMetaUrl);
|
|
4327
|
+
cachedParse5 = req("parse5");
|
|
4328
|
+
return cachedParse5;
|
|
4329
|
+
}
|
|
4330
|
+
function doParse(code, ctx) {
|
|
4331
|
+
const diagnostics = [];
|
|
4332
|
+
const doc = createDocument("html");
|
|
4333
|
+
const backref = doc.backref;
|
|
4334
|
+
const parse5 = loadParse5();
|
|
4335
|
+
const document2 = parse5.parse(code, { sourceCodeLocationInfo: true });
|
|
4336
|
+
const eol = code.includes("\r\n") ? "\r\n" : "\n";
|
|
4337
|
+
const sourceFile = {
|
|
4338
|
+
id: FILE_ID,
|
|
4339
|
+
path: ctx.id,
|
|
4340
|
+
text: code,
|
|
4341
|
+
frontend: "html",
|
|
4342
|
+
eol,
|
|
4343
|
+
indentUnit: " ",
|
|
4344
|
+
native: document2
|
|
4345
|
+
};
|
|
4346
|
+
doc.sources.set(FILE_ID, sourceFile);
|
|
4347
|
+
const resolveComputed = (tokens, tag, nodeId, meta) => {
|
|
4348
|
+
if (tokens.length === 0) return emptyStyleMap();
|
|
4349
|
+
const res = ctx.resolver.resolve({ classes: tokens, element: { tagName: tag, namespace: "html" } });
|
|
4350
|
+
if (res.unknown.length > 0) meta.hasUnresolvedClasses = true;
|
|
4351
|
+
for (const w of res.warnings) {
|
|
4352
|
+
diagnostics.push({
|
|
4353
|
+
code: "DF_STYLE_CONFLICT_UNRESOLVED",
|
|
4354
|
+
severity: w.severity,
|
|
4355
|
+
message: w.message,
|
|
4356
|
+
nodeId
|
|
4357
|
+
});
|
|
4358
|
+
}
|
|
4359
|
+
return ctx.normalizer.normalizeStyleMap(res.styles);
|
|
4360
|
+
};
|
|
4361
|
+
const splitTokens = (raw) => raw.split(/\s+/).filter((t) => t.length > 0);
|
|
4362
|
+
const appendChild = (node, parentId, out) => {
|
|
4363
|
+
const name = node.nodeName;
|
|
4364
|
+
if (name === "#text") {
|
|
4365
|
+
const value = node.value ?? "";
|
|
4366
|
+
const id = doc.alloc.next();
|
|
4367
|
+
const loc = node.sourceCodeLocation ?? null;
|
|
4368
|
+
doc.nodes.set(
|
|
4369
|
+
id,
|
|
4370
|
+
createText(id, value, {
|
|
4371
|
+
parent: parentId,
|
|
4372
|
+
span: loc ? span(loc.startOffset, loc.endOffset) : null,
|
|
4373
|
+
collapsible: /^\s*$/.test(value)
|
|
4374
|
+
})
|
|
4375
|
+
);
|
|
4376
|
+
out.push(id);
|
|
4377
|
+
return;
|
|
4378
|
+
}
|
|
4379
|
+
if (name === "#comment") {
|
|
4380
|
+
const id = doc.alloc.next();
|
|
4381
|
+
const loc = node.sourceCodeLocation ?? null;
|
|
4382
|
+
doc.nodes.set(
|
|
4383
|
+
id,
|
|
4384
|
+
createComment(id, node.data ?? "", {
|
|
4385
|
+
parent: parentId,
|
|
4386
|
+
span: loc ? span(loc.startOffset, loc.endOffset) : null
|
|
4387
|
+
})
|
|
4388
|
+
);
|
|
4389
|
+
out.push(id);
|
|
4390
|
+
return;
|
|
4391
|
+
}
|
|
4392
|
+
if (name === "#documentType") return;
|
|
4393
|
+
if (name.startsWith("#")) {
|
|
4394
|
+
for (const c of node.childNodes ?? []) appendChild(c, parentId, out);
|
|
4395
|
+
return;
|
|
4396
|
+
}
|
|
4397
|
+
out.push(buildElement(node, parentId));
|
|
4398
|
+
};
|
|
4399
|
+
const buildElement = (node, parentId) => {
|
|
4400
|
+
const id = doc.alloc.next();
|
|
4401
|
+
const tag = (node.tagName ?? node.nodeName).toLowerCase();
|
|
4402
|
+
const loc = node.sourceCodeLocation ?? null;
|
|
4403
|
+
const attrsArr = node.attrs ?? [];
|
|
4404
|
+
const opaqueSubtree = isOpaqueSubtreeTag(tag);
|
|
4405
|
+
const synthetic = loc == null;
|
|
4406
|
+
const opaque2 = opaqueSubtree || synthetic || elementIsOpaque(attrsArr);
|
|
4407
|
+
const meta = defaultMeta();
|
|
4408
|
+
meta.hasEventHandlers = hasEventHandler(attrsArr);
|
|
4409
|
+
meta.safetyFloor = opaque2 ? 0 : 3;
|
|
4410
|
+
let classes = emptyClassList();
|
|
4411
|
+
let classTokens = [];
|
|
4412
|
+
const entries = /* @__PURE__ */ new Map();
|
|
4413
|
+
const order = [];
|
|
4414
|
+
for (const a of attrsArr) {
|
|
4415
|
+
if (a.name.toLowerCase() === "class") {
|
|
4416
|
+
classTokens = splitTokens(a.value);
|
|
4417
|
+
const valueSpan = classValueSpan(loc, code);
|
|
4418
|
+
const clAttr = attrsLocOf(loc)?.["class"];
|
|
4419
|
+
const seg = {
|
|
4420
|
+
kind: "static",
|
|
4421
|
+
span: valueSpan ?? void 0,
|
|
4422
|
+
tokens: classTokens.map((value) => ({ value }))
|
|
4423
|
+
};
|
|
4424
|
+
classes = {
|
|
4425
|
+
form: "string-literal",
|
|
4426
|
+
segments: [seg],
|
|
4427
|
+
valueSpan,
|
|
4428
|
+
attrSpan: clAttr ? span(clAttr.startOffset, clAttr.endOffset) : void 0,
|
|
4429
|
+
hasDynamic: false,
|
|
4430
|
+
opaque: false,
|
|
4431
|
+
rewritable: valueSpan != null
|
|
4432
|
+
};
|
|
4433
|
+
continue;
|
|
4434
|
+
}
|
|
4435
|
+
const v = a.value;
|
|
4436
|
+
entries.set(a.name, { kind: "static", value: v === "" ? true : v });
|
|
4437
|
+
order.push(a.name);
|
|
4438
|
+
}
|
|
4439
|
+
const attrs = { entries, spreads: [], order };
|
|
4440
|
+
const computed2 = resolveComputed(classTokens, tag, id, meta);
|
|
4441
|
+
const children = [];
|
|
4442
|
+
if (!opaqueSubtree) {
|
|
4443
|
+
for (const c of node.childNodes ?? []) appendChild(c, id, children);
|
|
4444
|
+
}
|
|
4445
|
+
const el = createElement(id, {
|
|
4446
|
+
tag,
|
|
4447
|
+
namespace: "html",
|
|
4448
|
+
isComponent: false,
|
|
4449
|
+
selfClosing: loc ? loc.endTag == null : false,
|
|
4450
|
+
classes,
|
|
4451
|
+
computed: computed2,
|
|
4452
|
+
attrs,
|
|
4453
|
+
children,
|
|
4454
|
+
parent: parentId,
|
|
4455
|
+
span: loc ? span(loc.startOffset, loc.endOffset) : null,
|
|
4456
|
+
meta
|
|
4457
|
+
});
|
|
4458
|
+
doc.nodes.set(id, el);
|
|
4459
|
+
if (loc) {
|
|
4460
|
+
backref.set(id, {
|
|
4461
|
+
nodeId: id,
|
|
4462
|
+
span: span(loc.startOffset, loc.endOffset),
|
|
4463
|
+
openTagSpan: loc.startTag ? span(loc.startTag.startOffset, loc.startTag.endOffset) : null,
|
|
4464
|
+
closeTagSpan: loc.endTag ? span(loc.endTag.startOffset, loc.endTag.endOffset) : null,
|
|
4465
|
+
innerSpan: null,
|
|
4466
|
+
selfClosing: loc.endTag == null
|
|
4467
|
+
});
|
|
4468
|
+
}
|
|
4469
|
+
return id;
|
|
4470
|
+
};
|
|
4471
|
+
const rootFrag = doc.nodes.get(doc.root);
|
|
4472
|
+
appendChild(document2, doc.root, rootFrag.children);
|
|
4473
|
+
return { doc, diagnostics };
|
|
4474
|
+
}
|
|
4475
|
+
|
|
4476
|
+
// ../frontend-html/src/index.ts
|
|
4477
|
+
var htmlFrontend = {
|
|
4478
|
+
name: "html",
|
|
4479
|
+
langs: HTML_LANGS,
|
|
4480
|
+
canParse(id, code) {
|
|
4481
|
+
return looksLikeHtml(id, code);
|
|
4482
|
+
},
|
|
4483
|
+
parse(code, ctx) {
|
|
4484
|
+
return doParse(code, ctx);
|
|
4485
|
+
}
|
|
4486
|
+
};
|
|
4487
|
+
function createHtmlFrontend() {
|
|
4488
|
+
return htmlFrontend;
|
|
4489
|
+
}
|
|
4490
|
+
var htmlBackend = {
|
|
4491
|
+
name: "html",
|
|
4492
|
+
langs: HTML_LANGS,
|
|
4493
|
+
print(doc, _plan, _ctx) {
|
|
4494
|
+
return { code: doPrint(doc), map: null, edits: [], diagnostics: [] };
|
|
4495
|
+
}
|
|
4496
|
+
};
|
|
4497
|
+
function createHtmlBackend() {
|
|
4498
|
+
return htmlBackend;
|
|
4499
|
+
}
|
|
4500
|
+
|
|
4627
4501
|
// ../frontend-jsx/src/frontend-ast.ts
|
|
4628
4502
|
var import_traverse = __toESM(require("@babel/traverse"), 1);
|
|
4629
4503
|
var traverse = typeof import_traverse.default === "function" ? import_traverse.default : import_traverse.default.default;
|
|
4630
4504
|
var JSX_LANGS = ["jsx", "tsx"];
|
|
4631
|
-
var
|
|
4505
|
+
var FILE_ID2 = 1;
|
|
4632
4506
|
function jsxName(node) {
|
|
4633
4507
|
switch (node.type) {
|
|
4634
4508
|
case "JSXIdentifier":
|
|
@@ -4752,7 +4626,7 @@ function looksLikeJsx(id, code) {
|
|
|
4752
4626
|
|
|
4753
4627
|
// ../frontend-jsx/src/frontend-parse.ts
|
|
4754
4628
|
var import_parser = require("@babel/parser");
|
|
4755
|
-
function
|
|
4629
|
+
function doParse2(code, ctx) {
|
|
4756
4630
|
const diagnostics = [];
|
|
4757
4631
|
const doc = createDocument("jsx");
|
|
4758
4632
|
const backref = doc.backref;
|
|
@@ -4762,7 +4636,7 @@ function doParse(code, ctx) {
|
|
|
4762
4636
|
});
|
|
4763
4637
|
const eol = code.includes("\r\n") ? "\r\n" : "\n";
|
|
4764
4638
|
const sourceFile = {
|
|
4765
|
-
id:
|
|
4639
|
+
id: FILE_ID2,
|
|
4766
4640
|
path: ctx.id,
|
|
4767
4641
|
text: code,
|
|
4768
4642
|
frontend: "jsx",
|
|
@@ -4770,23 +4644,23 @@ function doParse(code, ctx) {
|
|
|
4770
4644
|
indentUnit: " ",
|
|
4771
4645
|
native: ast
|
|
4772
4646
|
};
|
|
4773
|
-
doc.sources.set(
|
|
4647
|
+
doc.sources.set(FILE_ID2, sourceFile);
|
|
4774
4648
|
const spanOf = (node) => {
|
|
4775
4649
|
if (node.start == null || node.end == null) return null;
|
|
4776
|
-
const
|
|
4777
|
-
file:
|
|
4650
|
+
const span2 = {
|
|
4651
|
+
file: FILE_ID2,
|
|
4778
4652
|
start: node.start,
|
|
4779
4653
|
end: node.end,
|
|
4780
4654
|
startLoc: node.loc ? { line: node.loc.start.line, column: node.loc.start.column } : void 0,
|
|
4781
4655
|
endLoc: node.loc ? { line: node.loc.end.line, column: node.loc.end.column } : void 0
|
|
4782
4656
|
};
|
|
4783
|
-
return
|
|
4657
|
+
return span2;
|
|
4784
4658
|
};
|
|
4785
4659
|
const sliceOf = (node) => node.start == null || node.end == null ? "" : code.slice(node.start, node.end);
|
|
4786
4660
|
const internExpr = (node, spread) => {
|
|
4787
4661
|
const payload = { text: sliceOf(node), spread };
|
|
4788
4662
|
return doc.exprs.intern({
|
|
4789
|
-
span: spanOf(node) ?? { file:
|
|
4663
|
+
span: spanOf(node) ?? { file: FILE_ID2, start: 0, end: 0 },
|
|
4790
4664
|
kind: exprKind(node),
|
|
4791
4665
|
payload
|
|
4792
4666
|
});
|
|
@@ -4832,7 +4706,7 @@ function doParse(code, ctx) {
|
|
|
4832
4706
|
}
|
|
4833
4707
|
return emptyClassList();
|
|
4834
4708
|
};
|
|
4835
|
-
const
|
|
4709
|
+
const staticTokensOf4 = (classes) => {
|
|
4836
4710
|
const out = [];
|
|
4837
4711
|
for (const seg of classes.segments) {
|
|
4838
4712
|
if (seg.kind === "static") for (const t of seg.tokens) out.push(t.value);
|
|
@@ -4910,7 +4784,7 @@ function doParse(code, ctx) {
|
|
|
4910
4784
|
doc.nodes.set(id, createFragment(id, { children, parent: parentId, span: spanOf(node) }));
|
|
4911
4785
|
backref.set(id, {
|
|
4912
4786
|
nodeId: id,
|
|
4913
|
-
span: spanOf(node) ?? { file:
|
|
4787
|
+
span: spanOf(node) ?? { file: FILE_ID2, start: 0, end: 0 },
|
|
4914
4788
|
openTagSpan: spanOf(node.openingFragment),
|
|
4915
4789
|
closeTagSpan: spanOf(node.closingFragment),
|
|
4916
4790
|
innerSpan: null,
|
|
@@ -4959,13 +4833,14 @@ function doParse(code, ctx) {
|
|
|
4959
4833
|
}
|
|
4960
4834
|
let computed2 = emptyStyleMap();
|
|
4961
4835
|
if (!classes.hasDynamic) {
|
|
4962
|
-
const tokens =
|
|
4836
|
+
const tokens = staticTokensOf4(classes);
|
|
4963
4837
|
if (tokens.length > 0) {
|
|
4964
4838
|
const res = ctx.resolver.resolve({
|
|
4965
4839
|
classes: tokens,
|
|
4966
4840
|
element: { tagName: tag, namespace: component ? void 0 : "html" }
|
|
4967
4841
|
});
|
|
4968
4842
|
computed2 = ctx.normalizer.normalizeStyleMap(res.styles);
|
|
4843
|
+
if (res.unknown.length > 0) meta.hasUnresolvedClasses = true;
|
|
4969
4844
|
for (const w of res.warnings) {
|
|
4970
4845
|
diagnostics.push({
|
|
4971
4846
|
code: "DF_STYLE_CONFLICT_UNRESOLVED",
|
|
@@ -4992,13 +4867,13 @@ function doParse(code, ctx) {
|
|
|
4992
4867
|
});
|
|
4993
4868
|
doc.nodes.set(id, el);
|
|
4994
4869
|
const inner = children.length > 0 ? spanOf(node.children[0]) && spanOf(node.children.at(-1)) ? {
|
|
4995
|
-
file:
|
|
4870
|
+
file: FILE_ID2,
|
|
4996
4871
|
start: spanOf(node.children[0]).start,
|
|
4997
4872
|
end: spanOf(node.children.at(-1)).end
|
|
4998
4873
|
} : null : null;
|
|
4999
4874
|
backref.set(id, {
|
|
5000
4875
|
nodeId: id,
|
|
5001
|
-
span: spanOf(node) ?? { file:
|
|
4876
|
+
span: spanOf(node) ?? { file: FILE_ID2, start: 0, end: 0 },
|
|
5002
4877
|
openTagSpan: spanOf(opening),
|
|
5003
4878
|
closeTagSpan: node.closingElement ? spanOf(node.closingElement) : null,
|
|
5004
4879
|
innerSpan: inner,
|
|
@@ -5008,13 +4883,13 @@ function doParse(code, ctx) {
|
|
|
5008
4883
|
};
|
|
5009
4884
|
const roots = [];
|
|
5010
4885
|
traverse(ast, {
|
|
5011
|
-
JSXElement(
|
|
5012
|
-
roots.push(
|
|
5013
|
-
|
|
4886
|
+
JSXElement(path5) {
|
|
4887
|
+
roots.push(path5.node);
|
|
4888
|
+
path5.skip();
|
|
5014
4889
|
},
|
|
5015
|
-
JSXFragment(
|
|
5016
|
-
roots.push(
|
|
5017
|
-
|
|
4890
|
+
JSXFragment(path5) {
|
|
4891
|
+
roots.push(path5.node);
|
|
4892
|
+
path5.skip();
|
|
5018
4893
|
}
|
|
5019
4894
|
});
|
|
5020
4895
|
const rootFrag = doc.nodes.get(doc.root);
|
|
@@ -5033,7 +4908,7 @@ var jsxFrontend = {
|
|
|
5033
4908
|
return looksLikeJsx(id, code);
|
|
5034
4909
|
},
|
|
5035
4910
|
parse(code, ctx) {
|
|
5036
|
-
return
|
|
4911
|
+
return doParse2(code, ctx);
|
|
5037
4912
|
}
|
|
5038
4913
|
};
|
|
5039
4914
|
function createJsxFrontend() {
|
|
@@ -5041,7 +4916,7 @@ function createJsxFrontend() {
|
|
|
5041
4916
|
}
|
|
5042
4917
|
|
|
5043
4918
|
// ../frontend-jsx/src/backend.ts
|
|
5044
|
-
var
|
|
4919
|
+
var import_magic_string2 = __toESM(require("magic-string"), 1);
|
|
5045
4920
|
var JSX_LANGS2 = ["jsx", "tsx"];
|
|
5046
4921
|
function exprText(doc, ref) {
|
|
5047
4922
|
const rec = doc.exprs.get(ref);
|
|
@@ -5055,20 +4930,20 @@ function exprText(doc, ref) {
|
|
|
5055
4930
|
}
|
|
5056
4931
|
return { text: "", spread: false };
|
|
5057
4932
|
}
|
|
5058
|
-
function
|
|
4933
|
+
function staticTokensOf3(classes) {
|
|
5059
4934
|
const out = [];
|
|
5060
4935
|
for (const seg of classes.segments) {
|
|
5061
4936
|
if (seg.kind === "static") for (const t of seg.tokens) out.push(t.value);
|
|
5062
4937
|
}
|
|
5063
4938
|
return out;
|
|
5064
4939
|
}
|
|
5065
|
-
function
|
|
4940
|
+
function primarySource2(doc) {
|
|
5066
4941
|
for (const sf of doc.sources.values()) {
|
|
5067
4942
|
if (typeof sf.text === "string" && sf.text.length > 0) return sf;
|
|
5068
4943
|
}
|
|
5069
4944
|
return null;
|
|
5070
4945
|
}
|
|
5071
|
-
function
|
|
4946
|
+
function collectKept2(doc) {
|
|
5072
4947
|
const out = [];
|
|
5073
4948
|
const seen = /* @__PURE__ */ new Set();
|
|
5074
4949
|
const visit = (id) => {
|
|
@@ -5082,15 +4957,15 @@ function collectKept(doc) {
|
|
|
5082
4957
|
visit(doc.root);
|
|
5083
4958
|
return out;
|
|
5084
4959
|
}
|
|
5085
|
-
function
|
|
4960
|
+
function strictlyContains2(a, b) {
|
|
5086
4961
|
if (a.file !== b.file) return false;
|
|
5087
4962
|
if (a.start <= b.start && b.end <= a.end) return !(a.start === b.start && a.end === b.end);
|
|
5088
4963
|
return false;
|
|
5089
4964
|
}
|
|
5090
|
-
function
|
|
4965
|
+
function editClasses2(ms, doc, sf, el) {
|
|
5091
4966
|
const classes = el.classes;
|
|
5092
4967
|
if (classes.hasDynamic || classes.opaque) return false;
|
|
5093
|
-
const tokens =
|
|
4968
|
+
const tokens = staticTokensOf3(classes);
|
|
5094
4969
|
const valueSpan = classes.valueSpan;
|
|
5095
4970
|
if (valueSpan && valueSpan.file === sf.id) {
|
|
5096
4971
|
const current = sf.text.slice(valueSpan.start, valueSpan.end);
|
|
@@ -5154,10 +5029,10 @@ function transferKeyOnUnwrap(ms, doc, sf, region, kept) {
|
|
|
5154
5029
|
const inside = [];
|
|
5155
5030
|
for (const n of kept) {
|
|
5156
5031
|
if (n.kind !== "element" || !n.span || n.span.file !== sf.id) continue;
|
|
5157
|
-
if (
|
|
5032
|
+
if (strictlyContains2(region.span, n.span)) inside.push(n);
|
|
5158
5033
|
}
|
|
5159
5034
|
const maximal = inside.filter(
|
|
5160
|
-
(n) => !inside.some((o) => o !== n && o.span && n.span &&
|
|
5035
|
+
(n) => !inside.some((o) => o !== n && o.span && n.span && strictlyContains2(o.span, n.span))
|
|
5161
5036
|
);
|
|
5162
5037
|
if (maximal.length !== 1) return;
|
|
5163
5038
|
const child = maximal[0];
|
|
@@ -5166,25 +5041,25 @@ function transferKeyOnUnwrap(ms, doc, sf, region, kept) {
|
|
|
5166
5041
|
if (extractKeyAttr(sf.text.slice(childOpen.start, childOpen.end))) return;
|
|
5167
5042
|
ms.appendLeft(childOpen.start + 1 + child.tag.length, ` ${keyAttr}`);
|
|
5168
5043
|
}
|
|
5169
|
-
function
|
|
5170
|
-
const sf =
|
|
5044
|
+
function surgicalPrint2(doc) {
|
|
5045
|
+
const sf = primarySource2(doc);
|
|
5171
5046
|
if (!sf) return null;
|
|
5172
|
-
const ms = new
|
|
5173
|
-
const kept =
|
|
5047
|
+
const ms = new import_magic_string2.default(sf.text);
|
|
5048
|
+
const kept = collectKept2(doc);
|
|
5174
5049
|
const keptSpans = [];
|
|
5175
5050
|
for (const n of kept) if (n.span && n.span.file === sf.id) keptSpans.push(n.span);
|
|
5176
5051
|
const removed = [];
|
|
5177
|
-
for (const id of
|
|
5052
|
+
for (const id of backrefIds2(doc)) {
|
|
5178
5053
|
if (doc.nodes.has(id)) continue;
|
|
5179
5054
|
const back = doc.backref.get(id);
|
|
5180
5055
|
if (!back || back.span.file !== sf.id) continue;
|
|
5181
|
-
const unwrapped = keptSpans.some((k) =>
|
|
5056
|
+
const unwrapped = keptSpans.some((k) => strictlyContains2(back.span, k));
|
|
5182
5057
|
removed.push({ backref: back, unwrapped });
|
|
5183
5058
|
}
|
|
5184
5059
|
const fullRemovals = removed.filter((r) => !r.unwrapped).map((r) => r.backref.span);
|
|
5185
5060
|
for (const r of removed) {
|
|
5186
|
-
const
|
|
5187
|
-
const coveredByFull = fullRemovals.some((f) => f !==
|
|
5061
|
+
const span2 = r.backref.span;
|
|
5062
|
+
const coveredByFull = fullRemovals.some((f) => f !== span2 && strictlyContains2(f, span2));
|
|
5188
5063
|
if (coveredByFull) continue;
|
|
5189
5064
|
if (r.unwrapped) {
|
|
5190
5065
|
transferKeyOnUnwrap(ms, doc, sf, r.backref, kept);
|
|
@@ -5195,15 +5070,15 @@ function surgicalPrint(doc) {
|
|
|
5195
5070
|
ms.remove(close.start, close.end);
|
|
5196
5071
|
}
|
|
5197
5072
|
} else {
|
|
5198
|
-
ms.remove(
|
|
5073
|
+
ms.remove(span2.start, span2.end);
|
|
5199
5074
|
}
|
|
5200
5075
|
}
|
|
5201
5076
|
for (const n of kept) {
|
|
5202
|
-
if (n.kind === "element")
|
|
5077
|
+
if (n.kind === "element") editClasses2(ms, doc, sf, n);
|
|
5203
5078
|
}
|
|
5204
5079
|
return ms.toString();
|
|
5205
5080
|
}
|
|
5206
|
-
function
|
|
5081
|
+
function backrefIds2(doc) {
|
|
5207
5082
|
const out = [];
|
|
5208
5083
|
const max = doc.alloc.peek;
|
|
5209
5084
|
for (let i = 1; i < max; i += 1) {
|
|
@@ -5218,7 +5093,7 @@ function classText(doc, classes) {
|
|
|
5218
5093
|
if (dynamic && dynamic.kind === "dynamic") {
|
|
5219
5094
|
return `className={${exprText(doc, dynamic.expr).text}}`;
|
|
5220
5095
|
}
|
|
5221
|
-
const tokens =
|
|
5096
|
+
const tokens = staticTokensOf3(classes);
|
|
5222
5097
|
return `className="${tokens.join(" ")}"`;
|
|
5223
5098
|
}
|
|
5224
5099
|
function attrText(doc, name, value) {
|
|
@@ -5271,15 +5146,15 @@ function rePrint(doc) {
|
|
|
5271
5146
|
if (!root || root.kind !== "fragment") return printNode(doc, doc.root);
|
|
5272
5147
|
return root.children.map((c) => printNode(doc, c)).join("");
|
|
5273
5148
|
}
|
|
5274
|
-
function
|
|
5275
|
-
const surgical =
|
|
5149
|
+
function doPrint2(doc) {
|
|
5150
|
+
const surgical = surgicalPrint2(doc);
|
|
5276
5151
|
return surgical ?? rePrint(doc);
|
|
5277
5152
|
}
|
|
5278
5153
|
var jsxBackend = {
|
|
5279
5154
|
name: "babel-jsx",
|
|
5280
5155
|
langs: JSX_LANGS2,
|
|
5281
5156
|
print(doc, _plan, _ctx) {
|
|
5282
|
-
const code =
|
|
5157
|
+
const code = doPrint2(doc);
|
|
5283
5158
|
return { code, map: null, edits: [], diagnostics: [] };
|
|
5284
5159
|
}
|
|
5285
5160
|
};
|
|
@@ -5288,12 +5163,38 @@ function createJsxBackend() {
|
|
|
5288
5163
|
}
|
|
5289
5164
|
|
|
5290
5165
|
// src/pipeline-run.ts
|
|
5166
|
+
function bytes(s) {
|
|
5167
|
+
return Buffer.byteLength(s, "utf8");
|
|
5168
|
+
}
|
|
5169
|
+
function countClassTokens(code) {
|
|
5170
|
+
let total = 0;
|
|
5171
|
+
const re = /\b(?:className|class)\s*=\s*"([^"]*)"/g;
|
|
5172
|
+
let m;
|
|
5173
|
+
while ((m = re.exec(code)) !== null) {
|
|
5174
|
+
total += m[1].split(/\s+/).filter((t) => t.length > 0).length;
|
|
5175
|
+
}
|
|
5176
|
+
return total;
|
|
5177
|
+
}
|
|
5178
|
+
function computeStats(code, out, nodesIn, nodesOut) {
|
|
5179
|
+
const classesBefore = countClassTokens(code);
|
|
5180
|
+
const classesAfter = countClassTokens(out);
|
|
5181
|
+
return {
|
|
5182
|
+
nodesRemoved: Math.max(0, nodesIn - nodesOut),
|
|
5183
|
+
classesSaved: Math.max(0, classesBefore - classesAfter),
|
|
5184
|
+
bytesSaved: bytes(code) - bytes(out)
|
|
5185
|
+
};
|
|
5186
|
+
}
|
|
5291
5187
|
function jsxKindOf(id) {
|
|
5292
5188
|
const clean = id.split("?", 1)[0] ?? id;
|
|
5293
5189
|
if (clean.endsWith(".tsx")) return "tsx";
|
|
5294
5190
|
if (clean.endsWith(".jsx")) return "jsx";
|
|
5295
5191
|
return null;
|
|
5296
5192
|
}
|
|
5193
|
+
function htmlKindOf(id) {
|
|
5194
|
+
const clean = (id.split("?", 1)[0] ?? id).toLowerCase();
|
|
5195
|
+
if (clean.endsWith(".html") || clean.endsWith(".htm")) return "html";
|
|
5196
|
+
return null;
|
|
5197
|
+
}
|
|
5297
5198
|
function eolOf2(doc) {
|
|
5298
5199
|
for (const src of doc.sources.values()) return src.eol;
|
|
5299
5200
|
return "\n";
|
|
@@ -5358,12 +5259,85 @@ function finishPipeline(optimized, id, resolver) {
|
|
|
5358
5259
|
}
|
|
5359
5260
|
function runJsxPipeline(code, id, kind, resolver, patterns, safety) {
|
|
5360
5261
|
const { doc, ctx, passes } = preparePipeline(code, id, kind, resolver, patterns, safety, "provably-safe");
|
|
5262
|
+
const nodesIn = doc.nodes.size;
|
|
5263
|
+
const { doc: optimized } = runPasses(doc, passes, ctx);
|
|
5264
|
+
const out = finishPipeline(optimized, id, resolver);
|
|
5265
|
+
return { code: out, stats: computeStats(code, out, nodesIn, optimized.nodes.size) };
|
|
5266
|
+
}
|
|
5267
|
+
function prepareHtml(code, id, resolver, patterns, safety, gate) {
|
|
5268
|
+
const parsed = createHtmlFrontend().parse(code, {
|
|
5269
|
+
id,
|
|
5270
|
+
kind: "html",
|
|
5271
|
+
resolver,
|
|
5272
|
+
normalizer,
|
|
5273
|
+
config: {},
|
|
5274
|
+
onDiagnostic: () => {
|
|
5275
|
+
}
|
|
5276
|
+
});
|
|
5277
|
+
const doc = parsed.doc;
|
|
5278
|
+
const ctx = {
|
|
5279
|
+
doc,
|
|
5280
|
+
safetyCeiling: safety,
|
|
5281
|
+
normalizer,
|
|
5282
|
+
selectors: buildSelectorIndex(doc, resolver),
|
|
5283
|
+
resolver,
|
|
5284
|
+
gate
|
|
5285
|
+
};
|
|
5286
|
+
return { doc, ctx, passes: buildPasses(patterns) };
|
|
5287
|
+
}
|
|
5288
|
+
function finishHtmlPipeline(optimized, id, resolver) {
|
|
5289
|
+
syncClassesFromComputed(optimized, resolver, normalizer);
|
|
5290
|
+
const printed = createHtmlBackend().print(
|
|
5291
|
+
optimized,
|
|
5292
|
+
{ moduleId: id, ops: [], provenance: /* @__PURE__ */ new Map() },
|
|
5293
|
+
{
|
|
5294
|
+
normalizer,
|
|
5295
|
+
resolver,
|
|
5296
|
+
sink: createSyntheticSink(),
|
|
5297
|
+
eol: eolOf2(optimized),
|
|
5298
|
+
onDiagnostic: () => {
|
|
5299
|
+
}
|
|
5300
|
+
}
|
|
5301
|
+
);
|
|
5302
|
+
return printed.code;
|
|
5303
|
+
}
|
|
5304
|
+
function runHtmlPipeline(code, id, resolver, patterns, safety) {
|
|
5305
|
+
const { doc, ctx, passes } = prepareHtml(code, id, resolver, patterns, safety, "provably-safe");
|
|
5306
|
+
const nodesIn = doc.nodes.size;
|
|
5361
5307
|
const { doc: optimized } = runPasses(doc, passes, ctx);
|
|
5362
|
-
|
|
5308
|
+
const out = finishHtmlPipeline(optimized, id, resolver);
|
|
5309
|
+
return { code: out, stats: computeStats(code, out, nodesIn, optimized.nodes.size) };
|
|
5310
|
+
}
|
|
5311
|
+
|
|
5312
|
+
// src/summary.ts
|
|
5313
|
+
function zeroStats() {
|
|
5314
|
+
return { nodesRemoved: 0, classesSaved: 0, bytesSaved: 0 };
|
|
5315
|
+
}
|
|
5316
|
+
function emptyTotals() {
|
|
5317
|
+
return { files: 0, nodesRemoved: 0, classesCompressed: 0, bytesSaved: 0 };
|
|
5318
|
+
}
|
|
5319
|
+
function addStats(t, s, changed) {
|
|
5320
|
+
if (!changed) return;
|
|
5321
|
+
t.files += 1;
|
|
5322
|
+
t.nodesRemoved += s.nodesRemoved;
|
|
5323
|
+
t.classesCompressed += s.classesSaved;
|
|
5324
|
+
t.bytesSaved += s.bytesSaved;
|
|
5325
|
+
}
|
|
5326
|
+
var RULE = ` ${"\u2500".repeat(32)}`;
|
|
5327
|
+
var TOTALS_KEY = /* @__PURE__ */ Symbol.for("domflax.buildTotals");
|
|
5328
|
+
function accumulateOnCompilation(compilation, stats, changed) {
|
|
5329
|
+
if (compilation === null || typeof compilation !== "object") return;
|
|
5330
|
+
const bag = compilation;
|
|
5331
|
+
let totals = bag[TOTALS_KEY];
|
|
5332
|
+
if (!totals) {
|
|
5333
|
+
totals = emptyTotals();
|
|
5334
|
+
bag[TOTALS_KEY] = totals;
|
|
5335
|
+
}
|
|
5336
|
+
addStats(totals, stats, changed);
|
|
5363
5337
|
}
|
|
5364
5338
|
|
|
5365
5339
|
// src/index.ts
|
|
5366
|
-
var DEFAULT_INCLUDE = [".jsx", ".tsx", ".html"];
|
|
5340
|
+
var DEFAULT_INCLUDE = [".jsx", ".tsx", ".html", ".htm"];
|
|
5367
5341
|
function resolveOptions(options) {
|
|
5368
5342
|
return {
|
|
5369
5343
|
provider: options.provider ?? "auto",
|
|
@@ -5397,11 +5371,17 @@ function createDomflax(options = {}) {
|
|
|
5397
5371
|
},
|
|
5398
5372
|
patterns,
|
|
5399
5373
|
transform(code, id) {
|
|
5400
|
-
if (!isSupported(id, resolved.include)) return { code, map: null };
|
|
5374
|
+
if (!isSupported(id, resolved.include)) return { code, map: null, stats: zeroStats() };
|
|
5401
5375
|
const kind = jsxKindOf(id);
|
|
5402
|
-
if (kind
|
|
5403
|
-
|
|
5404
|
-
|
|
5376
|
+
if (kind !== null) {
|
|
5377
|
+
const out = runJsxPipeline(code, id, kind, getResolver(), patterns, resolved.safety);
|
|
5378
|
+
return { code: out.code, map: null, stats: out.stats };
|
|
5379
|
+
}
|
|
5380
|
+
if (htmlKindOf(id) !== null) {
|
|
5381
|
+
const out = runHtmlPipeline(code, id, getResolver(), patterns, resolved.safety);
|
|
5382
|
+
return { code: out.code, map: null, stats: out.stats };
|
|
5383
|
+
}
|
|
5384
|
+
return { code, map: null, stats: zeroStats() };
|
|
5405
5385
|
}
|
|
5406
5386
|
};
|
|
5407
5387
|
}
|
|
@@ -5420,6 +5400,8 @@ function engineFor(options) {
|
|
|
5420
5400
|
function domflaxLoader(source) {
|
|
5421
5401
|
const options = this.getOptions?.() ?? {};
|
|
5422
5402
|
const engine = engineFor(options);
|
|
5423
|
-
|
|
5403
|
+
const out = engine.transform(source, this.resourcePath);
|
|
5404
|
+
accumulateOnCompilation(this._compilation, out.stats, out.code !== source);
|
|
5405
|
+
return out.code;
|
|
5424
5406
|
}
|
|
5425
5407
|
//# sourceMappingURL=webpack-loader.cjs.map
|