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/index.cjs
CHANGED
|
@@ -33,11 +33,10 @@ __export(src_exports, {
|
|
|
33
33
|
BASE_CONDITION: () => BASE_CONDITION,
|
|
34
34
|
BASE_CONDITION_KEY: () => BASE_CONDITION_KEY,
|
|
35
35
|
DEFAULT_FIXPOINT: () => DEFAULT_FIXPOINT,
|
|
36
|
+
DEFAULT_MAX_UNIVERSE: () => DEFAULT_MAX_UNIVERSE,
|
|
36
37
|
PHASE_ORDER: () => PHASE_ORDER,
|
|
37
38
|
applyGroups: () => applyGroups,
|
|
38
39
|
applyOps: () => applyOps,
|
|
39
|
-
borderRadiusShorthand: () => borderRadiusShorthand,
|
|
40
|
-
borderShorthand: () => borderShorthand,
|
|
41
40
|
buildMatchContext: () => buildMatchContext,
|
|
42
41
|
buildSelectorIndex: () => buildSelectorIndex,
|
|
43
42
|
builtinPatterns: () => builtinPatterns,
|
|
@@ -61,7 +60,6 @@ __export(src_exports, {
|
|
|
61
60
|
createRewriteFactory: () => createRewriteFactory,
|
|
62
61
|
createSyntheticSink: () => createSyntheticSink,
|
|
63
62
|
createText: () => createText,
|
|
64
|
-
dedupeClasses: () => dedupeClasses,
|
|
65
63
|
default: () => src_default,
|
|
66
64
|
defaultMeta: () => defaultMeta,
|
|
67
65
|
displayContentsWrapper: () => displayContentsWrapper,
|
|
@@ -77,29 +75,21 @@ __export(src_exports, {
|
|
|
77
75
|
flattenVerdict: () => flattenVerdict,
|
|
78
76
|
flattenWouldDropStyle: () => flattenWouldDropStyle,
|
|
79
77
|
flexCenterWrapper: () => flexCenterWrapper,
|
|
80
|
-
gapShorthand: () => gapShorthand,
|
|
81
78
|
getElement: () => getElement,
|
|
82
79
|
getNode: () => getNode,
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
nestedFlexMerge: () => nestedFlexMerge,
|
|
87
|
-
nestedGridMerge: () => nestedGridMerge,
|
|
88
|
-
overflowShorthand: () => overflowShorthand,
|
|
89
|
-
overscrollBehaviorShorthand: () => overscrollBehaviorShorthand,
|
|
90
|
-
paddingShorthand: () => paddingShorthand,
|
|
80
|
+
gridCenterWrapper: () => gridCenterWrapper,
|
|
81
|
+
inheritedOnlyWrapper: () => inheritedOnlyWrapper,
|
|
82
|
+
minStringCover: () => minStringCover,
|
|
91
83
|
passthroughWrapper: () => passthroughWrapper,
|
|
92
84
|
patternsForPhase: () => patternsForPhase,
|
|
93
|
-
placeShorthand: () => placeShorthand,
|
|
94
85
|
redundantFragment: () => redundantFragment,
|
|
95
86
|
redundantInlineWrapper: () => redundantInlineWrapper,
|
|
96
87
|
revertDiagnostic: () => revertDiagnostic,
|
|
97
88
|
runPasses: () => runPasses,
|
|
98
|
-
scrollMarginShorthand: () => scrollMarginShorthand,
|
|
99
|
-
scrollPaddingShorthand: () => scrollPaddingShorthand,
|
|
100
|
-
sizeShorthand: () => sizeShorthand,
|
|
101
89
|
stampOrigin: () => stampOrigin,
|
|
90
|
+
styleMapTuples: () => styleMapTuples,
|
|
102
91
|
syncClassesFromComputed: () => syncClassesFromComputed,
|
|
92
|
+
tupleKey: () => tupleKey,
|
|
103
93
|
vite: () => vite,
|
|
104
94
|
walk: () => walk,
|
|
105
95
|
webpack: () => webpack
|
|
@@ -168,6 +158,7 @@ function defaultMeta(safetyFloor = 0) {
|
|
|
168
158
|
hasDynamicChildren: false,
|
|
169
159
|
isComponent: false,
|
|
170
160
|
hasDangerousHtml: false,
|
|
161
|
+
hasUnresolvedClasses: false,
|
|
171
162
|
targetedByCombinator: false,
|
|
172
163
|
targetedByStructuralPseudo: false,
|
|
173
164
|
selectorDependents: 0,
|
|
@@ -179,6 +170,7 @@ function defaultMeta(safetyFloor = 0) {
|
|
|
179
170
|
declaresCustomProperties: false,
|
|
180
171
|
whitespaceSensitive: false,
|
|
181
172
|
touched: false,
|
|
173
|
+
styleDirty: false,
|
|
182
174
|
synthetic: false,
|
|
183
175
|
safetyFloor
|
|
184
176
|
};
|
|
@@ -392,6 +384,14 @@ function markTouched(state, id) {
|
|
|
392
384
|
state.touched.add(id);
|
|
393
385
|
}
|
|
394
386
|
}
|
|
387
|
+
function markStyleDirty(state, id) {
|
|
388
|
+
const n = state.doc.nodes.get(id);
|
|
389
|
+
if (n) {
|
|
390
|
+
n.meta.touched = true;
|
|
391
|
+
n.meta.styleDirty = true;
|
|
392
|
+
state.touched.add(id);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
395
|
function removeSubtree(state, id) {
|
|
396
396
|
const node = state.doc.nodes.get(id);
|
|
397
397
|
if (!node) return;
|
|
@@ -683,7 +683,7 @@ function applyOne(state, op) {
|
|
|
683
683
|
return [precond(op, op.target, "setClassList target is not an element")];
|
|
684
684
|
}
|
|
685
685
|
el.computed = cloneStyleMap(op.style);
|
|
686
|
-
|
|
686
|
+
markStyleDirty(state, op.target);
|
|
687
687
|
return [];
|
|
688
688
|
}
|
|
689
689
|
case "mergeStyle": {
|
|
@@ -706,7 +706,7 @@ function applyOne(state, op) {
|
|
|
706
706
|
const src = doc.nodes.get(op.source);
|
|
707
707
|
if (src) markTouched(state, op.source);
|
|
708
708
|
}
|
|
709
|
-
|
|
709
|
+
markStyleDirty(state, op.target);
|
|
710
710
|
return [];
|
|
711
711
|
}
|
|
712
712
|
case "foldInheritedStyles":
|
|
@@ -757,7 +757,7 @@ function applyFold(state, op) {
|
|
|
757
757
|
}
|
|
758
758
|
if (folded) {
|
|
759
759
|
into.computed = { blocks: nextBlocks };
|
|
760
|
-
|
|
760
|
+
markStyleDirty(state, intoId);
|
|
761
761
|
}
|
|
762
762
|
}
|
|
763
763
|
for (const d of issues) state.diagnostics.push(d);
|
|
@@ -1090,8 +1090,123 @@ function buildMatchContext(doc, elementId, resolver, selectors, safety, phase, i
|
|
|
1090
1090
|
var DISPLAY = "display";
|
|
1091
1091
|
var POSITION = "position";
|
|
1092
1092
|
var TRANSFORM = "transform";
|
|
1093
|
+
var ALIGN_ITEMS = "align-items";
|
|
1094
|
+
var JUSTIFY_CONTENT = "justify-content";
|
|
1095
|
+
var JUSTIFY_ITEMS = "justify-items";
|
|
1096
|
+
var PLACE_ITEMS = "place-items";
|
|
1097
|
+
var PLACE_SELF = "place-self";
|
|
1093
1098
|
var CONTEXT_SAFE_DISPLAYS = /* @__PURE__ */ new Set(["block", "contents", ""]);
|
|
1094
1099
|
var STATIC_POSITIONS = /* @__PURE__ */ new Set(["static", ""]);
|
|
1100
|
+
var CENTERING_DISPLAYS = /* @__PURE__ */ new Set(["flex", "grid"]);
|
|
1101
|
+
var GRID_PARENT_DISPLAYS = /* @__PURE__ */ new Set(["grid"]);
|
|
1102
|
+
var STRETCHY_ITEM_ALIGN = /* @__PURE__ */ new Set(["normal", "stretch"]);
|
|
1103
|
+
var PARENT_ITEMS_ALIGN_PROPS = [ALIGN_ITEMS, JUSTIFY_ITEMS, PLACE_ITEMS];
|
|
1104
|
+
function isBaseCondition(block) {
|
|
1105
|
+
const c = block.condition;
|
|
1106
|
+
return c.media === "" && c.states.length === 0 && c.pseudoElement === "";
|
|
1107
|
+
}
|
|
1108
|
+
function baseValue(sm, prop) {
|
|
1109
|
+
for (const block of sm.blocks.values()) {
|
|
1110
|
+
if (!isBaseCondition(block)) continue;
|
|
1111
|
+
const d = block.decls.get(prop);
|
|
1112
|
+
return d ? String(d.value) : null;
|
|
1113
|
+
}
|
|
1114
|
+
return null;
|
|
1115
|
+
}
|
|
1116
|
+
function parentIsFillingGrid(before, wrapper, norm) {
|
|
1117
|
+
if (wrapper.parent == null) return false;
|
|
1118
|
+
const p = before.nodes.get(wrapper.parent);
|
|
1119
|
+
if (!p || p.kind !== "element") return false;
|
|
1120
|
+
const pc2 = norm.normalizeStyleMap(p.computed);
|
|
1121
|
+
let baseIsGrid = false;
|
|
1122
|
+
for (const block of pc2.blocks.values()) {
|
|
1123
|
+
const disp = block.decls.get(DISPLAY);
|
|
1124
|
+
if (disp) {
|
|
1125
|
+
if (!GRID_PARENT_DISPLAYS.has(String(disp.value))) return false;
|
|
1126
|
+
if (isBaseCondition(block)) baseIsGrid = true;
|
|
1127
|
+
}
|
|
1128
|
+
for (const prop of PARENT_ITEMS_ALIGN_PROPS) {
|
|
1129
|
+
const d = block.decls.get(prop);
|
|
1130
|
+
if (d && !STRETCHY_ITEM_ALIGN.has(String(d.value))) return false;
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
return baseIsGrid;
|
|
1134
|
+
}
|
|
1135
|
+
function wrapperHasOnlyCenteringStyle(wrapperComputed, childComputed, norm) {
|
|
1136
|
+
for (const block of wrapperComputed.blocks.values()) {
|
|
1137
|
+
const base = isBaseCondition(block);
|
|
1138
|
+
const ck = conditionKey(block.condition);
|
|
1139
|
+
for (const [prop, decl] of block.decls) {
|
|
1140
|
+
const val = String(decl.value);
|
|
1141
|
+
if (prop === DISPLAY) {
|
|
1142
|
+
if (base && CENTERING_DISPLAYS.has(val)) continue;
|
|
1143
|
+
return false;
|
|
1144
|
+
}
|
|
1145
|
+
if (prop === ALIGN_ITEMS) {
|
|
1146
|
+
if (base && val === "center") continue;
|
|
1147
|
+
return false;
|
|
1148
|
+
}
|
|
1149
|
+
if (prop === JUSTIFY_CONTENT) {
|
|
1150
|
+
if (base && val === "center") continue;
|
|
1151
|
+
return false;
|
|
1152
|
+
}
|
|
1153
|
+
if (prop === POSITION) {
|
|
1154
|
+
if (STATIC_POSITIONS.has(val)) continue;
|
|
1155
|
+
return false;
|
|
1156
|
+
}
|
|
1157
|
+
if (prop === TRANSFORM) {
|
|
1158
|
+
if (val === "none") continue;
|
|
1159
|
+
return false;
|
|
1160
|
+
}
|
|
1161
|
+
if (isInherited2(decl, norm)) continue;
|
|
1162
|
+
if (childReproduces(childComputed, ck, prop, val)) continue;
|
|
1163
|
+
return false;
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
return true;
|
|
1167
|
+
}
|
|
1168
|
+
function wrapperCentersSingleElement(before, wrapper) {
|
|
1169
|
+
let elements = 0;
|
|
1170
|
+
for (const cid of wrapper.children) {
|
|
1171
|
+
const n = before.nodes.get(cid);
|
|
1172
|
+
if (!n) continue;
|
|
1173
|
+
if (n.kind === "element") {
|
|
1174
|
+
elements += 1;
|
|
1175
|
+
continue;
|
|
1176
|
+
}
|
|
1177
|
+
if (n.kind === "comment") continue;
|
|
1178
|
+
if (n.kind === "text" && n.value.trim() === "") continue;
|
|
1179
|
+
return false;
|
|
1180
|
+
}
|
|
1181
|
+
return elements === 1;
|
|
1182
|
+
}
|
|
1183
|
+
function childHasSelfAlign(childBefore, norm) {
|
|
1184
|
+
if (!childBefore) return false;
|
|
1185
|
+
const sm = norm.normalizeStyleMap(childBefore);
|
|
1186
|
+
for (const block of sm.blocks.values()) {
|
|
1187
|
+
for (const prop of SELF_ALIGN_PROPS) {
|
|
1188
|
+
const d = block.decls.get(prop);
|
|
1189
|
+
if (d && !NEUTRAL_ALIGN.has(String(d.value))) return true;
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
return false;
|
|
1193
|
+
}
|
|
1194
|
+
function childGainsPlaceSelfCenter(childAfter) {
|
|
1195
|
+
if (baseValue(childAfter, PLACE_SELF) === "center") return true;
|
|
1196
|
+
return baseValue(childAfter, "align-self") === "center" && baseValue(childAfter, "justify-self") === "center";
|
|
1197
|
+
}
|
|
1198
|
+
function isContextCompensatedCentering(before, wrapper, wrapperComputed, childBefore, childAfter, norm) {
|
|
1199
|
+
if (!childAfter) return false;
|
|
1200
|
+
if (!CENTERING_DISPLAYS.has(baseValue(wrapperComputed, DISPLAY) ?? "")) return false;
|
|
1201
|
+
if (baseValue(wrapperComputed, ALIGN_ITEMS) !== "center") return false;
|
|
1202
|
+
if (baseValue(wrapperComputed, JUSTIFY_CONTENT) !== "center") return false;
|
|
1203
|
+
const childAfterNorm = norm.normalizeStyleMap(childAfter);
|
|
1204
|
+
if (!childGainsPlaceSelfCenter(childAfterNorm)) return false;
|
|
1205
|
+
if (!wrapperHasOnlyCenteringStyle(wrapperComputed, childAfterNorm, norm)) return false;
|
|
1206
|
+
if (!wrapperCentersSingleElement(before, wrapper)) return false;
|
|
1207
|
+
if (childHasSelfAlign(childBefore, norm)) return false;
|
|
1208
|
+
return parentIsFillingGrid(before, wrapper, norm);
|
|
1209
|
+
}
|
|
1095
1210
|
var SELF_ALIGN_PROPS = [
|
|
1096
1211
|
"place-self",
|
|
1097
1212
|
"align-self",
|
|
@@ -1187,10 +1302,16 @@ function classifyFlattenOps(before, after, ops, norm) {
|
|
|
1187
1302
|
if (!wrapper || wrapper.kind !== "element") {
|
|
1188
1303
|
return { kind: "provably-safe", wrapperId: null, childId: null };
|
|
1189
1304
|
}
|
|
1305
|
+
if (wrapper.meta.hasUnresolvedClasses) {
|
|
1306
|
+
return { kind: "needs-verification", wrapperId, childId: survivingChildOf(ops, wrapper, before) };
|
|
1307
|
+
}
|
|
1190
1308
|
const childId = survivingChildOf(ops, wrapper, before);
|
|
1191
1309
|
const wrapperComputed = norm.normalizeStyleMap(wrapper.computed);
|
|
1192
1310
|
const childAfter = childId != null ? getElement(after, childId)?.computed ?? null : null;
|
|
1193
1311
|
const childBefore = childId != null ? getElement(before, childId)?.computed ?? null : null;
|
|
1312
|
+
if (isContextCompensatedCentering(before, wrapper, wrapperComputed, childBefore, childAfter, norm)) {
|
|
1313
|
+
return { kind: "provably-safe", wrapperId, childId };
|
|
1314
|
+
}
|
|
1194
1315
|
if (establishesChildContext(wrapperComputed)) {
|
|
1195
1316
|
return { kind: "needs-verification", wrapperId, childId };
|
|
1196
1317
|
}
|
|
@@ -1626,22 +1747,52 @@ function sameTokens(a, b) {
|
|
|
1626
1747
|
for (let i = 0; i < a.length; i += 1) if (a[i] !== b[i]) return false;
|
|
1627
1748
|
return true;
|
|
1628
1749
|
}
|
|
1750
|
+
function residualStyle(computed2, covered, norm) {
|
|
1751
|
+
const cov = norm.normalizeStyleMap(covered);
|
|
1752
|
+
const blocks = /* @__PURE__ */ new Map();
|
|
1753
|
+
for (const [key, block] of norm.normalizeStyleMap(computed2).blocks) {
|
|
1754
|
+
const covBlock = cov.blocks.get(key);
|
|
1755
|
+
const decls = /* @__PURE__ */ new Map();
|
|
1756
|
+
for (const [prop, decl] of block.decls) {
|
|
1757
|
+
const covDecl = covBlock?.decls.get(prop);
|
|
1758
|
+
if (covDecl && covDecl.value === decl.value && covDecl.important === decl.important) continue;
|
|
1759
|
+
decls.set(prop, decl);
|
|
1760
|
+
}
|
|
1761
|
+
if (decls.size > 0) blocks.set(key, { condition: block.condition, decls });
|
|
1762
|
+
}
|
|
1763
|
+
return { blocks };
|
|
1764
|
+
}
|
|
1765
|
+
function joinedLength(tokens) {
|
|
1766
|
+
if (tokens.length === 0) return 0;
|
|
1767
|
+
let len = tokens.length - 1;
|
|
1768
|
+
for (const t of tokens) len += t.length;
|
|
1769
|
+
return len;
|
|
1770
|
+
}
|
|
1771
|
+
var COMPRESS_FLOOR = 1;
|
|
1629
1772
|
function syncClassesFromComputed(doc, resolver, norm) {
|
|
1630
1773
|
const sink = createSyntheticSink();
|
|
1774
|
+
const isDroppable = (t) => resolver.owns(t) && resolver.selectorUsage(t).droppable;
|
|
1631
1775
|
for (const id of elementIds(doc)) {
|
|
1632
1776
|
const el = getElement(doc, id);
|
|
1633
|
-
if (!el
|
|
1777
|
+
if (!el) continue;
|
|
1634
1778
|
if (el.classes.opaque || el.classes.hasDynamic) continue;
|
|
1779
|
+
const compressOnly = !el.meta.styleDirty;
|
|
1780
|
+
if (compressOnly && el.meta.safetyFloor < COMPRESS_FLOOR) continue;
|
|
1635
1781
|
const tokens = staticTokensOf(el.classes);
|
|
1782
|
+
if (tokens.length === 0) continue;
|
|
1783
|
+
const retained = tokens.filter((t) => !isDroppable(t));
|
|
1784
|
+
if (compressOnly && retained.length === tokens.length) continue;
|
|
1785
|
+
const covered = retained.length > 0 ? resolver.resolve({ classes: retained }).styles : null;
|
|
1786
|
+
const target = covered ? residualStyle(el.computed, covered, norm) : el.computed;
|
|
1636
1787
|
const ctx = { normalizer: norm, sink };
|
|
1637
|
-
const emitted = resolver.emit(
|
|
1788
|
+
const emitted = resolver.emit(target, ctx).classes;
|
|
1638
1789
|
if (emitted.length === 0) continue;
|
|
1639
1790
|
const emittedSet = new Set(emitted);
|
|
1640
1791
|
const next = [];
|
|
1641
1792
|
const seen = /* @__PURE__ */ new Set();
|
|
1642
1793
|
for (const t of tokens) {
|
|
1643
1794
|
if (seen.has(t)) continue;
|
|
1644
|
-
const keep = emittedSet.has(t) || !
|
|
1795
|
+
const keep = emittedSet.has(t) || !isDroppable(t);
|
|
1645
1796
|
if (keep) {
|
|
1646
1797
|
next.push(t);
|
|
1647
1798
|
seen.add(t);
|
|
@@ -1653,10 +1804,98 @@ function syncClassesFromComputed(doc, resolver, norm) {
|
|
|
1653
1804
|
seen.add(c);
|
|
1654
1805
|
}
|
|
1655
1806
|
if (sameTokens(next, tokens)) continue;
|
|
1807
|
+
if (compressOnly) {
|
|
1808
|
+
if (!norm.equals(resolver.resolve({ classes: next }).styles, el.computed)) continue;
|
|
1809
|
+
if (joinedLength(next) > joinedLength(tokens)) continue;
|
|
1810
|
+
}
|
|
1656
1811
|
el.classes = staticClassList(el.classes, next);
|
|
1657
1812
|
}
|
|
1658
1813
|
}
|
|
1659
1814
|
|
|
1815
|
+
// ../core/src/compress-engine.ts
|
|
1816
|
+
var SEP = "";
|
|
1817
|
+
function tupleKey(condition, property, value, important) {
|
|
1818
|
+
return `${condition}${SEP}${property}${SEP}${value}${SEP}${important ? "1" : "0"}`;
|
|
1819
|
+
}
|
|
1820
|
+
function styleMapTuples(map, norm) {
|
|
1821
|
+
const out = [];
|
|
1822
|
+
const normalized = norm.normalizeStyleMap(map);
|
|
1823
|
+
for (const [ck, block] of normalized.blocks) {
|
|
1824
|
+
for (const [prop, decl] of block.decls) {
|
|
1825
|
+
out.push(tupleKey(String(ck), String(prop), String(decl.value), decl.important));
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
return out;
|
|
1829
|
+
}
|
|
1830
|
+
var DEFAULT_MAX_UNIVERSE = 20;
|
|
1831
|
+
function minStringCover(universe, vocabulary, options = {}) {
|
|
1832
|
+
const uniq = [...new Set(universe)];
|
|
1833
|
+
if (uniq.length === 0) return [];
|
|
1834
|
+
const n = uniq.length;
|
|
1835
|
+
const max = options.maxUniverse ?? DEFAULT_MAX_UNIVERSE;
|
|
1836
|
+
if (n > max) return null;
|
|
1837
|
+
const bitOf = /* @__PURE__ */ new Map();
|
|
1838
|
+
uniq.forEach((t, i) => bitOf.set(t, i));
|
|
1839
|
+
const byMask = /* @__PURE__ */ new Map();
|
|
1840
|
+
for (const entry of vocabulary) {
|
|
1841
|
+
if (entry.tuples.length === 0) continue;
|
|
1842
|
+
let mask = 0;
|
|
1843
|
+
let ok = true;
|
|
1844
|
+
for (const t of entry.tuples) {
|
|
1845
|
+
const b = bitOf.get(t);
|
|
1846
|
+
if (b === void 0) {
|
|
1847
|
+
ok = false;
|
|
1848
|
+
break;
|
|
1849
|
+
}
|
|
1850
|
+
mask |= 1 << b;
|
|
1851
|
+
}
|
|
1852
|
+
if (!ok || mask === 0) continue;
|
|
1853
|
+
const cost = entry.token.length + 1;
|
|
1854
|
+
const prev = byMask.get(mask);
|
|
1855
|
+
if (!prev || cost < prev.cost || cost === prev.cost && entry.token < prev.token) {
|
|
1856
|
+
byMask.set(mask, { token: entry.token, mask, cost });
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
const cands = [...byMask.values()];
|
|
1860
|
+
if (cands.length === 0) return null;
|
|
1861
|
+
const full = (1 << n) - 1;
|
|
1862
|
+
const byBit = Array.from({ length: n }, () => []);
|
|
1863
|
+
cands.forEach((c, ci) => {
|
|
1864
|
+
for (let b = 0; b < n; b += 1) if (c.mask & 1 << b) byBit[b].push(ci);
|
|
1865
|
+
});
|
|
1866
|
+
const size = full + 1;
|
|
1867
|
+
const dp = new Float64Array(size).fill(Infinity);
|
|
1868
|
+
const fromCand = new Int32Array(size).fill(-1);
|
|
1869
|
+
const fromMask = new Int32Array(size).fill(-1);
|
|
1870
|
+
dp[0] = 0;
|
|
1871
|
+
for (let mask = 0; mask < full; mask += 1) {
|
|
1872
|
+
const cur = dp[mask];
|
|
1873
|
+
if (!Number.isFinite(cur)) continue;
|
|
1874
|
+
let b = 0;
|
|
1875
|
+
while (b < n && mask & 1 << b) b += 1;
|
|
1876
|
+
for (const ci of byBit[b]) {
|
|
1877
|
+
const c = cands[ci];
|
|
1878
|
+
const nm = mask | c.mask;
|
|
1879
|
+
const cost = cur + c.cost;
|
|
1880
|
+
if (cost < dp[nm]) {
|
|
1881
|
+
dp[nm] = cost;
|
|
1882
|
+
fromCand[nm] = ci;
|
|
1883
|
+
fromMask[nm] = mask;
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
if (!Number.isFinite(dp[full])) return null;
|
|
1888
|
+
const chosen = [];
|
|
1889
|
+
let m = full;
|
|
1890
|
+
while (m !== 0) {
|
|
1891
|
+
const ci = fromCand[m];
|
|
1892
|
+
if (ci < 0) return null;
|
|
1893
|
+
chosen.push(cands[ci].token);
|
|
1894
|
+
m = fromMask[m];
|
|
1895
|
+
}
|
|
1896
|
+
return [...new Set(chosen)].sort();
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1660
1899
|
// ../pattern-kit/src/normalize.ts
|
|
1661
1900
|
var INHERITED_PROPERTIES = [
|
|
1662
1901
|
"azimuth",
|
|
@@ -1741,6 +1980,18 @@ var BOX_SIDES = {
|
|
|
1741
1980
|
padding: ["padding-top", "padding-right", "padding-bottom", "padding-left"],
|
|
1742
1981
|
margin: ["margin-top", "margin-right", "margin-bottom", "margin-left"],
|
|
1743
1982
|
inset: ["top", "right", "bottom", "left"],
|
|
1983
|
+
"scroll-margin": [
|
|
1984
|
+
"scroll-margin-top",
|
|
1985
|
+
"scroll-margin-right",
|
|
1986
|
+
"scroll-margin-bottom",
|
|
1987
|
+
"scroll-margin-left"
|
|
1988
|
+
],
|
|
1989
|
+
"scroll-padding": [
|
|
1990
|
+
"scroll-padding-top",
|
|
1991
|
+
"scroll-padding-right",
|
|
1992
|
+
"scroll-padding-bottom",
|
|
1993
|
+
"scroll-padding-left"
|
|
1994
|
+
],
|
|
1744
1995
|
"border-width": [
|
|
1745
1996
|
"border-top-width",
|
|
1746
1997
|
"border-right-width",
|
|
@@ -1758,8 +2009,35 @@ var BOX_SIDES = {
|
|
|
1758
2009
|
"border-right-color",
|
|
1759
2010
|
"border-bottom-color",
|
|
1760
2011
|
"border-left-color"
|
|
2012
|
+
],
|
|
2013
|
+
// `border-radius` 1–4 value form maps to the four CORNERS (TL, TR, BR, BL) — the same positional
|
|
2014
|
+
// pattern boxFourSides implements. Only the slash-free form is expanded (see expandShorthand).
|
|
2015
|
+
"border-radius": [
|
|
2016
|
+
"border-top-left-radius",
|
|
2017
|
+
"border-top-right-radius",
|
|
2018
|
+
"border-bottom-right-radius",
|
|
2019
|
+
"border-bottom-left-radius"
|
|
1761
2020
|
]
|
|
1762
2021
|
};
|
|
2022
|
+
var AXIS_PAIRS = {
|
|
2023
|
+
overflow: ["overflow-x", "overflow-y"],
|
|
2024
|
+
"overscroll-behavior": ["overscroll-behavior-x", "overscroll-behavior-y"],
|
|
2025
|
+
"place-items": ["align-items", "justify-items"],
|
|
2026
|
+
"place-content": ["align-content", "justify-content"],
|
|
2027
|
+
"place-self": ["align-self", "justify-self"]
|
|
2028
|
+
};
|
|
2029
|
+
var LOGICAL_PAIRS = {
|
|
2030
|
+
"padding-inline": ["padding-left", "padding-right"],
|
|
2031
|
+
"padding-block": ["padding-top", "padding-bottom"],
|
|
2032
|
+
"margin-inline": ["margin-left", "margin-right"],
|
|
2033
|
+
"margin-block": ["margin-top", "margin-bottom"],
|
|
2034
|
+
"inset-inline": ["left", "right"],
|
|
2035
|
+
"inset-block": ["top", "bottom"],
|
|
2036
|
+
"scroll-padding-inline": ["scroll-padding-left", "scroll-padding-right"],
|
|
2037
|
+
"scroll-padding-block": ["scroll-padding-top", "scroll-padding-bottom"],
|
|
2038
|
+
"scroll-margin-inline": ["scroll-margin-left", "scroll-margin-right"],
|
|
2039
|
+
"scroll-margin-block": ["scroll-margin-top", "scroll-margin-bottom"]
|
|
2040
|
+
};
|
|
1763
2041
|
function splitTopLevel(value) {
|
|
1764
2042
|
const out = [];
|
|
1765
2043
|
let depth = 0;
|
|
@@ -1793,6 +2071,7 @@ function boxFourSides(values) {
|
|
|
1793
2071
|
}
|
|
1794
2072
|
}
|
|
1795
2073
|
function expandShorthand(prop, value) {
|
|
2074
|
+
if (prop === "border-radius" && value.includes("/")) return [[prop, value]];
|
|
1796
2075
|
const box = BOX_SIDES[prop];
|
|
1797
2076
|
if (box) {
|
|
1798
2077
|
const parts = splitTopLevel(value);
|
|
@@ -1802,6 +2081,19 @@ function expandShorthand(prop, value) {
|
|
|
1802
2081
|
}
|
|
1803
2082
|
return [[prop, value]];
|
|
1804
2083
|
}
|
|
2084
|
+
const axis = AXIS_PAIRS[prop];
|
|
2085
|
+
if (axis) {
|
|
2086
|
+
const parts = splitTopLevel(value);
|
|
2087
|
+
if (parts.length === 1) return [[axis[0], parts[0]], [axis[1], parts[0]]];
|
|
2088
|
+
if (parts.length === 2) return [[axis[0], parts[0]], [axis[1], parts[1]]];
|
|
2089
|
+
return [[prop, value]];
|
|
2090
|
+
}
|
|
2091
|
+
const logical = LOGICAL_PAIRS[prop];
|
|
2092
|
+
if (logical) {
|
|
2093
|
+
const parts = splitTopLevel(value);
|
|
2094
|
+
if (parts.length === 1) return [[logical[0], parts[0]], [logical[1], parts[0]]];
|
|
2095
|
+
return [[prop, value]];
|
|
2096
|
+
}
|
|
1805
2097
|
if (prop === "gap" || prop === "grid-gap") {
|
|
1806
2098
|
const parts = splitTopLevel(value);
|
|
1807
2099
|
if (parts.length === 1) {
|
|
@@ -1959,7 +2251,12 @@ var VISUAL_PROPERTIES = /* @__PURE__ */ new Set([
|
|
|
1959
2251
|
"border-right-color",
|
|
1960
2252
|
"border-bottom-color",
|
|
1961
2253
|
"border-left-color",
|
|
1962
|
-
|
|
2254
|
+
// `border-radius` is expanded to its four corner longhands by the shared normalizer, so the
|
|
2255
|
+
// paint-establishing check must match those (a rounded wrapper still clips its background).
|
|
2256
|
+
"border-top-left-radius",
|
|
2257
|
+
"border-top-right-radius",
|
|
2258
|
+
"border-bottom-right-radius",
|
|
2259
|
+
"border-bottom-left-radius",
|
|
1963
2260
|
"box-shadow",
|
|
1964
2261
|
"outline",
|
|
1965
2262
|
"outline-width",
|
|
@@ -1985,6 +2282,7 @@ var hasOwnVisualStyle = (node, ctx) => {
|
|
|
1985
2282
|
const el = asElement(node);
|
|
1986
2283
|
if (!el) return false;
|
|
1987
2284
|
if (el.meta.hasOwnVisualStyle) return true;
|
|
2285
|
+
if (el.meta.hasUnresolvedClasses) return true;
|
|
1988
2286
|
const computedMap = ctx.computedOf(el) ?? el.computed;
|
|
1989
2287
|
const norm = normalizer.normalizeStyleMap(computedMap);
|
|
1990
2288
|
for (const block of norm.blocks.values()) {
|
|
@@ -2189,7 +2487,160 @@ function definePattern(config) {
|
|
|
2189
2487
|
return validatePattern(spec);
|
|
2190
2488
|
}
|
|
2191
2489
|
|
|
2192
|
-
// ../patterns/src/library/
|
|
2490
|
+
// ../patterns/src/library/flex/flex-center-wrapper.pattern.ts
|
|
2491
|
+
var flexCenterWrapper = definePattern({
|
|
2492
|
+
name: "flex-center-wrapper",
|
|
2493
|
+
category: "flatten/flex/flex-center-wrapper",
|
|
2494
|
+
safety: 2,
|
|
2495
|
+
doc: {
|
|
2496
|
+
title: "Flatten flex-centering wrapper",
|
|
2497
|
+
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.",
|
|
2498
|
+
before: '<div style="display:flex;align-items:center;justify-content:center"><Child/></div>',
|
|
2499
|
+
after: '<Child style="place-self:center"/>',
|
|
2500
|
+
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."
|
|
2501
|
+
},
|
|
2502
|
+
match: {
|
|
2503
|
+
tag: "div",
|
|
2504
|
+
style: { display: "flex", alignItems: "center", justifyContent: "center" },
|
|
2505
|
+
onlyChild: "element",
|
|
2506
|
+
paintsNothing: true
|
|
2507
|
+
},
|
|
2508
|
+
rewrite: {
|
|
2509
|
+
flattenInto: "child",
|
|
2510
|
+
childGains: { placeSelf: "center" }
|
|
2511
|
+
},
|
|
2512
|
+
// Collapsing a flex-centering wrapper to `place-self:center` on the child is render-identical ONLY
|
|
2513
|
+
// when the child's NEW parent is a statically-known GRID that lets the wrapper fill its area (there
|
|
2514
|
+
// `place-self`'s align-self AND justify-self both take effect). Under that ONE context the flatten is
|
|
2515
|
+
// classified `provably-safe` and commits; under a flex/block/unknown parent — or when the wrapper
|
|
2516
|
+
// drops any own style — it stays `needs-verification` and the conservative production gate PRESERVES
|
|
2517
|
+
// it. Op-level correctness (purity, id-preserving unwrap, opacity-barrier safety) is additionally
|
|
2518
|
+
// asserted by the invariant suite over every pattern.
|
|
2519
|
+
test: {
|
|
2520
|
+
cases: [
|
|
2521
|
+
{
|
|
2522
|
+
name: "grid parent \u2192 flattened (child gains place-self-center)",
|
|
2523
|
+
before: '<div className="grid"><div className="flex items-center justify-center"><span className="bg-red-200">x</span></div></div>',
|
|
2524
|
+
after: '<div className="grid"><span className="bg-red-200 place-self-center">x</span></div>'
|
|
2525
|
+
}
|
|
2526
|
+
],
|
|
2527
|
+
noMatch: [
|
|
2528
|
+
// Non-grid (flex) parent (document root): `justify-self` is ignored in flex → not provably safe.
|
|
2529
|
+
'<div className="flex justify-center items-center"><div className="bg-red-200">Hello</div></div>',
|
|
2530
|
+
// Grid parent, but the wrapper drops padding when removed → not layout-neutral (rule 3).
|
|
2531
|
+
'<div className="grid"><div className="p-4 flex items-center justify-center"><span className="bg-red-200">x</span></div></div>',
|
|
2532
|
+
// Grid parent forcing place-items-center: the wrapper would not fill its area → fill guard skips.
|
|
2533
|
+
'<div className="grid place-items-center"><div className="flex items-center justify-center"><span className="bg-red-200">x</span></div></div>',
|
|
2534
|
+
// onClick is a hard opacity barrier → the wrapper is load-bearing regardless of the gate.
|
|
2535
|
+
'<div className="flex justify-center items-center" onClick={handleClick}><div className="bg-red-200">Hello</div></div>'
|
|
2536
|
+
]
|
|
2537
|
+
}
|
|
2538
|
+
});
|
|
2539
|
+
|
|
2540
|
+
// ../patterns/src/library/fragment/redundant-fragment.pattern.ts
|
|
2541
|
+
function parentIsRedundantFragment(node, ctx) {
|
|
2542
|
+
const el = node;
|
|
2543
|
+
if (el.kind !== "element") return false;
|
|
2544
|
+
const parentId = el.parent;
|
|
2545
|
+
if (parentId == null) return false;
|
|
2546
|
+
const parent = ctx.doc.nodes.get(parentId);
|
|
2547
|
+
if (!parent || parent.kind !== "fragment") return false;
|
|
2548
|
+
if (parent.parent == null) return false;
|
|
2549
|
+
if (parent.children.length !== 1) return false;
|
|
2550
|
+
const m = parent.meta;
|
|
2551
|
+
if (m.hasKey || m.hasRef || m.hasEventHandlers || m.hasDynamicChildren || m.hasDangerousHtml || m.hasSpreadAttrs || m.isComponent) {
|
|
2552
|
+
return false;
|
|
2553
|
+
}
|
|
2554
|
+
if (m.targetedByCombinator || m.targetedByStructuralPseudo) return false;
|
|
2555
|
+
const fid = parentId;
|
|
2556
|
+
if (ctx.selectors.targetedByCombinator(fid) || ctx.selectors.targetedByStructuralPseudo(fid)) {
|
|
2557
|
+
return false;
|
|
2558
|
+
}
|
|
2559
|
+
if (ctx.selectors.reparentImpact(fid).size > 0) return false;
|
|
2560
|
+
return true;
|
|
2561
|
+
}
|
|
2562
|
+
var redundantFragment = definePattern({
|
|
2563
|
+
name: "redundant-fragment",
|
|
2564
|
+
category: "flatten/fragment/redundant-fragment",
|
|
2565
|
+
safety: 1,
|
|
2566
|
+
doc: {
|
|
2567
|
+
title: "Flatten redundant single-child fragment",
|
|
2568
|
+
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.",
|
|
2569
|
+
before: "<><Child/></>",
|
|
2570
|
+
after: "<Child/>",
|
|
2571
|
+
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."
|
|
2572
|
+
},
|
|
2573
|
+
match: parentIsRedundantFragment,
|
|
2574
|
+
rewrite: (ctx, rw) => {
|
|
2575
|
+
const parentId = ctx.node.parent;
|
|
2576
|
+
if (parentId == null) return null;
|
|
2577
|
+
const fragment = ctx.doc.nodes.get(parentId);
|
|
2578
|
+
if (!fragment || fragment.kind !== "fragment") return null;
|
|
2579
|
+
return [rw.unwrap(fragment)];
|
|
2580
|
+
},
|
|
2581
|
+
test: {
|
|
2582
|
+
cases: [
|
|
2583
|
+
{
|
|
2584
|
+
// A fragment renders no box, so unwrapping a single-child fragment is always layout-identical
|
|
2585
|
+
// → a provably-safe flatten: the child is spliced up into the fragment's slot.
|
|
2586
|
+
before: '<><span className="bg-red-200">Hi</span></>',
|
|
2587
|
+
after: '<span className="bg-red-200">Hi</span>'
|
|
2588
|
+
}
|
|
2589
|
+
],
|
|
2590
|
+
noMatch: [
|
|
2591
|
+
// Two children ⇒ not a single-child fragment, so the fragment is load-bearing and stays.
|
|
2592
|
+
'<><span className="bg-red-200">A</span><span className="bg-green-200">B</span></>'
|
|
2593
|
+
]
|
|
2594
|
+
}
|
|
2595
|
+
});
|
|
2596
|
+
|
|
2597
|
+
// ../patterns/src/library/grid/grid-center-wrapper.pattern.ts
|
|
2598
|
+
var gridCenterWrapper = definePattern({
|
|
2599
|
+
name: "grid-center-wrapper",
|
|
2600
|
+
category: "flatten/grid/grid-center-wrapper",
|
|
2601
|
+
safety: 2,
|
|
2602
|
+
doc: {
|
|
2603
|
+
title: "Flatten grid-centering wrapper",
|
|
2604
|
+
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.",
|
|
2605
|
+
before: '<div style="display:grid;align-items:center;justify-content:center"><Child/></div>',
|
|
2606
|
+
after: '<Child style="place-self:center"/>',
|
|
2607
|
+
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."
|
|
2608
|
+
},
|
|
2609
|
+
match: {
|
|
2610
|
+
tag: "div",
|
|
2611
|
+
style: { display: "grid", alignItems: "center", justifyContent: "center" },
|
|
2612
|
+
onlyChild: "element",
|
|
2613
|
+
paintsNothing: true
|
|
2614
|
+
},
|
|
2615
|
+
rewrite: {
|
|
2616
|
+
flattenInto: "child",
|
|
2617
|
+
childGains: { placeSelf: "center" }
|
|
2618
|
+
},
|
|
2619
|
+
// Like `flex-center-wrapper`, collapsing to `place-self:center` is render-identical ONLY when the
|
|
2620
|
+
// child's NEW parent is a statically-known GRID that lets the wrapper fill its area (there both halves
|
|
2621
|
+
// of place-self take effect). Under that ONE context the flatten is `provably-safe` and commits; under
|
|
2622
|
+
// a flex/block/unknown parent — or when the wrapper drops any own style — it stays `needs-verification`
|
|
2623
|
+
// and the conservative production gate PRESERVES it. Op-level correctness is asserted by the invariant suite.
|
|
2624
|
+
test: {
|
|
2625
|
+
cases: [
|
|
2626
|
+
{
|
|
2627
|
+
name: "grid parent \u2192 flattened (child gains place-self-center)",
|
|
2628
|
+
before: '<div className="grid"><div className="grid items-center justify-center"><span className="bg-red-200">x</span></div></div>',
|
|
2629
|
+
after: '<div className="grid"><span className="bg-red-200 place-self-center">x</span></div>'
|
|
2630
|
+
}
|
|
2631
|
+
],
|
|
2632
|
+
noMatch: [
|
|
2633
|
+
// Non-grid (document-root) parent: justify-self is ignored outside a grid → not provably safe.
|
|
2634
|
+
'<div className="grid justify-center items-center"><div className="bg-red-200">Hello</div></div>',
|
|
2635
|
+
// Grid parent, but the wrapper drops padding when removed → not layout-neutral, preserved.
|
|
2636
|
+
'<div className="grid"><div className="p-4 grid items-center justify-center"><span className="bg-red-200">x</span></div></div>',
|
|
2637
|
+
// onClick is a hard opacity barrier → the wrapper is load-bearing regardless of the gate.
|
|
2638
|
+
'<div className="grid justify-center items-center" onClick={handleClick}><div className="bg-red-200">Hello</div></div>'
|
|
2639
|
+
]
|
|
2640
|
+
}
|
|
2641
|
+
});
|
|
2642
|
+
|
|
2643
|
+
// ../patterns/src/library/wrapper/display-contents-wrapper.pattern.ts
|
|
2193
2644
|
function asEl(node) {
|
|
2194
2645
|
const n = node;
|
|
2195
2646
|
return n.kind === "element" ? n : null;
|
|
@@ -2213,7 +2664,7 @@ var targetedByStructuralPseudo = (node, ctx) => {
|
|
|
2213
2664
|
};
|
|
2214
2665
|
var displayContentsWrapper = definePattern({
|
|
2215
2666
|
name: "display-contents-wrapper",
|
|
2216
|
-
category: "flatten/display-contents-wrapper",
|
|
2667
|
+
category: "flatten/wrapper/display-contents-wrapper",
|
|
2217
2668
|
safety: 2,
|
|
2218
2669
|
doc: {
|
|
2219
2670
|
title: "Flatten display:contents wrapper",
|
|
@@ -2253,7 +2704,7 @@ var displayContentsWrapper = definePattern({
|
|
|
2253
2704
|
}
|
|
2254
2705
|
});
|
|
2255
2706
|
|
|
2256
|
-
// ../patterns/src/library/
|
|
2707
|
+
// ../patterns/src/library/wrapper/empty-style-div.pattern.ts
|
|
2257
2708
|
function asEl2(node) {
|
|
2258
2709
|
const n = node;
|
|
2259
2710
|
return n.kind === "element" ? n : null;
|
|
@@ -2285,7 +2736,7 @@ var hasNonBlockDisplay = (node, ctx) => {
|
|
|
2285
2736
|
};
|
|
2286
2737
|
var emptyStyleDiv = definePattern({
|
|
2287
2738
|
name: "empty-style-div",
|
|
2288
|
-
category: "flatten/empty-style-div",
|
|
2739
|
+
category: "flatten/wrapper/empty-style-div",
|
|
2289
2740
|
safety: 1,
|
|
2290
2741
|
doc: {
|
|
2291
2742
|
title: "Flatten empty-style div wrapper",
|
|
@@ -2324,326 +2775,66 @@ var emptyStyleDiv = definePattern({
|
|
|
2324
2775
|
}
|
|
2325
2776
|
});
|
|
2326
2777
|
|
|
2327
|
-
// ../patterns/src/library/
|
|
2328
|
-
var
|
|
2329
|
-
|
|
2330
|
-
|
|
2778
|
+
// ../patterns/src/library/wrapper/inherited-only-wrapper.pattern.ts
|
|
2779
|
+
var INERT_HOST_TAGS = /* @__PURE__ */ new Set(["div", "span"]);
|
|
2780
|
+
var isInertHostTag = (node) => {
|
|
2781
|
+
const n = node;
|
|
2782
|
+
if (n.kind !== "element") return false;
|
|
2783
|
+
return INERT_HOST_TAGS.has(String(n.tag).toLowerCase());
|
|
2784
|
+
};
|
|
2785
|
+
var isComponentNode2 = (node) => {
|
|
2786
|
+
const n = node;
|
|
2787
|
+
return n.kind === "element" ? n.meta.isComponent : false;
|
|
2788
|
+
};
|
|
2789
|
+
var hasOnlyInheritedStyle = (node, ctx) => {
|
|
2790
|
+
const sm = normalizer.normalizeStyleMap(ctx.computed());
|
|
2791
|
+
let sawAny = false;
|
|
2792
|
+
for (const block of sm.blocks.values()) {
|
|
2793
|
+
for (const decl of block.decls.values()) {
|
|
2794
|
+
sawAny = true;
|
|
2795
|
+
const inherited = decl.inherited || normalizer.inherited.isInherited(decl.property);
|
|
2796
|
+
if (!inherited) return false;
|
|
2797
|
+
}
|
|
2798
|
+
}
|
|
2799
|
+
return sawAny;
|
|
2800
|
+
};
|
|
2801
|
+
var inheritedOnlyWrapper = definePattern({
|
|
2802
|
+
name: "inherited-only-wrapper",
|
|
2803
|
+
category: "flatten/wrapper/inherited-only-wrapper",
|
|
2331
2804
|
safety: 2,
|
|
2332
2805
|
doc: {
|
|
2333
|
-
title: "Flatten
|
|
2334
|
-
summary: "A
|
|
2335
|
-
before: '<div style="
|
|
2336
|
-
after: '<Child style="
|
|
2337
|
-
safetyRationale: "
|
|
2806
|
+
title: "Flatten inherited-only styling wrapper",
|
|
2807
|
+
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.",
|
|
2808
|
+
before: '<div style="text-align:center"><Child/></div>',
|
|
2809
|
+
after: '<Child style="text-align:center"/>',
|
|
2810
|
+
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."
|
|
2338
2811
|
},
|
|
2339
2812
|
match: {
|
|
2340
|
-
tag: "div",
|
|
2341
|
-
style: { display: "flex", alignItems: "center", justifyContent: "center" },
|
|
2342
2813
|
onlyChild: "element",
|
|
2343
|
-
paintsNothing: true
|
|
2344
|
-
|
|
2345
|
-
rewrite: {
|
|
2346
|
-
flattenInto: "child",
|
|
2347
|
-
childGains: { placeSelf: "center" }
|
|
2814
|
+
paintsNothing: true,
|
|
2815
|
+
where: [isInertHostTag, not(isComponentNode2), hasOnlyInheritedStyle]
|
|
2348
2816
|
},
|
|
2349
|
-
|
|
2350
|
-
// the child's NEW parent is flex/grid; moreover the wrapper's own `display:flex` establishes a
|
|
2351
|
-
// formatting context. Both make this a `needs-verification` flatten, which the conservative
|
|
2352
|
-
// production gate (`'provably-safe'`, used by the harness) intentionally REVERTS — so every case
|
|
2353
|
-
// here is a no-match: the wrapper is preserved. Op-level rewrite correctness (purity, id-preserving
|
|
2354
|
-
// unwrap, opacity-barrier safety) is still asserted by the invariant suite over every pattern.
|
|
2817
|
+
rewrite: { flattenInto: "child" },
|
|
2355
2818
|
test: {
|
|
2819
|
+
cases: [
|
|
2820
|
+
{
|
|
2821
|
+
// `text-align:center` is inherited → folded onto the child; the paint-free wrapper is removed.
|
|
2822
|
+
before: '<div className="text-center"><p className="bg-red-200">x</p></div>',
|
|
2823
|
+
after: '<p className="bg-red-200 text-center">x</p>'
|
|
2824
|
+
}
|
|
2825
|
+
],
|
|
2356
2826
|
noMatch: [
|
|
2357
|
-
//
|
|
2358
|
-
//
|
|
2359
|
-
'<div className="
|
|
2360
|
-
//
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
'<div className="flex justify-center items-center" onClick={handleClick}><div className="bg-red-200">Hello</div></div>'
|
|
2827
|
+
// `p-4` is a NON-inherited padding: removing the box would drop it, so the flatten-safety gate
|
|
2828
|
+
// reverts the unwrap and the wrapper is left unchanged.
|
|
2829
|
+
'<div className="p-4"><p className="bg-red-200">x</p></div>',
|
|
2830
|
+
// A `<p>` wrapper is NOT an inert host box: its UA default display/margins are not captured in the
|
|
2831
|
+
// class-derived computed style, so removing it is not provably layout-neutral → left unchanged.
|
|
2832
|
+
'<p className="text-center"><span className="bg-red-200">x</span></p>'
|
|
2364
2833
|
]
|
|
2365
2834
|
}
|
|
2366
2835
|
});
|
|
2367
2836
|
|
|
2368
|
-
// ../patterns/src/library/
|
|
2369
|
-
var inlineFlexCenterWrapper = definePattern({
|
|
2370
|
-
name: "inline-flex-center-wrapper",
|
|
2371
|
-
category: "flatten/inline-flex-center-wrapper",
|
|
2372
|
-
safety: 2,
|
|
2373
|
-
doc: {
|
|
2374
|
-
title: "Flatten inline-flex-centering wrapper",
|
|
2375
|
-
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.",
|
|
2376
|
-
before: '<div style="display:inline-flex;align-items:center;justify-content:center"><Child/></div>',
|
|
2377
|
-
after: '<Child style="place-self:center"/>',
|
|
2378
|
-
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."
|
|
2379
|
-
},
|
|
2380
|
-
match: {
|
|
2381
|
-
tag: "div",
|
|
2382
|
-
style: { display: "inline-flex", alignItems: "center", justifyContent: "center" },
|
|
2383
|
-
onlyChild: "element",
|
|
2384
|
-
paintsNothing: true
|
|
2385
|
-
},
|
|
2386
|
-
rewrite: {
|
|
2387
|
-
flattenInto: "child",
|
|
2388
|
-
childGains: { placeSelf: "center" }
|
|
2389
|
-
},
|
|
2390
|
-
// Like its block-level sibling, this centering flatten is `needs-verification` (the wrapper's own
|
|
2391
|
-
// `display:inline-flex` establishes a formatting context, and place-self centering only holds under
|
|
2392
|
-
// a flex/grid parent), so the conservative production gate (`'provably-safe'`) REVERTS it — every
|
|
2393
|
-
// case here is a no-match. Op-level correctness is covered by the invariant suite.
|
|
2394
|
-
test: {
|
|
2395
|
-
noMatch: [
|
|
2396
|
-
// Even under a static flex/grid parent the centering flatten is not provably layout-neutral.
|
|
2397
|
-
'<div className="grid"><div className="inline-flex items-center justify-center"><span className="bg-red-200">x</span></div></div>',
|
|
2398
|
-
// Non-flex/grid parent (document root) → left unchanged.
|
|
2399
|
-
'<div className="inline-flex justify-center items-center"><div className="bg-red-200">Hello</div></div>',
|
|
2400
|
-
// onClick is a hard opacity barrier → the wrapper is load-bearing regardless of the gate.
|
|
2401
|
-
'<div className="inline-flex justify-center items-center" onClick={handleClick}><div className="bg-red-200">Hello</div></div>'
|
|
2402
|
-
]
|
|
2403
|
-
}
|
|
2404
|
-
});
|
|
2405
|
-
|
|
2406
|
-
// ../patterns/src/library/flatten/nested-flex-merge.pattern.ts
|
|
2407
|
-
function baseConditionStyleMap(decls) {
|
|
2408
|
-
const map = /* @__PURE__ */ new Map();
|
|
2409
|
-
for (const [prop, value] of decls) {
|
|
2410
|
-
for (const decl of normalizer.normalizeDeclaration(prop, value, false)) {
|
|
2411
|
-
map.set(decl.property, decl);
|
|
2412
|
-
}
|
|
2413
|
-
}
|
|
2414
|
-
const block = { condition: BASE_CONDITION, decls: map };
|
|
2415
|
-
const blocks = /* @__PURE__ */ new Map([[conditionKey(BASE_CONDITION), block]]);
|
|
2416
|
-
return { blocks };
|
|
2417
|
-
}
|
|
2418
|
-
var DISPLAY_FLEX = baseConditionStyleMap([["display", "flex"]]);
|
|
2419
|
-
var FLEX_CONTAINER_PROPERTIES = /* @__PURE__ */ new Set([
|
|
2420
|
-
"display",
|
|
2421
|
-
"flex-direction",
|
|
2422
|
-
"flex-wrap",
|
|
2423
|
-
"justify-content",
|
|
2424
|
-
"align-items",
|
|
2425
|
-
"align-content",
|
|
2426
|
-
"place-content",
|
|
2427
|
-
"place-items",
|
|
2428
|
-
"row-gap",
|
|
2429
|
-
"column-gap"
|
|
2430
|
-
]);
|
|
2431
|
-
function outerMergeSafe(sm) {
|
|
2432
|
-
const norm = normalizer.normalizeStyleMap(sm);
|
|
2433
|
-
for (const block of norm.blocks.values()) {
|
|
2434
|
-
for (const decl of block.decls.values()) {
|
|
2435
|
-
if (FLEX_CONTAINER_PROPERTIES.has(String(decl.property))) continue;
|
|
2436
|
-
if (decl.inherited) continue;
|
|
2437
|
-
return false;
|
|
2438
|
-
}
|
|
2439
|
-
}
|
|
2440
|
-
return true;
|
|
2441
|
-
}
|
|
2442
|
-
function flexConflict(outer, inner) {
|
|
2443
|
-
const a = normalizer.normalizeStyleMap(outer);
|
|
2444
|
-
const b = normalizer.normalizeStyleMap(inner);
|
|
2445
|
-
for (const [key, blockA] of a.blocks) {
|
|
2446
|
-
const blockB = b.blocks.get(key);
|
|
2447
|
-
if (!blockB) continue;
|
|
2448
|
-
for (const [prop, declA] of blockA.decls) {
|
|
2449
|
-
if (!FLEX_CONTAINER_PROPERTIES.has(String(prop))) continue;
|
|
2450
|
-
const declB = blockB.decls.get(prop);
|
|
2451
|
-
if (declB && declB.value !== declA.value) return true;
|
|
2452
|
-
}
|
|
2453
|
-
}
|
|
2454
|
-
return false;
|
|
2455
|
-
}
|
|
2456
|
-
function extractFlexStyle(sm) {
|
|
2457
|
-
const blocks = /* @__PURE__ */ new Map();
|
|
2458
|
-
for (const [key, block] of sm.blocks) {
|
|
2459
|
-
const decls = /* @__PURE__ */ new Map();
|
|
2460
|
-
for (const [prop, decl] of block.decls) {
|
|
2461
|
-
if (FLEX_CONTAINER_PROPERTIES.has(String(prop))) decls.set(prop, decl);
|
|
2462
|
-
}
|
|
2463
|
-
if (decls.size > 0) blocks.set(key, { condition: block.condition, decls });
|
|
2464
|
-
}
|
|
2465
|
-
return { blocks };
|
|
2466
|
-
}
|
|
2467
|
-
var isInnerFlex = and(
|
|
2468
|
-
isElement("div"),
|
|
2469
|
-
computed(DISPLAY_FLEX),
|
|
2470
|
-
not(targetedByCombinator)
|
|
2471
|
-
);
|
|
2472
|
-
var nestedFlexMerge = definePattern({
|
|
2473
|
-
name: "nested-flex-merge",
|
|
2474
|
-
category: "flatten/nested-flex-merge",
|
|
2475
|
-
safety: 2,
|
|
2476
|
-
doc: {
|
|
2477
|
-
title: "Merge nested flex containers",
|
|
2478
|
-
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.",
|
|
2479
|
-
before: '<div style="display:flex;align-items:center;gap:8px"><div style="display:flex;flex-direction:column"/></div>',
|
|
2480
|
-
after: '<div style="display:flex;flex-direction:column;align-items:center;gap:8px"/>',
|
|
2481
|
-
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."
|
|
2482
|
-
},
|
|
2483
|
-
match: {
|
|
2484
|
-
tag: "div",
|
|
2485
|
-
style: { display: "flex" },
|
|
2486
|
-
onlyChild: "element",
|
|
2487
|
-
paintsNothing: true
|
|
2488
|
-
},
|
|
2489
|
-
rewrite: (ctx, rw) => {
|
|
2490
|
-
const outer = ctx.node;
|
|
2491
|
-
const inner = ctx.onlyElementChild();
|
|
2492
|
-
if (!inner) return null;
|
|
2493
|
-
if (!isInnerFlex(inner, ctx)) return null;
|
|
2494
|
-
const outerStyle = ctx.computed();
|
|
2495
|
-
const innerStyle = ctx.computedOf(inner);
|
|
2496
|
-
if (!outerMergeSafe(outerStyle)) return null;
|
|
2497
|
-
if (flexConflict(outerStyle, innerStyle)) return null;
|
|
2498
|
-
return [
|
|
2499
|
-
// 1. Preserve inheritable values (color/font/…) by folding them onto the child first.
|
|
2500
|
-
rw.foldInheritedStyles(outer, inner, { conditions: "all" }),
|
|
2501
|
-
// 2. Transfer the wrapper's flex-container declarations onto the child (target-wins keeps the
|
|
2502
|
-
// child's value for any shared property — identical anyway, we proved non-conflict).
|
|
2503
|
-
rw.mergeStyle(inner, null, extractFlexStyle(outerStyle), "target-wins"),
|
|
2504
|
-
// 3. Remove the wrapper (structural-safe; hoists the child and preserves its IRNodeId).
|
|
2505
|
-
rw.unwrap(outer)
|
|
2506
|
-
];
|
|
2507
|
-
},
|
|
2508
|
-
// Merging the outer flex container into the inner removes the outer's box, but a `display:flex`
|
|
2509
|
-
// wrapper establishes a formatting context, so this is a `needs-verification` flatten that the
|
|
2510
|
-
// conservative production gate (`'provably-safe'`) REVERTS — every case here is a no-match. The
|
|
2511
|
-
// merge's op-level correctness (purity, id-preserving unwrap, opacity-barrier safety) is asserted
|
|
2512
|
-
// by the invariant suite over every pattern.
|
|
2513
|
-
test: {
|
|
2514
|
-
noMatch: [
|
|
2515
|
-
// The merge is real but not provably layout-neutral (the wrapper establishes a flex context),
|
|
2516
|
-
// so under the conservative gate the nested containers are left in place.
|
|
2517
|
-
'<div className="flex items-center gap-2" data-x="1"><div className="flex flex-col">X</div></div>',
|
|
2518
|
-
// A non-flex wrapper does not match the flex-container signature → left unchanged anyway.
|
|
2519
|
-
'<div className="block bg-blue-500"><div className="flex flex-col">X</div></div>'
|
|
2520
|
-
]
|
|
2521
|
-
}
|
|
2522
|
-
});
|
|
2523
|
-
|
|
2524
|
-
// ../patterns/src/library/flatten/nested-grid-merge.pattern.ts
|
|
2525
|
-
function baseConditionStyleMap2(decls) {
|
|
2526
|
-
const map = /* @__PURE__ */ new Map();
|
|
2527
|
-
for (const [prop, value] of decls) {
|
|
2528
|
-
for (const decl of normalizer.normalizeDeclaration(prop, value, false)) {
|
|
2529
|
-
map.set(decl.property, decl);
|
|
2530
|
-
}
|
|
2531
|
-
}
|
|
2532
|
-
const block = { condition: BASE_CONDITION, decls: map };
|
|
2533
|
-
const blocks = /* @__PURE__ */ new Map([[conditionKey(BASE_CONDITION), block]]);
|
|
2534
|
-
return { blocks };
|
|
2535
|
-
}
|
|
2536
|
-
var DISPLAY_GRID = baseConditionStyleMap2([["display", "grid"]]);
|
|
2537
|
-
var GRID_CONTAINER_PROPERTIES = /* @__PURE__ */ new Set([
|
|
2538
|
-
"display",
|
|
2539
|
-
"grid-template-columns",
|
|
2540
|
-
"grid-template-rows",
|
|
2541
|
-
"grid-template-areas",
|
|
2542
|
-
"grid-auto-columns",
|
|
2543
|
-
"grid-auto-rows",
|
|
2544
|
-
"grid-auto-flow",
|
|
2545
|
-
"justify-content",
|
|
2546
|
-
"align-content",
|
|
2547
|
-
"place-content",
|
|
2548
|
-
"justify-items",
|
|
2549
|
-
"align-items",
|
|
2550
|
-
"place-items",
|
|
2551
|
-
"row-gap",
|
|
2552
|
-
"column-gap"
|
|
2553
|
-
]);
|
|
2554
|
-
function outerMergeSafe2(sm) {
|
|
2555
|
-
const norm = normalizer.normalizeStyleMap(sm);
|
|
2556
|
-
for (const block of norm.blocks.values()) {
|
|
2557
|
-
for (const decl of block.decls.values()) {
|
|
2558
|
-
if (GRID_CONTAINER_PROPERTIES.has(String(decl.property))) continue;
|
|
2559
|
-
if (decl.inherited) continue;
|
|
2560
|
-
return false;
|
|
2561
|
-
}
|
|
2562
|
-
}
|
|
2563
|
-
return true;
|
|
2564
|
-
}
|
|
2565
|
-
function gridConflict(outer, inner) {
|
|
2566
|
-
const a = normalizer.normalizeStyleMap(outer);
|
|
2567
|
-
const b = normalizer.normalizeStyleMap(inner);
|
|
2568
|
-
for (const [key, blockA] of a.blocks) {
|
|
2569
|
-
const blockB = b.blocks.get(key);
|
|
2570
|
-
if (!blockB) continue;
|
|
2571
|
-
for (const [prop, declA] of blockA.decls) {
|
|
2572
|
-
if (!GRID_CONTAINER_PROPERTIES.has(String(prop))) continue;
|
|
2573
|
-
const declB = blockB.decls.get(prop);
|
|
2574
|
-
if (declB && declB.value !== declA.value) return true;
|
|
2575
|
-
}
|
|
2576
|
-
}
|
|
2577
|
-
return false;
|
|
2578
|
-
}
|
|
2579
|
-
function extractGridStyle(sm) {
|
|
2580
|
-
const blocks = /* @__PURE__ */ new Map();
|
|
2581
|
-
for (const [key, block] of sm.blocks) {
|
|
2582
|
-
const decls = /* @__PURE__ */ new Map();
|
|
2583
|
-
for (const [prop, decl] of block.decls) {
|
|
2584
|
-
if (GRID_CONTAINER_PROPERTIES.has(String(prop))) decls.set(prop, decl);
|
|
2585
|
-
}
|
|
2586
|
-
if (decls.size > 0) blocks.set(key, { condition: block.condition, decls });
|
|
2587
|
-
}
|
|
2588
|
-
return { blocks };
|
|
2589
|
-
}
|
|
2590
|
-
var isInnerGrid = and(
|
|
2591
|
-
isElement("div"),
|
|
2592
|
-
computed(DISPLAY_GRID),
|
|
2593
|
-
not(targetedByCombinator)
|
|
2594
|
-
);
|
|
2595
|
-
var nestedGridMerge = definePattern({
|
|
2596
|
-
name: "nested-grid-merge",
|
|
2597
|
-
category: "flatten/nested-grid-merge",
|
|
2598
|
-
safety: 2,
|
|
2599
|
-
doc: {
|
|
2600
|
-
title: "Merge nested grid containers",
|
|
2601
|
-
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.",
|
|
2602
|
-
before: '<div style="display:grid;gap:8px"><div style="display:grid;grid-template-columns:1fr 1fr"/></div>',
|
|
2603
|
-
after: '<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px"/>',
|
|
2604
|
-
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."
|
|
2605
|
-
},
|
|
2606
|
-
match: {
|
|
2607
|
-
tag: "div",
|
|
2608
|
-
style: { display: "grid" },
|
|
2609
|
-
onlyChild: "element",
|
|
2610
|
-
paintsNothing: true
|
|
2611
|
-
},
|
|
2612
|
-
rewrite: (ctx, rw) => {
|
|
2613
|
-
const outer = ctx.node;
|
|
2614
|
-
const inner = ctx.onlyElementChild();
|
|
2615
|
-
if (!inner) return null;
|
|
2616
|
-
if (!isInnerGrid(inner, ctx)) return null;
|
|
2617
|
-
const outerStyle = ctx.computed();
|
|
2618
|
-
const innerStyle = ctx.computedOf(inner);
|
|
2619
|
-
if (!outerMergeSafe2(outerStyle)) return null;
|
|
2620
|
-
if (gridConflict(outerStyle, innerStyle)) return null;
|
|
2621
|
-
return [
|
|
2622
|
-
// 1. Preserve inheritable values (color/font/…) by folding them onto the child first.
|
|
2623
|
-
rw.foldInheritedStyles(outer, inner, { conditions: "all" }),
|
|
2624
|
-
// 2. Transfer the wrapper's grid-container declarations onto the child (target-wins keeps the
|
|
2625
|
-
// child's value for any shared property — identical anyway, we proved non-conflict).
|
|
2626
|
-
rw.mergeStyle(inner, null, extractGridStyle(outerStyle), "target-wins"),
|
|
2627
|
-
// 3. Remove the wrapper (structural-safe; hoists the child and preserves its IRNodeId).
|
|
2628
|
-
rw.unwrap(outer)
|
|
2629
|
-
];
|
|
2630
|
-
},
|
|
2631
|
-
// Like its flex sibling, this merge removes the outer container's box, but a `display:grid` wrapper
|
|
2632
|
-
// establishes a formatting context, so it is a `needs-verification` flatten that the conservative
|
|
2633
|
-
// production gate (`'provably-safe'`) REVERTS — every case here is a no-match. Op-level correctness
|
|
2634
|
-
// is asserted by the invariant suite over every pattern.
|
|
2635
|
-
test: {
|
|
2636
|
-
noMatch: [
|
|
2637
|
-
// The merge is real but not provably layout-neutral (the wrapper establishes a grid context),
|
|
2638
|
-
// so under the conservative gate the nested containers are left in place.
|
|
2639
|
-
'<div className="grid gap-2" data-x="1"><div className="grid grid-cols-2">X</div></div>',
|
|
2640
|
-
// A non-grid wrapper does not match the grid-container signature → left unchanged anyway.
|
|
2641
|
-
'<div className="block bg-blue-500"><div className="grid grid-cols-2">X</div></div>'
|
|
2642
|
-
]
|
|
2643
|
-
}
|
|
2644
|
-
});
|
|
2645
|
-
|
|
2646
|
-
// ../patterns/src/library/flatten/passthrough-wrapper.pattern.ts
|
|
2837
|
+
// ../patterns/src/library/wrapper/passthrough-wrapper.pattern.ts
|
|
2647
2838
|
function metaOf2(node) {
|
|
2648
2839
|
const n = node;
|
|
2649
2840
|
return n.kind === "element" ? n.meta : null;
|
|
@@ -2658,7 +2849,7 @@ var establishesContext = (node) => {
|
|
|
2658
2849
|
return m.establishesBox || m.establishesFormattingContext || m.establishesStackingContext || m.isContainingBlock || m.declaresCustomProperties;
|
|
2659
2850
|
};
|
|
2660
2851
|
var hasSpreadAttrs2 = (node) => metaOf2(node)?.hasSpreadAttrs ?? false;
|
|
2661
|
-
var
|
|
2852
|
+
var isComponentNode3 = (node) => metaOf2(node)?.isComponent ?? false;
|
|
2662
2853
|
var hasOwnAttrs2 = (node) => {
|
|
2663
2854
|
const el = elementOf(node);
|
|
2664
2855
|
if (!el) return false;
|
|
@@ -2668,1251 +2859,145 @@ var targetedByStructuralPseudo3 = (node, ctx) => {
|
|
|
2668
2859
|
const el = elementOf(node);
|
|
2669
2860
|
if (!el) return false;
|
|
2670
2861
|
if (el.meta.targetedByStructuralPseudo) return true;
|
|
2671
|
-
return ctx.selectors.targetedByStructuralPseudo(el.id);
|
|
2672
|
-
};
|
|
2673
|
-
var passthroughWrapper = definePattern({
|
|
2674
|
-
name: "passthrough-wrapper",
|
|
2675
|
-
category: "flatten/passthrough-wrapper",
|
|
2676
|
-
safety: 2,
|
|
2677
|
-
doc: {
|
|
2678
|
-
title: "Flatten passthrough wrapper",
|
|
2679
|
-
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.",
|
|
2680
|
-
before: "<div><Child/></div>",
|
|
2681
|
-
after: "<Child/>",
|
|
2682
|
-
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."
|
|
2683
|
-
},
|
|
2684
|
-
match: {
|
|
2685
|
-
tag: "div",
|
|
2686
|
-
onlyChild: "element",
|
|
2687
|
-
paintsNothing: true,
|
|
2688
|
-
where: [
|
|
2689
|
-
not(establishesContext),
|
|
2690
|
-
not(hasOwnAttrs2),
|
|
2691
|
-
not(hasDynamicClasses),
|
|
2692
|
-
not(hasSpreadAttrs2),
|
|
2693
|
-
not(isComponentNode2),
|
|
2694
|
-
not(targetedByStructuralPseudo3)
|
|
2695
|
-
]
|
|
2696
|
-
},
|
|
2697
|
-
rewrite: { flattenInto: "child" },
|
|
2698
|
-
test: {
|
|
2699
|
-
cases: [
|
|
2700
|
-
{
|
|
2701
|
-
// A plain, style-free wrapper paints nothing and establishes no context → a provably-safe
|
|
2702
|
-
// flatten under the conservative gate: the wrapper is removed and its sole child hoisted.
|
|
2703
|
-
before: '<div><a className="bg-red-200">Link</a></div>',
|
|
2704
|
-
after: '<a className="bg-red-200">Link</a>'
|
|
2705
|
-
}
|
|
2706
|
-
],
|
|
2707
|
-
noMatch: [
|
|
2708
|
-
// A ref pins the wrapper's element identity (a hard opacity barrier) → not a passthrough.
|
|
2709
|
-
'<div ref={rootRef}><a className="bg-red-200">Link</a></div>',
|
|
2710
|
-
// A `display:flex` wrapper establishes a formatting context, so removing its box is NOT
|
|
2711
|
-
// provably layout-neutral → the conservative gate leaves it in place.
|
|
2712
|
-
'<div className="flex"><a className="bg-red-200">Link</a></div>'
|
|
2713
|
-
]
|
|
2714
|
-
}
|
|
2715
|
-
});
|
|
2716
|
-
|
|
2717
|
-
// ../patterns/src/library/flatten/redundant-fragment.pattern.ts
|
|
2718
|
-
function parentIsRedundantFragment(node, ctx) {
|
|
2719
|
-
const el = node;
|
|
2720
|
-
if (el.kind !== "element") return false;
|
|
2721
|
-
const parentId = el.parent;
|
|
2722
|
-
if (parentId == null) return false;
|
|
2723
|
-
const parent = ctx.doc.nodes.get(parentId);
|
|
2724
|
-
if (!parent || parent.kind !== "fragment") return false;
|
|
2725
|
-
if (parent.parent == null) return false;
|
|
2726
|
-
if (parent.children.length !== 1) return false;
|
|
2727
|
-
const m = parent.meta;
|
|
2728
|
-
if (m.hasKey || m.hasRef || m.hasEventHandlers || m.hasDynamicChildren || m.hasDangerousHtml || m.hasSpreadAttrs || m.isComponent) {
|
|
2729
|
-
return false;
|
|
2730
|
-
}
|
|
2731
|
-
if (m.targetedByCombinator || m.targetedByStructuralPseudo) return false;
|
|
2732
|
-
const fid = parentId;
|
|
2733
|
-
if (ctx.selectors.targetedByCombinator(fid) || ctx.selectors.targetedByStructuralPseudo(fid)) {
|
|
2734
|
-
return false;
|
|
2735
|
-
}
|
|
2736
|
-
if (ctx.selectors.reparentImpact(fid).size > 0) return false;
|
|
2737
|
-
return true;
|
|
2738
|
-
}
|
|
2739
|
-
var redundantFragment = definePattern({
|
|
2740
|
-
name: "redundant-fragment",
|
|
2741
|
-
category: "flatten/redundant-fragment",
|
|
2742
|
-
safety: 1,
|
|
2743
|
-
doc: {
|
|
2744
|
-
title: "Flatten redundant single-child fragment",
|
|
2745
|
-
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.",
|
|
2746
|
-
before: "<><Child/></>",
|
|
2747
|
-
after: "<Child/>",
|
|
2748
|
-
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."
|
|
2749
|
-
},
|
|
2750
|
-
match: parentIsRedundantFragment,
|
|
2751
|
-
rewrite: (ctx, rw) => {
|
|
2752
|
-
const parentId = ctx.node.parent;
|
|
2753
|
-
if (parentId == null) return null;
|
|
2754
|
-
const fragment = ctx.doc.nodes.get(parentId);
|
|
2755
|
-
if (!fragment || fragment.kind !== "fragment") return null;
|
|
2756
|
-
return [rw.unwrap(fragment)];
|
|
2757
|
-
},
|
|
2758
|
-
test: {
|
|
2759
|
-
cases: [
|
|
2760
|
-
{
|
|
2761
|
-
// A fragment renders no box, so unwrapping a single-child fragment is always layout-identical
|
|
2762
|
-
// → a provably-safe flatten: the child is spliced up into the fragment's slot.
|
|
2763
|
-
before: '<><span className="bg-red-200">Hi</span></>',
|
|
2764
|
-
after: '<span className="bg-red-200">Hi</span>'
|
|
2765
|
-
}
|
|
2766
|
-
],
|
|
2767
|
-
noMatch: [
|
|
2768
|
-
// Two children ⇒ not a single-child fragment, so the fragment is load-bearing and stays.
|
|
2769
|
-
'<><span className="bg-red-200">A</span><span className="bg-green-200">B</span></>'
|
|
2770
|
-
]
|
|
2771
|
-
}
|
|
2772
|
-
});
|
|
2773
|
-
|
|
2774
|
-
// ../patterns/src/library/flatten/redundant-inline-wrapper.pattern.ts
|
|
2775
|
-
function asEl3(node) {
|
|
2776
|
-
const n = node;
|
|
2777
|
-
return n.kind === "element" ? n : null;
|
|
2778
|
-
}
|
|
2779
|
-
function metaOf3(node) {
|
|
2780
|
-
return asEl3(node)?.meta ?? null;
|
|
2781
|
-
}
|
|
2782
|
-
var establishesContext2 = (node) => {
|
|
2783
|
-
const m = metaOf3(node);
|
|
2784
|
-
if (!m) return false;
|
|
2785
|
-
return m.establishesBox || m.establishesFormattingContext || m.establishesStackingContext || m.isContainingBlock || m.declaresCustomProperties;
|
|
2786
|
-
};
|
|
2787
|
-
var hasSpreadAttrs3 = (node) => metaOf3(node)?.hasSpreadAttrs ?? false;
|
|
2788
|
-
var isComponentNode3 = (node) => metaOf3(node)?.isComponent ?? false;
|
|
2789
|
-
var hasOwnAttrs3 = (node) => {
|
|
2790
|
-
const el = asEl3(node);
|
|
2791
|
-
if (!el) return false;
|
|
2792
|
-
return el.attrs.entries.size > 0 || el.attrs.spreads.length > 0;
|
|
2793
|
-
};
|
|
2794
|
-
var targetedByStructuralPseudo4 = (node, ctx) => {
|
|
2795
|
-
const el = asEl3(node);
|
|
2796
|
-
if (!el) return false;
|
|
2797
|
-
if (el.meta.targetedByStructuralPseudo) return true;
|
|
2798
|
-
return ctx.selectors.targetedByStructuralPseudo(el.id);
|
|
2799
|
-
};
|
|
2800
|
-
var DISPLAY3 = "display";
|
|
2801
|
-
var hasNonInlineDisplay = (node, ctx) => {
|
|
2802
|
-
const el = asEl3(node);
|
|
2803
|
-
if (!el) return false;
|
|
2804
|
-
const sm = ctx.computedOf(el) ?? el.computed;
|
|
2805
|
-
for (const block of sm.blocks.values()) {
|
|
2806
|
-
const decl = block.decls.get(DISPLAY3);
|
|
2807
|
-
if (decl && String(decl.value) !== "inline") return true;
|
|
2808
|
-
}
|
|
2809
|
-
return false;
|
|
2810
|
-
};
|
|
2811
|
-
var redundantInlineWrapper = definePattern({
|
|
2812
|
-
name: "redundant-inline-wrapper",
|
|
2813
|
-
category: "flatten/redundant-inline-wrapper",
|
|
2814
|
-
safety: 2,
|
|
2815
|
-
doc: {
|
|
2816
|
-
title: "Flatten redundant inline wrapper",
|
|
2817
|
-
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.",
|
|
2818
|
-
before: "<span><Child/></span>",
|
|
2819
|
-
after: "<Child/>",
|
|
2820
|
-
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."
|
|
2821
|
-
},
|
|
2822
|
-
match: {
|
|
2823
|
-
tag: "span",
|
|
2824
|
-
onlyChild: "element",
|
|
2825
|
-
paintsNothing: true,
|
|
2826
|
-
where: [
|
|
2827
|
-
not(hasNonInlineDisplay),
|
|
2828
|
-
not(establishesContext2),
|
|
2829
|
-
not(hasOwnAttrs3),
|
|
2830
|
-
not(hasDynamicClasses),
|
|
2831
|
-
not(hasSpreadAttrs3),
|
|
2832
|
-
not(isComponentNode3),
|
|
2833
|
-
not(targetedByStructuralPseudo4)
|
|
2834
|
-
]
|
|
2835
|
-
},
|
|
2836
|
-
rewrite: { flattenInto: "child" },
|
|
2837
|
-
test: {
|
|
2838
|
-
cases: [
|
|
2839
|
-
{
|
|
2840
|
-
// An empty inline span paints nothing and establishes no context → a provably-safe flatten:
|
|
2841
|
-
// the span is removed and its sole child hoisted in place.
|
|
2842
|
-
before: '<span><a className="text-blue-500">Link</a></span>',
|
|
2843
|
-
after: '<a className="text-blue-500">Link</a>'
|
|
2844
|
-
}
|
|
2845
|
-
],
|
|
2846
|
-
noMatch: [
|
|
2847
|
-
// A ref pins the span's element identity (a hard opacity barrier) → not a passthrough.
|
|
2848
|
-
'<span ref={spanRef}><a className="text-blue-500">Link</a></span>',
|
|
2849
|
-
// The span paints its own background (own visual style) → kept.
|
|
2850
|
-
'<span className="bg-green-200"><a className="text-blue-500">Link</a></span>',
|
|
2851
|
-
// Non-inline display (inline-block) participates in layout differently → kept.
|
|
2852
|
-
'<span className="inline-block"><a className="text-blue-500">Link</a></span>'
|
|
2853
|
-
]
|
|
2854
|
-
}
|
|
2855
|
-
});
|
|
2856
|
-
|
|
2857
|
-
// ../patterns/src/library/compress/border-radius-shorthand.pattern.ts
|
|
2858
|
-
var CORNERS = [
|
|
2859
|
-
"border-top-left-radius",
|
|
2860
|
-
"border-top-right-radius",
|
|
2861
|
-
"border-bottom-right-radius",
|
|
2862
|
-
"border-bottom-left-radius"
|
|
2863
|
-
];
|
|
2864
|
-
var CORNER_SET = new Set(CORNERS);
|
|
2865
|
-
var BASE_KEY = conditionKey(BASE_CONDITION);
|
|
2866
|
-
var RADIUS = "border-radius";
|
|
2867
|
-
var NON_COLLAPSIBLE_VALUES = /* @__PURE__ */ new Set([
|
|
2868
|
-
"initial",
|
|
2869
|
-
"inherit",
|
|
2870
|
-
"unset",
|
|
2871
|
-
"revert",
|
|
2872
|
-
"revert-layer"
|
|
2873
|
-
]);
|
|
2874
|
-
function analyzeRadius(sm) {
|
|
2875
|
-
const block = sm.blocks.get(BASE_KEY);
|
|
2876
|
-
if (!block) return null;
|
|
2877
|
-
const corners = [];
|
|
2878
|
-
for (const corner of CORNERS) {
|
|
2879
|
-
const decl = block.decls.get(corner);
|
|
2880
|
-
if (!decl) return null;
|
|
2881
|
-
corners.push(decl);
|
|
2882
|
-
}
|
|
2883
|
-
const important = corners[0].important;
|
|
2884
|
-
if (!corners.every((d) => d.important === important)) return null;
|
|
2885
|
-
const value = String(corners[0].value);
|
|
2886
|
-
if (NON_COLLAPSIBLE_VALUES.has(value)) return null;
|
|
2887
|
-
if (!corners.every((d) => String(d.value) === value)) return null;
|
|
2888
|
-
const relative = corners.some((d) => d.relativeToParent);
|
|
2889
|
-
return { value, important, relative };
|
|
2890
|
-
}
|
|
2891
|
-
function withFoldedRadius(sm, fold) {
|
|
2892
|
-
const blocks = /* @__PURE__ */ new Map();
|
|
2893
|
-
for (const [key, block] of sm.blocks) {
|
|
2894
|
-
if (key !== BASE_KEY) {
|
|
2895
|
-
blocks.set(key, block);
|
|
2896
|
-
continue;
|
|
2897
|
-
}
|
|
2898
|
-
const decls = /* @__PURE__ */ new Map();
|
|
2899
|
-
for (const [prop, decl] of block.decls) {
|
|
2900
|
-
if (CORNER_SET.has(String(prop))) continue;
|
|
2901
|
-
decls.set(prop, decl);
|
|
2902
|
-
}
|
|
2903
|
-
const shorthand = {
|
|
2904
|
-
property: RADIUS,
|
|
2905
|
-
value: fold.value,
|
|
2906
|
-
important: fold.important,
|
|
2907
|
-
relativeToParent: fold.relative,
|
|
2908
|
-
inherited: false
|
|
2909
|
-
// border-radius is never inherited
|
|
2910
|
-
};
|
|
2911
|
-
decls.set(shorthand.property, shorthand);
|
|
2912
|
-
blocks.set(key, { condition: block.condition, decls });
|
|
2913
|
-
}
|
|
2914
|
-
return { blocks };
|
|
2915
|
-
}
|
|
2916
|
-
var borderRadiusShorthand = definePattern({
|
|
2917
|
-
name: "border-radius-shorthand",
|
|
2918
|
-
category: "compress/border-radius-shorthand",
|
|
2919
|
-
safety: 1,
|
|
2920
|
-
doc: {
|
|
2921
|
-
title: "Collapse equal corner radii into border-radius",
|
|
2922
|
-
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).",
|
|
2923
|
-
before: '<div class="rounded-tl-lg rounded-tr-lg rounded-br-lg rounded-bl-lg"/>',
|
|
2924
|
-
after: '<div class="rounded-lg"/>',
|
|
2925
|
-
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."
|
|
2926
|
-
},
|
|
2927
|
-
rewrite: {
|
|
2928
|
-
rewriteClasses(computed2) {
|
|
2929
|
-
const fold = analyzeRadius(computed2);
|
|
2930
|
-
return fold ? withFoldedRadius(computed2, fold) : null;
|
|
2931
|
-
}
|
|
2932
|
-
},
|
|
2933
|
-
test: {
|
|
2934
|
-
cases: [
|
|
2935
|
-
{
|
|
2936
|
-
// The four equal corner longhands collapse to a `border-radius` decl at the IR level; the
|
|
2937
|
-
// minimizing reverse-emit then picks the single shortest utility (`rounded-lg`) that reproduces
|
|
2938
|
-
// it, replacing the four `rounded-{tl,tr,br,bl}-lg` tokens. `bg-red-200` is preserved.
|
|
2939
|
-
before: '<div className="rounded-tl-lg rounded-tr-lg rounded-br-lg rounded-bl-lg bg-red-200">box</div>',
|
|
2940
|
-
after: '<div className="bg-red-200 rounded-lg">box</div>'
|
|
2941
|
-
}
|
|
2942
|
-
],
|
|
2943
|
-
// Corners differ (top corners vs bottom corners) → no all-equal collapse.
|
|
2944
|
-
noMatch: ['<div className="rounded-t-lg rounded-b-sm bg-red-200">box</div>']
|
|
2945
|
-
}
|
|
2946
|
-
});
|
|
2947
|
-
|
|
2948
|
-
// ../patterns/src/library/compress/border-shorthand.pattern.ts
|
|
2949
|
-
var WIDTH_SIDES = [
|
|
2950
|
-
"border-top-width",
|
|
2951
|
-
"border-right-width",
|
|
2952
|
-
"border-bottom-width",
|
|
2953
|
-
"border-left-width"
|
|
2954
|
-
];
|
|
2955
|
-
var WIDTH_SIDE_SET = new Set(WIDTH_SIDES);
|
|
2956
|
-
var BASE_KEY2 = conditionKey(BASE_CONDITION);
|
|
2957
|
-
var BORDER_WIDTH = "border-width";
|
|
2958
|
-
function analyzeWidth(sm) {
|
|
2959
|
-
const block = sm.blocks.get(BASE_KEY2);
|
|
2960
|
-
if (!block) return null;
|
|
2961
|
-
const sides = [];
|
|
2962
|
-
for (const side of WIDTH_SIDES) {
|
|
2963
|
-
const decl = block.decls.get(side);
|
|
2964
|
-
if (!decl) return null;
|
|
2965
|
-
sides.push(decl);
|
|
2966
|
-
}
|
|
2967
|
-
const [top, right, bottom, left] = sides;
|
|
2968
|
-
if (!(top.important === right.important && right.important === bottom.important && bottom.important === left.important)) {
|
|
2969
|
-
return null;
|
|
2970
|
-
}
|
|
2971
|
-
const tv = String(top.value);
|
|
2972
|
-
const rv = String(right.value);
|
|
2973
|
-
const bv = String(bottom.value);
|
|
2974
|
-
const lv = String(left.value);
|
|
2975
|
-
if (tv !== bv || lv !== rv) return null;
|
|
2976
|
-
const value = tv === lv ? tv : `${tv} ${lv}`;
|
|
2977
|
-
const relative = sides.some((d) => d.relativeToParent);
|
|
2978
|
-
return { value, important: top.important, relative };
|
|
2979
|
-
}
|
|
2980
|
-
function withFoldedWidth(sm, fold) {
|
|
2981
|
-
const blocks = /* @__PURE__ */ new Map();
|
|
2982
|
-
for (const [key, block] of sm.blocks) {
|
|
2983
|
-
if (key !== BASE_KEY2) {
|
|
2984
|
-
blocks.set(key, block);
|
|
2985
|
-
continue;
|
|
2986
|
-
}
|
|
2987
|
-
const decls = /* @__PURE__ */ new Map();
|
|
2988
|
-
for (const [prop, decl] of block.decls) {
|
|
2989
|
-
if (WIDTH_SIDE_SET.has(String(prop))) continue;
|
|
2990
|
-
decls.set(prop, decl);
|
|
2991
|
-
}
|
|
2992
|
-
const shorthand = {
|
|
2993
|
-
property: BORDER_WIDTH,
|
|
2994
|
-
value: fold.value,
|
|
2995
|
-
important: fold.important,
|
|
2996
|
-
relativeToParent: fold.relative,
|
|
2997
|
-
inherited: false
|
|
2998
|
-
// border-width is never inherited
|
|
2999
|
-
};
|
|
3000
|
-
decls.set(shorthand.property, shorthand);
|
|
3001
|
-
blocks.set(key, { condition: block.condition, decls });
|
|
3002
|
-
}
|
|
3003
|
-
return { blocks };
|
|
3004
|
-
}
|
|
3005
|
-
var borderShorthand = definePattern({
|
|
3006
|
-
name: "border-shorthand",
|
|
3007
|
-
category: "compress/border-shorthand",
|
|
3008
|
-
safety: 1,
|
|
3009
|
-
doc: {
|
|
3010
|
-
title: "Collapse border-width longhands to shorthand",
|
|
3011
|
-
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-*).",
|
|
3012
|
-
before: '<div class="border-t-2 border-r-2 border-b-2 border-l-2"/>',
|
|
3013
|
-
after: '<div class="border-2"/>',
|
|
3014
|
-
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."
|
|
3015
|
-
},
|
|
3016
|
-
rewrite: {
|
|
3017
|
-
rewriteClasses(computed2) {
|
|
3018
|
-
const fold = analyzeWidth(computed2);
|
|
3019
|
-
return fold ? withFoldedWidth(computed2, fold) : null;
|
|
3020
|
-
}
|
|
3021
|
-
},
|
|
3022
|
-
test: {
|
|
3023
|
-
cases: [
|
|
3024
|
-
{
|
|
3025
|
-
// The four equal width longhands collapse to a `border-width` shorthand at the IR level, and the
|
|
3026
|
-
// minimizing reverse-emit picks the single shortest utility (`border-2`) that reproduces it,
|
|
3027
|
-
// replacing the four `border-{t,r,b,l}-2` tokens. `bg-red-200` is preserved.
|
|
3028
|
-
before: '<div className="border-t-2 border-r-2 border-b-2 border-l-2 bg-red-200">box</div>',
|
|
3029
|
-
after: '<div className="bg-red-200 border-2">box</div>'
|
|
3030
|
-
}
|
|
3031
|
-
],
|
|
3032
|
-
// Asymmetric widths (top != bottom) cannot fold into a shorthand.
|
|
3033
|
-
noMatch: ['<div className="border-t-2 border-r-4 border-b-8 border-l-4 bg-red-200">box</div>']
|
|
3034
|
-
}
|
|
3035
|
-
});
|
|
3036
|
-
|
|
3037
|
-
// ../patterns/src/library/compress/dedupe-classes.pattern.ts
|
|
3038
|
-
function findRedundantClasses(computed2) {
|
|
3039
|
-
const winners = /* @__PURE__ */ new Set();
|
|
3040
|
-
const shadowed = /* @__PURE__ */ new Set();
|
|
3041
|
-
for (const block of computed2.blocks.values()) {
|
|
3042
|
-
for (const decl of block.decls.values()) {
|
|
3043
|
-
if (decl.origin && decl.origin.kind === "class") winners.add(decl.origin.className);
|
|
3044
|
-
for (const o of decl.shadowed ?? []) {
|
|
3045
|
-
if (o.kind === "class") shadowed.add(o.className);
|
|
3046
|
-
}
|
|
3047
|
-
}
|
|
3048
|
-
}
|
|
3049
|
-
return { winners, shadowed };
|
|
3050
|
-
}
|
|
3051
|
-
var dedupeClasses = definePattern({
|
|
3052
|
-
name: "dedupe-classes",
|
|
3053
|
-
category: "compress/dedupe-classes",
|
|
3054
|
-
safety: 1,
|
|
3055
|
-
doc: {
|
|
3056
|
-
title: "Dedupe fully-overridden class tokens",
|
|
3057
|
-
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.",
|
|
3058
|
-
before: '<p class="text-sm text-lg" />',
|
|
3059
|
-
after: '<p class="text-lg" />',
|
|
3060
|
-
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."
|
|
3061
|
-
},
|
|
3062
|
-
rewrite: {
|
|
3063
|
-
dropClasses(computed2, ctx) {
|
|
3064
|
-
const { winners, shadowed } = findRedundantClasses(computed2);
|
|
3065
|
-
const drop = /* @__PURE__ */ new Set();
|
|
3066
|
-
for (const cls of shadowed) {
|
|
3067
|
-
if (winners.has(cls)) continue;
|
|
3068
|
-
if (!ctx.resolver.selectorUsage(cls).droppable) continue;
|
|
3069
|
-
drop.add(cls);
|
|
3070
|
-
}
|
|
3071
|
-
return drop;
|
|
3072
|
-
}
|
|
3073
|
-
},
|
|
3074
|
-
test: {
|
|
3075
|
-
cases: [
|
|
3076
|
-
{
|
|
3077
|
-
// `text-sm` is fully overridden by `text-lg` (both set font-size + line-height). The resolver
|
|
3078
|
-
// records that shadowing in provenance and reports the Tailwind utility as droppable, so the
|
|
3079
|
-
// pattern drops `text-sm`; the reverse-emit then re-derives the minimal set (`text-lg`).
|
|
3080
|
-
before: '<p className="text-sm text-lg">Hi</p>',
|
|
3081
|
-
after: '<p className="text-lg">Hi</p>'
|
|
3082
|
-
}
|
|
3083
|
-
],
|
|
3084
|
-
// Both tokens win a distinct property (no full override) → nothing to dedupe.
|
|
3085
|
-
noMatch: ['<p className="text-lg font-bold">Hi</p>']
|
|
3086
|
-
}
|
|
3087
|
-
});
|
|
3088
|
-
|
|
3089
|
-
// ../patterns/src/library/compress/gap-shorthand.pattern.ts
|
|
3090
|
-
var ROW_GAP = "row-gap";
|
|
3091
|
-
var COLUMN_GAP = "column-gap";
|
|
3092
|
-
var GAP = "gap";
|
|
3093
|
-
var BASE_KEY3 = conditionKey(BASE_CONDITION);
|
|
3094
|
-
function withGapShorthand(sm, gapDecl) {
|
|
3095
|
-
const blocks = /* @__PURE__ */ new Map();
|
|
3096
|
-
for (const [key, block] of sm.blocks) {
|
|
3097
|
-
if (key !== BASE_KEY3) {
|
|
3098
|
-
blocks.set(key, block);
|
|
3099
|
-
continue;
|
|
3100
|
-
}
|
|
3101
|
-
const decls = /* @__PURE__ */ new Map();
|
|
3102
|
-
for (const [prop, decl] of block.decls) {
|
|
3103
|
-
if (prop === ROW_GAP || prop === COLUMN_GAP) continue;
|
|
3104
|
-
decls.set(prop, decl);
|
|
3105
|
-
}
|
|
3106
|
-
decls.set(gapDecl.property, gapDecl);
|
|
3107
|
-
blocks.set(key, { condition: block.condition, decls });
|
|
3108
|
-
}
|
|
3109
|
-
return { blocks };
|
|
3110
|
-
}
|
|
3111
|
-
var gapShorthand = definePattern({
|
|
3112
|
-
name: "gap-shorthand",
|
|
3113
|
-
category: "compress/gap-shorthand",
|
|
3114
|
-
safety: 1,
|
|
3115
|
-
doc: {
|
|
3116
|
-
title: "Collapse equal row/column gap into the `gap` shorthand",
|
|
3117
|
-
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-*).",
|
|
3118
|
-
before: '<div style="row-gap:16px;column-gap:16px"/>',
|
|
3119
|
-
after: '<div style="gap:16px"/>',
|
|
3120
|
-
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."
|
|
3121
|
-
},
|
|
3122
|
-
rewrite: {
|
|
3123
|
-
rewriteClasses(computed2) {
|
|
3124
|
-
const base = computed2.blocks.get(BASE_KEY3);
|
|
3125
|
-
if (!base) return null;
|
|
3126
|
-
const rowGap = base.decls.get(ROW_GAP);
|
|
3127
|
-
const colGap = base.decls.get(COLUMN_GAP);
|
|
3128
|
-
if (!rowGap || !colGap) return null;
|
|
3129
|
-
if (rowGap.important !== colGap.important) return null;
|
|
3130
|
-
if (rowGap.value !== colGap.value) return null;
|
|
3131
|
-
const gapDecl = {
|
|
3132
|
-
property: GAP,
|
|
3133
|
-
value: rowGap.value,
|
|
3134
|
-
important: rowGap.important,
|
|
3135
|
-
relativeToParent: rowGap.relativeToParent || colGap.relativeToParent,
|
|
3136
|
-
inherited: false
|
|
3137
|
-
// gap is not an inherited property
|
|
3138
|
-
};
|
|
3139
|
-
return withGapShorthand(computed2, gapDecl);
|
|
3140
|
-
}
|
|
3141
|
-
},
|
|
3142
|
-
test: {
|
|
3143
|
-
cases: [
|
|
3144
|
-
{
|
|
3145
|
-
// Equal row/column gap collapse to a `gap` decl at the IR level; the minimizing reverse-emit
|
|
3146
|
-
// re-expands `gap` to row-gap+column-gap and picks the single utility covering both (`gap-4`),
|
|
3147
|
-
// replacing the `gap-x-4`+`gap-y-4` pair. `bg-red-200` is preserved.
|
|
3148
|
-
before: '<div className="gap-x-4 gap-y-4 bg-red-200">box</div>',
|
|
3149
|
-
after: '<div className="bg-red-200 gap-4">box</div>'
|
|
3150
|
-
}
|
|
3151
|
-
],
|
|
3152
|
-
// Unequal axes (row-gap != column-gap) have no single-value `gap` equivalent → not collapsed.
|
|
3153
|
-
noMatch: ['<div className="gap-x-2 gap-y-4 bg-red-200">box</div>']
|
|
3154
|
-
}
|
|
3155
|
-
});
|
|
3156
|
-
|
|
3157
|
-
// ../patterns/src/library/compress/inset-shorthand.pattern.ts
|
|
3158
|
-
var TOP = "top";
|
|
3159
|
-
var RIGHT = "right";
|
|
3160
|
-
var BOTTOM = "bottom";
|
|
3161
|
-
var LEFT = "left";
|
|
3162
|
-
var INSET = "inset";
|
|
3163
|
-
var INSET_BLOCK = "inset-block";
|
|
3164
|
-
var INSET_INLINE = "inset-inline";
|
|
3165
|
-
function sameSide(a, b) {
|
|
3166
|
-
return a !== void 0 && b !== void 0 && a.value === b.value && a.important === b.important;
|
|
3167
|
-
}
|
|
3168
|
-
function asProperty(src, property) {
|
|
3169
|
-
return { ...src, property, inherited: normalizer.inherited.isInherited(property) };
|
|
3170
|
-
}
|
|
3171
|
-
function withBaseDecls(src, baseDecls) {
|
|
3172
|
-
const blocks = /* @__PURE__ */ new Map();
|
|
3173
|
-
for (const [key, block] of src.blocks) {
|
|
3174
|
-
const decls = key === BASE_CONDITION_KEY ? baseDecls : new Map(block.decls);
|
|
3175
|
-
blocks.set(key, { condition: block.condition, decls });
|
|
3176
|
-
}
|
|
3177
|
-
return { blocks };
|
|
3178
|
-
}
|
|
3179
|
-
var insetShorthand = definePattern({
|
|
3180
|
-
name: "inset-shorthand",
|
|
3181
|
-
category: "compress/inset-shorthand",
|
|
3182
|
-
safety: 2,
|
|
3183
|
-
doc: {
|
|
3184
|
-
title: "Compress inset longhands into a shorthand",
|
|
3185
|
-
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).",
|
|
3186
|
-
before: '<div style="top:10px;right:10px;bottom:10px;left:10px"/>',
|
|
3187
|
-
after: '<div style="inset:10px"/>',
|
|
3188
|
-
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."
|
|
3189
|
-
},
|
|
3190
|
-
rewrite: {
|
|
3191
|
-
rewriteClasses(computed2) {
|
|
3192
|
-
const base = computed2.blocks.get(BASE_CONDITION_KEY);
|
|
3193
|
-
if (!base) return null;
|
|
3194
|
-
const top = base.decls.get(TOP);
|
|
3195
|
-
const right = base.decls.get(RIGHT);
|
|
3196
|
-
const bottom = base.decls.get(BOTTOM);
|
|
3197
|
-
const left = base.decls.get(LEFT);
|
|
3198
|
-
const next = new Map(base.decls);
|
|
3199
|
-
if (top && sameSide(top, right) && sameSide(top, bottom) && sameSide(top, left)) {
|
|
3200
|
-
next.delete(TOP);
|
|
3201
|
-
next.delete(RIGHT);
|
|
3202
|
-
next.delete(BOTTOM);
|
|
3203
|
-
next.delete(LEFT);
|
|
3204
|
-
next.set(INSET, asProperty(top, INSET));
|
|
3205
|
-
} else {
|
|
3206
|
-
let collapsed = false;
|
|
3207
|
-
if (sameSide(top, bottom)) {
|
|
3208
|
-
next.delete(TOP);
|
|
3209
|
-
next.delete(BOTTOM);
|
|
3210
|
-
next.set(INSET_BLOCK, asProperty(top, INSET_BLOCK));
|
|
3211
|
-
collapsed = true;
|
|
3212
|
-
}
|
|
3213
|
-
if (sameSide(left, right)) {
|
|
3214
|
-
next.delete(LEFT);
|
|
3215
|
-
next.delete(RIGHT);
|
|
3216
|
-
next.set(INSET_INLINE, asProperty(left, INSET_INLINE));
|
|
3217
|
-
collapsed = true;
|
|
3218
|
-
}
|
|
3219
|
-
if (!collapsed) return null;
|
|
3220
|
-
}
|
|
3221
|
-
return withBaseDecls(computed2, next);
|
|
3222
|
-
}
|
|
3223
|
-
},
|
|
3224
|
-
test: {
|
|
3225
|
-
cases: [
|
|
3226
|
-
{
|
|
3227
|
-
// The four equal inset longhands collapse to an `inset` shorthand at the IR level; the
|
|
3228
|
-
// minimizing reverse-emit expands it back to top/right/bottom/left and picks the single utility
|
|
3229
|
-
// covering all four (`inset-0`), replacing the four physical-side tokens. `bg-red-200` survives.
|
|
3230
|
-
before: '<div className="top-0 right-0 bottom-0 left-0 bg-red-200">box</div>',
|
|
3231
|
-
after: '<div className="bg-red-200 inset-0">box</div>'
|
|
3232
|
-
}
|
|
3233
|
-
],
|
|
3234
|
-
// No matching inset pair (all four distinct) → nothing collapses.
|
|
3235
|
-
noMatch: ['<div className="top-0 right-1 bottom-2 left-3 bg-red-200">box</div>']
|
|
3236
|
-
}
|
|
3237
|
-
});
|
|
3238
|
-
|
|
3239
|
-
// ../patterns/src/library/compress/margin-shorthand.pattern.ts
|
|
3240
|
-
var MARGIN_SIDES = [
|
|
3241
|
-
"margin-top",
|
|
3242
|
-
"margin-right",
|
|
3243
|
-
"margin-bottom",
|
|
3244
|
-
"margin-left"
|
|
3245
|
-
];
|
|
3246
|
-
var MARGIN_SIDE_SET = new Set(MARGIN_SIDES);
|
|
3247
|
-
var BASE_KEY4 = conditionKey(BASE_CONDITION);
|
|
3248
|
-
function collapseMarginValue(top, right, bottom, left) {
|
|
3249
|
-
if (right === left) {
|
|
3250
|
-
if (top === bottom) {
|
|
3251
|
-
return top === right ? top : `${top} ${right}`;
|
|
3252
|
-
}
|
|
3253
|
-
return `${top} ${right} ${bottom}`;
|
|
3254
|
-
}
|
|
3255
|
-
return `${top} ${right} ${bottom} ${left}`;
|
|
3256
|
-
}
|
|
3257
|
-
function withFoldedMargin(sm, marginDecl) {
|
|
3258
|
-
const blocks = /* @__PURE__ */ new Map();
|
|
3259
|
-
for (const [key, block] of sm.blocks) {
|
|
3260
|
-
if (key !== BASE_KEY4) {
|
|
3261
|
-
blocks.set(key, block);
|
|
3262
|
-
continue;
|
|
3263
|
-
}
|
|
3264
|
-
const decls = /* @__PURE__ */ new Map();
|
|
3265
|
-
for (const [prop, decl] of block.decls) {
|
|
3266
|
-
if (!MARGIN_SIDE_SET.has(String(prop))) decls.set(prop, decl);
|
|
3267
|
-
}
|
|
3268
|
-
decls.set(marginDecl.property, marginDecl);
|
|
3269
|
-
blocks.set(key, { condition: block.condition, decls });
|
|
3270
|
-
}
|
|
3271
|
-
return { blocks };
|
|
3272
|
-
}
|
|
3273
|
-
var marginShorthand = definePattern({
|
|
3274
|
-
name: "margin-shorthand",
|
|
3275
|
-
category: "compress/margin-shorthand",
|
|
3276
|
-
safety: 2,
|
|
3277
|
-
doc: {
|
|
3278
|
-
title: "Compress margin longhands into the `margin` shorthand",
|
|
3279
|
-
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.",
|
|
3280
|
-
before: '<div style="margin-top:8px;margin-right:8px;margin-bottom:8px;margin-left:8px"/>',
|
|
3281
|
-
after: '<div style="margin:8px"/>',
|
|
3282
|
-
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."
|
|
3283
|
-
},
|
|
3284
|
-
rewrite: {
|
|
3285
|
-
rewriteClasses(computed2) {
|
|
3286
|
-
const base = computed2.blocks.get(BASE_KEY4);
|
|
3287
|
-
if (!base) return null;
|
|
3288
|
-
const sides = MARGIN_SIDES.map((p) => base.decls.get(p));
|
|
3289
|
-
if (sides.some((d) => d === void 0)) return null;
|
|
3290
|
-
const [mt, mr, mb, ml] = sides;
|
|
3291
|
-
if (mt.important || mr.important || mb.important || ml.important) return null;
|
|
3292
|
-
const value = collapseMarginValue(
|
|
3293
|
-
String(mt.value),
|
|
3294
|
-
String(mr.value),
|
|
3295
|
-
String(mb.value),
|
|
3296
|
-
String(ml.value)
|
|
3297
|
-
);
|
|
3298
|
-
const marginDecl = {
|
|
3299
|
-
property: "margin",
|
|
3300
|
-
value,
|
|
3301
|
-
important: false,
|
|
3302
|
-
relativeToParent: mt.relativeToParent || mr.relativeToParent || mb.relativeToParent || ml.relativeToParent,
|
|
3303
|
-
inherited: false
|
|
3304
|
-
// margin is not an inherited property
|
|
3305
|
-
};
|
|
3306
|
-
return withFoldedMargin(computed2, marginDecl);
|
|
3307
|
-
}
|
|
3308
|
-
},
|
|
3309
|
-
test: {
|
|
3310
|
-
cases: [
|
|
3311
|
-
{
|
|
3312
|
-
// The four equal margin longhands collapse to a `margin` shorthand at the IR level, and the
|
|
3313
|
-
// minimizing reverse-emit picks the single shortest utility (`m-2`) reproducing it, replacing
|
|
3314
|
-
// the four `m{t,r,b,l}-2` tokens. `bg-red-200` is preserved.
|
|
3315
|
-
before: '<div className="mt-2 mr-2 mb-2 ml-2 bg-red-200">box</div>',
|
|
3316
|
-
after: '<div className="bg-red-200 m-2">box</div>'
|
|
3317
|
-
}
|
|
3318
|
-
],
|
|
3319
|
-
// Only two margin sides set → the four-longhand `margin` collapse does not apply.
|
|
3320
|
-
noMatch: ['<div className="mt-2 mb-2 bg-red-200">box</div>']
|
|
3321
|
-
}
|
|
3322
|
-
});
|
|
3323
|
-
|
|
3324
|
-
// ../patterns/src/library/compress/overflow-shorthand.pattern.ts
|
|
3325
|
-
var OVERFLOW_X = "overflow-x";
|
|
3326
|
-
var OVERFLOW_Y = "overflow-y";
|
|
3327
|
-
var OVERFLOW = "overflow";
|
|
3328
|
-
var BASE_KEY5 = conditionKey(BASE_CONDITION);
|
|
3329
|
-
function withOverflowShorthand(sm, overflowDecl) {
|
|
3330
|
-
const blocks = /* @__PURE__ */ new Map();
|
|
3331
|
-
for (const [key, block] of sm.blocks) {
|
|
3332
|
-
if (key !== BASE_KEY5) {
|
|
3333
|
-
blocks.set(key, block);
|
|
3334
|
-
continue;
|
|
3335
|
-
}
|
|
3336
|
-
const decls = /* @__PURE__ */ new Map();
|
|
3337
|
-
for (const [prop, decl] of block.decls) {
|
|
3338
|
-
if (prop === OVERFLOW_X || prop === OVERFLOW_Y) continue;
|
|
3339
|
-
decls.set(prop, decl);
|
|
3340
|
-
}
|
|
3341
|
-
decls.set(overflowDecl.property, overflowDecl);
|
|
3342
|
-
blocks.set(key, { condition: block.condition, decls });
|
|
3343
|
-
}
|
|
3344
|
-
return { blocks };
|
|
3345
|
-
}
|
|
3346
|
-
var overflowShorthand = definePattern({
|
|
3347
|
-
name: "overflow-shorthand",
|
|
3348
|
-
category: "compress/overflow-shorthand",
|
|
3349
|
-
safety: 1,
|
|
3350
|
-
doc: {
|
|
3351
|
-
title: "Collapse equal overflow axes into the `overflow` shorthand",
|
|
3352
|
-
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-*).",
|
|
3353
|
-
before: '<div style="overflow-x:auto;overflow-y:auto"/>',
|
|
3354
|
-
after: '<div style="overflow:auto"/>',
|
|
3355
|
-
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."
|
|
3356
|
-
},
|
|
3357
|
-
rewrite: {
|
|
3358
|
-
rewriteClasses(computed2) {
|
|
3359
|
-
const base = computed2.blocks.get(BASE_KEY5);
|
|
3360
|
-
if (!base) return null;
|
|
3361
|
-
const overflowX = base.decls.get(OVERFLOW_X);
|
|
3362
|
-
const overflowY = base.decls.get(OVERFLOW_Y);
|
|
3363
|
-
if (!overflowX || !overflowY) return null;
|
|
3364
|
-
if (overflowX.important !== overflowY.important) return null;
|
|
3365
|
-
if (overflowX.value !== overflowY.value) return null;
|
|
3366
|
-
const overflowDecl = {
|
|
3367
|
-
property: OVERFLOW,
|
|
3368
|
-
value: overflowX.value,
|
|
3369
|
-
important: overflowX.important,
|
|
3370
|
-
relativeToParent: overflowX.relativeToParent || overflowY.relativeToParent,
|
|
3371
|
-
inherited: false
|
|
3372
|
-
// overflow is not an inherited property
|
|
3373
|
-
};
|
|
3374
|
-
return withOverflowShorthand(computed2, overflowDecl);
|
|
3375
|
-
}
|
|
3376
|
-
},
|
|
3377
|
-
test: {
|
|
3378
|
-
cases: [
|
|
3379
|
-
{
|
|
3380
|
-
// Equal overflow axes collapse to an `overflow` decl at the IR level; the minimizing
|
|
3381
|
-
// reverse-emit picks the single utility covering both (`overflow-auto`), replacing the
|
|
3382
|
-
// `overflow-x-auto`+`overflow-y-auto` pair. `bg-red-200` is preserved.
|
|
3383
|
-
before: '<div className="overflow-x-auto overflow-y-auto bg-red-200">box</div>',
|
|
3384
|
-
after: '<div className="bg-red-200 overflow-auto">box</div>'
|
|
3385
|
-
}
|
|
3386
|
-
],
|
|
3387
|
-
// Mismatched axes (overflow-x != overflow-y) have no single-keyword equivalent → not collapsed.
|
|
3388
|
-
noMatch: ['<div className="overflow-x-auto overflow-y-hidden bg-red-200">box</div>']
|
|
3389
|
-
}
|
|
3390
|
-
});
|
|
3391
|
-
|
|
3392
|
-
// ../patterns/src/library/compress/overscroll-behavior-shorthand.pattern.ts
|
|
3393
|
-
var OVERSCROLL_X = "overscroll-behavior-x";
|
|
3394
|
-
var OVERSCROLL_Y = "overscroll-behavior-y";
|
|
3395
|
-
var OVERSCROLL = "overscroll-behavior";
|
|
3396
|
-
var BASE_KEY6 = conditionKey(BASE_CONDITION);
|
|
3397
|
-
var NON_COLLAPSIBLE_VALUES2 = /* @__PURE__ */ new Set([
|
|
3398
|
-
"initial",
|
|
3399
|
-
"inherit",
|
|
3400
|
-
"unset",
|
|
3401
|
-
"revert",
|
|
3402
|
-
"revert-layer"
|
|
3403
|
-
]);
|
|
3404
|
-
function withOverscrollShorthand(sm, shorthand) {
|
|
3405
|
-
const blocks = /* @__PURE__ */ new Map();
|
|
3406
|
-
for (const [key, block] of sm.blocks) {
|
|
3407
|
-
if (key !== BASE_KEY6) {
|
|
3408
|
-
blocks.set(key, block);
|
|
3409
|
-
continue;
|
|
3410
|
-
}
|
|
3411
|
-
const decls = /* @__PURE__ */ new Map();
|
|
3412
|
-
for (const [prop, decl] of block.decls) {
|
|
3413
|
-
if (prop === OVERSCROLL_X || prop === OVERSCROLL_Y) continue;
|
|
3414
|
-
decls.set(prop, decl);
|
|
3415
|
-
}
|
|
3416
|
-
decls.set(shorthand.property, shorthand);
|
|
3417
|
-
blocks.set(key, { condition: block.condition, decls });
|
|
3418
|
-
}
|
|
3419
|
-
return { blocks };
|
|
3420
|
-
}
|
|
3421
|
-
var overscrollBehaviorShorthand = definePattern({
|
|
3422
|
-
name: "overscroll-behavior-shorthand",
|
|
3423
|
-
category: "compress/overscroll-behavior-shorthand",
|
|
3424
|
-
safety: 1,
|
|
3425
|
-
doc: {
|
|
3426
|
-
title: "Collapse equal overscroll-behavior axes into overscroll-behavior",
|
|
3427
|
-
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-*).",
|
|
3428
|
-
before: '<div style="overscroll-behavior-x:contain;overscroll-behavior-y:contain"/>',
|
|
3429
|
-
after: '<div class="overscroll-contain"/>',
|
|
3430
|
-
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."
|
|
3431
|
-
},
|
|
3432
|
-
rewrite: {
|
|
3433
|
-
rewriteClasses(computed2) {
|
|
3434
|
-
const base = computed2.blocks.get(BASE_KEY6);
|
|
3435
|
-
if (!base) return null;
|
|
3436
|
-
const x = base.decls.get(OVERSCROLL_X);
|
|
3437
|
-
const y = base.decls.get(OVERSCROLL_Y);
|
|
3438
|
-
if (!x || !y) return null;
|
|
3439
|
-
if (x.important !== y.important) return null;
|
|
3440
|
-
const value = String(x.value);
|
|
3441
|
-
if (NON_COLLAPSIBLE_VALUES2.has(value)) return null;
|
|
3442
|
-
if (value !== String(y.value)) return null;
|
|
3443
|
-
const shorthand = {
|
|
3444
|
-
property: OVERSCROLL,
|
|
3445
|
-
value: x.value,
|
|
3446
|
-
important: x.important,
|
|
3447
|
-
relativeToParent: x.relativeToParent || y.relativeToParent,
|
|
3448
|
-
inherited: false
|
|
3449
|
-
// overscroll-behavior is not an inherited property
|
|
3450
|
-
};
|
|
3451
|
-
return withOverscrollShorthand(computed2, shorthand);
|
|
3452
|
-
}
|
|
3453
|
-
},
|
|
3454
|
-
test: {
|
|
3455
|
-
cases: [
|
|
3456
|
-
{
|
|
3457
|
-
// Equal x/y axes collapse to an `overscroll-behavior` decl at the IR level; the minimizing
|
|
3458
|
-
// reverse-emit picks the single utility covering both (`overscroll-contain`), replacing the
|
|
3459
|
-
// `overscroll-x-contain`+`overscroll-y-contain` pair. `bg-red-200` is preserved.
|
|
3460
|
-
before: '<div className="overscroll-x-contain overscroll-y-contain bg-red-200">box</div>',
|
|
3461
|
-
after: '<div className="bg-red-200 overscroll-contain">box</div>'
|
|
3462
|
-
}
|
|
3463
|
-
],
|
|
3464
|
-
// Axes differ (x != y) → no equal-axis collapse.
|
|
3465
|
-
noMatch: ['<div className="overscroll-x-contain overscroll-y-auto bg-red-200">box</div>']
|
|
3466
|
-
}
|
|
3467
|
-
});
|
|
3468
|
-
|
|
3469
|
-
// ../patterns/src/library/compress/padding-shorthand.pattern.ts
|
|
3470
|
-
var PADDING_SIDES = [
|
|
3471
|
-
"padding-top",
|
|
3472
|
-
"padding-right",
|
|
3473
|
-
"padding-bottom",
|
|
3474
|
-
"padding-left"
|
|
3475
|
-
];
|
|
3476
|
-
var PADDING_SIDE_SET = new Set(PADDING_SIDES);
|
|
3477
|
-
var BASE_KEY7 = conditionKey(BASE_CONDITION);
|
|
3478
|
-
function analyzePadding(sm) {
|
|
3479
|
-
const block = sm.blocks.get(BASE_KEY7);
|
|
3480
|
-
if (!block) return null;
|
|
3481
|
-
const sides = [];
|
|
3482
|
-
for (const side of PADDING_SIDES) {
|
|
3483
|
-
const decl = block.decls.get(side);
|
|
3484
|
-
if (!decl) return null;
|
|
3485
|
-
sides.push(decl);
|
|
3486
|
-
}
|
|
3487
|
-
const [top, right, bottom, left] = sides;
|
|
3488
|
-
if (!(top.important === right.important && right.important === bottom.important && bottom.important === left.important)) {
|
|
3489
|
-
return null;
|
|
3490
|
-
}
|
|
3491
|
-
const tv = String(top.value);
|
|
3492
|
-
const rv = String(right.value);
|
|
3493
|
-
const bv = String(bottom.value);
|
|
3494
|
-
const lv = String(left.value);
|
|
3495
|
-
if (tv !== bv || lv !== rv) return null;
|
|
3496
|
-
const value = tv === lv ? tv : `${tv} ${lv}`;
|
|
3497
|
-
const relative = sides.some((d) => d.relativeToParent);
|
|
3498
|
-
return { value, important: top.important, relative };
|
|
3499
|
-
}
|
|
3500
|
-
function withFoldedPadding(sm, fold) {
|
|
3501
|
-
const blocks = /* @__PURE__ */ new Map();
|
|
3502
|
-
for (const [key, block] of sm.blocks) {
|
|
3503
|
-
if (key !== BASE_KEY7) {
|
|
3504
|
-
blocks.set(key, block);
|
|
3505
|
-
continue;
|
|
3506
|
-
}
|
|
3507
|
-
const decls = /* @__PURE__ */ new Map();
|
|
3508
|
-
for (const [prop, decl] of block.decls) {
|
|
3509
|
-
if (PADDING_SIDE_SET.has(String(prop))) continue;
|
|
3510
|
-
decls.set(prop, decl);
|
|
3511
|
-
}
|
|
3512
|
-
const shorthand = {
|
|
3513
|
-
property: "padding",
|
|
3514
|
-
value: fold.value,
|
|
3515
|
-
important: fold.important,
|
|
3516
|
-
relativeToParent: fold.relative,
|
|
3517
|
-
inherited: false
|
|
3518
|
-
// padding is never inherited
|
|
3519
|
-
};
|
|
3520
|
-
decls.set(shorthand.property, shorthand);
|
|
3521
|
-
blocks.set(key, { condition: block.condition, decls });
|
|
3522
|
-
}
|
|
3523
|
-
return { blocks };
|
|
3524
|
-
}
|
|
3525
|
-
var paddingShorthand = definePattern({
|
|
3526
|
-
name: "padding-shorthand",
|
|
3527
|
-
category: "compress/padding-shorthand",
|
|
3528
|
-
safety: 1,
|
|
3529
|
-
doc: {
|
|
3530
|
-
title: "Collapse padding longhands to shorthand",
|
|
3531
|
-
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-*).",
|
|
3532
|
-
before: '<div class="pt-4 pr-4 pb-4 pl-4"/>',
|
|
3533
|
-
after: '<div class="p-4"/>',
|
|
3534
|
-
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."
|
|
3535
|
-
},
|
|
3536
|
-
rewrite: {
|
|
3537
|
-
rewriteClasses(computed2) {
|
|
3538
|
-
const fold = analyzePadding(computed2);
|
|
3539
|
-
return fold ? withFoldedPadding(computed2, fold) : null;
|
|
3540
|
-
}
|
|
3541
|
-
},
|
|
3542
|
-
test: {
|
|
3543
|
-
cases: [
|
|
3544
|
-
{
|
|
3545
|
-
// The four equal padding longhands collapse to a `padding` shorthand at the IR level, and the
|
|
3546
|
-
// minimizing reverse-emit picks the single shortest utility (`p-4`) that reproduces it,
|
|
3547
|
-
// replacing the four `p{t,r,b,l}-4` tokens. `bg-red-200` is preserved (its order is stable).
|
|
3548
|
-
before: '<div className="pt-4 pr-4 pb-4 pl-4 bg-red-200">box</div>',
|
|
3549
|
-
after: '<div className="bg-red-200 p-4">box</div>'
|
|
3550
|
-
},
|
|
3551
|
-
{
|
|
3552
|
-
// A dynamic `{x}` child no longer blocks compress: only the element's OWN class tokens are
|
|
3553
|
-
// rewritten (px-4 py-4 → p-4); the dynamic child is untouched by a class-only change. This is
|
|
3554
|
-
// the real-app common case (most elements have dynamic content).
|
|
3555
|
-
before: '<div className="px-4 py-4">{x}</div>',
|
|
3556
|
-
after: '<div className="p-4">{x}</div>'
|
|
3557
|
-
}
|
|
3558
|
-
],
|
|
3559
|
-
// Asymmetric padding (top != bottom) cannot fold into a shorthand → left unchanged.
|
|
3560
|
-
noMatch: ['<div className="pt-2 pr-4 pb-8 pl-4 bg-red-200">box</div>']
|
|
3561
|
-
}
|
|
3562
|
-
});
|
|
3563
|
-
|
|
3564
|
-
// ../patterns/src/library/compress/place-shorthand.pattern.ts
|
|
3565
|
-
var ALIGN_ITEMS = "align-items";
|
|
3566
|
-
var JUSTIFY_ITEMS = "justify-items";
|
|
3567
|
-
var PLACE_ITEMS = "place-items";
|
|
3568
|
-
var ALIGN_CONTENT = "align-content";
|
|
3569
|
-
var JUSTIFY_CONTENT = "justify-content";
|
|
3570
|
-
var PLACE_CONTENT = "place-content";
|
|
3571
|
-
var BASE_KEY8 = conditionKey(BASE_CONDITION);
|
|
3572
|
-
function samePair(a, b) {
|
|
3573
|
-
return a !== void 0 && b !== void 0 && a.value === b.value && a.important === b.important;
|
|
3574
|
-
}
|
|
3575
|
-
function placeDecl(property, align) {
|
|
3576
|
-
return {
|
|
3577
|
-
property,
|
|
3578
|
-
value: align.value,
|
|
3579
|
-
important: align.important,
|
|
3580
|
-
relativeToParent: false,
|
|
3581
|
-
// alignment keywords (center/start/stretch/…) are not length-relative
|
|
3582
|
-
inherited: false
|
|
3583
|
-
// none of the place-* alignment properties are inherited
|
|
3584
|
-
};
|
|
3585
|
-
}
|
|
3586
|
-
function withBaseDecls2(sm, baseDecls) {
|
|
3587
|
-
const blocks = /* @__PURE__ */ new Map();
|
|
3588
|
-
for (const [key, block] of sm.blocks) {
|
|
3589
|
-
const decls = key === BASE_KEY8 ? new Map(baseDecls) : block.decls;
|
|
3590
|
-
blocks.set(key, { condition: block.condition, decls });
|
|
3591
|
-
}
|
|
3592
|
-
return { blocks };
|
|
3593
|
-
}
|
|
3594
|
-
var placeShorthand = definePattern({
|
|
3595
|
-
name: "place-shorthand",
|
|
3596
|
-
category: "compress/place-shorthand",
|
|
3597
|
-
safety: 1,
|
|
3598
|
-
doc: {
|
|
3599
|
-
title: "Collapse matching alignment pairs into `place-*` shorthands",
|
|
3600
|
-
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.",
|
|
3601
|
-
before: '<div style="align-items:center;justify-items:center"/>',
|
|
3602
|
-
after: '<div style="place-items:center"/>',
|
|
3603
|
-
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."
|
|
3604
|
-
},
|
|
3605
|
-
rewrite: {
|
|
3606
|
-
rewriteClasses(computed2) {
|
|
3607
|
-
const base = computed2.blocks.get(BASE_KEY8);
|
|
3608
|
-
if (!base) return null;
|
|
3609
|
-
const alignItems = base.decls.get(ALIGN_ITEMS);
|
|
3610
|
-
const justifyItems = base.decls.get(JUSTIFY_ITEMS);
|
|
3611
|
-
const alignContent = base.decls.get(ALIGN_CONTENT);
|
|
3612
|
-
const justifyContent = base.decls.get(JUSTIFY_CONTENT);
|
|
3613
|
-
const next = new Map(base.decls);
|
|
3614
|
-
let collapsed = false;
|
|
3615
|
-
if (samePair(alignItems, justifyItems)) {
|
|
3616
|
-
next.delete(ALIGN_ITEMS);
|
|
3617
|
-
next.delete(JUSTIFY_ITEMS);
|
|
3618
|
-
next.set(PLACE_ITEMS, placeDecl(PLACE_ITEMS, alignItems));
|
|
3619
|
-
collapsed = true;
|
|
3620
|
-
}
|
|
3621
|
-
if (samePair(alignContent, justifyContent)) {
|
|
3622
|
-
next.delete(ALIGN_CONTENT);
|
|
3623
|
-
next.delete(JUSTIFY_CONTENT);
|
|
3624
|
-
next.set(PLACE_CONTENT, placeDecl(PLACE_CONTENT, alignContent));
|
|
3625
|
-
collapsed = true;
|
|
3626
|
-
}
|
|
3627
|
-
if (!collapsed) return null;
|
|
3628
|
-
return withBaseDecls2(computed2, next);
|
|
3629
|
-
}
|
|
3630
|
-
},
|
|
3631
|
-
test: {
|
|
3632
|
-
cases: [
|
|
3633
|
-
{
|
|
3634
|
-
// The matching items pair collapses to a `place-items` decl at the IR level; the minimizing
|
|
3635
|
-
// reverse-emit picks the single utility covering both (`place-items-center`), replacing the
|
|
3636
|
-
// `items-center`+`justify-items-center` pair. `bg-red-200` is preserved.
|
|
3637
|
-
before: '<div className="items-center justify-items-center bg-red-200">box</div>',
|
|
3638
|
-
after: '<div className="bg-red-200 place-items-center">box</div>'
|
|
3639
|
-
}
|
|
3640
|
-
],
|
|
3641
|
-
// Mismatched alignment (align-items != justify-items, no content pair) → nothing collapses.
|
|
3642
|
-
noMatch: ['<div className="items-center justify-items-start bg-red-200">box</div>']
|
|
3643
|
-
}
|
|
3644
|
-
});
|
|
3645
|
-
|
|
3646
|
-
// ../patterns/src/library/compress/scroll-margin-shorthand.pattern.ts
|
|
3647
|
-
var SCROLL_MARGIN_SIDES = [
|
|
3648
|
-
"scroll-margin-top",
|
|
3649
|
-
"scroll-margin-right",
|
|
3650
|
-
"scroll-margin-bottom",
|
|
3651
|
-
"scroll-margin-left"
|
|
3652
|
-
];
|
|
3653
|
-
var SIDE_SET = new Set(SCROLL_MARGIN_SIDES);
|
|
3654
|
-
var BASE_KEY9 = conditionKey(BASE_CONDITION);
|
|
3655
|
-
var SCROLL_MARGIN = "scroll-margin";
|
|
3656
|
-
var NON_COLLAPSIBLE_VALUES3 = /* @__PURE__ */ new Set([
|
|
3657
|
-
"initial",
|
|
3658
|
-
"inherit",
|
|
3659
|
-
"unset",
|
|
3660
|
-
"revert",
|
|
3661
|
-
"revert-layer"
|
|
3662
|
-
]);
|
|
3663
|
-
function analyzeScrollMargin(sm) {
|
|
3664
|
-
const block = sm.blocks.get(BASE_KEY9);
|
|
3665
|
-
if (!block) return null;
|
|
3666
|
-
const sides = [];
|
|
3667
|
-
for (const side of SCROLL_MARGIN_SIDES) {
|
|
3668
|
-
const decl = block.decls.get(side);
|
|
3669
|
-
if (!decl) return null;
|
|
3670
|
-
sides.push(decl);
|
|
3671
|
-
}
|
|
3672
|
-
const important = sides[0].important;
|
|
3673
|
-
if (!sides.every((d) => d.important === important)) return null;
|
|
3674
|
-
const value = String(sides[0].value);
|
|
3675
|
-
if (NON_COLLAPSIBLE_VALUES3.has(value)) return null;
|
|
3676
|
-
if (!sides.every((d) => String(d.value) === value)) return null;
|
|
3677
|
-
const relative = sides.some((d) => d.relativeToParent);
|
|
3678
|
-
return { value, important, relative };
|
|
3679
|
-
}
|
|
3680
|
-
function withFoldedScrollMargin(sm, fold) {
|
|
3681
|
-
const blocks = /* @__PURE__ */ new Map();
|
|
3682
|
-
for (const [key, block] of sm.blocks) {
|
|
3683
|
-
if (key !== BASE_KEY9) {
|
|
3684
|
-
blocks.set(key, block);
|
|
3685
|
-
continue;
|
|
3686
|
-
}
|
|
3687
|
-
const decls = /* @__PURE__ */ new Map();
|
|
3688
|
-
for (const [prop, decl] of block.decls) {
|
|
3689
|
-
if (SIDE_SET.has(String(prop))) continue;
|
|
3690
|
-
decls.set(prop, decl);
|
|
3691
|
-
}
|
|
3692
|
-
const shorthand = {
|
|
3693
|
-
property: SCROLL_MARGIN,
|
|
3694
|
-
value: fold.value,
|
|
3695
|
-
important: fold.important,
|
|
3696
|
-
relativeToParent: fold.relative,
|
|
3697
|
-
inherited: false
|
|
3698
|
-
// scroll-margin is never inherited
|
|
3699
|
-
};
|
|
3700
|
-
decls.set(shorthand.property, shorthand);
|
|
3701
|
-
blocks.set(key, { condition: block.condition, decls });
|
|
3702
|
-
}
|
|
3703
|
-
return { blocks };
|
|
3704
|
-
}
|
|
3705
|
-
var scrollMarginShorthand = definePattern({
|
|
3706
|
-
name: "scroll-margin-shorthand",
|
|
3707
|
-
category: "compress/scroll-margin-shorthand",
|
|
3708
|
-
safety: 1,
|
|
3709
|
-
doc: {
|
|
3710
|
-
title: "Collapse equal scroll-margin sides into scroll-margin",
|
|
3711
|
-
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).",
|
|
3712
|
-
before: '<div class="scroll-mt-4 scroll-mr-4 scroll-mb-4 scroll-ml-4"/>',
|
|
3713
|
-
after: '<div class="scroll-m-4"/>',
|
|
3714
|
-
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."
|
|
3715
|
-
},
|
|
3716
|
-
rewrite: {
|
|
3717
|
-
rewriteClasses(computed2) {
|
|
3718
|
-
const fold = analyzeScrollMargin(computed2);
|
|
3719
|
-
return fold ? withFoldedScrollMargin(computed2, fold) : null;
|
|
3720
|
-
}
|
|
3721
|
-
},
|
|
3722
|
-
test: {
|
|
3723
|
-
cases: [
|
|
3724
|
-
{
|
|
3725
|
-
// The four equal scroll-margin longhands collapse to a `scroll-margin` decl at the IR level; the
|
|
3726
|
-
// minimizing reverse-emit then picks the single shortest utility (`scroll-m-4`) that reproduces
|
|
3727
|
-
// it, replacing the four `scroll-m{t,r,b,l}-4` tokens. `bg-red-200` is preserved.
|
|
3728
|
-
before: '<div className="scroll-mt-4 scroll-mr-4 scroll-mb-4 scroll-ml-4 bg-red-200">box</div>',
|
|
3729
|
-
after: '<div className="bg-red-200 scroll-m-4">box</div>'
|
|
3730
|
-
}
|
|
3731
|
-
],
|
|
3732
|
-
// Sides differ (top != bottom) → no all-equal collapse.
|
|
3733
|
-
noMatch: ['<div className="scroll-mt-2 scroll-mr-4 scroll-mb-8 scroll-ml-4 bg-red-200">box</div>']
|
|
3734
|
-
}
|
|
3735
|
-
});
|
|
3736
|
-
|
|
3737
|
-
// ../patterns/src/library/compress/scroll-padding-shorthand.pattern.ts
|
|
3738
|
-
var SCROLL_PADDING_SIDES = [
|
|
3739
|
-
"scroll-padding-top",
|
|
3740
|
-
"scroll-padding-right",
|
|
3741
|
-
"scroll-padding-bottom",
|
|
3742
|
-
"scroll-padding-left"
|
|
3743
|
-
];
|
|
3744
|
-
var SIDE_SET2 = new Set(SCROLL_PADDING_SIDES);
|
|
3745
|
-
var BASE_KEY10 = conditionKey(BASE_CONDITION);
|
|
3746
|
-
var SCROLL_PADDING = "scroll-padding";
|
|
3747
|
-
var NON_COLLAPSIBLE_VALUES4 = /* @__PURE__ */ new Set([
|
|
3748
|
-
"initial",
|
|
3749
|
-
"inherit",
|
|
3750
|
-
"unset",
|
|
3751
|
-
"revert",
|
|
3752
|
-
"revert-layer"
|
|
3753
|
-
]);
|
|
3754
|
-
function analyzeScrollPadding(sm) {
|
|
3755
|
-
const block = sm.blocks.get(BASE_KEY10);
|
|
3756
|
-
if (!block) return null;
|
|
3757
|
-
const sides = [];
|
|
3758
|
-
for (const side of SCROLL_PADDING_SIDES) {
|
|
3759
|
-
const decl = block.decls.get(side);
|
|
3760
|
-
if (!decl) return null;
|
|
3761
|
-
sides.push(decl);
|
|
3762
|
-
}
|
|
3763
|
-
const important = sides[0].important;
|
|
3764
|
-
if (!sides.every((d) => d.important === important)) return null;
|
|
3765
|
-
const value = String(sides[0].value);
|
|
3766
|
-
if (NON_COLLAPSIBLE_VALUES4.has(value)) return null;
|
|
3767
|
-
if (!sides.every((d) => String(d.value) === value)) return null;
|
|
3768
|
-
const relative = sides.some((d) => d.relativeToParent);
|
|
3769
|
-
return { value, important, relative };
|
|
3770
|
-
}
|
|
3771
|
-
function withFoldedScrollPadding(sm, fold) {
|
|
3772
|
-
const blocks = /* @__PURE__ */ new Map();
|
|
3773
|
-
for (const [key, block] of sm.blocks) {
|
|
3774
|
-
if (key !== BASE_KEY10) {
|
|
3775
|
-
blocks.set(key, block);
|
|
3776
|
-
continue;
|
|
3777
|
-
}
|
|
3778
|
-
const decls = /* @__PURE__ */ new Map();
|
|
3779
|
-
for (const [prop, decl] of block.decls) {
|
|
3780
|
-
if (SIDE_SET2.has(String(prop))) continue;
|
|
3781
|
-
decls.set(prop, decl);
|
|
3782
|
-
}
|
|
3783
|
-
const shorthand = {
|
|
3784
|
-
property: SCROLL_PADDING,
|
|
3785
|
-
value: fold.value,
|
|
3786
|
-
important: fold.important,
|
|
3787
|
-
relativeToParent: fold.relative,
|
|
3788
|
-
inherited: false
|
|
3789
|
-
// scroll-padding is never inherited
|
|
3790
|
-
};
|
|
3791
|
-
decls.set(shorthand.property, shorthand);
|
|
3792
|
-
blocks.set(key, { condition: block.condition, decls });
|
|
3793
|
-
}
|
|
3794
|
-
return { blocks };
|
|
3795
|
-
}
|
|
3796
|
-
var scrollPaddingShorthand = definePattern({
|
|
3797
|
-
name: "scroll-padding-shorthand",
|
|
3798
|
-
category: "compress/scroll-padding-shorthand",
|
|
3799
|
-
safety: 1,
|
|
2862
|
+
return ctx.selectors.targetedByStructuralPseudo(el.id);
|
|
2863
|
+
};
|
|
2864
|
+
var passthroughWrapper = definePattern({
|
|
2865
|
+
name: "passthrough-wrapper",
|
|
2866
|
+
category: "flatten/wrapper/passthrough-wrapper",
|
|
2867
|
+
safety: 2,
|
|
3800
2868
|
doc: {
|
|
3801
|
-
title: "
|
|
3802
|
-
summary: "
|
|
3803
|
-
before:
|
|
3804
|
-
after:
|
|
3805
|
-
safetyRationale: "
|
|
2869
|
+
title: "Flatten passthrough wrapper",
|
|
2870
|
+
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.",
|
|
2871
|
+
before: "<div><Child/></div>",
|
|
2872
|
+
after: "<Child/>",
|
|
2873
|
+
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."
|
|
3806
2874
|
},
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
2875
|
+
match: {
|
|
2876
|
+
tag: "div",
|
|
2877
|
+
onlyChild: "element",
|
|
2878
|
+
paintsNothing: true,
|
|
2879
|
+
where: [
|
|
2880
|
+
not(establishesContext),
|
|
2881
|
+
not(hasOwnAttrs2),
|
|
2882
|
+
not(hasDynamicClasses),
|
|
2883
|
+
not(hasSpreadAttrs2),
|
|
2884
|
+
not(isComponentNode3),
|
|
2885
|
+
not(targetedByStructuralPseudo3)
|
|
2886
|
+
]
|
|
3812
2887
|
},
|
|
2888
|
+
rewrite: { flattenInto: "child" },
|
|
3813
2889
|
test: {
|
|
3814
2890
|
cases: [
|
|
3815
2891
|
{
|
|
3816
|
-
//
|
|
3817
|
-
//
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
after: '<div className="bg-red-200 scroll-p-4">box</div>'
|
|
2892
|
+
// A plain, style-free wrapper paints nothing and establishes no context → a provably-safe
|
|
2893
|
+
// flatten under the conservative gate: the wrapper is removed and its sole child hoisted.
|
|
2894
|
+
before: '<div><a className="bg-red-200">Link</a></div>',
|
|
2895
|
+
after: '<a className="bg-red-200">Link</a>'
|
|
3821
2896
|
}
|
|
3822
2897
|
],
|
|
3823
|
-
|
|
3824
|
-
|
|
2898
|
+
noMatch: [
|
|
2899
|
+
// A ref pins the wrapper's element identity (a hard opacity barrier) → not a passthrough.
|
|
2900
|
+
'<div ref={rootRef}><a className="bg-red-200">Link</a></div>',
|
|
2901
|
+
// A `display:flex` wrapper establishes a formatting context, so removing its box is NOT
|
|
2902
|
+
// provably layout-neutral → the conservative gate leaves it in place.
|
|
2903
|
+
'<div className="flex"><a className="bg-red-200">Link</a></div>'
|
|
2904
|
+
]
|
|
3825
2905
|
}
|
|
3826
2906
|
});
|
|
3827
2907
|
|
|
3828
|
-
// ../patterns/src/library/
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
var NON_COLLAPSIBLE_VALUES5 = /* @__PURE__ */ new Set(["auto", "initial", "unset"]);
|
|
3833
|
-
function baseBlock(sm) {
|
|
3834
|
-
return sm.blocks.get(conditionKey(BASE_CONDITION));
|
|
3835
|
-
}
|
|
3836
|
-
function withSizeShorthand(sm, value, important) {
|
|
3837
|
-
const baseKey = conditionKey(BASE_CONDITION);
|
|
3838
|
-
const blocks = /* @__PURE__ */ new Map();
|
|
3839
|
-
for (const [key, block] of sm.blocks) {
|
|
3840
|
-
if (key !== baseKey) {
|
|
3841
|
-
blocks.set(key, block);
|
|
3842
|
-
continue;
|
|
3843
|
-
}
|
|
3844
|
-
const decls = new Map(block.decls);
|
|
3845
|
-
decls.delete(WIDTH);
|
|
3846
|
-
decls.delete(HEIGHT);
|
|
3847
|
-
for (const decl of normalizer.normalizeDeclaration(String(SIZE), value, important)) {
|
|
3848
|
-
decls.set(decl.property, decl);
|
|
3849
|
-
}
|
|
3850
|
-
blocks.set(key, { condition: block.condition, decls });
|
|
3851
|
-
}
|
|
3852
|
-
return { blocks };
|
|
2908
|
+
// ../patterns/src/library/wrapper/redundant-inline-wrapper.pattern.ts
|
|
2909
|
+
function asEl3(node) {
|
|
2910
|
+
const n = node;
|
|
2911
|
+
return n.kind === "element" ? n : null;
|
|
3853
2912
|
}
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
2913
|
+
function metaOf3(node) {
|
|
2914
|
+
return asEl3(node)?.meta ?? null;
|
|
2915
|
+
}
|
|
2916
|
+
var establishesContext2 = (node) => {
|
|
2917
|
+
const m = metaOf3(node);
|
|
2918
|
+
if (!m) return false;
|
|
2919
|
+
return m.establishesBox || m.establishesFormattingContext || m.establishesStackingContext || m.isContainingBlock || m.declaresCustomProperties;
|
|
2920
|
+
};
|
|
2921
|
+
var hasSpreadAttrs3 = (node) => metaOf3(node)?.hasSpreadAttrs ?? false;
|
|
2922
|
+
var isComponentNode4 = (node) => metaOf3(node)?.isComponent ?? false;
|
|
2923
|
+
var hasOwnAttrs3 = (node) => {
|
|
2924
|
+
const el = asEl3(node);
|
|
2925
|
+
if (!el) return false;
|
|
2926
|
+
return el.attrs.entries.size > 0 || el.attrs.spreads.length > 0;
|
|
2927
|
+
};
|
|
2928
|
+
var targetedByStructuralPseudo4 = (node, ctx) => {
|
|
2929
|
+
const el = asEl3(node);
|
|
2930
|
+
if (!el) return false;
|
|
2931
|
+
if (el.meta.targetedByStructuralPseudo) return true;
|
|
2932
|
+
return ctx.selectors.targetedByStructuralPseudo(el.id);
|
|
2933
|
+
};
|
|
2934
|
+
var DISPLAY3 = "display";
|
|
2935
|
+
var hasNonInlineDisplay = (node, ctx) => {
|
|
2936
|
+
const el = asEl3(node);
|
|
2937
|
+
if (!el) return false;
|
|
2938
|
+
const sm = ctx.computedOf(el) ?? el.computed;
|
|
2939
|
+
for (const block of sm.blocks.values()) {
|
|
2940
|
+
const decl = block.decls.get(DISPLAY3);
|
|
2941
|
+
if (decl && String(decl.value) !== "inline") return true;
|
|
2942
|
+
}
|
|
2943
|
+
return false;
|
|
2944
|
+
};
|
|
2945
|
+
var redundantInlineWrapper = definePattern({
|
|
2946
|
+
name: "redundant-inline-wrapper",
|
|
2947
|
+
category: "flatten/wrapper/redundant-inline-wrapper",
|
|
3857
2948
|
safety: 2,
|
|
3858
2949
|
doc: {
|
|
3859
|
-
title: "
|
|
3860
|
-
summary: "An
|
|
3861
|
-
before:
|
|
3862
|
-
after:
|
|
3863
|
-
safetyRationale: "
|
|
2950
|
+
title: "Flatten redundant inline wrapper",
|
|
2951
|
+
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.",
|
|
2952
|
+
before: "<span><Child/></span>",
|
|
2953
|
+
after: "<Child/>",
|
|
2954
|
+
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."
|
|
3864
2955
|
},
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
2956
|
+
match: {
|
|
2957
|
+
tag: "span",
|
|
2958
|
+
onlyChild: "element",
|
|
2959
|
+
paintsNothing: true,
|
|
2960
|
+
where: [
|
|
2961
|
+
not(hasNonInlineDisplay),
|
|
2962
|
+
not(establishesContext2),
|
|
2963
|
+
not(hasOwnAttrs3),
|
|
2964
|
+
not(hasDynamicClasses),
|
|
2965
|
+
not(hasSpreadAttrs3),
|
|
2966
|
+
not(isComponentNode4),
|
|
2967
|
+
not(targetedByStructuralPseudo4)
|
|
2968
|
+
]
|
|
3876
2969
|
},
|
|
2970
|
+
rewrite: { flattenInto: "child" },
|
|
3877
2971
|
test: {
|
|
3878
2972
|
cases: [
|
|
3879
2973
|
{
|
|
3880
|
-
//
|
|
3881
|
-
//
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
after: '<div className="bg-red-200 size-10">box</div>'
|
|
2974
|
+
// An empty inline span paints nothing and establishes no context → a provably-safe flatten:
|
|
2975
|
+
// the span is removed and its sole child hoisted in place.
|
|
2976
|
+
before: '<span><a className="text-blue-500">Link</a></span>',
|
|
2977
|
+
after: '<a className="text-blue-500">Link</a>'
|
|
3885
2978
|
}
|
|
3886
2979
|
],
|
|
3887
|
-
|
|
3888
|
-
|
|
2980
|
+
noMatch: [
|
|
2981
|
+
// A ref pins the span's element identity (a hard opacity barrier) → not a passthrough.
|
|
2982
|
+
'<span ref={spanRef}><a className="text-blue-500">Link</a></span>',
|
|
2983
|
+
// The span paints its own background (own visual style) → kept.
|
|
2984
|
+
'<span className="bg-green-200"><a className="text-blue-500">Link</a></span>',
|
|
2985
|
+
// Non-inline display (inline-block) participates in layout differently → kept.
|
|
2986
|
+
'<span className="inline-block"><a className="text-blue-500">Link</a></span>'
|
|
2987
|
+
]
|
|
3889
2988
|
}
|
|
3890
2989
|
});
|
|
3891
2990
|
|
|
3892
2991
|
// ../patterns/src/_registry.generated.ts
|
|
3893
2992
|
var builtinPatterns = [
|
|
2993
|
+
flexCenterWrapper,
|
|
2994
|
+
redundantFragment,
|
|
2995
|
+
gridCenterWrapper,
|
|
3894
2996
|
displayContentsWrapper,
|
|
3895
2997
|
emptyStyleDiv,
|
|
3896
|
-
|
|
3897
|
-
inlineFlexCenterWrapper,
|
|
3898
|
-
nestedFlexMerge,
|
|
3899
|
-
nestedGridMerge,
|
|
2998
|
+
inheritedOnlyWrapper,
|
|
3900
2999
|
passthroughWrapper,
|
|
3901
|
-
|
|
3902
|
-
redundantInlineWrapper,
|
|
3903
|
-
borderRadiusShorthand,
|
|
3904
|
-
borderShorthand,
|
|
3905
|
-
dedupeClasses,
|
|
3906
|
-
gapShorthand,
|
|
3907
|
-
insetShorthand,
|
|
3908
|
-
marginShorthand,
|
|
3909
|
-
overflowShorthand,
|
|
3910
|
-
overscrollBehaviorShorthand,
|
|
3911
|
-
paddingShorthand,
|
|
3912
|
-
placeShorthand,
|
|
3913
|
-
scrollMarginShorthand,
|
|
3914
|
-
scrollPaddingShorthand,
|
|
3915
|
-
sizeShorthand
|
|
3000
|
+
redundantInlineWrapper
|
|
3916
3001
|
];
|
|
3917
3002
|
|
|
3918
3003
|
// ../resolver-tailwind/src/tailwind/fingerprint.ts
|
|
@@ -3963,14 +3048,312 @@ function synthesizeResidual(remaining, ctx) {
|
|
|
3963
3048
|
|
|
3964
3049
|
// ../resolver-tailwind/src/tailwind/engine.ts
|
|
3965
3050
|
var import_node_module = require("module");
|
|
3051
|
+
var path3 = __toESM(require("path"), 1);
|
|
3052
|
+
|
|
3053
|
+
// ../resolver-tailwind/src/tailwind/engine-v4.ts
|
|
3054
|
+
var import_node_fs2 = require("fs");
|
|
3055
|
+
var path2 = __toESM(require("path"), 1);
|
|
3056
|
+
|
|
3057
|
+
// ../resolver-tailwind/src/tailwind/v4-bridge.ts
|
|
3058
|
+
var import_node_child_process = require("child_process");
|
|
3059
|
+
var import_node_fs = require("fs");
|
|
3060
|
+
var import_node_os = require("os");
|
|
3966
3061
|
var path = __toESM(require("path"), 1);
|
|
3062
|
+
var CHILD_SOURCE = String.raw`
|
|
3063
|
+
import { createRequire } from 'node:module';
|
|
3064
|
+
import { pathToFileURL } from 'node:url';
|
|
3065
|
+
import * as fs from 'node:fs';
|
|
3066
|
+
import * as path from 'node:path';
|
|
3067
|
+
|
|
3068
|
+
function out(obj) { process.stdout.write(JSON.stringify(obj)); process.exit(0); }
|
|
3069
|
+
|
|
3070
|
+
let payload;
|
|
3071
|
+
try { payload = JSON.parse(fs.readFileSync(process.argv[2], 'utf8')); }
|
|
3072
|
+
catch { out({ ok: false }); }
|
|
3073
|
+
|
|
3074
|
+
const projectRoot = payload.projectRoot;
|
|
3075
|
+
const entries = payload.entries || [];
|
|
3076
|
+
const req = createRequire(path.join(projectRoot, '__domflax_tw4__.js'));
|
|
3077
|
+
|
|
3078
|
+
async function importFrom(id) {
|
|
3079
|
+
const resolved = req.resolve(id);
|
|
3080
|
+
return import(pathToFileURL(resolved).href);
|
|
3081
|
+
}
|
|
3082
|
+
|
|
3083
|
+
// Primary loader: @tailwindcss/node (the companion every v4 build tool installs). It resolves
|
|
3084
|
+
// '@import "tailwindcss"' and @theme against the project on disk.
|
|
3085
|
+
async function loadViaNode() {
|
|
3086
|
+
let mod;
|
|
3087
|
+
try { mod = await importFrom('@tailwindcss/node'); } catch { return null; }
|
|
3088
|
+
if (!mod || typeof mod.__unstable__loadDesignSystem !== 'function') return null;
|
|
3089
|
+
for (const e of entries) {
|
|
3090
|
+
try { return await mod.__unstable__loadDesignSystem(e.css, { base: e.base }); } catch {}
|
|
3091
|
+
}
|
|
3092
|
+
return null;
|
|
3093
|
+
}
|
|
3094
|
+
|
|
3095
|
+
// Secondary loader: bare 'tailwindcss' with a filesystem stylesheet resolver (best-effort).
|
|
3096
|
+
async function loadViaCore() {
|
|
3097
|
+
let tw;
|
|
3098
|
+
try { tw = await importFrom('tailwindcss'); } catch { return null; }
|
|
3099
|
+
if (!tw || typeof tw.__unstable__loadDesignSystem !== 'function') return null;
|
|
3100
|
+
const loadStylesheet = async (id, base) => {
|
|
3101
|
+
const r = createRequire(path.join(base, '__domflax_tw4__.js'));
|
|
3102
|
+
let p;
|
|
3103
|
+
const tries = id === 'tailwindcss' ? ['tailwindcss/index.css', 'tailwindcss'] : [id, id + '/index.css'];
|
|
3104
|
+
for (const t of tries) { try { p = r.resolve(t); break; } catch {} }
|
|
3105
|
+
if (!p) p = path.resolve(base, id);
|
|
3106
|
+
return { path: p, base: path.dirname(p), content: fs.readFileSync(p, 'utf8') };
|
|
3107
|
+
};
|
|
3108
|
+
const loadModule = async (id, base) => {
|
|
3109
|
+
const r = createRequire(path.join(base, '__domflax_tw4__.js'));
|
|
3110
|
+
const p = r.resolve(id);
|
|
3111
|
+
return { path: p, base: path.dirname(p), module: (await import(pathToFileURL(p).href)).default };
|
|
3112
|
+
};
|
|
3113
|
+
for (const e of entries) {
|
|
3114
|
+
try { return await tw.__unstable__loadDesignSystem(e.css, { base: e.base, loadStylesheet, loadModule }); } catch {}
|
|
3115
|
+
}
|
|
3116
|
+
return null;
|
|
3117
|
+
}
|
|
3118
|
+
|
|
3119
|
+
const ds = (await loadViaNode()) || (await loadViaCore());
|
|
3120
|
+
if (!ds) out({ ok: false });
|
|
3121
|
+
|
|
3122
|
+
let names = [];
|
|
3123
|
+
try {
|
|
3124
|
+
names = ds.getClassList().map((e) => (Array.isArray(e) ? e[0] : e)).filter((n) => typeof n === 'string');
|
|
3125
|
+
} catch { out({ ok: false }); }
|
|
3126
|
+
|
|
3127
|
+
let css = [];
|
|
3128
|
+
try { css = ds.candidatesToCss(names); } catch { out({ ok: false }); }
|
|
3129
|
+
|
|
3130
|
+
const result = [];
|
|
3131
|
+
for (let i = 0; i < names.length; i += 1) {
|
|
3132
|
+
const c = css[i];
|
|
3133
|
+
if (typeof c === 'string' && c.length > 0) result.push([names[i], c]);
|
|
3134
|
+
}
|
|
3135
|
+
out({ ok: true, entries: result });
|
|
3136
|
+
`;
|
|
3137
|
+
function runV4Bridge(payload) {
|
|
3138
|
+
let dir = null;
|
|
3139
|
+
try {
|
|
3140
|
+
dir = (0, import_node_fs.mkdtempSync)(path.join((0, import_node_os.tmpdir)(), "domflax-tw4-"));
|
|
3141
|
+
const scriptPath = path.join(dir, "bridge.mjs");
|
|
3142
|
+
const payloadPath = path.join(dir, "payload.json");
|
|
3143
|
+
(0, import_node_fs.writeFileSync)(scriptPath, CHILD_SOURCE, "utf8");
|
|
3144
|
+
(0, import_node_fs.writeFileSync)(payloadPath, JSON.stringify(payload), "utf8");
|
|
3145
|
+
const stdout = (0, import_node_child_process.execFileSync)(process.execPath, [scriptPath, payloadPath], {
|
|
3146
|
+
cwd: payload.projectRoot,
|
|
3147
|
+
encoding: "utf8",
|
|
3148
|
+
timeout: 9e4,
|
|
3149
|
+
maxBuffer: 256 * 1024 * 1024,
|
|
3150
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
3151
|
+
});
|
|
3152
|
+
const parsed = JSON.parse(stdout);
|
|
3153
|
+
if (!parsed.ok || !Array.isArray(parsed.entries) || parsed.entries.length === 0) return null;
|
|
3154
|
+
const entries = parsed.entries.filter(
|
|
3155
|
+
(e) => Array.isArray(e) && typeof e[0] === "string" && typeof e[1] === "string"
|
|
3156
|
+
);
|
|
3157
|
+
return entries.length > 0 ? { entries } : null;
|
|
3158
|
+
} catch {
|
|
3159
|
+
return null;
|
|
3160
|
+
} finally {
|
|
3161
|
+
if (dir) {
|
|
3162
|
+
try {
|
|
3163
|
+
(0, import_node_fs.rmSync)(dir, { recursive: true, force: true });
|
|
3164
|
+
} catch {
|
|
3165
|
+
}
|
|
3166
|
+
}
|
|
3167
|
+
}
|
|
3168
|
+
}
|
|
3169
|
+
|
|
3170
|
+
// ../resolver-tailwind/src/tailwind/v4-css.ts
|
|
3171
|
+
function stripComments(src) {
|
|
3172
|
+
return src.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
3173
|
+
}
|
|
3174
|
+
function toDecl(buffer) {
|
|
3175
|
+
const buf = buffer.trim();
|
|
3176
|
+
if (buf.length === 0 || buf[0] === "@") return null;
|
|
3177
|
+
const colon = buf.indexOf(":");
|
|
3178
|
+
if (colon <= 0) return null;
|
|
3179
|
+
const prop = buf.slice(0, colon).trim();
|
|
3180
|
+
let value = buf.slice(colon + 1).trim();
|
|
3181
|
+
if (prop.length === 0 || value.length === 0) return null;
|
|
3182
|
+
let important = false;
|
|
3183
|
+
const bang = /!\s*important\s*$/i.exec(value);
|
|
3184
|
+
if (bang) {
|
|
3185
|
+
important = true;
|
|
3186
|
+
value = value.slice(0, bang.index).trim();
|
|
3187
|
+
}
|
|
3188
|
+
return { type: "decl", prop, value, important };
|
|
3189
|
+
}
|
|
3190
|
+
function splitAtRule(prelude) {
|
|
3191
|
+
const m = /^@([A-Za-z-]+)\s*([\s\S]*)$/.exec(prelude);
|
|
3192
|
+
if (!m) return { name: prelude.slice(1).trim(), params: "" };
|
|
3193
|
+
return { name: m[1].toLowerCase(), params: m[2].trim() };
|
|
3194
|
+
}
|
|
3195
|
+
function parseBlock(src, start) {
|
|
3196
|
+
const nodes = [];
|
|
3197
|
+
let buf = "";
|
|
3198
|
+
let i = start;
|
|
3199
|
+
while (i < src.length) {
|
|
3200
|
+
const c = src[i];
|
|
3201
|
+
if (c === "{") {
|
|
3202
|
+
const prelude = buf.trim();
|
|
3203
|
+
buf = "";
|
|
3204
|
+
const inner = parseBlock(src, i + 1);
|
|
3205
|
+
i = inner.next;
|
|
3206
|
+
if (prelude.startsWith("@")) {
|
|
3207
|
+
const { name, params } = splitAtRule(prelude);
|
|
3208
|
+
nodes.push({ type: "atrule", name, params, nodes: inner.nodes });
|
|
3209
|
+
} else if (prelude.length > 0) {
|
|
3210
|
+
nodes.push({ type: "rule", selector: prelude, nodes: inner.nodes });
|
|
3211
|
+
}
|
|
3212
|
+
} else if (c === "}") {
|
|
3213
|
+
const d = toDecl(buf);
|
|
3214
|
+
if (d) nodes.push(d);
|
|
3215
|
+
return { nodes, next: i + 1 };
|
|
3216
|
+
} else if (c === ";") {
|
|
3217
|
+
const d = toDecl(buf);
|
|
3218
|
+
if (d) nodes.push(d);
|
|
3219
|
+
buf = "";
|
|
3220
|
+
i += 1;
|
|
3221
|
+
} else {
|
|
3222
|
+
buf += c;
|
|
3223
|
+
i += 1;
|
|
3224
|
+
}
|
|
3225
|
+
}
|
|
3226
|
+
const tail = toDecl(buf);
|
|
3227
|
+
if (tail) nodes.push(tail);
|
|
3228
|
+
return { nodes, next: i };
|
|
3229
|
+
}
|
|
3230
|
+
function resolveNesting(child, parent) {
|
|
3231
|
+
const c = child.trim();
|
|
3232
|
+
if (parent.length === 0) return c;
|
|
3233
|
+
if (c.includes("&")) return c.split("&").join(parent);
|
|
3234
|
+
return `${parent} ${c}`;
|
|
3235
|
+
}
|
|
3236
|
+
var DROP_ATRULES = /* @__PURE__ */ new Set(["property", "keyframes", "font-face", "charset", "import"]);
|
|
3237
|
+
function flattenNodes(nodes, selector, at, out) {
|
|
3238
|
+
const own = [];
|
|
3239
|
+
for (const n of nodes) if (n.type === "decl") own.push(n);
|
|
3240
|
+
if (own.length > 0 && selector.length > 0) out.push({ selector, at: [...at], decls: own });
|
|
3241
|
+
for (const n of nodes) {
|
|
3242
|
+
if (n.type === "rule") {
|
|
3243
|
+
flattenNodes(n.nodes, resolveNesting(n.selector, selector), at, out);
|
|
3244
|
+
} else if (n.type === "atrule") {
|
|
3245
|
+
if (n.name === "media") {
|
|
3246
|
+
flattenNodes(n.nodes, selector, [...at, { name: "media", params: n.params }], out);
|
|
3247
|
+
} else if (n.name === "layer") {
|
|
3248
|
+
flattenNodes(n.nodes, selector, at, out);
|
|
3249
|
+
} else if (!DROP_ATRULES.has(n.name)) {
|
|
3250
|
+
flattenNodes(n.nodes, selector, [...at, { name: n.name, params: n.params }], out);
|
|
3251
|
+
}
|
|
3252
|
+
}
|
|
3253
|
+
}
|
|
3254
|
+
}
|
|
3255
|
+
function leafToNode(leaf) {
|
|
3256
|
+
const declNodes = leaf.decls.map((d) => ({
|
|
3257
|
+
type: "decl",
|
|
3258
|
+
prop: d.prop,
|
|
3259
|
+
value: d.value,
|
|
3260
|
+
important: d.important
|
|
3261
|
+
}));
|
|
3262
|
+
let node = { type: "rule", selector: leaf.selector, nodes: declNodes };
|
|
3263
|
+
for (let i = leaf.at.length - 1; i >= 0; i -= 1) {
|
|
3264
|
+
node = { type: "atrule", name: leaf.at[i].name, params: leaf.at[i].params, nodes: [node] };
|
|
3265
|
+
}
|
|
3266
|
+
return node;
|
|
3267
|
+
}
|
|
3268
|
+
function parseUtilityCss(css) {
|
|
3269
|
+
try {
|
|
3270
|
+
const { nodes } = parseBlock(stripComments(css), 0);
|
|
3271
|
+
const leaves = [];
|
|
3272
|
+
flattenNodes(nodes, "", [], leaves);
|
|
3273
|
+
return leaves.map(leafToNode);
|
|
3274
|
+
} catch {
|
|
3275
|
+
return [];
|
|
3276
|
+
}
|
|
3277
|
+
}
|
|
3278
|
+
|
|
3279
|
+
// ../resolver-tailwind/src/tailwind/engine-v4.ts
|
|
3280
|
+
var SEARCH_DIRS = ["", "src", "app", "styles", "src/styles", "src/app", "app/styles", "assets/css", "css"];
|
|
3281
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", "out", ".next", "coverage"]);
|
|
3282
|
+
var ENTRY_RE = /@import\s+["']tailwindcss["']|@tailwind\b|@theme\b/;
|
|
3283
|
+
function scanDir(dir) {
|
|
3284
|
+
let names;
|
|
3285
|
+
try {
|
|
3286
|
+
names = (0, import_node_fs2.readdirSync)(dir);
|
|
3287
|
+
} catch {
|
|
3288
|
+
return null;
|
|
3289
|
+
}
|
|
3290
|
+
for (const name of names) {
|
|
3291
|
+
if (!name.toLowerCase().endsWith(".css")) continue;
|
|
3292
|
+
const file = path2.join(dir, name);
|
|
3293
|
+
try {
|
|
3294
|
+
if (!(0, import_node_fs2.statSync)(file).isFile()) continue;
|
|
3295
|
+
const css = (0, import_node_fs2.readFileSync)(file, "utf8");
|
|
3296
|
+
if (ENTRY_RE.test(css)) return { css, base: path2.dirname(file) };
|
|
3297
|
+
} catch {
|
|
3298
|
+
}
|
|
3299
|
+
}
|
|
3300
|
+
return null;
|
|
3301
|
+
}
|
|
3302
|
+
function findCssEntries(projectRoot) {
|
|
3303
|
+
const out = [];
|
|
3304
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3305
|
+
for (const rel of SEARCH_DIRS) {
|
|
3306
|
+
const dir = path2.resolve(projectRoot, rel);
|
|
3307
|
+
if (seen.has(dir) || [...SKIP_DIRS].some((s) => dir.includes(`${path2.sep}${s}`))) continue;
|
|
3308
|
+
seen.add(dir);
|
|
3309
|
+
const hit = scanDir(dir);
|
|
3310
|
+
if (hit) {
|
|
3311
|
+
out.push(hit);
|
|
3312
|
+
break;
|
|
3313
|
+
}
|
|
3314
|
+
}
|
|
3315
|
+
out.push({ css: '@import "tailwindcss";', base: projectRoot });
|
|
3316
|
+
return out;
|
|
3317
|
+
}
|
|
3318
|
+
function makeV4Engine(entries, version) {
|
|
3319
|
+
const cssByClass = new Map(entries.map(([name, css]) => [name, css]));
|
|
3320
|
+
const nodeCache = /* @__PURE__ */ new Map();
|
|
3321
|
+
const nodesFor = (token) => {
|
|
3322
|
+
let cached = nodeCache.get(token);
|
|
3323
|
+
if (!cached) {
|
|
3324
|
+
const css = cssByClass.get(token);
|
|
3325
|
+
cached = css ? parseUtilityCss(css) : [];
|
|
3326
|
+
nodeCache.set(token, cached);
|
|
3327
|
+
}
|
|
3328
|
+
return cached;
|
|
3329
|
+
};
|
|
3330
|
+
return {
|
|
3331
|
+
version,
|
|
3332
|
+
context: {
|
|
3333
|
+
// The resolver keeps only string entries; we hand it the concrete class names directly.
|
|
3334
|
+
getClassList: () => [...cssByClass.keys()]
|
|
3335
|
+
},
|
|
3336
|
+
generate(candidates) {
|
|
3337
|
+
const out = [];
|
|
3338
|
+
for (const c of candidates) for (const n of nodesFor(c)) out.push(n);
|
|
3339
|
+
return out;
|
|
3340
|
+
}
|
|
3341
|
+
};
|
|
3342
|
+
}
|
|
3343
|
+
function loadV4Engine(projectRoot, version) {
|
|
3344
|
+
const snapshot = runV4Bridge({ projectRoot, entries: findCssEntries(projectRoot) });
|
|
3345
|
+
if (!snapshot) return null;
|
|
3346
|
+
return makeV4Engine(snapshot.entries, version);
|
|
3347
|
+
}
|
|
3348
|
+
|
|
3349
|
+
// ../resolver-tailwind/src/tailwind/engine.ts
|
|
3967
3350
|
function moduleBase() {
|
|
3968
3351
|
return typeof __filename === "string" ? __filename : importMetaUrl;
|
|
3969
3352
|
}
|
|
3970
3353
|
function projectRequire(projectRoot) {
|
|
3971
3354
|
const bases = [];
|
|
3972
|
-
if (projectRoot) bases.push(
|
|
3973
|
-
bases.push(
|
|
3355
|
+
if (projectRoot) bases.push(path3.join(projectRoot, "__domflax__.js"));
|
|
3356
|
+
bases.push(path3.join(process.cwd(), "__domflax__.js"));
|
|
3974
3357
|
bases.push(moduleBase());
|
|
3975
3358
|
for (const base of bases) {
|
|
3976
3359
|
try {
|
|
@@ -3982,14 +3365,36 @@ function projectRequire(projectRoot) {
|
|
|
3982
3365
|
}
|
|
3983
3366
|
return null;
|
|
3984
3367
|
}
|
|
3368
|
+
var FIRST_UNSUPPORTED_MAJOR = 4;
|
|
3369
|
+
function majorOf(version) {
|
|
3370
|
+
const m = /^\s*(\d+)/.exec(version);
|
|
3371
|
+
return m ? Number(m[1]) : null;
|
|
3372
|
+
}
|
|
3985
3373
|
function loadEngine(options) {
|
|
3986
3374
|
const req = projectRequire(options.projectRoot);
|
|
3987
|
-
if (!req) return null;
|
|
3375
|
+
if (!req) return { engine: null, version: null, unsupportedMajor: null };
|
|
3376
|
+
let version = null;
|
|
3377
|
+
try {
|
|
3378
|
+
version = req("tailwindcss/package.json").version;
|
|
3379
|
+
} catch {
|
|
3380
|
+
return { engine: null, version: null, unsupportedMajor: null };
|
|
3381
|
+
}
|
|
3382
|
+
const major = majorOf(version);
|
|
3383
|
+
if (major !== null && major >= FIRST_UNSUPPORTED_MAJOR) {
|
|
3384
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
3385
|
+
let v4 = null;
|
|
3386
|
+
try {
|
|
3387
|
+
v4 = loadV4Engine(projectRoot, version);
|
|
3388
|
+
} catch {
|
|
3389
|
+
v4 = null;
|
|
3390
|
+
}
|
|
3391
|
+
if (v4) return { engine: v4, version, unsupportedMajor: null };
|
|
3392
|
+
return { engine: null, version, unsupportedMajor: major };
|
|
3393
|
+
}
|
|
3988
3394
|
try {
|
|
3989
3395
|
const resolveConfig = req("tailwindcss/resolveConfig.js");
|
|
3990
3396
|
const { createContext } = req("tailwindcss/lib/lib/setupContextUtils.js");
|
|
3991
3397
|
const { generateRules } = req("tailwindcss/lib/lib/generateRules.js");
|
|
3992
|
-
const pkg = req("tailwindcss/package.json");
|
|
3993
3398
|
let userConfig = options.config ?? { content: [{ raw: "" }] };
|
|
3994
3399
|
if (options.configPath !== void 0) {
|
|
3995
3400
|
const loadConfig = req("tailwindcss/loadConfig.js");
|
|
@@ -3998,15 +3403,19 @@ function loadEngine(options) {
|
|
|
3998
3403
|
const resolved = resolveConfig(userConfig);
|
|
3999
3404
|
const context = createContext(resolved);
|
|
4000
3405
|
return {
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
3406
|
+
engine: {
|
|
3407
|
+
version,
|
|
3408
|
+
context,
|
|
3409
|
+
generate(candidates) {
|
|
3410
|
+
const rules = generateRules(new Set(candidates), context);
|
|
3411
|
+
return rules.map(([, node]) => node);
|
|
3412
|
+
}
|
|
3413
|
+
},
|
|
3414
|
+
version,
|
|
3415
|
+
unsupportedMajor: null
|
|
4007
3416
|
};
|
|
4008
3417
|
} catch {
|
|
4009
|
-
return null;
|
|
3418
|
+
return { engine: null, version, unsupportedMajor: null };
|
|
4010
3419
|
}
|
|
4011
3420
|
}
|
|
4012
3421
|
|
|
@@ -4185,22 +3594,39 @@ var DROPPABLE_USAGE = {
|
|
|
4185
3594
|
};
|
|
4186
3595
|
|
|
4187
3596
|
// ../resolver-tailwind/src/tailwind/resolver.ts
|
|
3597
|
+
var warnedUnsupported = /* @__PURE__ */ new Set();
|
|
4188
3598
|
var TailwindResolver = class {
|
|
4189
3599
|
id = "tailwind";
|
|
4190
3600
|
provider;
|
|
4191
3601
|
fingerprint;
|
|
3602
|
+
/**
|
|
3603
|
+
* SAFETY (Layer 1): the detected Tailwind MAJOR when the project's version is one this resolver
|
|
3604
|
+
* cannot drive (v4+), else `null`. When set, {@link resolve} reports every token as unknown, so
|
|
3605
|
+
* downstream files are left unchanged (never mis-optimized). Exposed for diagnostics/tests.
|
|
3606
|
+
*/
|
|
3607
|
+
unsupportedMajor;
|
|
4192
3608
|
#engine;
|
|
4193
3609
|
/** Per-token extraction cache (engine output is pure for a fixed config). */
|
|
4194
3610
|
#tokenCache = /* @__PURE__ */ new Map();
|
|
4195
3611
|
/** Per-class-set forward-resolution cache. */
|
|
4196
3612
|
#resolveCache = /* @__PURE__ */ new Map();
|
|
4197
|
-
/** Lazily built reverse index for {@link emit}. */
|
|
3613
|
+
/** Lazily built reverse index for the greedy {@link emit} fallback. */
|
|
4198
3614
|
#reverseIndex = null;
|
|
3615
|
+
/** Lazily built cover vocabulary (base-condition tuple sets) for the exact-cover engine. */
|
|
3616
|
+
#coverVocab = null;
|
|
4199
3617
|
constructor(config = {}) {
|
|
4200
|
-
|
|
4201
|
-
this
|
|
3618
|
+
const loaded = loadEngine(config);
|
|
3619
|
+
this.#engine = loaded.engine;
|
|
3620
|
+
this.unsupportedMajor = loaded.unsupportedMajor;
|
|
3621
|
+
this.provider = config.provider ?? (loaded.version ? `tailwindcss@${loaded.version}` : "tailwindcss");
|
|
4202
3622
|
const seed = JSON.stringify(config.config ?? {}) + (config.configPath ?? "");
|
|
4203
3623
|
this.fingerprint = config.fingerprint ?? `${this.provider}/${fnv1a(seed)}`;
|
|
3624
|
+
if (this.unsupportedMajor !== null && !warnedUnsupported.has(this.provider)) {
|
|
3625
|
+
warnedUnsupported.add(this.provider);
|
|
3626
|
+
console.warn(
|
|
3627
|
+
`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.`
|
|
3628
|
+
);
|
|
3629
|
+
}
|
|
4204
3630
|
}
|
|
4205
3631
|
/** Engine-backed, cached single-token extraction. */
|
|
4206
3632
|
#extract(token) {
|
|
@@ -4309,9 +3735,75 @@ var TailwindResolver = class {
|
|
|
4309
3735
|
this.#reverseIndex = index;
|
|
4310
3736
|
return index;
|
|
4311
3737
|
}
|
|
3738
|
+
/**
|
|
3739
|
+
* The cover vocabulary: every base-condition, plain-subject utility mapped to the {@link tupleKey}s
|
|
3740
|
+
* of its full normalized-longhand declaration set. Built once from a SINGLE engine `generate` over
|
|
3741
|
+
* the enumerable class list (grouped by selector), so it is the same cost as {@link #buildReverseIndex}.
|
|
3742
|
+
* This is what the provider-uniform exact-cover engine searches; the element's own droppable tokens
|
|
3743
|
+
* are members of it, guaranteeing feasibility. Variant / combinator / pseudo utilities are excluded
|
|
3744
|
+
* (their effect is not the element's own base box), so a target carrying such conditions simply finds
|
|
3745
|
+
* no cover and falls back to the greedy emit.
|
|
3746
|
+
*/
|
|
3747
|
+
#buildCoverVocab() {
|
|
3748
|
+
if (this.#coverVocab) return this.#coverVocab;
|
|
3749
|
+
const baseCk = String(conditionKey(BASE_CONDITION));
|
|
3750
|
+
const out = [];
|
|
3751
|
+
if (this.#engine) {
|
|
3752
|
+
try {
|
|
3753
|
+
const classes = this.#engine.context.getClassList().filter((c) => typeof c === "string");
|
|
3754
|
+
const nodes = this.#engine.generate(classes);
|
|
3755
|
+
for (const node of nodes) {
|
|
3756
|
+
if (node.type !== "rule") continue;
|
|
3757
|
+
const rule = node;
|
|
3758
|
+
const parsed = parseSelector(rule.selector);
|
|
3759
|
+
if (parsed.kind !== "simple" || parsed.states.length > 0 || parsed.pseudoElement !== "") {
|
|
3760
|
+
continue;
|
|
3761
|
+
}
|
|
3762
|
+
const className = unescapeClass(rule.selector);
|
|
3763
|
+
if (className === null) continue;
|
|
3764
|
+
const tuples = [];
|
|
3765
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3766
|
+
for (const child of rule.nodes ?? []) {
|
|
3767
|
+
if (child.type !== "decl") continue;
|
|
3768
|
+
const d = child;
|
|
3769
|
+
if (typeof d.value !== "string") continue;
|
|
3770
|
+
for (const decl of normalizer.normalizeDeclaration(d.prop, d.value, d.important === true)) {
|
|
3771
|
+
const k = tupleKey(baseCk, String(decl.property), String(decl.value), decl.important);
|
|
3772
|
+
if (!seen.has(k)) {
|
|
3773
|
+
seen.add(k);
|
|
3774
|
+
tuples.push(k);
|
|
3775
|
+
}
|
|
3776
|
+
}
|
|
3777
|
+
}
|
|
3778
|
+
if (tuples.length > 0) out.push({ token: className, tuples });
|
|
3779
|
+
}
|
|
3780
|
+
} catch {
|
|
3781
|
+
}
|
|
3782
|
+
}
|
|
3783
|
+
this.#coverVocab = out;
|
|
3784
|
+
return out;
|
|
3785
|
+
}
|
|
3786
|
+
/**
|
|
3787
|
+
* Try the minimal-string exact-cover engine over the WHOLE utility vocabulary. On success the chosen
|
|
3788
|
+
* set is verified by the mandatory CORRECTNESS BACKSTOP — re-resolve it and assert it reproduces the
|
|
3789
|
+
* target's tuples EXACTLY — before it is returned; any mismatch (or no cover / oversize universe)
|
|
3790
|
+
* yields `null` so {@link emit} uses its greedy fallback. Never returns a set that misrepresents `U`.
|
|
3791
|
+
*/
|
|
3792
|
+
#tryCover(normalized, norm) {
|
|
3793
|
+
const universe = styleMapTuples(normalized, norm);
|
|
3794
|
+
if (universe.length === 0) return { classes: [], exact: true, warnings: [] };
|
|
3795
|
+
const chosen = minStringCover(universe, this.#buildCoverVocab());
|
|
3796
|
+
if (!chosen || chosen.length === 0) return null;
|
|
3797
|
+
const reTuples = new Set(styleMapTuples(this.resolve({ classes: chosen }).styles, norm));
|
|
3798
|
+
if (reTuples.size !== universe.length) return null;
|
|
3799
|
+
for (const t of universe) if (!reTuples.has(t)) return null;
|
|
3800
|
+
return { classes: chosen, exact: true, warnings: [] };
|
|
3801
|
+
}
|
|
4312
3802
|
emit(styles, ctx) {
|
|
4313
3803
|
const norm = ctx.normalizer ?? normalizer;
|
|
4314
3804
|
const normalized = norm.normalizeStyleMap(styles);
|
|
3805
|
+
const cover = this.#tryCover(normalized, norm);
|
|
3806
|
+
if (cover) return cover;
|
|
4315
3807
|
const base = normalized.blocks.get(conditionKey(BASE_CONDITION));
|
|
4316
3808
|
if (!base || base.decls.size === 0) return { classes: [], exact: true, warnings: [] };
|
|
4317
3809
|
const hasNonBase = normalized.blocks.size > 1;
|
|
@@ -4341,13 +3833,13 @@ var TailwindResolver = class {
|
|
|
4341
3833
|
let bestCover = 0;
|
|
4342
3834
|
for (const entry of candidates) {
|
|
4343
3835
|
const [token, declMap] = entry;
|
|
4344
|
-
let
|
|
4345
|
-
for (const prop of declMap.keys()) if (remaining.has(prop))
|
|
4346
|
-
if (
|
|
4347
|
-
const better = best === null ||
|
|
3836
|
+
let cover2 = 0;
|
|
3837
|
+
for (const prop of declMap.keys()) if (remaining.has(prop)) cover2 += 1;
|
|
3838
|
+
if (cover2 === 0) continue;
|
|
3839
|
+
const better = best === null || cover2 > bestCover || cover2 === bestCover && declMap.size < best[1].size || cover2 === bestCover && declMap.size === best[1].size && token < best[0];
|
|
4348
3840
|
if (better) {
|
|
4349
3841
|
best = entry;
|
|
4350
|
-
bestCover =
|
|
3842
|
+
bestCover = cover2;
|
|
4351
3843
|
}
|
|
4352
3844
|
}
|
|
4353
3845
|
if (!best) break;
|
|
@@ -4418,14 +3910,14 @@ var LEGACY_PSEUDO_ELEMENTS2 = /* @__PURE__ */ new Set([
|
|
|
4418
3910
|
|
|
4419
3911
|
// ../resolver-css/src/engine.ts
|
|
4420
3912
|
var import_node_module2 = require("module");
|
|
4421
|
-
var
|
|
3913
|
+
var path4 = __toESM(require("path"), 1);
|
|
4422
3914
|
function moduleBase2() {
|
|
4423
3915
|
return typeof __filename === "string" ? __filename : importMetaUrl;
|
|
4424
3916
|
}
|
|
4425
3917
|
function loadPostcssEngine(projectRoot) {
|
|
4426
3918
|
const bases = [];
|
|
4427
|
-
if (projectRoot) bases.push(
|
|
4428
|
-
bases.push(
|
|
3919
|
+
if (projectRoot) bases.push(path4.join(projectRoot, "__domflax__.js"));
|
|
3920
|
+
bases.push(path4.join(process.cwd(), "__domflax__.js"));
|
|
4429
3921
|
bases.push(moduleBase2());
|
|
4430
3922
|
for (const base of bases) {
|
|
4431
3923
|
try {
|
|
@@ -4478,15 +3970,15 @@ function collectDecls(rule) {
|
|
|
4478
3970
|
}
|
|
4479
3971
|
|
|
4480
3972
|
// ../resolver-css/src/misc-helpers.ts
|
|
4481
|
-
var
|
|
3973
|
+
var import_node_fs3 = require("fs");
|
|
4482
3974
|
function isPlainClassToken(token) {
|
|
4483
3975
|
return token.length > 0 && !/[\s.#>+~:[\]()]/.test(token);
|
|
4484
3976
|
}
|
|
4485
|
-
function readCssPath(
|
|
3977
|
+
function readCssPath(path5) {
|
|
4486
3978
|
try {
|
|
4487
|
-
return { id:
|
|
3979
|
+
return { id: path5, css: (0, import_node_fs3.readFileSync)(path5, "utf8") };
|
|
4488
3980
|
} catch (cause) {
|
|
4489
|
-
throw new Error(`resolver-css: cannot read CSS file "${
|
|
3981
|
+
throw new Error(`resolver-css: cannot read CSS file "${path5}"`, { cause });
|
|
4490
3982
|
}
|
|
4491
3983
|
}
|
|
4492
3984
|
function deriveFingerprint(provider, files) {
|
|
@@ -4541,6 +4033,8 @@ var CustomCSSResolver = class {
|
|
|
4541
4033
|
/** Distinct COMPLEX selectors (combinator or structural pseudo), sorted. */
|
|
4542
4034
|
#complex;
|
|
4543
4035
|
#reverse = null;
|
|
4036
|
+
/** Lazily built cover vocabulary (full condition-keyed tuple sets) for the exact-cover engine. */
|
|
4037
|
+
#coverVocab = null;
|
|
4544
4038
|
constructor(cssFiles = [], options = {}) {
|
|
4545
4039
|
ensurePostcss(options.projectRoot);
|
|
4546
4040
|
const fromDisk = (options.files ?? []).map(readCssPath);
|
|
@@ -4573,8 +4067,23 @@ var CustomCSSResolver = class {
|
|
|
4573
4067
|
}
|
|
4574
4068
|
emit(styles, ctx) {
|
|
4575
4069
|
const norm = ctx.normalizer ?? normalizer;
|
|
4070
|
+
const normalized = norm.normalizeStyleMap(styles);
|
|
4071
|
+
const universe = styleMapTuples(normalized, norm);
|
|
4072
|
+
if (universe.length === 0) return { classes: [], exact: true, warnings: [] };
|
|
4073
|
+
const chosen = minStringCover(universe, this.#buildCoverVocab());
|
|
4074
|
+
if (chosen && chosen.length > 0) {
|
|
4075
|
+
const reTuples = new Set(styleMapTuples(this.resolve({ classes: chosen }).styles, norm));
|
|
4076
|
+
let ok = reTuples.size === universe.length;
|
|
4077
|
+
if (ok) {
|
|
4078
|
+
for (const t of universe) if (!reTuples.has(t)) {
|
|
4079
|
+
ok = false;
|
|
4080
|
+
break;
|
|
4081
|
+
}
|
|
4082
|
+
}
|
|
4083
|
+
if (ok) return { classes: chosen, exact: true, warnings: [] };
|
|
4084
|
+
}
|
|
4576
4085
|
const remaining = /* @__PURE__ */ new Map();
|
|
4577
|
-
for (const [ck, block] of
|
|
4086
|
+
for (const [ck, block] of normalized.blocks) {
|
|
4578
4087
|
for (const [prop, decl] of block.decls) {
|
|
4579
4088
|
remaining.set(`${ck} ${prop}`, String(decl.value));
|
|
4580
4089
|
}
|
|
@@ -4781,10 +4290,26 @@ var CustomCSSResolver = class {
|
|
|
4781
4290
|
if (block.decls.size === 0) continue;
|
|
4782
4291
|
rawBlocks.set(key, { condition: block.condition, decls: block.decls });
|
|
4783
4292
|
}
|
|
4784
|
-
if (rawBlocks.size === 0) return emptyStyleMap();
|
|
4785
|
-
return normalizer.normalizeStyleMap({ blocks: rawBlocks });
|
|
4293
|
+
if (rawBlocks.size === 0) return emptyStyleMap();
|
|
4294
|
+
return normalizer.normalizeStyleMap({ blocks: rawBlocks });
|
|
4295
|
+
}
|
|
4296
|
+
/**
|
|
4297
|
+
* Build (once) the cover vocabulary for the exact-cover engine: every forward-resolvable class
|
|
4298
|
+
* mapped to the {@link styleMapTuples} of its full (condition-keyed, `!important`-aware) declaration
|
|
4299
|
+
* set. Unlike {@link #reverseIndex} this carries ALL style conditions and the important flag, so the
|
|
4300
|
+
* engine can pick a custom class covering hover/media declarations too.
|
|
4301
|
+
*/
|
|
4302
|
+
#buildCoverVocab() {
|
|
4303
|
+
if (this.#coverVocab) return this.#coverVocab;
|
|
4304
|
+
const out = [];
|
|
4305
|
+
for (const token of this.#classIndex.keys()) {
|
|
4306
|
+
const tuples = styleMapTuples(this.#resolveTokens([token], [token]), normalizer);
|
|
4307
|
+
if (tuples.length > 0) out.push({ token, tuples });
|
|
4308
|
+
}
|
|
4309
|
+
this.#coverVocab = out;
|
|
4310
|
+
return out;
|
|
4786
4311
|
}
|
|
4787
|
-
/** Build (once) the reverse index used by {@link emit}. */
|
|
4312
|
+
/** Build (once) the reverse index used by the greedy {@link emit} fallback. */
|
|
4788
4313
|
#reverseIndex() {
|
|
4789
4314
|
if (this.#reverse) return this.#reverse;
|
|
4790
4315
|
const out = [];
|
|
@@ -4809,11 +4334,350 @@ function createCssResolver(cssFiles = [], options) {
|
|
|
4809
4334
|
var import_node_path = require("path");
|
|
4810
4335
|
var import_node_url = require("url");
|
|
4811
4336
|
|
|
4337
|
+
// ../frontend-html/src/backend.ts
|
|
4338
|
+
var import_magic_string = __toESM(require("magic-string"), 1);
|
|
4339
|
+
function staticTokensOf2(classes) {
|
|
4340
|
+
const out = [];
|
|
4341
|
+
for (const seg of classes.segments) {
|
|
4342
|
+
if (seg.kind === "static") for (const t of seg.tokens) out.push(t.value);
|
|
4343
|
+
}
|
|
4344
|
+
return out;
|
|
4345
|
+
}
|
|
4346
|
+
function sameTokens2(a, b) {
|
|
4347
|
+
if (a.length !== b.length) return false;
|
|
4348
|
+
for (let i = 0; i < a.length; i += 1) if (a[i] !== b[i]) return false;
|
|
4349
|
+
return true;
|
|
4350
|
+
}
|
|
4351
|
+
function primarySource(doc) {
|
|
4352
|
+
for (const sf of doc.sources.values()) {
|
|
4353
|
+
if (typeof sf.text === "string" && sf.text.length > 0) return sf;
|
|
4354
|
+
}
|
|
4355
|
+
return null;
|
|
4356
|
+
}
|
|
4357
|
+
function collectKept(doc) {
|
|
4358
|
+
const out = [];
|
|
4359
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4360
|
+
const visit = (id) => {
|
|
4361
|
+
if (seen.has(id)) return;
|
|
4362
|
+
seen.add(id);
|
|
4363
|
+
const n = doc.nodes.get(id);
|
|
4364
|
+
if (!n) return;
|
|
4365
|
+
out.push(n);
|
|
4366
|
+
if (n.kind === "element" || n.kind === "fragment") for (const c of n.children) visit(c);
|
|
4367
|
+
};
|
|
4368
|
+
visit(doc.root);
|
|
4369
|
+
return out;
|
|
4370
|
+
}
|
|
4371
|
+
function strictlyContains(a, b) {
|
|
4372
|
+
if (a.file !== b.file) return false;
|
|
4373
|
+
if (a.start <= b.start && b.end <= a.end) return !(a.start === b.start && a.end === b.end);
|
|
4374
|
+
return false;
|
|
4375
|
+
}
|
|
4376
|
+
function backrefIds(doc) {
|
|
4377
|
+
const out = [];
|
|
4378
|
+
const max = doc.alloc.peek;
|
|
4379
|
+
for (let i = 1; i < max; i += 1) {
|
|
4380
|
+
const id = i;
|
|
4381
|
+
if (doc.backref.get(id)) out.push(id);
|
|
4382
|
+
}
|
|
4383
|
+
return out;
|
|
4384
|
+
}
|
|
4385
|
+
function currentTokens(sf, valueSpan) {
|
|
4386
|
+
const raw = sf.text.slice(valueSpan.start, valueSpan.end).trim();
|
|
4387
|
+
const unquoted = raw.replace(/^['"]/, "").replace(/['"]$/, "");
|
|
4388
|
+
return unquoted.split(/\s+/).filter((t) => t.length > 0);
|
|
4389
|
+
}
|
|
4390
|
+
function editClasses(ms, doc, sf, el) {
|
|
4391
|
+
const classes = el.classes;
|
|
4392
|
+
if (classes.hasDynamic || classes.opaque) return false;
|
|
4393
|
+
const tokens = staticTokensOf2(classes);
|
|
4394
|
+
const valueSpan = classes.valueSpan;
|
|
4395
|
+
if (valueSpan && valueSpan.file === sf.id) {
|
|
4396
|
+
if (sameTokens2(currentTokens(sf, valueSpan), tokens)) return false;
|
|
4397
|
+
const current = sf.text.slice(valueSpan.start, valueSpan.end).trim();
|
|
4398
|
+
const quote = current.startsWith("'") ? "'" : '"';
|
|
4399
|
+
ms.overwrite(valueSpan.start, valueSpan.end, `${quote}${tokens.join(" ")}${quote}`);
|
|
4400
|
+
return true;
|
|
4401
|
+
}
|
|
4402
|
+
if (tokens.length === 0) return false;
|
|
4403
|
+
const openTag = doc.backref.get(el.id)?.openTagSpan;
|
|
4404
|
+
if (!openTag || openTag.file !== sf.id) return false;
|
|
4405
|
+
ms.appendLeft(openTag.start + 1 + el.tag.length, ` class="${tokens.join(" ")}"`);
|
|
4406
|
+
return true;
|
|
4407
|
+
}
|
|
4408
|
+
function surgicalPrint(doc) {
|
|
4409
|
+
const sf = primarySource(doc);
|
|
4410
|
+
if (!sf) return null;
|
|
4411
|
+
const ms = new import_magic_string.default(sf.text);
|
|
4412
|
+
const kept = collectKept(doc);
|
|
4413
|
+
const keptSpans = [];
|
|
4414
|
+
for (const n of kept) if (n.span && n.span.file === sf.id) keptSpans.push(n.span);
|
|
4415
|
+
const removed = [];
|
|
4416
|
+
for (const id of backrefIds(doc)) {
|
|
4417
|
+
if (doc.nodes.has(id)) continue;
|
|
4418
|
+
const back = doc.backref.get(id);
|
|
4419
|
+
if (!back || back.span.file !== sf.id) continue;
|
|
4420
|
+
const unwrapped = keptSpans.some((k) => strictlyContains(back.span, k));
|
|
4421
|
+
removed.push({ backref: back, unwrapped });
|
|
4422
|
+
}
|
|
4423
|
+
const fullRemovals = removed.filter((r) => !r.unwrapped).map((r) => r.backref.span);
|
|
4424
|
+
for (const r of removed) {
|
|
4425
|
+
const s = r.backref.span;
|
|
4426
|
+
if (fullRemovals.some((f) => f !== s && strictlyContains(f, s))) continue;
|
|
4427
|
+
if (r.unwrapped) {
|
|
4428
|
+
const open = r.backref.openTagSpan;
|
|
4429
|
+
const close = r.backref.closeTagSpan;
|
|
4430
|
+
if (open && open.file === sf.id && open.end > open.start) ms.remove(open.start, open.end);
|
|
4431
|
+
if (close && close.file === sf.id && close.end > close.start) ms.remove(close.start, close.end);
|
|
4432
|
+
} else {
|
|
4433
|
+
ms.remove(s.start, s.end);
|
|
4434
|
+
}
|
|
4435
|
+
}
|
|
4436
|
+
for (const n of kept) if (n.kind === "element") editClasses(ms, doc, sf, n);
|
|
4437
|
+
return ms.toString();
|
|
4438
|
+
}
|
|
4439
|
+
function doPrint(doc) {
|
|
4440
|
+
return surgicalPrint(doc) ?? "";
|
|
4441
|
+
}
|
|
4442
|
+
|
|
4443
|
+
// ../frontend-html/src/parse.ts
|
|
4444
|
+
var import_node_module3 = require("module");
|
|
4445
|
+
|
|
4446
|
+
// ../frontend-html/src/walk.ts
|
|
4447
|
+
var HTML_LANGS = ["html"];
|
|
4448
|
+
var FILE_ID = 1;
|
|
4449
|
+
function looksLikeHtml(id, code) {
|
|
4450
|
+
if (/\.html?$/i.test(id)) return true;
|
|
4451
|
+
const head = code.slice(0, 256).trimStart().toLowerCase();
|
|
4452
|
+
return head.startsWith("<!doctype html") || head.startsWith("<html") || head.startsWith("<");
|
|
4453
|
+
}
|
|
4454
|
+
var OPAQUE_SUBTREE_TAGS = /* @__PURE__ */ new Set([
|
|
4455
|
+
"script",
|
|
4456
|
+
"style",
|
|
4457
|
+
"template",
|
|
4458
|
+
"svg",
|
|
4459
|
+
"pre",
|
|
4460
|
+
"textarea"
|
|
4461
|
+
]);
|
|
4462
|
+
function isOpaqueSubtreeTag(tag) {
|
|
4463
|
+
return OPAQUE_SUBTREE_TAGS.has(tag);
|
|
4464
|
+
}
|
|
4465
|
+
function elementIsOpaque(attrs) {
|
|
4466
|
+
for (const a of attrs) {
|
|
4467
|
+
const n = a.name.toLowerCase();
|
|
4468
|
+
if (n === "id" || n === "contenteditable") return true;
|
|
4469
|
+
if (n.startsWith("on")) return true;
|
|
4470
|
+
}
|
|
4471
|
+
return false;
|
|
4472
|
+
}
|
|
4473
|
+
function hasEventHandler(attrs) {
|
|
4474
|
+
for (const a of attrs) if (/^on/i.test(a.name)) return true;
|
|
4475
|
+
return false;
|
|
4476
|
+
}
|
|
4477
|
+
function span(start, end) {
|
|
4478
|
+
return { file: FILE_ID, start, end };
|
|
4479
|
+
}
|
|
4480
|
+
function attrsLocOf(loc) {
|
|
4481
|
+
if (!loc) return void 0;
|
|
4482
|
+
return loc.startTag?.attrs ?? loc.attrs;
|
|
4483
|
+
}
|
|
4484
|
+
function classValueSpan(loc, source) {
|
|
4485
|
+
const attrsLoc = attrsLocOf(loc);
|
|
4486
|
+
const cl = attrsLoc?.["class"];
|
|
4487
|
+
if (!cl) return null;
|
|
4488
|
+
const text = source.slice(cl.startOffset, cl.endOffset);
|
|
4489
|
+
const eq = text.indexOf("=");
|
|
4490
|
+
if (eq === -1) return null;
|
|
4491
|
+
let i = eq + 1;
|
|
4492
|
+
while (i < text.length && /\s/.test(text[i])) i += 1;
|
|
4493
|
+
if (i >= text.length) return null;
|
|
4494
|
+
return span(cl.startOffset + i, cl.endOffset);
|
|
4495
|
+
}
|
|
4496
|
+
|
|
4497
|
+
// ../frontend-html/src/parse.ts
|
|
4498
|
+
var cachedParse5 = null;
|
|
4499
|
+
function loadParse5() {
|
|
4500
|
+
if (cachedParse5) return cachedParse5;
|
|
4501
|
+
const req = (0, import_node_module3.createRequire)(importMetaUrl);
|
|
4502
|
+
cachedParse5 = req("parse5");
|
|
4503
|
+
return cachedParse5;
|
|
4504
|
+
}
|
|
4505
|
+
function doParse(code, ctx) {
|
|
4506
|
+
const diagnostics = [];
|
|
4507
|
+
const doc = createDocument("html");
|
|
4508
|
+
const backref = doc.backref;
|
|
4509
|
+
const parse5 = loadParse5();
|
|
4510
|
+
const document2 = parse5.parse(code, { sourceCodeLocationInfo: true });
|
|
4511
|
+
const eol = code.includes("\r\n") ? "\r\n" : "\n";
|
|
4512
|
+
const sourceFile = {
|
|
4513
|
+
id: FILE_ID,
|
|
4514
|
+
path: ctx.id,
|
|
4515
|
+
text: code,
|
|
4516
|
+
frontend: "html",
|
|
4517
|
+
eol,
|
|
4518
|
+
indentUnit: " ",
|
|
4519
|
+
native: document2
|
|
4520
|
+
};
|
|
4521
|
+
doc.sources.set(FILE_ID, sourceFile);
|
|
4522
|
+
const resolveComputed = (tokens, tag, nodeId, meta) => {
|
|
4523
|
+
if (tokens.length === 0) return emptyStyleMap();
|
|
4524
|
+
const res = ctx.resolver.resolve({ classes: tokens, element: { tagName: tag, namespace: "html" } });
|
|
4525
|
+
if (res.unknown.length > 0) meta.hasUnresolvedClasses = true;
|
|
4526
|
+
for (const w of res.warnings) {
|
|
4527
|
+
diagnostics.push({
|
|
4528
|
+
code: "DF_STYLE_CONFLICT_UNRESOLVED",
|
|
4529
|
+
severity: w.severity,
|
|
4530
|
+
message: w.message,
|
|
4531
|
+
nodeId
|
|
4532
|
+
});
|
|
4533
|
+
}
|
|
4534
|
+
return ctx.normalizer.normalizeStyleMap(res.styles);
|
|
4535
|
+
};
|
|
4536
|
+
const splitTokens = (raw) => raw.split(/\s+/).filter((t) => t.length > 0);
|
|
4537
|
+
const appendChild = (node, parentId, out) => {
|
|
4538
|
+
const name = node.nodeName;
|
|
4539
|
+
if (name === "#text") {
|
|
4540
|
+
const value = node.value ?? "";
|
|
4541
|
+
const id = doc.alloc.next();
|
|
4542
|
+
const loc = node.sourceCodeLocation ?? null;
|
|
4543
|
+
doc.nodes.set(
|
|
4544
|
+
id,
|
|
4545
|
+
createText(id, value, {
|
|
4546
|
+
parent: parentId,
|
|
4547
|
+
span: loc ? span(loc.startOffset, loc.endOffset) : null,
|
|
4548
|
+
collapsible: /^\s*$/.test(value)
|
|
4549
|
+
})
|
|
4550
|
+
);
|
|
4551
|
+
out.push(id);
|
|
4552
|
+
return;
|
|
4553
|
+
}
|
|
4554
|
+
if (name === "#comment") {
|
|
4555
|
+
const id = doc.alloc.next();
|
|
4556
|
+
const loc = node.sourceCodeLocation ?? null;
|
|
4557
|
+
doc.nodes.set(
|
|
4558
|
+
id,
|
|
4559
|
+
createComment(id, node.data ?? "", {
|
|
4560
|
+
parent: parentId,
|
|
4561
|
+
span: loc ? span(loc.startOffset, loc.endOffset) : null
|
|
4562
|
+
})
|
|
4563
|
+
);
|
|
4564
|
+
out.push(id);
|
|
4565
|
+
return;
|
|
4566
|
+
}
|
|
4567
|
+
if (name === "#documentType") return;
|
|
4568
|
+
if (name.startsWith("#")) {
|
|
4569
|
+
for (const c of node.childNodes ?? []) appendChild(c, parentId, out);
|
|
4570
|
+
return;
|
|
4571
|
+
}
|
|
4572
|
+
out.push(buildElement(node, parentId));
|
|
4573
|
+
};
|
|
4574
|
+
const buildElement = (node, parentId) => {
|
|
4575
|
+
const id = doc.alloc.next();
|
|
4576
|
+
const tag = (node.tagName ?? node.nodeName).toLowerCase();
|
|
4577
|
+
const loc = node.sourceCodeLocation ?? null;
|
|
4578
|
+
const attrsArr = node.attrs ?? [];
|
|
4579
|
+
const opaqueSubtree = isOpaqueSubtreeTag(tag);
|
|
4580
|
+
const synthetic = loc == null;
|
|
4581
|
+
const opaque2 = opaqueSubtree || synthetic || elementIsOpaque(attrsArr);
|
|
4582
|
+
const meta = defaultMeta();
|
|
4583
|
+
meta.hasEventHandlers = hasEventHandler(attrsArr);
|
|
4584
|
+
meta.safetyFloor = opaque2 ? 0 : 3;
|
|
4585
|
+
let classes = emptyClassList();
|
|
4586
|
+
let classTokens = [];
|
|
4587
|
+
const entries = /* @__PURE__ */ new Map();
|
|
4588
|
+
const order = [];
|
|
4589
|
+
for (const a of attrsArr) {
|
|
4590
|
+
if (a.name.toLowerCase() === "class") {
|
|
4591
|
+
classTokens = splitTokens(a.value);
|
|
4592
|
+
const valueSpan = classValueSpan(loc, code);
|
|
4593
|
+
const clAttr = attrsLocOf(loc)?.["class"];
|
|
4594
|
+
const seg = {
|
|
4595
|
+
kind: "static",
|
|
4596
|
+
span: valueSpan ?? void 0,
|
|
4597
|
+
tokens: classTokens.map((value) => ({ value }))
|
|
4598
|
+
};
|
|
4599
|
+
classes = {
|
|
4600
|
+
form: "string-literal",
|
|
4601
|
+
segments: [seg],
|
|
4602
|
+
valueSpan,
|
|
4603
|
+
attrSpan: clAttr ? span(clAttr.startOffset, clAttr.endOffset) : void 0,
|
|
4604
|
+
hasDynamic: false,
|
|
4605
|
+
opaque: false,
|
|
4606
|
+
rewritable: valueSpan != null
|
|
4607
|
+
};
|
|
4608
|
+
continue;
|
|
4609
|
+
}
|
|
4610
|
+
const v = a.value;
|
|
4611
|
+
entries.set(a.name, { kind: "static", value: v === "" ? true : v });
|
|
4612
|
+
order.push(a.name);
|
|
4613
|
+
}
|
|
4614
|
+
const attrs = { entries, spreads: [], order };
|
|
4615
|
+
const computed2 = resolveComputed(classTokens, tag, id, meta);
|
|
4616
|
+
const children = [];
|
|
4617
|
+
if (!opaqueSubtree) {
|
|
4618
|
+
for (const c of node.childNodes ?? []) appendChild(c, id, children);
|
|
4619
|
+
}
|
|
4620
|
+
const el = createElement(id, {
|
|
4621
|
+
tag,
|
|
4622
|
+
namespace: "html",
|
|
4623
|
+
isComponent: false,
|
|
4624
|
+
selfClosing: loc ? loc.endTag == null : false,
|
|
4625
|
+
classes,
|
|
4626
|
+
computed: computed2,
|
|
4627
|
+
attrs,
|
|
4628
|
+
children,
|
|
4629
|
+
parent: parentId,
|
|
4630
|
+
span: loc ? span(loc.startOffset, loc.endOffset) : null,
|
|
4631
|
+
meta
|
|
4632
|
+
});
|
|
4633
|
+
doc.nodes.set(id, el);
|
|
4634
|
+
if (loc) {
|
|
4635
|
+
backref.set(id, {
|
|
4636
|
+
nodeId: id,
|
|
4637
|
+
span: span(loc.startOffset, loc.endOffset),
|
|
4638
|
+
openTagSpan: loc.startTag ? span(loc.startTag.startOffset, loc.startTag.endOffset) : null,
|
|
4639
|
+
closeTagSpan: loc.endTag ? span(loc.endTag.startOffset, loc.endTag.endOffset) : null,
|
|
4640
|
+
innerSpan: null,
|
|
4641
|
+
selfClosing: loc.endTag == null
|
|
4642
|
+
});
|
|
4643
|
+
}
|
|
4644
|
+
return id;
|
|
4645
|
+
};
|
|
4646
|
+
const rootFrag = doc.nodes.get(doc.root);
|
|
4647
|
+
appendChild(document2, doc.root, rootFrag.children);
|
|
4648
|
+
return { doc, diagnostics };
|
|
4649
|
+
}
|
|
4650
|
+
|
|
4651
|
+
// ../frontend-html/src/index.ts
|
|
4652
|
+
var htmlFrontend = {
|
|
4653
|
+
name: "html",
|
|
4654
|
+
langs: HTML_LANGS,
|
|
4655
|
+
canParse(id, code) {
|
|
4656
|
+
return looksLikeHtml(id, code);
|
|
4657
|
+
},
|
|
4658
|
+
parse(code, ctx) {
|
|
4659
|
+
return doParse(code, ctx);
|
|
4660
|
+
}
|
|
4661
|
+
};
|
|
4662
|
+
function createHtmlFrontend() {
|
|
4663
|
+
return htmlFrontend;
|
|
4664
|
+
}
|
|
4665
|
+
var htmlBackend = {
|
|
4666
|
+
name: "html",
|
|
4667
|
+
langs: HTML_LANGS,
|
|
4668
|
+
print(doc, _plan, _ctx) {
|
|
4669
|
+
return { code: doPrint(doc), map: null, edits: [], diagnostics: [] };
|
|
4670
|
+
}
|
|
4671
|
+
};
|
|
4672
|
+
function createHtmlBackend() {
|
|
4673
|
+
return htmlBackend;
|
|
4674
|
+
}
|
|
4675
|
+
|
|
4812
4676
|
// ../frontend-jsx/src/frontend-ast.ts
|
|
4813
4677
|
var import_traverse = __toESM(require("@babel/traverse"), 1);
|
|
4814
4678
|
var traverse = typeof import_traverse.default === "function" ? import_traverse.default : import_traverse.default.default;
|
|
4815
4679
|
var JSX_LANGS = ["jsx", "tsx"];
|
|
4816
|
-
var
|
|
4680
|
+
var FILE_ID2 = 1;
|
|
4817
4681
|
function jsxName(node) {
|
|
4818
4682
|
switch (node.type) {
|
|
4819
4683
|
case "JSXIdentifier":
|
|
@@ -4937,7 +4801,7 @@ function looksLikeJsx(id, code) {
|
|
|
4937
4801
|
|
|
4938
4802
|
// ../frontend-jsx/src/frontend-parse.ts
|
|
4939
4803
|
var import_parser = require("@babel/parser");
|
|
4940
|
-
function
|
|
4804
|
+
function doParse2(code, ctx) {
|
|
4941
4805
|
const diagnostics = [];
|
|
4942
4806
|
const doc = createDocument("jsx");
|
|
4943
4807
|
const backref = doc.backref;
|
|
@@ -4947,7 +4811,7 @@ function doParse(code, ctx) {
|
|
|
4947
4811
|
});
|
|
4948
4812
|
const eol = code.includes("\r\n") ? "\r\n" : "\n";
|
|
4949
4813
|
const sourceFile = {
|
|
4950
|
-
id:
|
|
4814
|
+
id: FILE_ID2,
|
|
4951
4815
|
path: ctx.id,
|
|
4952
4816
|
text: code,
|
|
4953
4817
|
frontend: "jsx",
|
|
@@ -4955,23 +4819,23 @@ function doParse(code, ctx) {
|
|
|
4955
4819
|
indentUnit: " ",
|
|
4956
4820
|
native: ast
|
|
4957
4821
|
};
|
|
4958
|
-
doc.sources.set(
|
|
4822
|
+
doc.sources.set(FILE_ID2, sourceFile);
|
|
4959
4823
|
const spanOf = (node) => {
|
|
4960
4824
|
if (node.start == null || node.end == null) return null;
|
|
4961
|
-
const
|
|
4962
|
-
file:
|
|
4825
|
+
const span2 = {
|
|
4826
|
+
file: FILE_ID2,
|
|
4963
4827
|
start: node.start,
|
|
4964
4828
|
end: node.end,
|
|
4965
4829
|
startLoc: node.loc ? { line: node.loc.start.line, column: node.loc.start.column } : void 0,
|
|
4966
4830
|
endLoc: node.loc ? { line: node.loc.end.line, column: node.loc.end.column } : void 0
|
|
4967
4831
|
};
|
|
4968
|
-
return
|
|
4832
|
+
return span2;
|
|
4969
4833
|
};
|
|
4970
4834
|
const sliceOf = (node) => node.start == null || node.end == null ? "" : code.slice(node.start, node.end);
|
|
4971
4835
|
const internExpr = (node, spread) => {
|
|
4972
4836
|
const payload = { text: sliceOf(node), spread };
|
|
4973
4837
|
return doc.exprs.intern({
|
|
4974
|
-
span: spanOf(node) ?? { file:
|
|
4838
|
+
span: spanOf(node) ?? { file: FILE_ID2, start: 0, end: 0 },
|
|
4975
4839
|
kind: exprKind(node),
|
|
4976
4840
|
payload
|
|
4977
4841
|
});
|
|
@@ -5017,7 +4881,7 @@ function doParse(code, ctx) {
|
|
|
5017
4881
|
}
|
|
5018
4882
|
return emptyClassList();
|
|
5019
4883
|
};
|
|
5020
|
-
const
|
|
4884
|
+
const staticTokensOf4 = (classes) => {
|
|
5021
4885
|
const out = [];
|
|
5022
4886
|
for (const seg of classes.segments) {
|
|
5023
4887
|
if (seg.kind === "static") for (const t of seg.tokens) out.push(t.value);
|
|
@@ -5095,7 +4959,7 @@ function doParse(code, ctx) {
|
|
|
5095
4959
|
doc.nodes.set(id, createFragment(id, { children, parent: parentId, span: spanOf(node) }));
|
|
5096
4960
|
backref.set(id, {
|
|
5097
4961
|
nodeId: id,
|
|
5098
|
-
span: spanOf(node) ?? { file:
|
|
4962
|
+
span: spanOf(node) ?? { file: FILE_ID2, start: 0, end: 0 },
|
|
5099
4963
|
openTagSpan: spanOf(node.openingFragment),
|
|
5100
4964
|
closeTagSpan: spanOf(node.closingFragment),
|
|
5101
4965
|
innerSpan: null,
|
|
@@ -5144,13 +5008,14 @@ function doParse(code, ctx) {
|
|
|
5144
5008
|
}
|
|
5145
5009
|
let computed2 = emptyStyleMap();
|
|
5146
5010
|
if (!classes.hasDynamic) {
|
|
5147
|
-
const tokens =
|
|
5011
|
+
const tokens = staticTokensOf4(classes);
|
|
5148
5012
|
if (tokens.length > 0) {
|
|
5149
5013
|
const res = ctx.resolver.resolve({
|
|
5150
5014
|
classes: tokens,
|
|
5151
5015
|
element: { tagName: tag, namespace: component ? void 0 : "html" }
|
|
5152
5016
|
});
|
|
5153
5017
|
computed2 = ctx.normalizer.normalizeStyleMap(res.styles);
|
|
5018
|
+
if (res.unknown.length > 0) meta.hasUnresolvedClasses = true;
|
|
5154
5019
|
for (const w of res.warnings) {
|
|
5155
5020
|
diagnostics.push({
|
|
5156
5021
|
code: "DF_STYLE_CONFLICT_UNRESOLVED",
|
|
@@ -5177,13 +5042,13 @@ function doParse(code, ctx) {
|
|
|
5177
5042
|
});
|
|
5178
5043
|
doc.nodes.set(id, el);
|
|
5179
5044
|
const inner = children.length > 0 ? spanOf(node.children[0]) && spanOf(node.children.at(-1)) ? {
|
|
5180
|
-
file:
|
|
5045
|
+
file: FILE_ID2,
|
|
5181
5046
|
start: spanOf(node.children[0]).start,
|
|
5182
5047
|
end: spanOf(node.children.at(-1)).end
|
|
5183
5048
|
} : null : null;
|
|
5184
5049
|
backref.set(id, {
|
|
5185
5050
|
nodeId: id,
|
|
5186
|
-
span: spanOf(node) ?? { file:
|
|
5051
|
+
span: spanOf(node) ?? { file: FILE_ID2, start: 0, end: 0 },
|
|
5187
5052
|
openTagSpan: spanOf(opening),
|
|
5188
5053
|
closeTagSpan: node.closingElement ? spanOf(node.closingElement) : null,
|
|
5189
5054
|
innerSpan: inner,
|
|
@@ -5193,13 +5058,13 @@ function doParse(code, ctx) {
|
|
|
5193
5058
|
};
|
|
5194
5059
|
const roots = [];
|
|
5195
5060
|
traverse(ast, {
|
|
5196
|
-
JSXElement(
|
|
5197
|
-
roots.push(
|
|
5198
|
-
|
|
5061
|
+
JSXElement(path5) {
|
|
5062
|
+
roots.push(path5.node);
|
|
5063
|
+
path5.skip();
|
|
5199
5064
|
},
|
|
5200
|
-
JSXFragment(
|
|
5201
|
-
roots.push(
|
|
5202
|
-
|
|
5065
|
+
JSXFragment(path5) {
|
|
5066
|
+
roots.push(path5.node);
|
|
5067
|
+
path5.skip();
|
|
5203
5068
|
}
|
|
5204
5069
|
});
|
|
5205
5070
|
const rootFrag = doc.nodes.get(doc.root);
|
|
@@ -5218,7 +5083,7 @@ var jsxFrontend = {
|
|
|
5218
5083
|
return looksLikeJsx(id, code);
|
|
5219
5084
|
},
|
|
5220
5085
|
parse(code, ctx) {
|
|
5221
|
-
return
|
|
5086
|
+
return doParse2(code, ctx);
|
|
5222
5087
|
}
|
|
5223
5088
|
};
|
|
5224
5089
|
function createJsxFrontend() {
|
|
@@ -5226,7 +5091,7 @@ function createJsxFrontend() {
|
|
|
5226
5091
|
}
|
|
5227
5092
|
|
|
5228
5093
|
// ../frontend-jsx/src/backend.ts
|
|
5229
|
-
var
|
|
5094
|
+
var import_magic_string2 = __toESM(require("magic-string"), 1);
|
|
5230
5095
|
var JSX_LANGS2 = ["jsx", "tsx"];
|
|
5231
5096
|
function exprText(doc, ref) {
|
|
5232
5097
|
const rec = doc.exprs.get(ref);
|
|
@@ -5240,20 +5105,20 @@ function exprText(doc, ref) {
|
|
|
5240
5105
|
}
|
|
5241
5106
|
return { text: "", spread: false };
|
|
5242
5107
|
}
|
|
5243
|
-
function
|
|
5108
|
+
function staticTokensOf3(classes) {
|
|
5244
5109
|
const out = [];
|
|
5245
5110
|
for (const seg of classes.segments) {
|
|
5246
5111
|
if (seg.kind === "static") for (const t of seg.tokens) out.push(t.value);
|
|
5247
5112
|
}
|
|
5248
5113
|
return out;
|
|
5249
5114
|
}
|
|
5250
|
-
function
|
|
5115
|
+
function primarySource2(doc) {
|
|
5251
5116
|
for (const sf of doc.sources.values()) {
|
|
5252
5117
|
if (typeof sf.text === "string" && sf.text.length > 0) return sf;
|
|
5253
5118
|
}
|
|
5254
5119
|
return null;
|
|
5255
5120
|
}
|
|
5256
|
-
function
|
|
5121
|
+
function collectKept2(doc) {
|
|
5257
5122
|
const out = [];
|
|
5258
5123
|
const seen = /* @__PURE__ */ new Set();
|
|
5259
5124
|
const visit = (id) => {
|
|
@@ -5267,15 +5132,15 @@ function collectKept(doc) {
|
|
|
5267
5132
|
visit(doc.root);
|
|
5268
5133
|
return out;
|
|
5269
5134
|
}
|
|
5270
|
-
function
|
|
5135
|
+
function strictlyContains2(a, b) {
|
|
5271
5136
|
if (a.file !== b.file) return false;
|
|
5272
5137
|
if (a.start <= b.start && b.end <= a.end) return !(a.start === b.start && a.end === b.end);
|
|
5273
5138
|
return false;
|
|
5274
5139
|
}
|
|
5275
|
-
function
|
|
5140
|
+
function editClasses2(ms, doc, sf, el) {
|
|
5276
5141
|
const classes = el.classes;
|
|
5277
5142
|
if (classes.hasDynamic || classes.opaque) return false;
|
|
5278
|
-
const tokens =
|
|
5143
|
+
const tokens = staticTokensOf3(classes);
|
|
5279
5144
|
const valueSpan = classes.valueSpan;
|
|
5280
5145
|
if (valueSpan && valueSpan.file === sf.id) {
|
|
5281
5146
|
const current = sf.text.slice(valueSpan.start, valueSpan.end);
|
|
@@ -5339,10 +5204,10 @@ function transferKeyOnUnwrap(ms, doc, sf, region, kept) {
|
|
|
5339
5204
|
const inside = [];
|
|
5340
5205
|
for (const n of kept) {
|
|
5341
5206
|
if (n.kind !== "element" || !n.span || n.span.file !== sf.id) continue;
|
|
5342
|
-
if (
|
|
5207
|
+
if (strictlyContains2(region.span, n.span)) inside.push(n);
|
|
5343
5208
|
}
|
|
5344
5209
|
const maximal = inside.filter(
|
|
5345
|
-
(n) => !inside.some((o) => o !== n && o.span && n.span &&
|
|
5210
|
+
(n) => !inside.some((o) => o !== n && o.span && n.span && strictlyContains2(o.span, n.span))
|
|
5346
5211
|
);
|
|
5347
5212
|
if (maximal.length !== 1) return;
|
|
5348
5213
|
const child = maximal[0];
|
|
@@ -5351,25 +5216,25 @@ function transferKeyOnUnwrap(ms, doc, sf, region, kept) {
|
|
|
5351
5216
|
if (extractKeyAttr(sf.text.slice(childOpen.start, childOpen.end))) return;
|
|
5352
5217
|
ms.appendLeft(childOpen.start + 1 + child.tag.length, ` ${keyAttr}`);
|
|
5353
5218
|
}
|
|
5354
|
-
function
|
|
5355
|
-
const sf =
|
|
5219
|
+
function surgicalPrint2(doc) {
|
|
5220
|
+
const sf = primarySource2(doc);
|
|
5356
5221
|
if (!sf) return null;
|
|
5357
|
-
const ms = new
|
|
5358
|
-
const kept =
|
|
5222
|
+
const ms = new import_magic_string2.default(sf.text);
|
|
5223
|
+
const kept = collectKept2(doc);
|
|
5359
5224
|
const keptSpans = [];
|
|
5360
5225
|
for (const n of kept) if (n.span && n.span.file === sf.id) keptSpans.push(n.span);
|
|
5361
5226
|
const removed = [];
|
|
5362
|
-
for (const id of
|
|
5227
|
+
for (const id of backrefIds2(doc)) {
|
|
5363
5228
|
if (doc.nodes.has(id)) continue;
|
|
5364
5229
|
const back = doc.backref.get(id);
|
|
5365
5230
|
if (!back || back.span.file !== sf.id) continue;
|
|
5366
|
-
const unwrapped = keptSpans.some((k) =>
|
|
5231
|
+
const unwrapped = keptSpans.some((k) => strictlyContains2(back.span, k));
|
|
5367
5232
|
removed.push({ backref: back, unwrapped });
|
|
5368
5233
|
}
|
|
5369
5234
|
const fullRemovals = removed.filter((r) => !r.unwrapped).map((r) => r.backref.span);
|
|
5370
5235
|
for (const r of removed) {
|
|
5371
|
-
const
|
|
5372
|
-
const coveredByFull = fullRemovals.some((f) => f !==
|
|
5236
|
+
const span2 = r.backref.span;
|
|
5237
|
+
const coveredByFull = fullRemovals.some((f) => f !== span2 && strictlyContains2(f, span2));
|
|
5373
5238
|
if (coveredByFull) continue;
|
|
5374
5239
|
if (r.unwrapped) {
|
|
5375
5240
|
transferKeyOnUnwrap(ms, doc, sf, r.backref, kept);
|
|
@@ -5380,15 +5245,15 @@ function surgicalPrint(doc) {
|
|
|
5380
5245
|
ms.remove(close.start, close.end);
|
|
5381
5246
|
}
|
|
5382
5247
|
} else {
|
|
5383
|
-
ms.remove(
|
|
5248
|
+
ms.remove(span2.start, span2.end);
|
|
5384
5249
|
}
|
|
5385
5250
|
}
|
|
5386
5251
|
for (const n of kept) {
|
|
5387
|
-
if (n.kind === "element")
|
|
5252
|
+
if (n.kind === "element") editClasses2(ms, doc, sf, n);
|
|
5388
5253
|
}
|
|
5389
5254
|
return ms.toString();
|
|
5390
5255
|
}
|
|
5391
|
-
function
|
|
5256
|
+
function backrefIds2(doc) {
|
|
5392
5257
|
const out = [];
|
|
5393
5258
|
const max = doc.alloc.peek;
|
|
5394
5259
|
for (let i = 1; i < max; i += 1) {
|
|
@@ -5403,7 +5268,7 @@ function classText(doc, classes) {
|
|
|
5403
5268
|
if (dynamic && dynamic.kind === "dynamic") {
|
|
5404
5269
|
return `className={${exprText(doc, dynamic.expr).text}}`;
|
|
5405
5270
|
}
|
|
5406
|
-
const tokens =
|
|
5271
|
+
const tokens = staticTokensOf3(classes);
|
|
5407
5272
|
return `className="${tokens.join(" ")}"`;
|
|
5408
5273
|
}
|
|
5409
5274
|
function attrText(doc, name, value) {
|
|
@@ -5456,15 +5321,15 @@ function rePrint(doc) {
|
|
|
5456
5321
|
if (!root || root.kind !== "fragment") return printNode(doc, doc.root);
|
|
5457
5322
|
return root.children.map((c) => printNode(doc, c)).join("");
|
|
5458
5323
|
}
|
|
5459
|
-
function
|
|
5460
|
-
const surgical =
|
|
5324
|
+
function doPrint2(doc) {
|
|
5325
|
+
const surgical = surgicalPrint2(doc);
|
|
5461
5326
|
return surgical ?? rePrint(doc);
|
|
5462
5327
|
}
|
|
5463
5328
|
var jsxBackend = {
|
|
5464
5329
|
name: "babel-jsx",
|
|
5465
5330
|
langs: JSX_LANGS2,
|
|
5466
5331
|
print(doc, _plan, _ctx) {
|
|
5467
|
-
const code =
|
|
5332
|
+
const code = doPrint2(doc);
|
|
5468
5333
|
return { code, map: null, edits: [], diagnostics: [] };
|
|
5469
5334
|
}
|
|
5470
5335
|
};
|
|
@@ -5473,12 +5338,38 @@ function createJsxBackend() {
|
|
|
5473
5338
|
}
|
|
5474
5339
|
|
|
5475
5340
|
// src/pipeline-run.ts
|
|
5341
|
+
function bytes(s) {
|
|
5342
|
+
return Buffer.byteLength(s, "utf8");
|
|
5343
|
+
}
|
|
5344
|
+
function countClassTokens(code) {
|
|
5345
|
+
let total = 0;
|
|
5346
|
+
const re = /\b(?:className|class)\s*=\s*"([^"]*)"/g;
|
|
5347
|
+
let m;
|
|
5348
|
+
while ((m = re.exec(code)) !== null) {
|
|
5349
|
+
total += m[1].split(/\s+/).filter((t) => t.length > 0).length;
|
|
5350
|
+
}
|
|
5351
|
+
return total;
|
|
5352
|
+
}
|
|
5353
|
+
function computeStats(code, out, nodesIn, nodesOut) {
|
|
5354
|
+
const classesBefore = countClassTokens(code);
|
|
5355
|
+
const classesAfter = countClassTokens(out);
|
|
5356
|
+
return {
|
|
5357
|
+
nodesRemoved: Math.max(0, nodesIn - nodesOut),
|
|
5358
|
+
classesSaved: Math.max(0, classesBefore - classesAfter),
|
|
5359
|
+
bytesSaved: bytes(code) - bytes(out)
|
|
5360
|
+
};
|
|
5361
|
+
}
|
|
5476
5362
|
function jsxKindOf(id) {
|
|
5477
5363
|
const clean = id.split("?", 1)[0] ?? id;
|
|
5478
5364
|
if (clean.endsWith(".tsx")) return "tsx";
|
|
5479
5365
|
if (clean.endsWith(".jsx")) return "jsx";
|
|
5480
5366
|
return null;
|
|
5481
5367
|
}
|
|
5368
|
+
function htmlKindOf(id) {
|
|
5369
|
+
const clean = (id.split("?", 1)[0] ?? id).toLowerCase();
|
|
5370
|
+
if (clean.endsWith(".html") || clean.endsWith(".htm")) return "html";
|
|
5371
|
+
return null;
|
|
5372
|
+
}
|
|
5482
5373
|
function eolOf2(doc) {
|
|
5483
5374
|
for (const src of doc.sources.values()) return src.eol;
|
|
5484
5375
|
return "\n";
|
|
@@ -5543,12 +5434,122 @@ function finishPipeline(optimized, id, resolver) {
|
|
|
5543
5434
|
}
|
|
5544
5435
|
function runJsxPipeline(code, id, kind, resolver, patterns, safety) {
|
|
5545
5436
|
const { doc, ctx, passes } = preparePipeline(code, id, kind, resolver, patterns, safety, "provably-safe");
|
|
5437
|
+
const nodesIn = doc.nodes.size;
|
|
5438
|
+
const { doc: optimized } = runPasses(doc, passes, ctx);
|
|
5439
|
+
const out = finishPipeline(optimized, id, resolver);
|
|
5440
|
+
return { code: out, stats: computeStats(code, out, nodesIn, optimized.nodes.size) };
|
|
5441
|
+
}
|
|
5442
|
+
function prepareHtml(code, id, resolver, patterns, safety, gate) {
|
|
5443
|
+
const parsed = createHtmlFrontend().parse(code, {
|
|
5444
|
+
id,
|
|
5445
|
+
kind: "html",
|
|
5446
|
+
resolver,
|
|
5447
|
+
normalizer,
|
|
5448
|
+
config: {},
|
|
5449
|
+
onDiagnostic: () => {
|
|
5450
|
+
}
|
|
5451
|
+
});
|
|
5452
|
+
const doc = parsed.doc;
|
|
5453
|
+
const ctx = {
|
|
5454
|
+
doc,
|
|
5455
|
+
safetyCeiling: safety,
|
|
5456
|
+
normalizer,
|
|
5457
|
+
selectors: buildSelectorIndex(doc, resolver),
|
|
5458
|
+
resolver,
|
|
5459
|
+
gate
|
|
5460
|
+
};
|
|
5461
|
+
return { doc, ctx, passes: buildPasses(patterns) };
|
|
5462
|
+
}
|
|
5463
|
+
function finishHtmlPipeline(optimized, id, resolver) {
|
|
5464
|
+
syncClassesFromComputed(optimized, resolver, normalizer);
|
|
5465
|
+
const printed = createHtmlBackend().print(
|
|
5466
|
+
optimized,
|
|
5467
|
+
{ moduleId: id, ops: [], provenance: /* @__PURE__ */ new Map() },
|
|
5468
|
+
{
|
|
5469
|
+
normalizer,
|
|
5470
|
+
resolver,
|
|
5471
|
+
sink: createSyntheticSink(),
|
|
5472
|
+
eol: eolOf2(optimized),
|
|
5473
|
+
onDiagnostic: () => {
|
|
5474
|
+
}
|
|
5475
|
+
}
|
|
5476
|
+
);
|
|
5477
|
+
return printed.code;
|
|
5478
|
+
}
|
|
5479
|
+
function runHtmlPipeline(code, id, resolver, patterns, safety) {
|
|
5480
|
+
const { doc, ctx, passes } = prepareHtml(code, id, resolver, patterns, safety, "provably-safe");
|
|
5481
|
+
const nodesIn = doc.nodes.size;
|
|
5546
5482
|
const { doc: optimized } = runPasses(doc, passes, ctx);
|
|
5547
|
-
|
|
5483
|
+
const out = finishHtmlPipeline(optimized, id, resolver);
|
|
5484
|
+
return { code: out, stats: computeStats(code, out, nodesIn, optimized.nodes.size) };
|
|
5485
|
+
}
|
|
5486
|
+
|
|
5487
|
+
// src/summary.ts
|
|
5488
|
+
function zeroStats() {
|
|
5489
|
+
return { nodesRemoved: 0, classesSaved: 0, bytesSaved: 0 };
|
|
5490
|
+
}
|
|
5491
|
+
function emptyTotals() {
|
|
5492
|
+
return { files: 0, nodesRemoved: 0, classesCompressed: 0, bytesSaved: 0 };
|
|
5493
|
+
}
|
|
5494
|
+
function resetTotals(t) {
|
|
5495
|
+
t.files = 0;
|
|
5496
|
+
t.nodesRemoved = 0;
|
|
5497
|
+
t.classesCompressed = 0;
|
|
5498
|
+
t.bytesSaved = 0;
|
|
5499
|
+
}
|
|
5500
|
+
function addStats(t, s, changed) {
|
|
5501
|
+
if (!changed) return;
|
|
5502
|
+
t.files += 1;
|
|
5503
|
+
t.nodesRemoved += s.nodesRemoved;
|
|
5504
|
+
t.classesCompressed += s.classesSaved;
|
|
5505
|
+
t.bytesSaved += s.bytesSaved;
|
|
5506
|
+
}
|
|
5507
|
+
var BYTE_UNITS = ["KB", "MB", "GB", "TB"];
|
|
5508
|
+
function formatBytes(n) {
|
|
5509
|
+
const abs = Math.abs(n);
|
|
5510
|
+
if (abs < 1024) return `${n} B`;
|
|
5511
|
+
let value = n / 1024;
|
|
5512
|
+
let unit = 0;
|
|
5513
|
+
while (Math.abs(value) >= 1024 && unit < BYTE_UNITS.length - 1) {
|
|
5514
|
+
value /= 1024;
|
|
5515
|
+
unit += 1;
|
|
5516
|
+
}
|
|
5517
|
+
return `${value.toFixed(1)} ${BYTE_UNITS[unit]}`;
|
|
5518
|
+
}
|
|
5519
|
+
function formatCount(n) {
|
|
5520
|
+
return Math.trunc(n).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
5521
|
+
}
|
|
5522
|
+
var LABEL_WIDTH = 20;
|
|
5523
|
+
var RULE = ` ${"\u2500".repeat(32)}`;
|
|
5524
|
+
function row(label, value) {
|
|
5525
|
+
return ` ${label.padEnd(LABEL_WIDTH)}${value}`;
|
|
5526
|
+
}
|
|
5527
|
+
function renderSummary(totals) {
|
|
5528
|
+
return [
|
|
5529
|
+
"",
|
|
5530
|
+
" \u25B2 domflax",
|
|
5531
|
+
RULE,
|
|
5532
|
+
row("files optimized", formatCount(totals.files)),
|
|
5533
|
+
row("DOM nodes removed", formatCount(totals.nodesRemoved)),
|
|
5534
|
+
row("classes compressed", formatCount(totals.classesCompressed)),
|
|
5535
|
+
row("size saved", formatBytes(totals.bytesSaved)),
|
|
5536
|
+
RULE,
|
|
5537
|
+
""
|
|
5538
|
+
].join("\n");
|
|
5539
|
+
}
|
|
5540
|
+
var TOTALS_KEY = /* @__PURE__ */ Symbol.for("domflax.buildTotals");
|
|
5541
|
+
var PRINTED_KEY = /* @__PURE__ */ Symbol.for("domflax.summaryPrinted");
|
|
5542
|
+
function printCompilationSummary(compilation) {
|
|
5543
|
+
if (compilation === null || typeof compilation !== "object") return;
|
|
5544
|
+
const bag = compilation;
|
|
5545
|
+
if (bag[PRINTED_KEY]) return;
|
|
5546
|
+
bag[PRINTED_KEY] = true;
|
|
5547
|
+
const totals = bag[TOTALS_KEY];
|
|
5548
|
+
if (totals && totals.files > 0) process.stdout.write(renderSummary(totals));
|
|
5548
5549
|
}
|
|
5549
5550
|
|
|
5550
5551
|
// src/index.ts
|
|
5551
|
-
var DEFAULT_INCLUDE = [".jsx", ".tsx", ".html"];
|
|
5552
|
+
var DEFAULT_INCLUDE = [".jsx", ".tsx", ".html", ".htm"];
|
|
5552
5553
|
function resolveOptions(options) {
|
|
5553
5554
|
return {
|
|
5554
5555
|
provider: options.provider ?? "auto",
|
|
@@ -5582,23 +5583,48 @@ function createDomflax(options = {}) {
|
|
|
5582
5583
|
},
|
|
5583
5584
|
patterns,
|
|
5584
5585
|
transform(code, id) {
|
|
5585
|
-
if (!isSupported(id, resolved.include)) return { code, map: null };
|
|
5586
|
+
if (!isSupported(id, resolved.include)) return { code, map: null, stats: zeroStats() };
|
|
5586
5587
|
const kind = jsxKindOf(id);
|
|
5587
|
-
if (kind
|
|
5588
|
-
|
|
5589
|
-
|
|
5588
|
+
if (kind !== null) {
|
|
5589
|
+
const out = runJsxPipeline(code, id, kind, getResolver(), patterns, resolved.safety);
|
|
5590
|
+
return { code: out.code, map: null, stats: out.stats };
|
|
5591
|
+
}
|
|
5592
|
+
if (htmlKindOf(id) !== null) {
|
|
5593
|
+
const out = runHtmlPipeline(code, id, getResolver(), patterns, resolved.safety);
|
|
5594
|
+
return { code: out.code, map: null, stats: out.stats };
|
|
5595
|
+
}
|
|
5596
|
+
return { code, map: null, stats: zeroStats() };
|
|
5590
5597
|
}
|
|
5591
5598
|
};
|
|
5592
5599
|
}
|
|
5593
5600
|
function vite(options = {}) {
|
|
5594
5601
|
const engine = createDomflax(options);
|
|
5602
|
+
const totals = emptyTotals();
|
|
5603
|
+
let printed = false;
|
|
5604
|
+
const printSummary = () => {
|
|
5605
|
+
if (printed) return;
|
|
5606
|
+
printed = true;
|
|
5607
|
+
if (totals.files > 0) process.stdout.write(renderSummary(totals));
|
|
5608
|
+
};
|
|
5595
5609
|
return {
|
|
5596
5610
|
name: "domflax",
|
|
5597
5611
|
enforce: "pre",
|
|
5612
|
+
buildStart() {
|
|
5613
|
+
resetTotals(totals);
|
|
5614
|
+
printed = false;
|
|
5615
|
+
},
|
|
5598
5616
|
transform(code, id) {
|
|
5599
5617
|
if (!isSupported(id, engine.options.include)) return null;
|
|
5600
5618
|
const out = engine.transform(code, id);
|
|
5601
|
-
|
|
5619
|
+
const changed = out.code !== code;
|
|
5620
|
+
addStats(totals, out.stats, changed);
|
|
5621
|
+
return changed ? out : null;
|
|
5622
|
+
},
|
|
5623
|
+
buildEnd() {
|
|
5624
|
+
printSummary();
|
|
5625
|
+
},
|
|
5626
|
+
closeBundle() {
|
|
5627
|
+
printSummary();
|
|
5602
5628
|
}
|
|
5603
5629
|
};
|
|
5604
5630
|
}
|
|
@@ -5607,6 +5633,24 @@ function webpackLoaderPath() {
|
|
|
5607
5633
|
const here = (0, import_node_path.dirname)((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
5608
5634
|
return (0, import_node_path.join)(here, "webpack-loader.cjs");
|
|
5609
5635
|
}
|
|
5636
|
+
function tapWebpackSummary(compiler) {
|
|
5637
|
+
const done = compiler.hooks?.done;
|
|
5638
|
+
if (typeof done?.tap !== "function") return;
|
|
5639
|
+
done.tap("domflax", (stats) => {
|
|
5640
|
+
const compilation = stats?.compilation ?? stats;
|
|
5641
|
+
printCompilationSummary(compilation);
|
|
5642
|
+
});
|
|
5643
|
+
}
|
|
5644
|
+
function installWebpackSummary(compiler, host) {
|
|
5645
|
+
if (typeof compiler.hooks?.done?.tap === "function") {
|
|
5646
|
+
tapWebpackSummary(compiler);
|
|
5647
|
+
return;
|
|
5648
|
+
}
|
|
5649
|
+
const plugins = host.plugins ??= [];
|
|
5650
|
+
if (Array.isArray(plugins)) {
|
|
5651
|
+
plugins.push({ apply: (real) => tapWebpackSummary(real) });
|
|
5652
|
+
}
|
|
5653
|
+
}
|
|
5610
5654
|
function webpack(options = {}) {
|
|
5611
5655
|
createDomflax(options);
|
|
5612
5656
|
return {
|
|
@@ -5622,6 +5666,7 @@ function webpack(options = {}) {
|
|
|
5622
5666
|
use: [{ loader: webpackLoaderPath(), options }]
|
|
5623
5667
|
};
|
|
5624
5668
|
rules.push(rule);
|
|
5669
|
+
installWebpackSummary(compiler, host);
|
|
5625
5670
|
}
|
|
5626
5671
|
};
|
|
5627
5672
|
}
|
|
@@ -5632,11 +5677,10 @@ var src_default = domflax;
|
|
|
5632
5677
|
BASE_CONDITION,
|
|
5633
5678
|
BASE_CONDITION_KEY,
|
|
5634
5679
|
DEFAULT_FIXPOINT,
|
|
5680
|
+
DEFAULT_MAX_UNIVERSE,
|
|
5635
5681
|
PHASE_ORDER,
|
|
5636
5682
|
applyGroups,
|
|
5637
5683
|
applyOps,
|
|
5638
|
-
borderRadiusShorthand,
|
|
5639
|
-
borderShorthand,
|
|
5640
5684
|
buildMatchContext,
|
|
5641
5685
|
buildSelectorIndex,
|
|
5642
5686
|
builtinPatterns,
|
|
@@ -5660,7 +5704,6 @@ var src_default = domflax;
|
|
|
5660
5704
|
createRewriteFactory,
|
|
5661
5705
|
createSyntheticSink,
|
|
5662
5706
|
createText,
|
|
5663
|
-
dedupeClasses,
|
|
5664
5707
|
defaultMeta,
|
|
5665
5708
|
displayContentsWrapper,
|
|
5666
5709
|
docFingerprint,
|
|
@@ -5675,29 +5718,21 @@ var src_default = domflax;
|
|
|
5675
5718
|
flattenVerdict,
|
|
5676
5719
|
flattenWouldDropStyle,
|
|
5677
5720
|
flexCenterWrapper,
|
|
5678
|
-
gapShorthand,
|
|
5679
5721
|
getElement,
|
|
5680
5722
|
getNode,
|
|
5681
|
-
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
nestedFlexMerge,
|
|
5685
|
-
nestedGridMerge,
|
|
5686
|
-
overflowShorthand,
|
|
5687
|
-
overscrollBehaviorShorthand,
|
|
5688
|
-
paddingShorthand,
|
|
5723
|
+
gridCenterWrapper,
|
|
5724
|
+
inheritedOnlyWrapper,
|
|
5725
|
+
minStringCover,
|
|
5689
5726
|
passthroughWrapper,
|
|
5690
5727
|
patternsForPhase,
|
|
5691
|
-
placeShorthand,
|
|
5692
5728
|
redundantFragment,
|
|
5693
5729
|
redundantInlineWrapper,
|
|
5694
5730
|
revertDiagnostic,
|
|
5695
5731
|
runPasses,
|
|
5696
|
-
scrollMarginShorthand,
|
|
5697
|
-
scrollPaddingShorthand,
|
|
5698
|
-
sizeShorthand,
|
|
5699
5732
|
stampOrigin,
|
|
5733
|
+
styleMapTuples,
|
|
5700
5734
|
syncClassesFromComputed,
|
|
5735
|
+
tupleKey,
|
|
5701
5736
|
vite,
|
|
5702
5737
|
walk,
|
|
5703
5738
|
webpack
|