domflax 0.1.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/LICENSE +214 -0
- package/dist/chunk-4HHISSMR.js +2227 -0
- package/dist/chunk-4HHISSMR.js.map +1 -0
- package/dist/chunk-6WVVF6AD.js +55 -0
- package/dist/chunk-6WVVF6AD.js.map +1 -0
- package/dist/chunk-77SLHRN6.js +2047 -0
- package/dist/chunk-77SLHRN6.js.map +1 -0
- package/dist/chunk-ZJ2S36GY.js +175 -0
- package/dist/chunk-ZJ2S36GY.js.map +1 -0
- package/dist/cli.cjs +5207 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +1310 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.cjs +4383 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +539 -0
- package/dist/index.d.ts +539 -0
- package/dist/index.js +110 -0
- package/dist/index.js.map +1 -0
- package/dist/pattern-CX6iBzTD.d.ts +237 -0
- package/dist/pattern-P4FIKAUB.d.cts +237 -0
- package/dist/pattern-kit.cjs +630 -0
- package/dist/pattern-kit.cjs.map +1 -0
- package/dist/pattern-kit.d.cts +80 -0
- package/dist/pattern-kit.d.ts +80 -0
- package/dist/pattern-kit.js +55 -0
- package/dist/pattern-kit.js.map +1 -0
- package/dist/types-BQ7l6dVe.d.cts +749 -0
- package/dist/types-BQ7l6dVe.d.ts +749 -0
- package/dist/verify.cjs +2747 -0
- package/dist/verify.cjs.map +1 -0
- package/dist/verify.d.cts +245 -0
- package/dist/verify.d.ts +245 -0
- package/dist/verify.js +2700 -0
- package/dist/verify.js.map +1 -0
- package/dist/webpack-loader.cjs +4149 -0
- package/dist/webpack-loader.cjs.map +1 -0
- package/dist/webpack-loader.d.cts +21 -0
- package/dist/webpack-loader.d.ts +21 -0
- package/dist/webpack-loader.js +30 -0
- package/dist/webpack-loader.js.map +1 -0
- package/package.json +99 -0
|
@@ -0,0 +1,2227 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BASE_CONDITION,
|
|
3
|
+
BASE_CONDITION_KEY,
|
|
4
|
+
and,
|
|
5
|
+
computed,
|
|
6
|
+
conditionKey,
|
|
7
|
+
createDocument,
|
|
8
|
+
createElement,
|
|
9
|
+
createExpr,
|
|
10
|
+
createFragment,
|
|
11
|
+
createText,
|
|
12
|
+
defaultMeta,
|
|
13
|
+
emptyClassList,
|
|
14
|
+
emptyStyleMap,
|
|
15
|
+
hasDynamicChildren,
|
|
16
|
+
hasDynamicClasses,
|
|
17
|
+
hasEventHandlers,
|
|
18
|
+
hasRef,
|
|
19
|
+
isElement,
|
|
20
|
+
normalizer,
|
|
21
|
+
not,
|
|
22
|
+
pattern,
|
|
23
|
+
targetedByCombinator
|
|
24
|
+
} from "./chunk-77SLHRN6.js";
|
|
25
|
+
import {
|
|
26
|
+
__filename,
|
|
27
|
+
init_esm_shims
|
|
28
|
+
} from "./chunk-6WVVF6AD.js";
|
|
29
|
+
|
|
30
|
+
// ../patterns/src/flatten/empty-style-div.pattern.ts
|
|
31
|
+
init_esm_shims();
|
|
32
|
+
function asEl(node) {
|
|
33
|
+
const n = node;
|
|
34
|
+
return n.kind === "element" ? n : null;
|
|
35
|
+
}
|
|
36
|
+
function metaFlag(flag) {
|
|
37
|
+
return (node) => Boolean(asEl(node)?.meta[flag]);
|
|
38
|
+
}
|
|
39
|
+
var establishesBox = metaFlag("establishesBox");
|
|
40
|
+
var establishesFormattingContext = metaFlag("establishesFormattingContext");
|
|
41
|
+
var establishesStackingContext = metaFlag("establishesStackingContext");
|
|
42
|
+
var isContainingBlock = metaFlag("isContainingBlock");
|
|
43
|
+
var declaresCustomProperties = metaFlag("declaresCustomProperties");
|
|
44
|
+
var targetedByStructuralPseudo = (node, ctx) => {
|
|
45
|
+
const el = asEl(node);
|
|
46
|
+
if (!el) return false;
|
|
47
|
+
if (el.meta.targetedByStructuralPseudo) return true;
|
|
48
|
+
return ctx.selectors.targetedByStructuralPseudo(el.id);
|
|
49
|
+
};
|
|
50
|
+
var DISPLAY = "display";
|
|
51
|
+
var hasNonBlockDisplay = (node, ctx) => {
|
|
52
|
+
const el = asEl(node);
|
|
53
|
+
if (!el) return false;
|
|
54
|
+
const sm = ctx.computedOf(el) ?? el.computed;
|
|
55
|
+
for (const block of sm.blocks.values()) {
|
|
56
|
+
const decl = block.decls.get(DISPLAY);
|
|
57
|
+
if (decl && String(decl.value) !== "block") return true;
|
|
58
|
+
}
|
|
59
|
+
return false;
|
|
60
|
+
};
|
|
61
|
+
var emptyStyleDiv = pattern({
|
|
62
|
+
name: "empty-style-div",
|
|
63
|
+
category: "flatten/empty-style-div",
|
|
64
|
+
safety: 1,
|
|
65
|
+
doc: {
|
|
66
|
+
title: "Flatten empty-style div wrapper",
|
|
67
|
+
summary: "A layout-neutral div (display:block default, no box/visual styles) that wraps a single child is removed; the child is hoisted into its place.",
|
|
68
|
+
before: "<div><Child/></div>",
|
|
69
|
+
after: "<Child/>",
|
|
70
|
+
safetyRationale: "Wrapper is a plain block box that paints nothing, establishes no box/formatting/stacking context, is no containing block, has no custom-property coupling, carries no ref/handlers/dynamic children/raw HTML, and is neither a combinator subject nor a structural-pseudo target; inheritable styles are folded onto the child before removal."
|
|
71
|
+
},
|
|
72
|
+
match: {
|
|
73
|
+
tag: "div",
|
|
74
|
+
onlyChild: "element",
|
|
75
|
+
paintsNothing: true,
|
|
76
|
+
where: [
|
|
77
|
+
not(hasNonBlockDisplay),
|
|
78
|
+
not(establishesBox),
|
|
79
|
+
not(establishesFormattingContext),
|
|
80
|
+
not(establishesStackingContext),
|
|
81
|
+
not(isContainingBlock),
|
|
82
|
+
not(declaresCustomProperties),
|
|
83
|
+
not(targetedByStructuralPseudo)
|
|
84
|
+
]
|
|
85
|
+
},
|
|
86
|
+
rewrite: { flattenInto: "child" },
|
|
87
|
+
examples: [
|
|
88
|
+
{
|
|
89
|
+
before: '<div><span className="bg-red-200">Hi</span></div>',
|
|
90
|
+
after: '<span className="bg-red-200">Hi</span>'
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
// The wrapper paints its own background (own visual style) → not layout-neutral, kept.
|
|
94
|
+
noMatch: '<div className="bg-blue-500"><span className="bg-red-200">Hi</span></div>'
|
|
95
|
+
}
|
|
96
|
+
]
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// ../patterns/src/flatten/flex-center-wrapper.pattern.ts
|
|
100
|
+
init_esm_shims();
|
|
101
|
+
var flexCenterWrapper = pattern({
|
|
102
|
+
name: "flex-center-wrapper",
|
|
103
|
+
category: "flatten/flex-center-wrapper",
|
|
104
|
+
safety: 2,
|
|
105
|
+
doc: {
|
|
106
|
+
title: "Flatten flex-centering wrapper",
|
|
107
|
+
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.",
|
|
108
|
+
before: '<div style="display:flex;align-items:center;justify-content:center"><Child/></div>',
|
|
109
|
+
after: '<Child style="place-self:center"/>',
|
|
110
|
+
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."
|
|
111
|
+
},
|
|
112
|
+
match: {
|
|
113
|
+
tag: "div",
|
|
114
|
+
style: { display: "flex", alignItems: "center", justifyContent: "center" },
|
|
115
|
+
onlyChild: "element",
|
|
116
|
+
paintsNothing: true
|
|
117
|
+
},
|
|
118
|
+
rewrite: {
|
|
119
|
+
flattenInto: "child",
|
|
120
|
+
childGains: { placeSelf: "center" }
|
|
121
|
+
},
|
|
122
|
+
examples: [
|
|
123
|
+
{
|
|
124
|
+
// The wrapper is removed; the surviving child gains `place-self-center` (reverse-emitted
|
|
125
|
+
// from the folded computed style by the resolver).
|
|
126
|
+
before: '<div className="flex justify-center items-center"><div className="bg-red-200">Hello</div></div>',
|
|
127
|
+
after: '<div className="bg-red-200 place-self-center">Hello</div>'
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
// onClick is a hard opacity barrier → the wrapper is load-bearing, no flatten.
|
|
131
|
+
noMatch: '<div className="flex justify-center items-center" onClick={handleClick}><div className="bg-red-200">Hello</div></div>'
|
|
132
|
+
}
|
|
133
|
+
]
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// ../patterns/src/flatten/nested-flex-merge.pattern.ts
|
|
137
|
+
init_esm_shims();
|
|
138
|
+
function baseConditionStyleMap(decls) {
|
|
139
|
+
const map = /* @__PURE__ */ new Map();
|
|
140
|
+
for (const [prop, value] of decls) {
|
|
141
|
+
for (const decl of normalizer.normalizeDeclaration(prop, value, false)) {
|
|
142
|
+
map.set(decl.property, decl);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
const block = { condition: BASE_CONDITION, decls: map };
|
|
146
|
+
const blocks = /* @__PURE__ */ new Map([[conditionKey(BASE_CONDITION), block]]);
|
|
147
|
+
return { blocks };
|
|
148
|
+
}
|
|
149
|
+
var DISPLAY_FLEX = baseConditionStyleMap([["display", "flex"]]);
|
|
150
|
+
var FLEX_CONTAINER_PROPERTIES = /* @__PURE__ */ new Set([
|
|
151
|
+
"display",
|
|
152
|
+
"flex-direction",
|
|
153
|
+
"flex-wrap",
|
|
154
|
+
"justify-content",
|
|
155
|
+
"align-items",
|
|
156
|
+
"align-content",
|
|
157
|
+
"place-content",
|
|
158
|
+
"place-items",
|
|
159
|
+
"row-gap",
|
|
160
|
+
"column-gap"
|
|
161
|
+
]);
|
|
162
|
+
function outerMergeSafe(sm) {
|
|
163
|
+
const norm = normalizer.normalizeStyleMap(sm);
|
|
164
|
+
for (const block of norm.blocks.values()) {
|
|
165
|
+
for (const decl of block.decls.values()) {
|
|
166
|
+
if (FLEX_CONTAINER_PROPERTIES.has(String(decl.property))) continue;
|
|
167
|
+
if (decl.inherited) continue;
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
function flexConflict(outer, inner) {
|
|
174
|
+
const a = normalizer.normalizeStyleMap(outer);
|
|
175
|
+
const b = normalizer.normalizeStyleMap(inner);
|
|
176
|
+
for (const [key, blockA] of a.blocks) {
|
|
177
|
+
const blockB = b.blocks.get(key);
|
|
178
|
+
if (!blockB) continue;
|
|
179
|
+
for (const [prop, declA] of blockA.decls) {
|
|
180
|
+
if (!FLEX_CONTAINER_PROPERTIES.has(String(prop))) continue;
|
|
181
|
+
const declB = blockB.decls.get(prop);
|
|
182
|
+
if (declB && declB.value !== declA.value) return true;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
function extractFlexStyle(sm) {
|
|
188
|
+
const blocks = /* @__PURE__ */ new Map();
|
|
189
|
+
for (const [key, block] of sm.blocks) {
|
|
190
|
+
const decls = /* @__PURE__ */ new Map();
|
|
191
|
+
for (const [prop, decl] of block.decls) {
|
|
192
|
+
if (FLEX_CONTAINER_PROPERTIES.has(String(prop))) decls.set(prop, decl);
|
|
193
|
+
}
|
|
194
|
+
if (decls.size > 0) blocks.set(key, { condition: block.condition, decls });
|
|
195
|
+
}
|
|
196
|
+
return { blocks };
|
|
197
|
+
}
|
|
198
|
+
var isInnerFlex = and(
|
|
199
|
+
isElement("div"),
|
|
200
|
+
computed(DISPLAY_FLEX),
|
|
201
|
+
not(targetedByCombinator)
|
|
202
|
+
);
|
|
203
|
+
var nestedFlexMerge = pattern({
|
|
204
|
+
name: "nested-flex-merge",
|
|
205
|
+
category: "flatten/nested-flex-merge",
|
|
206
|
+
safety: 2,
|
|
207
|
+
doc: {
|
|
208
|
+
title: "Merge nested flex containers",
|
|
209
|
+
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.",
|
|
210
|
+
before: '<div style="display:flex;align-items:center;gap:8px"><div style="display:flex;flex-direction:column"/></div>',
|
|
211
|
+
after: '<div style="display:flex;flex-direction:column;align-items:center;gap:8px"/>',
|
|
212
|
+
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."
|
|
213
|
+
},
|
|
214
|
+
match: {
|
|
215
|
+
tag: "div",
|
|
216
|
+
style: { display: "flex" },
|
|
217
|
+
onlyChild: "element",
|
|
218
|
+
paintsNothing: true
|
|
219
|
+
},
|
|
220
|
+
rewrite: (ctx, rw) => {
|
|
221
|
+
const outer = ctx.node;
|
|
222
|
+
const inner = ctx.onlyElementChild();
|
|
223
|
+
if (!inner) return null;
|
|
224
|
+
if (!isInnerFlex(inner, ctx)) return null;
|
|
225
|
+
const outerStyle = ctx.computed();
|
|
226
|
+
const innerStyle = ctx.computedOf(inner);
|
|
227
|
+
if (!outerMergeSafe(outerStyle)) return null;
|
|
228
|
+
if (flexConflict(outerStyle, innerStyle)) return null;
|
|
229
|
+
return [
|
|
230
|
+
// 1. Preserve inheritable values (color/font/…) by folding them onto the child first.
|
|
231
|
+
rw.foldInheritedStyles(outer, inner, { conditions: "all" }),
|
|
232
|
+
// 2. Transfer the wrapper's flex-container declarations onto the child (target-wins keeps the
|
|
233
|
+
// child's value for any shared property — identical anyway, we proved non-conflict).
|
|
234
|
+
rw.mergeStyle(inner, null, extractFlexStyle(outerStyle), "target-wins"),
|
|
235
|
+
// 3. Remove the wrapper (structural-safe; hoists the child and preserves its IRNodeId).
|
|
236
|
+
rw.unwrap(outer)
|
|
237
|
+
];
|
|
238
|
+
},
|
|
239
|
+
examples: [
|
|
240
|
+
{
|
|
241
|
+
// The wrapper's flex declarations (align-items / gap) merge onto the inner flex container,
|
|
242
|
+
// then the wrapper is removed (its own `data-x` here just blocks the more aggressive
|
|
243
|
+
// passthrough-wrapper so this merge is the one that fires).
|
|
244
|
+
before: '<div className="flex items-center gap-2" data-x="1"><div className="flex flex-col">X</div></div>',
|
|
245
|
+
after: '<div className="flex flex-col gap-2 items-center">X</div>'
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
// A non-flex wrapper does not match the flex-container signature → left unchanged.
|
|
249
|
+
noMatch: '<div className="block bg-blue-500"><div className="flex flex-col">X</div></div>'
|
|
250
|
+
}
|
|
251
|
+
]
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
// ../patterns/src/flatten/passthrough-wrapper.pattern.ts
|
|
255
|
+
init_esm_shims();
|
|
256
|
+
function metaOf(node) {
|
|
257
|
+
const n = node;
|
|
258
|
+
return n.kind === "element" ? n.meta : null;
|
|
259
|
+
}
|
|
260
|
+
function elementOf(node) {
|
|
261
|
+
const n = node;
|
|
262
|
+
return n.kind === "element" ? n : null;
|
|
263
|
+
}
|
|
264
|
+
var establishesContext = (node) => {
|
|
265
|
+
const m = metaOf(node);
|
|
266
|
+
if (!m) return false;
|
|
267
|
+
return m.establishesBox || m.establishesFormattingContext || m.establishesStackingContext || m.isContainingBlock || m.declaresCustomProperties;
|
|
268
|
+
};
|
|
269
|
+
var hasSpreadAttrs = (node) => metaOf(node)?.hasSpreadAttrs ?? false;
|
|
270
|
+
var isComponentNode = (node) => metaOf(node)?.isComponent ?? false;
|
|
271
|
+
var hasOwnAttrs = (node) => {
|
|
272
|
+
const el = elementOf(node);
|
|
273
|
+
if (!el) return false;
|
|
274
|
+
return el.attrs.entries.size > 0 || el.attrs.spreads.length > 0;
|
|
275
|
+
};
|
|
276
|
+
var targetedByStructuralPseudo2 = (node, ctx) => {
|
|
277
|
+
const el = elementOf(node);
|
|
278
|
+
if (!el) return false;
|
|
279
|
+
if (el.meta.targetedByStructuralPseudo) return true;
|
|
280
|
+
return ctx.selectors.targetedByStructuralPseudo(el.id);
|
|
281
|
+
};
|
|
282
|
+
var passthroughWrapper = pattern({
|
|
283
|
+
name: "passthrough-wrapper",
|
|
284
|
+
category: "flatten/passthrough-wrapper",
|
|
285
|
+
safety: 2,
|
|
286
|
+
doc: {
|
|
287
|
+
title: "Flatten passthrough wrapper",
|
|
288
|
+
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.",
|
|
289
|
+
before: "<div><Child/></div>",
|
|
290
|
+
after: "<Child/>",
|
|
291
|
+
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."
|
|
292
|
+
},
|
|
293
|
+
match: {
|
|
294
|
+
tag: "div",
|
|
295
|
+
onlyChild: "element",
|
|
296
|
+
paintsNothing: true,
|
|
297
|
+
where: [
|
|
298
|
+
not(establishesContext),
|
|
299
|
+
not(hasOwnAttrs),
|
|
300
|
+
not(hasDynamicClasses),
|
|
301
|
+
not(hasSpreadAttrs),
|
|
302
|
+
not(isComponentNode),
|
|
303
|
+
not(targetedByStructuralPseudo2)
|
|
304
|
+
]
|
|
305
|
+
},
|
|
306
|
+
rewrite: { flattenInto: "child" },
|
|
307
|
+
examples: [
|
|
308
|
+
{
|
|
309
|
+
before: '<div className="flex"><a className="bg-red-200">Link</a></div>',
|
|
310
|
+
after: '<a className="bg-red-200">Link</a>'
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
// A ref pins the wrapper's element identity (a hard opacity barrier) → not a passthrough.
|
|
314
|
+
noMatch: '<div ref={rootRef}><a className="bg-red-200">Link</a></div>'
|
|
315
|
+
}
|
|
316
|
+
]
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
// ../patterns/src/flatten/redundant-fragment.pattern.ts
|
|
320
|
+
init_esm_shims();
|
|
321
|
+
function parentIsRedundantFragment(node, ctx) {
|
|
322
|
+
const el = node;
|
|
323
|
+
if (el.kind !== "element") return false;
|
|
324
|
+
const parentId = el.parent;
|
|
325
|
+
if (parentId == null) return false;
|
|
326
|
+
const parent = ctx.doc.nodes.get(parentId);
|
|
327
|
+
if (!parent || parent.kind !== "fragment") return false;
|
|
328
|
+
if (parent.parent == null) return false;
|
|
329
|
+
if (parent.children.length !== 1) return false;
|
|
330
|
+
const m = parent.meta;
|
|
331
|
+
if (m.hasKey || m.hasRef || m.hasEventHandlers || m.hasDynamicChildren || m.hasDangerousHtml || m.hasSpreadAttrs || m.isComponent) {
|
|
332
|
+
return false;
|
|
333
|
+
}
|
|
334
|
+
if (m.targetedByCombinator || m.targetedByStructuralPseudo) return false;
|
|
335
|
+
const fid = parentId;
|
|
336
|
+
if (ctx.selectors.targetedByCombinator(fid) || ctx.selectors.targetedByStructuralPseudo(fid)) {
|
|
337
|
+
return false;
|
|
338
|
+
}
|
|
339
|
+
if (ctx.selectors.reparentImpact(fid).size > 0) return false;
|
|
340
|
+
return true;
|
|
341
|
+
}
|
|
342
|
+
var redundantFragment = pattern({
|
|
343
|
+
name: "redundant-fragment",
|
|
344
|
+
category: "flatten/redundant-fragment",
|
|
345
|
+
safety: 1,
|
|
346
|
+
doc: {
|
|
347
|
+
title: "Flatten redundant single-child fragment",
|
|
348
|
+
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.",
|
|
349
|
+
before: "<><Child/></>",
|
|
350
|
+
after: "<Child/>",
|
|
351
|
+
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."
|
|
352
|
+
},
|
|
353
|
+
match: parentIsRedundantFragment,
|
|
354
|
+
rewrite: (ctx, rw) => {
|
|
355
|
+
const parentId = ctx.node.parent;
|
|
356
|
+
if (parentId == null) return null;
|
|
357
|
+
const fragment = ctx.doc.nodes.get(parentId);
|
|
358
|
+
if (!fragment || fragment.kind !== "fragment") return null;
|
|
359
|
+
return [rw.unwrap(fragment)];
|
|
360
|
+
},
|
|
361
|
+
examples: [
|
|
362
|
+
{
|
|
363
|
+
before: '<><span className="bg-red-200">Hi</span></>',
|
|
364
|
+
after: '<span className="bg-red-200">Hi</span>'
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
// Two children ⇒ not a single-child fragment, so the fragment is load-bearing and stays.
|
|
368
|
+
noMatch: '<><span className="bg-red-200">A</span><span className="bg-green-200">B</span></>'
|
|
369
|
+
}
|
|
370
|
+
]
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
// ../patterns/src/compress/dedupe-classes.pattern.ts
|
|
374
|
+
init_esm_shims();
|
|
375
|
+
function elementOf2(node) {
|
|
376
|
+
const n = node;
|
|
377
|
+
return n.kind === "element" ? n : null;
|
|
378
|
+
}
|
|
379
|
+
var hasDangerousHtml = (node) => elementOf2(node)?.meta.hasDangerousHtml ?? false;
|
|
380
|
+
var isOpaque = (node, ctx) => ctx.isOpaque(node);
|
|
381
|
+
function findRedundantClasses(computed2) {
|
|
382
|
+
const winners = /* @__PURE__ */ new Set();
|
|
383
|
+
const shadowed = /* @__PURE__ */ new Set();
|
|
384
|
+
for (const block of computed2.blocks.values()) {
|
|
385
|
+
for (const decl of block.decls.values()) {
|
|
386
|
+
if (decl.origin && decl.origin.kind === "class") winners.add(decl.origin.className);
|
|
387
|
+
for (const o of decl.shadowed ?? []) {
|
|
388
|
+
if (o.kind === "class") shadowed.add(o.className);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
return { winners, shadowed };
|
|
393
|
+
}
|
|
394
|
+
var dedupeClasses = pattern({
|
|
395
|
+
name: "dedupe-classes",
|
|
396
|
+
category: "compress/dedupe-classes",
|
|
397
|
+
safety: 1,
|
|
398
|
+
doc: {
|
|
399
|
+
title: "Dedupe fully-overridden class tokens",
|
|
400
|
+
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.",
|
|
401
|
+
before: '<p class="text-sm text-lg" />',
|
|
402
|
+
after: '<p class="text-lg" />',
|
|
403
|
+
safetyRationale: "A fully-overridden token contributes nothing to the computed style in any condition, so removing it changes no pixels. Dynamic/opaque class lists, ref/handler/dynamic-children/raw-html barriers, combinator subjects, and selector-bound (non-droppable) tokens are excluded."
|
|
404
|
+
},
|
|
405
|
+
match: {
|
|
406
|
+
where: [
|
|
407
|
+
not(hasRef),
|
|
408
|
+
not(hasEventHandlers),
|
|
409
|
+
not(hasDynamicChildren),
|
|
410
|
+
not(hasDangerousHtml),
|
|
411
|
+
not(hasDynamicClasses),
|
|
412
|
+
not(isOpaque),
|
|
413
|
+
not(targetedByCombinator)
|
|
414
|
+
]
|
|
415
|
+
},
|
|
416
|
+
rewrite: {
|
|
417
|
+
dropClasses(computed2, ctx) {
|
|
418
|
+
const { winners, shadowed } = findRedundantClasses(computed2);
|
|
419
|
+
const drop = /* @__PURE__ */ new Set();
|
|
420
|
+
for (const cls of shadowed) {
|
|
421
|
+
if (winners.has(cls)) continue;
|
|
422
|
+
if (!ctx.resolver.selectorUsage(cls).droppable) continue;
|
|
423
|
+
drop.add(cls);
|
|
424
|
+
}
|
|
425
|
+
return drop;
|
|
426
|
+
}
|
|
427
|
+
},
|
|
428
|
+
examples: [
|
|
429
|
+
{
|
|
430
|
+
// `text-sm` is fully overridden by `text-lg` (both set font-size + line-height). The resolver
|
|
431
|
+
// records that shadowing in provenance and reports the Tailwind utility as droppable, so the
|
|
432
|
+
// pattern drops `text-sm`; the reverse-emit then re-derives the minimal set (`text-lg`).
|
|
433
|
+
before: '<p className="text-sm text-lg">Hi</p>',
|
|
434
|
+
after: '<p className="text-lg">Hi</p>'
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
// Both tokens win a distinct property (no full override) → nothing to dedupe.
|
|
438
|
+
noMatch: '<p className="text-lg font-bold">Hi</p>'
|
|
439
|
+
}
|
|
440
|
+
]
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
// ../patterns/src/compress/inset-shorthand.pattern.ts
|
|
444
|
+
init_esm_shims();
|
|
445
|
+
var TOP = "top";
|
|
446
|
+
var RIGHT = "right";
|
|
447
|
+
var BOTTOM = "bottom";
|
|
448
|
+
var LEFT = "left";
|
|
449
|
+
var INSET = "inset";
|
|
450
|
+
var INSET_BLOCK = "inset-block";
|
|
451
|
+
var INSET_INLINE = "inset-inline";
|
|
452
|
+
var hasRawHtml = (node) => {
|
|
453
|
+
const n = node;
|
|
454
|
+
return n.kind === "element" ? n.meta.hasDangerousHtml : false;
|
|
455
|
+
};
|
|
456
|
+
function sameSide(a, b) {
|
|
457
|
+
return a !== void 0 && b !== void 0 && a.value === b.value && a.important === b.important;
|
|
458
|
+
}
|
|
459
|
+
function asProperty(src, property) {
|
|
460
|
+
return { ...src, property, inherited: normalizer.inherited.isInherited(property) };
|
|
461
|
+
}
|
|
462
|
+
function withBaseDecls(src, baseDecls) {
|
|
463
|
+
const blocks = /* @__PURE__ */ new Map();
|
|
464
|
+
for (const [key, block] of src.blocks) {
|
|
465
|
+
const decls = key === BASE_CONDITION_KEY ? baseDecls : new Map(block.decls);
|
|
466
|
+
blocks.set(key, { condition: block.condition, decls });
|
|
467
|
+
}
|
|
468
|
+
return { blocks };
|
|
469
|
+
}
|
|
470
|
+
var insetShorthand = pattern({
|
|
471
|
+
name: "inset-shorthand",
|
|
472
|
+
category: "compress/inset-shorthand",
|
|
473
|
+
safety: 2,
|
|
474
|
+
doc: {
|
|
475
|
+
title: "Compress inset longhands into a shorthand",
|
|
476
|
+
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).",
|
|
477
|
+
before: '<div style="top:10px;right:10px;bottom:10px;left:10px"/>',
|
|
478
|
+
after: '<div style="inset:10px"/>',
|
|
479
|
+
safetyRationale: "Meaning-preserving shorthand compaction; the element is not a combinator subject and carries no ref/handlers/dynamic children/raw HTML, so neither selector matching nor behaviour changes."
|
|
480
|
+
},
|
|
481
|
+
match: {
|
|
482
|
+
where: [
|
|
483
|
+
not(hasRef),
|
|
484
|
+
not(hasEventHandlers),
|
|
485
|
+
not(hasDynamicChildren),
|
|
486
|
+
not(hasRawHtml),
|
|
487
|
+
not(hasDynamicClasses),
|
|
488
|
+
not(targetedByCombinator)
|
|
489
|
+
]
|
|
490
|
+
},
|
|
491
|
+
rewrite: {
|
|
492
|
+
rewriteClasses(computed2) {
|
|
493
|
+
const base = computed2.blocks.get(BASE_CONDITION_KEY);
|
|
494
|
+
if (!base) return null;
|
|
495
|
+
const top = base.decls.get(TOP);
|
|
496
|
+
const right = base.decls.get(RIGHT);
|
|
497
|
+
const bottom = base.decls.get(BOTTOM);
|
|
498
|
+
const left = base.decls.get(LEFT);
|
|
499
|
+
const next = new Map(base.decls);
|
|
500
|
+
if (top && sameSide(top, right) && sameSide(top, bottom) && sameSide(top, left)) {
|
|
501
|
+
next.delete(TOP);
|
|
502
|
+
next.delete(RIGHT);
|
|
503
|
+
next.delete(BOTTOM);
|
|
504
|
+
next.delete(LEFT);
|
|
505
|
+
next.set(INSET, asProperty(top, INSET));
|
|
506
|
+
} else {
|
|
507
|
+
let collapsed = false;
|
|
508
|
+
if (sameSide(top, bottom)) {
|
|
509
|
+
next.delete(TOP);
|
|
510
|
+
next.delete(BOTTOM);
|
|
511
|
+
next.set(INSET_BLOCK, asProperty(top, INSET_BLOCK));
|
|
512
|
+
collapsed = true;
|
|
513
|
+
}
|
|
514
|
+
if (sameSide(left, right)) {
|
|
515
|
+
next.delete(LEFT);
|
|
516
|
+
next.delete(RIGHT);
|
|
517
|
+
next.set(INSET_INLINE, asProperty(left, INSET_INLINE));
|
|
518
|
+
collapsed = true;
|
|
519
|
+
}
|
|
520
|
+
if (!collapsed) return null;
|
|
521
|
+
}
|
|
522
|
+
return withBaseDecls(computed2, next);
|
|
523
|
+
}
|
|
524
|
+
},
|
|
525
|
+
examples: [
|
|
526
|
+
{
|
|
527
|
+
// The four equal inset longhands collapse to an `inset` shorthand at the IR level; the
|
|
528
|
+
// minimizing reverse-emit expands it back to top/right/bottom/left and picks the single utility
|
|
529
|
+
// covering all four (`inset-0`), replacing the four physical-side tokens. `bg-red-200` survives.
|
|
530
|
+
before: '<div className="top-0 right-0 bottom-0 left-0 bg-red-200">box</div>',
|
|
531
|
+
after: '<div className="bg-red-200 inset-0">box</div>'
|
|
532
|
+
},
|
|
533
|
+
{
|
|
534
|
+
// No matching inset pair (all four distinct) → nothing collapses.
|
|
535
|
+
noMatch: '<div className="top-0 right-1 bottom-2 left-3 bg-red-200">box</div>'
|
|
536
|
+
}
|
|
537
|
+
]
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
// ../patterns/src/compress/margin-shorthand.pattern.ts
|
|
541
|
+
init_esm_shims();
|
|
542
|
+
var MARGIN_SIDES = [
|
|
543
|
+
"margin-top",
|
|
544
|
+
"margin-right",
|
|
545
|
+
"margin-bottom",
|
|
546
|
+
"margin-left"
|
|
547
|
+
];
|
|
548
|
+
var MARGIN_SIDE_SET = new Set(MARGIN_SIDES);
|
|
549
|
+
var BASE_KEY = conditionKey(BASE_CONDITION);
|
|
550
|
+
function asElement(node) {
|
|
551
|
+
const n = node;
|
|
552
|
+
return n.kind === "element" ? n : null;
|
|
553
|
+
}
|
|
554
|
+
var hasDangerousHtml2 = (node) => asElement(node)?.meta.hasDangerousHtml ?? false;
|
|
555
|
+
function collapseMarginValue(top, right, bottom, left) {
|
|
556
|
+
if (right === left) {
|
|
557
|
+
if (top === bottom) {
|
|
558
|
+
return top === right ? top : `${top} ${right}`;
|
|
559
|
+
}
|
|
560
|
+
return `${top} ${right} ${bottom}`;
|
|
561
|
+
}
|
|
562
|
+
return `${top} ${right} ${bottom} ${left}`;
|
|
563
|
+
}
|
|
564
|
+
function withFoldedMargin(sm, marginDecl) {
|
|
565
|
+
const blocks = /* @__PURE__ */ new Map();
|
|
566
|
+
for (const [key, block] of sm.blocks) {
|
|
567
|
+
if (key !== BASE_KEY) {
|
|
568
|
+
blocks.set(key, block);
|
|
569
|
+
continue;
|
|
570
|
+
}
|
|
571
|
+
const decls = /* @__PURE__ */ new Map();
|
|
572
|
+
for (const [prop, decl] of block.decls) {
|
|
573
|
+
if (!MARGIN_SIDE_SET.has(String(prop))) decls.set(prop, decl);
|
|
574
|
+
}
|
|
575
|
+
decls.set(marginDecl.property, marginDecl);
|
|
576
|
+
blocks.set(key, { condition: block.condition, decls });
|
|
577
|
+
}
|
|
578
|
+
return { blocks };
|
|
579
|
+
}
|
|
580
|
+
var marginShorthand = pattern({
|
|
581
|
+
name: "margin-shorthand",
|
|
582
|
+
category: "compress/margin-shorthand",
|
|
583
|
+
safety: 2,
|
|
584
|
+
doc: {
|
|
585
|
+
title: "Compress margin longhands into the `margin` shorthand",
|
|
586
|
+
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.",
|
|
587
|
+
before: '<div style="margin-top:8px;margin-right:8px;margin-bottom:8px;margin-left:8px"/>',
|
|
588
|
+
after: '<div style="margin:8px"/>',
|
|
589
|
+
safetyRationale: "Pure representation change (no pixels move); skips nodes with ref/handlers/dynamic children/raw html, dynamic class segments, or combinator-subject selectors."
|
|
590
|
+
},
|
|
591
|
+
match: {
|
|
592
|
+
where: [
|
|
593
|
+
not(hasRef),
|
|
594
|
+
not(hasEventHandlers),
|
|
595
|
+
not(hasDynamicChildren),
|
|
596
|
+
not(hasDynamicClasses),
|
|
597
|
+
not(hasDangerousHtml2),
|
|
598
|
+
not(targetedByCombinator)
|
|
599
|
+
]
|
|
600
|
+
},
|
|
601
|
+
rewrite: {
|
|
602
|
+
rewriteClasses(computed2) {
|
|
603
|
+
const base = computed2.blocks.get(BASE_KEY);
|
|
604
|
+
if (!base) return null;
|
|
605
|
+
const sides = MARGIN_SIDES.map((p) => base.decls.get(p));
|
|
606
|
+
if (sides.some((d) => d === void 0)) return null;
|
|
607
|
+
const [mt, mr, mb, ml] = sides;
|
|
608
|
+
if (mt.important || mr.important || mb.important || ml.important) return null;
|
|
609
|
+
const value = collapseMarginValue(
|
|
610
|
+
String(mt.value),
|
|
611
|
+
String(mr.value),
|
|
612
|
+
String(mb.value),
|
|
613
|
+
String(ml.value)
|
|
614
|
+
);
|
|
615
|
+
const marginDecl = {
|
|
616
|
+
property: "margin",
|
|
617
|
+
value,
|
|
618
|
+
important: false,
|
|
619
|
+
relativeToParent: mt.relativeToParent || mr.relativeToParent || mb.relativeToParent || ml.relativeToParent,
|
|
620
|
+
inherited: false
|
|
621
|
+
// margin is not an inherited property
|
|
622
|
+
};
|
|
623
|
+
return withFoldedMargin(computed2, marginDecl);
|
|
624
|
+
}
|
|
625
|
+
},
|
|
626
|
+
examples: [
|
|
627
|
+
{
|
|
628
|
+
// The four equal margin longhands collapse to a `margin` shorthand at the IR level, and the
|
|
629
|
+
// minimizing reverse-emit picks the single shortest utility (`m-2`) reproducing it, replacing
|
|
630
|
+
// the four `m{t,r,b,l}-2` tokens. `bg-red-200` is preserved.
|
|
631
|
+
before: '<div className="mt-2 mr-2 mb-2 ml-2 bg-red-200">box</div>',
|
|
632
|
+
after: '<div className="bg-red-200 m-2">box</div>'
|
|
633
|
+
},
|
|
634
|
+
{
|
|
635
|
+
// Only two margin sides set → the four-longhand `margin` collapse does not apply.
|
|
636
|
+
noMatch: '<div className="mt-2 mb-2 bg-red-200">box</div>'
|
|
637
|
+
}
|
|
638
|
+
]
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
// ../patterns/src/compress/padding-shorthand.pattern.ts
|
|
642
|
+
init_esm_shims();
|
|
643
|
+
var PADDING_SIDES = [
|
|
644
|
+
"padding-top",
|
|
645
|
+
"padding-right",
|
|
646
|
+
"padding-bottom",
|
|
647
|
+
"padding-left"
|
|
648
|
+
];
|
|
649
|
+
var PADDING_SIDE_SET = new Set(PADDING_SIDES);
|
|
650
|
+
var BASE_KEY2 = conditionKey(BASE_CONDITION);
|
|
651
|
+
function analyzePadding(sm) {
|
|
652
|
+
const block = sm.blocks.get(BASE_KEY2);
|
|
653
|
+
if (!block) return null;
|
|
654
|
+
const sides = [];
|
|
655
|
+
for (const side of PADDING_SIDES) {
|
|
656
|
+
const decl = block.decls.get(side);
|
|
657
|
+
if (!decl) return null;
|
|
658
|
+
sides.push(decl);
|
|
659
|
+
}
|
|
660
|
+
const [top, right, bottom, left] = sides;
|
|
661
|
+
if (!(top.important === right.important && right.important === bottom.important && bottom.important === left.important)) {
|
|
662
|
+
return null;
|
|
663
|
+
}
|
|
664
|
+
const tv = String(top.value);
|
|
665
|
+
const rv = String(right.value);
|
|
666
|
+
const bv = String(bottom.value);
|
|
667
|
+
const lv = String(left.value);
|
|
668
|
+
if (tv !== bv || lv !== rv) return null;
|
|
669
|
+
const value = tv === lv ? tv : `${tv} ${lv}`;
|
|
670
|
+
const relative = sides.some((d) => d.relativeToParent);
|
|
671
|
+
return { value, important: top.important, relative };
|
|
672
|
+
}
|
|
673
|
+
var isInert = (node) => {
|
|
674
|
+
const n = node;
|
|
675
|
+
if (n.kind !== "element") return false;
|
|
676
|
+
const el = n;
|
|
677
|
+
return !el.meta.hasDangerousHtml && !el.meta.hasSpreadAttrs && !el.isComponent;
|
|
678
|
+
};
|
|
679
|
+
function withFoldedPadding(sm, fold) {
|
|
680
|
+
const blocks = /* @__PURE__ */ new Map();
|
|
681
|
+
for (const [key, block] of sm.blocks) {
|
|
682
|
+
if (key !== BASE_KEY2) {
|
|
683
|
+
blocks.set(key, block);
|
|
684
|
+
continue;
|
|
685
|
+
}
|
|
686
|
+
const decls = /* @__PURE__ */ new Map();
|
|
687
|
+
for (const [prop, decl] of block.decls) {
|
|
688
|
+
if (PADDING_SIDE_SET.has(String(prop))) continue;
|
|
689
|
+
decls.set(prop, decl);
|
|
690
|
+
}
|
|
691
|
+
const shorthand = {
|
|
692
|
+
property: "padding",
|
|
693
|
+
value: fold.value,
|
|
694
|
+
important: fold.important,
|
|
695
|
+
relativeToParent: fold.relative,
|
|
696
|
+
inherited: false
|
|
697
|
+
// padding is never inherited
|
|
698
|
+
};
|
|
699
|
+
decls.set(shorthand.property, shorthand);
|
|
700
|
+
blocks.set(key, { condition: block.condition, decls });
|
|
701
|
+
}
|
|
702
|
+
return { blocks };
|
|
703
|
+
}
|
|
704
|
+
var paddingShorthand = pattern({
|
|
705
|
+
name: "padding-shorthand",
|
|
706
|
+
category: "compress/padding-shorthand",
|
|
707
|
+
safety: 1,
|
|
708
|
+
doc: {
|
|
709
|
+
title: "Collapse padding longhands to shorthand",
|
|
710
|
+
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-*).",
|
|
711
|
+
before: '<div class="pt-4 pr-4 pb-4 pl-4"/>',
|
|
712
|
+
after: '<div class="p-4"/>',
|
|
713
|
+
safetyRationale: "A value-preserving re-serialization of the same computed styles on the same node; it skips nodes with ref/handlers/dynamic children/dynamic classes/dangerous html and combinator subjects, so no JS identity, behaviour, or project selector is disturbed."
|
|
714
|
+
},
|
|
715
|
+
match: {
|
|
716
|
+
where: [
|
|
717
|
+
not(hasRef),
|
|
718
|
+
not(hasEventHandlers),
|
|
719
|
+
not(hasDynamicChildren),
|
|
720
|
+
not(hasDynamicClasses),
|
|
721
|
+
not(targetedByCombinator),
|
|
722
|
+
isInert
|
|
723
|
+
]
|
|
724
|
+
},
|
|
725
|
+
rewrite: {
|
|
726
|
+
rewriteClasses(computed2) {
|
|
727
|
+
const fold = analyzePadding(computed2);
|
|
728
|
+
return fold ? withFoldedPadding(computed2, fold) : null;
|
|
729
|
+
}
|
|
730
|
+
},
|
|
731
|
+
examples: [
|
|
732
|
+
{
|
|
733
|
+
// The four equal padding longhands collapse to a `padding` shorthand at the IR level, and the
|
|
734
|
+
// minimizing reverse-emit picks the single shortest utility (`p-4`) that reproduces it,
|
|
735
|
+
// replacing the four `p{t,r,b,l}-4` tokens. `bg-red-200` is preserved (its order is stable).
|
|
736
|
+
before: '<div className="pt-4 pr-4 pb-4 pl-4 bg-red-200">box</div>',
|
|
737
|
+
after: '<div className="bg-red-200 p-4">box</div>'
|
|
738
|
+
},
|
|
739
|
+
{
|
|
740
|
+
// Asymmetric padding (top != bottom) cannot fold into a shorthand.
|
|
741
|
+
noMatch: '<div className="pt-2 pr-4 pb-8 pl-4 bg-red-200">box</div>'
|
|
742
|
+
}
|
|
743
|
+
]
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
// ../patterns/src/compress/size-shorthand.pattern.ts
|
|
747
|
+
init_esm_shims();
|
|
748
|
+
var WIDTH = "width";
|
|
749
|
+
var HEIGHT = "height";
|
|
750
|
+
var SIZE = "size";
|
|
751
|
+
var NON_COLLAPSIBLE_VALUES = /* @__PURE__ */ new Set(["auto", "initial", "unset"]);
|
|
752
|
+
function asElement2(node) {
|
|
753
|
+
const n = node;
|
|
754
|
+
return n.kind === "element" ? n : null;
|
|
755
|
+
}
|
|
756
|
+
function baseBlock(sm) {
|
|
757
|
+
return sm.blocks.get(conditionKey(BASE_CONDITION));
|
|
758
|
+
}
|
|
759
|
+
var hasDangerousHtml3 = (node) => asElement2(node)?.meta.hasDangerousHtml ?? false;
|
|
760
|
+
function withSizeShorthand(sm, value, important) {
|
|
761
|
+
const baseKey = conditionKey(BASE_CONDITION);
|
|
762
|
+
const blocks = /* @__PURE__ */ new Map();
|
|
763
|
+
for (const [key, block] of sm.blocks) {
|
|
764
|
+
if (key !== baseKey) {
|
|
765
|
+
blocks.set(key, block);
|
|
766
|
+
continue;
|
|
767
|
+
}
|
|
768
|
+
const decls = new Map(block.decls);
|
|
769
|
+
decls.delete(WIDTH);
|
|
770
|
+
decls.delete(HEIGHT);
|
|
771
|
+
for (const decl of normalizer.normalizeDeclaration(String(SIZE), value, important)) {
|
|
772
|
+
decls.set(decl.property, decl);
|
|
773
|
+
}
|
|
774
|
+
blocks.set(key, { condition: block.condition, decls });
|
|
775
|
+
}
|
|
776
|
+
return { blocks };
|
|
777
|
+
}
|
|
778
|
+
var sizeShorthand = pattern({
|
|
779
|
+
name: "size-shorthand",
|
|
780
|
+
category: "compress/size-shorthand",
|
|
781
|
+
safety: 2,
|
|
782
|
+
doc: {
|
|
783
|
+
title: "Collapse equal width/height into size-*",
|
|
784
|
+
summary: "An element whose computed width and height are equal is rewritten to the single Tailwind size-* utility (size-* === width + height at the same value).",
|
|
785
|
+
before: '<div style="width:1rem;height:1rem"/>',
|
|
786
|
+
after: '<div class="size-4"/>',
|
|
787
|
+
safetyRationale: "size-* is value-identical to equal width+height; the element carries no ref/handlers/dynamic children/dangerous HTML, no dynamic class segment, and is not a combinator subject."
|
|
788
|
+
},
|
|
789
|
+
match: {
|
|
790
|
+
where: [
|
|
791
|
+
not(hasRef),
|
|
792
|
+
not(hasEventHandlers),
|
|
793
|
+
not(hasDynamicChildren),
|
|
794
|
+
not(hasDangerousHtml3),
|
|
795
|
+
not(hasDynamicClasses),
|
|
796
|
+
not(targetedByCombinator)
|
|
797
|
+
]
|
|
798
|
+
},
|
|
799
|
+
rewrite: {
|
|
800
|
+
rewriteClasses(computed2) {
|
|
801
|
+
const base = baseBlock(computed2);
|
|
802
|
+
const w = base?.decls.get(WIDTH);
|
|
803
|
+
const h = base?.decls.get(HEIGHT);
|
|
804
|
+
if (!w || !h) return null;
|
|
805
|
+
if (w.important !== h.important) return null;
|
|
806
|
+
if (NON_COLLAPSIBLE_VALUES.has(String(w.value))) return null;
|
|
807
|
+
if (w.value !== h.value) return null;
|
|
808
|
+
return withSizeShorthand(computed2, String(w.value), w.important);
|
|
809
|
+
}
|
|
810
|
+
},
|
|
811
|
+
examples: [
|
|
812
|
+
{
|
|
813
|
+
// Equal width/height collapse to a `size` decl at the IR level; the minimizing reverse-emit
|
|
814
|
+
// expands `size` back to width+height, finds the single utility covering both (`size-10`), and
|
|
815
|
+
// replaces the `h-10`+`w-10` pair with it. `bg-red-200` is preserved.
|
|
816
|
+
before: '<div className="h-10 w-10 bg-red-200">box</div>',
|
|
817
|
+
after: '<div className="bg-red-200 size-10">box</div>'
|
|
818
|
+
},
|
|
819
|
+
{
|
|
820
|
+
// Width and height differ → no equal-axis collapse.
|
|
821
|
+
noMatch: '<div className="h-10 w-20 bg-red-200">box</div>'
|
|
822
|
+
}
|
|
823
|
+
]
|
|
824
|
+
});
|
|
825
|
+
|
|
826
|
+
// ../patterns/src/_registry.generated.ts
|
|
827
|
+
init_esm_shims();
|
|
828
|
+
var builtinPatterns = [
|
|
829
|
+
emptyStyleDiv,
|
|
830
|
+
flexCenterWrapper,
|
|
831
|
+
nestedFlexMerge,
|
|
832
|
+
passthroughWrapper,
|
|
833
|
+
redundantFragment,
|
|
834
|
+
dedupeClasses,
|
|
835
|
+
insetShorthand,
|
|
836
|
+
marginShorthand,
|
|
837
|
+
paddingShorthand,
|
|
838
|
+
sizeShorthand
|
|
839
|
+
];
|
|
840
|
+
|
|
841
|
+
// ../patterns/src/index.ts
|
|
842
|
+
init_esm_shims();
|
|
843
|
+
|
|
844
|
+
// ../frontend-jsx/src/frontend.ts
|
|
845
|
+
init_esm_shims();
|
|
846
|
+
import { parse as babelParse } from "@babel/parser";
|
|
847
|
+
import babelTraverse from "@babel/traverse";
|
|
848
|
+
var traverse = typeof babelTraverse === "function" ? babelTraverse : babelTraverse.default;
|
|
849
|
+
var JSX_LANGS = ["jsx", "tsx"];
|
|
850
|
+
var FILE_ID = 1;
|
|
851
|
+
function jsxName(node) {
|
|
852
|
+
switch (node.type) {
|
|
853
|
+
case "JSXIdentifier":
|
|
854
|
+
return node.name;
|
|
855
|
+
case "JSXMemberExpression":
|
|
856
|
+
return `${jsxName(node.object)}.${node.property.name}`;
|
|
857
|
+
case "JSXNamespacedName":
|
|
858
|
+
return `${node.namespace.name}:${node.name.name}`;
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
function isComponentName(node) {
|
|
862
|
+
if (node.type === "JSXMemberExpression") return true;
|
|
863
|
+
if (node.type === "JSXNamespacedName") return false;
|
|
864
|
+
return /^[A-Z]/.test(node.name);
|
|
865
|
+
}
|
|
866
|
+
function attrName(name) {
|
|
867
|
+
return name.type === "JSXNamespacedName" ? `${name.namespace.name}:${name.name.name}` : name.name;
|
|
868
|
+
}
|
|
869
|
+
function exprKind(node) {
|
|
870
|
+
switch (node.type) {
|
|
871
|
+
case "CallExpression":
|
|
872
|
+
case "OptionalCallExpression":
|
|
873
|
+
return "call";
|
|
874
|
+
case "MemberExpression":
|
|
875
|
+
case "OptionalMemberExpression":
|
|
876
|
+
return "member";
|
|
877
|
+
case "ConditionalExpression":
|
|
878
|
+
case "LogicalExpression":
|
|
879
|
+
return "conditional";
|
|
880
|
+
case "TemplateLiteral":
|
|
881
|
+
case "TaggedTemplateExpression":
|
|
882
|
+
return "template";
|
|
883
|
+
case "Identifier":
|
|
884
|
+
return "identifier";
|
|
885
|
+
case "SpreadElement":
|
|
886
|
+
return "spread";
|
|
887
|
+
default:
|
|
888
|
+
return "other";
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
function classFormOf(node) {
|
|
892
|
+
switch (node.type) {
|
|
893
|
+
case "TemplateLiteral":
|
|
894
|
+
case "TaggedTemplateExpression":
|
|
895
|
+
return "template-literal";
|
|
896
|
+
case "CallExpression":
|
|
897
|
+
case "OptionalCallExpression":
|
|
898
|
+
return "call";
|
|
899
|
+
case "ConditionalExpression":
|
|
900
|
+
case "LogicalExpression":
|
|
901
|
+
return "conditional";
|
|
902
|
+
case "MemberExpression":
|
|
903
|
+
case "OptionalMemberExpression":
|
|
904
|
+
case "Identifier":
|
|
905
|
+
return "member";
|
|
906
|
+
default:
|
|
907
|
+
return "call";
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
function looksLikeJsx(id, code) {
|
|
911
|
+
if (/\.[jt]sx$/i.test(id)) return true;
|
|
912
|
+
return /<\/?[A-Za-z][\w.-]*|<>/.test(code);
|
|
913
|
+
}
|
|
914
|
+
function doParse(code, ctx) {
|
|
915
|
+
const diagnostics = [];
|
|
916
|
+
const doc = createDocument("jsx");
|
|
917
|
+
const backref = doc.backref;
|
|
918
|
+
const ast = babelParse(code, {
|
|
919
|
+
sourceType: "module",
|
|
920
|
+
plugins: ["jsx", "typescript"]
|
|
921
|
+
});
|
|
922
|
+
const eol = code.includes("\r\n") ? "\r\n" : "\n";
|
|
923
|
+
const sourceFile = {
|
|
924
|
+
id: FILE_ID,
|
|
925
|
+
path: ctx.id,
|
|
926
|
+
text: code,
|
|
927
|
+
frontend: "jsx",
|
|
928
|
+
eol,
|
|
929
|
+
indentUnit: " ",
|
|
930
|
+
native: ast
|
|
931
|
+
};
|
|
932
|
+
doc.sources.set(FILE_ID, sourceFile);
|
|
933
|
+
const spanOf = (node) => {
|
|
934
|
+
if (node.start == null || node.end == null) return null;
|
|
935
|
+
const span = {
|
|
936
|
+
file: FILE_ID,
|
|
937
|
+
start: node.start,
|
|
938
|
+
end: node.end,
|
|
939
|
+
startLoc: node.loc ? { line: node.loc.start.line, column: node.loc.start.column } : void 0,
|
|
940
|
+
endLoc: node.loc ? { line: node.loc.end.line, column: node.loc.end.column } : void 0
|
|
941
|
+
};
|
|
942
|
+
return span;
|
|
943
|
+
};
|
|
944
|
+
const sliceOf = (node) => node.start == null || node.end == null ? "" : code.slice(node.start, node.end);
|
|
945
|
+
const internExpr = (node, spread) => {
|
|
946
|
+
const payload = { text: sliceOf(node), spread };
|
|
947
|
+
return doc.exprs.intern({
|
|
948
|
+
span: spanOf(node) ?? { file: FILE_ID, start: 0, end: 0 },
|
|
949
|
+
kind: exprKind(node),
|
|
950
|
+
payload
|
|
951
|
+
});
|
|
952
|
+
};
|
|
953
|
+
const splitTokens = (raw) => raw.split(/\s+/).filter((t) => t.length > 0).map((value) => ({ value }));
|
|
954
|
+
const buildClassList = (attr) => {
|
|
955
|
+
const attrSpan = spanOf(attr) ?? void 0;
|
|
956
|
+
const v = attr.value;
|
|
957
|
+
const staticList = (tokens, valueSpan) => {
|
|
958
|
+
const seg = { kind: "static", span: valueSpan ?? void 0, tokens };
|
|
959
|
+
return {
|
|
960
|
+
form: "string-literal",
|
|
961
|
+
segments: [seg],
|
|
962
|
+
valueSpan,
|
|
963
|
+
attrSpan,
|
|
964
|
+
hasDynamic: false,
|
|
965
|
+
opaque: false,
|
|
966
|
+
rewritable: true
|
|
967
|
+
};
|
|
968
|
+
};
|
|
969
|
+
if (v == null) return staticList([], null);
|
|
970
|
+
if (v.type === "StringLiteral") {
|
|
971
|
+
return staticList(splitTokens(v.value), spanOf(v));
|
|
972
|
+
}
|
|
973
|
+
if (v.type === "JSXExpressionContainer") {
|
|
974
|
+
const expr = v.expression;
|
|
975
|
+
if (expr.type === "StringLiteral") {
|
|
976
|
+
return staticList(splitTokens(expr.value), spanOf(expr));
|
|
977
|
+
}
|
|
978
|
+
if (expr.type === "JSXEmptyExpression") return staticList([], null);
|
|
979
|
+
const ref = internExpr(expr, false);
|
|
980
|
+
const valueSpan = spanOf(expr);
|
|
981
|
+
const seg = { kind: "dynamic", span: valueSpan ?? void 0, expr: ref };
|
|
982
|
+
return {
|
|
983
|
+
form: classFormOf(expr),
|
|
984
|
+
segments: [seg],
|
|
985
|
+
valueSpan,
|
|
986
|
+
attrSpan,
|
|
987
|
+
hasDynamic: true,
|
|
988
|
+
opaque: true,
|
|
989
|
+
rewritable: false
|
|
990
|
+
};
|
|
991
|
+
}
|
|
992
|
+
return emptyClassList();
|
|
993
|
+
};
|
|
994
|
+
const staticTokensOf2 = (classes) => {
|
|
995
|
+
const out = [];
|
|
996
|
+
for (const seg of classes.segments) {
|
|
997
|
+
if (seg.kind === "static") for (const t of seg.tokens) out.push(t.value);
|
|
998
|
+
}
|
|
999
|
+
return out;
|
|
1000
|
+
};
|
|
1001
|
+
const buildAttrValue = (attr) => {
|
|
1002
|
+
const v = attr.value;
|
|
1003
|
+
if (v == null) return { kind: "static", value: true, span: spanOf(attr) ?? void 0 };
|
|
1004
|
+
if (v.type === "StringLiteral") {
|
|
1005
|
+
return { kind: "static", value: v.value, span: spanOf(v) ?? void 0 };
|
|
1006
|
+
}
|
|
1007
|
+
if (v.type === "JSXExpressionContainer") {
|
|
1008
|
+
if (v.expression.type === "JSXEmptyExpression") {
|
|
1009
|
+
return { kind: "static", value: true, span: spanOf(v) ?? void 0 };
|
|
1010
|
+
}
|
|
1011
|
+
return { kind: "dynamic", expr: internExpr(v.expression, false), span: spanOf(v) ?? void 0 };
|
|
1012
|
+
}
|
|
1013
|
+
return { kind: "dynamic", expr: internExpr(v, false), span: spanOf(v) ?? void 0 };
|
|
1014
|
+
};
|
|
1015
|
+
const buildChild = (node, parentId) => {
|
|
1016
|
+
switch (node.type) {
|
|
1017
|
+
case "JSXText": {
|
|
1018
|
+
const id = doc.alloc.next();
|
|
1019
|
+
doc.nodes.set(
|
|
1020
|
+
id,
|
|
1021
|
+
createText(id, node.value, {
|
|
1022
|
+
parent: parentId,
|
|
1023
|
+
span: spanOf(node),
|
|
1024
|
+
collapsible: /^\s*$/.test(node.value)
|
|
1025
|
+
})
|
|
1026
|
+
);
|
|
1027
|
+
return id;
|
|
1028
|
+
}
|
|
1029
|
+
case "JSXExpressionContainer": {
|
|
1030
|
+
if (node.expression.type === "JSXEmptyExpression") return null;
|
|
1031
|
+
const id = doc.alloc.next();
|
|
1032
|
+
const ref = internExpr(node.expression, false);
|
|
1033
|
+
doc.nodes.set(id, createExpr(id, ref, { parent: parentId, span: spanOf(node) }));
|
|
1034
|
+
return id;
|
|
1035
|
+
}
|
|
1036
|
+
case "JSXSpreadChild": {
|
|
1037
|
+
const id = doc.alloc.next();
|
|
1038
|
+
const ref = internExpr(node.expression, true);
|
|
1039
|
+
doc.nodes.set(id, createExpr(id, ref, { parent: parentId, span: spanOf(node) }));
|
|
1040
|
+
return id;
|
|
1041
|
+
}
|
|
1042
|
+
case "JSXElement":
|
|
1043
|
+
return buildElement(node, parentId);
|
|
1044
|
+
case "JSXFragment":
|
|
1045
|
+
return buildFragment(node, parentId);
|
|
1046
|
+
default:
|
|
1047
|
+
return null;
|
|
1048
|
+
}
|
|
1049
|
+
};
|
|
1050
|
+
const buildFragment = (node, parentId) => {
|
|
1051
|
+
const id = doc.alloc.next();
|
|
1052
|
+
const children = [];
|
|
1053
|
+
for (const c of node.children) {
|
|
1054
|
+
const cid = buildChild(c, id);
|
|
1055
|
+
if (cid != null) children.push(cid);
|
|
1056
|
+
}
|
|
1057
|
+
doc.nodes.set(id, createFragment(id, { children, parent: parentId, span: spanOf(node) }));
|
|
1058
|
+
backref.set(id, {
|
|
1059
|
+
nodeId: id,
|
|
1060
|
+
span: spanOf(node) ?? { file: FILE_ID, start: 0, end: 0 },
|
|
1061
|
+
openTagSpan: spanOf(node.openingFragment),
|
|
1062
|
+
closeTagSpan: spanOf(node.closingFragment),
|
|
1063
|
+
innerSpan: null,
|
|
1064
|
+
selfClosing: false
|
|
1065
|
+
});
|
|
1066
|
+
return id;
|
|
1067
|
+
};
|
|
1068
|
+
const buildElement = (node, parentId) => {
|
|
1069
|
+
const id = doc.alloc.next();
|
|
1070
|
+
const opening = node.openingElement;
|
|
1071
|
+
const tag = jsxName(opening.name);
|
|
1072
|
+
const component = isComponentName(opening.name);
|
|
1073
|
+
const meta = defaultMeta();
|
|
1074
|
+
meta.isComponent = component;
|
|
1075
|
+
let classes = emptyClassList();
|
|
1076
|
+
const entries = /* @__PURE__ */ new Map();
|
|
1077
|
+
const order = [];
|
|
1078
|
+
const spreads = [];
|
|
1079
|
+
for (const attr of opening.attributes) {
|
|
1080
|
+
if (attr.type === "JSXSpreadAttribute") {
|
|
1081
|
+
spreads.push(internExpr(attr.argument, true));
|
|
1082
|
+
meta.hasSpreadAttrs = true;
|
|
1083
|
+
continue;
|
|
1084
|
+
}
|
|
1085
|
+
const name = attrName(attr.name);
|
|
1086
|
+
if (name === "className" || name === "class") {
|
|
1087
|
+
classes = buildClassList(attr);
|
|
1088
|
+
continue;
|
|
1089
|
+
}
|
|
1090
|
+
if (name === "ref") meta.hasRef = true;
|
|
1091
|
+
else if (name === "key") meta.hasKey = true;
|
|
1092
|
+
else if (name === "dangerouslySetInnerHTML") meta.hasDangerousHtml = true;
|
|
1093
|
+
else if (/^on[A-Z]/.test(name)) meta.hasEventHandlers = true;
|
|
1094
|
+
entries.set(name, buildAttrValue(attr));
|
|
1095
|
+
order.push(name);
|
|
1096
|
+
}
|
|
1097
|
+
const attrs = { entries, spreads, order };
|
|
1098
|
+
const children = [];
|
|
1099
|
+
for (const c of node.children) {
|
|
1100
|
+
const cid = buildChild(c, id);
|
|
1101
|
+
if (cid != null) children.push(cid);
|
|
1102
|
+
}
|
|
1103
|
+
for (const cid of children) {
|
|
1104
|
+
const cn = doc.nodes.get(cid);
|
|
1105
|
+
if (cn && cn.kind === "expr") {
|
|
1106
|
+
meta.hasDynamicChildren = true;
|
|
1107
|
+
break;
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
let computed2 = emptyStyleMap();
|
|
1111
|
+
if (!classes.hasDynamic) {
|
|
1112
|
+
const tokens = staticTokensOf2(classes);
|
|
1113
|
+
if (tokens.length > 0) {
|
|
1114
|
+
const res = ctx.resolver.resolve({
|
|
1115
|
+
classes: tokens,
|
|
1116
|
+
element: { tagName: tag, namespace: component ? void 0 : "html" }
|
|
1117
|
+
});
|
|
1118
|
+
computed2 = ctx.normalizer.normalizeStyleMap(res.styles);
|
|
1119
|
+
for (const w of res.warnings) {
|
|
1120
|
+
diagnostics.push({
|
|
1121
|
+
code: "DF_STYLE_CONFLICT_UNRESOLVED",
|
|
1122
|
+
severity: w.severity,
|
|
1123
|
+
message: w.message,
|
|
1124
|
+
nodeId: id
|
|
1125
|
+
});
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
const namespace = component ? "component" : "html";
|
|
1130
|
+
const el = createElement(id, {
|
|
1131
|
+
tag,
|
|
1132
|
+
namespace,
|
|
1133
|
+
isComponent: component,
|
|
1134
|
+
selfClosing: opening.selfClosing,
|
|
1135
|
+
classes,
|
|
1136
|
+
computed: computed2,
|
|
1137
|
+
attrs,
|
|
1138
|
+
children,
|
|
1139
|
+
parent: parentId,
|
|
1140
|
+
span: spanOf(node),
|
|
1141
|
+
meta
|
|
1142
|
+
});
|
|
1143
|
+
doc.nodes.set(id, el);
|
|
1144
|
+
const inner = children.length > 0 ? spanOf(node.children[0]) && spanOf(node.children.at(-1)) ? {
|
|
1145
|
+
file: FILE_ID,
|
|
1146
|
+
start: spanOf(node.children[0]).start,
|
|
1147
|
+
end: spanOf(node.children.at(-1)).end
|
|
1148
|
+
} : null : null;
|
|
1149
|
+
backref.set(id, {
|
|
1150
|
+
nodeId: id,
|
|
1151
|
+
span: spanOf(node) ?? { file: FILE_ID, start: 0, end: 0 },
|
|
1152
|
+
openTagSpan: spanOf(opening),
|
|
1153
|
+
closeTagSpan: node.closingElement ? spanOf(node.closingElement) : null,
|
|
1154
|
+
innerSpan: inner,
|
|
1155
|
+
selfClosing: opening.selfClosing
|
|
1156
|
+
});
|
|
1157
|
+
return id;
|
|
1158
|
+
};
|
|
1159
|
+
const roots = [];
|
|
1160
|
+
traverse(ast, {
|
|
1161
|
+
JSXElement(path3) {
|
|
1162
|
+
roots.push(path3.node);
|
|
1163
|
+
path3.skip();
|
|
1164
|
+
},
|
|
1165
|
+
JSXFragment(path3) {
|
|
1166
|
+
roots.push(path3.node);
|
|
1167
|
+
path3.skip();
|
|
1168
|
+
}
|
|
1169
|
+
});
|
|
1170
|
+
const rootFrag = doc.nodes.get(doc.root);
|
|
1171
|
+
for (const r of roots) {
|
|
1172
|
+
const id = r.type === "JSXFragment" ? buildFragment(r, doc.root) : buildElement(r, doc.root);
|
|
1173
|
+
rootFrag.children.push(id);
|
|
1174
|
+
}
|
|
1175
|
+
return { doc, diagnostics };
|
|
1176
|
+
}
|
|
1177
|
+
var jsxFrontend = {
|
|
1178
|
+
name: "babel-jsx",
|
|
1179
|
+
langs: JSX_LANGS,
|
|
1180
|
+
canParse(id, code) {
|
|
1181
|
+
return looksLikeJsx(id, code);
|
|
1182
|
+
},
|
|
1183
|
+
parse(code, ctx) {
|
|
1184
|
+
return doParse(code, ctx);
|
|
1185
|
+
}
|
|
1186
|
+
};
|
|
1187
|
+
function createJsxFrontend() {
|
|
1188
|
+
return jsxFrontend;
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
// ../frontend-jsx/src/backend.ts
|
|
1192
|
+
init_esm_shims();
|
|
1193
|
+
import MagicString from "magic-string";
|
|
1194
|
+
var JSX_LANGS2 = ["jsx", "tsx"];
|
|
1195
|
+
function exprText(doc, ref) {
|
|
1196
|
+
const rec = doc.exprs.get(ref);
|
|
1197
|
+
const payload = rec?.payload;
|
|
1198
|
+
if (payload && typeof payload.text === "string") {
|
|
1199
|
+
return { text: payload.text, spread: payload.spread === true };
|
|
1200
|
+
}
|
|
1201
|
+
if (rec) {
|
|
1202
|
+
const sf = doc.sources.get(rec.span.file);
|
|
1203
|
+
if (sf) return { text: sf.text.slice(rec.span.start, rec.span.end), spread: false };
|
|
1204
|
+
}
|
|
1205
|
+
return { text: "", spread: false };
|
|
1206
|
+
}
|
|
1207
|
+
function staticTokensOf(classes) {
|
|
1208
|
+
const out = [];
|
|
1209
|
+
for (const seg of classes.segments) {
|
|
1210
|
+
if (seg.kind === "static") for (const t of seg.tokens) out.push(t.value);
|
|
1211
|
+
}
|
|
1212
|
+
return out;
|
|
1213
|
+
}
|
|
1214
|
+
function primarySource(doc) {
|
|
1215
|
+
for (const sf of doc.sources.values()) {
|
|
1216
|
+
if (typeof sf.text === "string" && sf.text.length > 0) return sf;
|
|
1217
|
+
}
|
|
1218
|
+
return null;
|
|
1219
|
+
}
|
|
1220
|
+
function collectKept(doc) {
|
|
1221
|
+
const out = [];
|
|
1222
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1223
|
+
const visit = (id) => {
|
|
1224
|
+
if (seen.has(id)) return;
|
|
1225
|
+
seen.add(id);
|
|
1226
|
+
const n = doc.nodes.get(id);
|
|
1227
|
+
if (!n) return;
|
|
1228
|
+
out.push(n);
|
|
1229
|
+
if (n.kind === "element" || n.kind === "fragment") for (const c of n.children) visit(c);
|
|
1230
|
+
};
|
|
1231
|
+
visit(doc.root);
|
|
1232
|
+
return out;
|
|
1233
|
+
}
|
|
1234
|
+
function strictlyContains(a, b) {
|
|
1235
|
+
if (a.file !== b.file) return false;
|
|
1236
|
+
if (a.start <= b.start && b.end <= a.end) return !(a.start === b.start && a.end === b.end);
|
|
1237
|
+
return false;
|
|
1238
|
+
}
|
|
1239
|
+
function editClasses(ms, doc, sf, el) {
|
|
1240
|
+
const classes = el.classes;
|
|
1241
|
+
if (classes.hasDynamic || classes.opaque) return false;
|
|
1242
|
+
const tokens = staticTokensOf(classes);
|
|
1243
|
+
const valueSpan = classes.valueSpan;
|
|
1244
|
+
if (valueSpan && valueSpan.file === sf.id) {
|
|
1245
|
+
const current = sf.text.slice(valueSpan.start, valueSpan.end);
|
|
1246
|
+
const quote = current.startsWith("'") ? "'" : '"';
|
|
1247
|
+
const next = `${quote}${tokens.join(" ")}${quote}`;
|
|
1248
|
+
if (current !== next) {
|
|
1249
|
+
ms.overwrite(valueSpan.start, valueSpan.end, next);
|
|
1250
|
+
return true;
|
|
1251
|
+
}
|
|
1252
|
+
return false;
|
|
1253
|
+
}
|
|
1254
|
+
if (tokens.length === 0) return false;
|
|
1255
|
+
if (el.isComponent) return false;
|
|
1256
|
+
const back = doc.backref.get(el.id);
|
|
1257
|
+
const openTag = back?.openTagSpan;
|
|
1258
|
+
if (!openTag || openTag.file !== sf.id) return false;
|
|
1259
|
+
const insertAt = openTag.start + 1 + el.tag.length;
|
|
1260
|
+
ms.appendLeft(insertAt, ` className="${tokens.join(" ")}"`);
|
|
1261
|
+
return true;
|
|
1262
|
+
}
|
|
1263
|
+
function surgicalPrint(doc) {
|
|
1264
|
+
const sf = primarySource(doc);
|
|
1265
|
+
if (!sf) return null;
|
|
1266
|
+
const ms = new MagicString(sf.text);
|
|
1267
|
+
const kept = collectKept(doc);
|
|
1268
|
+
const keptSpans = [];
|
|
1269
|
+
for (const n of kept) if (n.span && n.span.file === sf.id) keptSpans.push(n.span);
|
|
1270
|
+
const removed = [];
|
|
1271
|
+
for (const id of backrefIds(doc)) {
|
|
1272
|
+
if (doc.nodes.has(id)) continue;
|
|
1273
|
+
const back = doc.backref.get(id);
|
|
1274
|
+
if (!back || back.span.file !== sf.id) continue;
|
|
1275
|
+
const unwrapped = keptSpans.some((k) => strictlyContains(back.span, k));
|
|
1276
|
+
removed.push({ backref: back, unwrapped });
|
|
1277
|
+
}
|
|
1278
|
+
const fullRemovals = removed.filter((r) => !r.unwrapped).map((r) => r.backref.span);
|
|
1279
|
+
for (const r of removed) {
|
|
1280
|
+
const span = r.backref.span;
|
|
1281
|
+
const coveredByFull = fullRemovals.some((f) => f !== span && strictlyContains(f, span));
|
|
1282
|
+
if (coveredByFull) continue;
|
|
1283
|
+
if (r.unwrapped) {
|
|
1284
|
+
const open = r.backref.openTagSpan;
|
|
1285
|
+
const close = r.backref.closeTagSpan;
|
|
1286
|
+
if (open && open.file === sf.id && open.end > open.start) ms.remove(open.start, open.end);
|
|
1287
|
+
if (close && close.file === sf.id && close.end > close.start) {
|
|
1288
|
+
ms.remove(close.start, close.end);
|
|
1289
|
+
}
|
|
1290
|
+
} else {
|
|
1291
|
+
ms.remove(span.start, span.end);
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
for (const n of kept) {
|
|
1295
|
+
if (n.kind === "element") editClasses(ms, doc, sf, n);
|
|
1296
|
+
}
|
|
1297
|
+
return ms.toString();
|
|
1298
|
+
}
|
|
1299
|
+
function backrefIds(doc) {
|
|
1300
|
+
const out = [];
|
|
1301
|
+
const max = doc.alloc.peek;
|
|
1302
|
+
for (let i = 1; i < max; i += 1) {
|
|
1303
|
+
const id = i;
|
|
1304
|
+
if (doc.backref.get(id)) out.push(id);
|
|
1305
|
+
}
|
|
1306
|
+
return out;
|
|
1307
|
+
}
|
|
1308
|
+
function classText(doc, classes) {
|
|
1309
|
+
if (classes.form === "absent" || classes.segments.length === 0) return null;
|
|
1310
|
+
const dynamic = classes.segments.find((s) => s.kind === "dynamic");
|
|
1311
|
+
if (dynamic && dynamic.kind === "dynamic") {
|
|
1312
|
+
return `className={${exprText(doc, dynamic.expr).text}}`;
|
|
1313
|
+
}
|
|
1314
|
+
const tokens = staticTokensOf(classes);
|
|
1315
|
+
return `className="${tokens.join(" ")}"`;
|
|
1316
|
+
}
|
|
1317
|
+
function attrText(doc, name, value) {
|
|
1318
|
+
if (value.kind === "static") {
|
|
1319
|
+
if (value.value === true) return name;
|
|
1320
|
+
if (value.value === false) return "";
|
|
1321
|
+
return `${name}="${String(value.value)}"`;
|
|
1322
|
+
}
|
|
1323
|
+
return `${name}={${exprText(doc, value.expr).text}}`;
|
|
1324
|
+
}
|
|
1325
|
+
function printElement(doc, el) {
|
|
1326
|
+
const parts = [];
|
|
1327
|
+
const cls = classText(doc, el.classes);
|
|
1328
|
+
if (cls !== null) parts.push(cls);
|
|
1329
|
+
for (const name of el.attrs.order) {
|
|
1330
|
+
const v = el.attrs.entries.get(name);
|
|
1331
|
+
if (!v) continue;
|
|
1332
|
+
const text = attrText(doc, name, v);
|
|
1333
|
+
if (text.length > 0) parts.push(text);
|
|
1334
|
+
}
|
|
1335
|
+
for (const ref of el.attrs.spreads) parts.push(`{...${exprText(doc, ref).text}}`);
|
|
1336
|
+
const attrStr = parts.length > 0 ? ` ${parts.join(" ")}` : "";
|
|
1337
|
+
const tag = el.tag;
|
|
1338
|
+
if (el.children.length === 0) {
|
|
1339
|
+
return el.selfClosing ? `<${tag}${attrStr} />` : `<${tag}${attrStr}></${tag}>`;
|
|
1340
|
+
}
|
|
1341
|
+
const inner = el.children.map((c) => printNode(doc, c)).join("");
|
|
1342
|
+
return `<${tag}${attrStr}>${inner}</${tag}>`;
|
|
1343
|
+
}
|
|
1344
|
+
function printNode(doc, id) {
|
|
1345
|
+
const node = doc.nodes.get(id);
|
|
1346
|
+
if (!node) return "";
|
|
1347
|
+
switch (node.kind) {
|
|
1348
|
+
case "text":
|
|
1349
|
+
return node.value;
|
|
1350
|
+
case "comment":
|
|
1351
|
+
return `{/*${node.value}*/}`;
|
|
1352
|
+
case "expr": {
|
|
1353
|
+
const { text, spread } = exprText(doc, node.expr);
|
|
1354
|
+
return spread ? `{...${text}}` : `{${text}}`;
|
|
1355
|
+
}
|
|
1356
|
+
case "fragment":
|
|
1357
|
+
return `<>${node.children.map((c) => printNode(doc, c)).join("")}</>`;
|
|
1358
|
+
case "element":
|
|
1359
|
+
return printElement(doc, node);
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
function rePrint(doc) {
|
|
1363
|
+
const root = doc.nodes.get(doc.root);
|
|
1364
|
+
if (!root || root.kind !== "fragment") return printNode(doc, doc.root);
|
|
1365
|
+
return root.children.map((c) => printNode(doc, c)).join("");
|
|
1366
|
+
}
|
|
1367
|
+
function doPrint(doc) {
|
|
1368
|
+
const surgical = surgicalPrint(doc);
|
|
1369
|
+
return surgical ?? rePrint(doc);
|
|
1370
|
+
}
|
|
1371
|
+
var jsxBackend = {
|
|
1372
|
+
name: "babel-jsx",
|
|
1373
|
+
langs: JSX_LANGS2,
|
|
1374
|
+
print(doc, _plan, _ctx) {
|
|
1375
|
+
const code = doPrint(doc);
|
|
1376
|
+
return { code, map: null, edits: [], diagnostics: [] };
|
|
1377
|
+
}
|
|
1378
|
+
};
|
|
1379
|
+
function createJsxBackend() {
|
|
1380
|
+
return jsxBackend;
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
// ../frontend-jsx/src/index.ts
|
|
1384
|
+
init_esm_shims();
|
|
1385
|
+
|
|
1386
|
+
// ../resolver-tailwind/src/index.ts
|
|
1387
|
+
init_esm_shims();
|
|
1388
|
+
import { createRequire } from "module";
|
|
1389
|
+
import * as path from "path";
|
|
1390
|
+
function moduleBase() {
|
|
1391
|
+
return typeof __filename === "string" ? __filename : import.meta.url;
|
|
1392
|
+
}
|
|
1393
|
+
function projectRequire(projectRoot) {
|
|
1394
|
+
const bases = [];
|
|
1395
|
+
if (projectRoot) bases.push(path.join(projectRoot, "__domflax__.js"));
|
|
1396
|
+
bases.push(path.join(process.cwd(), "__domflax__.js"));
|
|
1397
|
+
bases.push(moduleBase());
|
|
1398
|
+
for (const base of bases) {
|
|
1399
|
+
try {
|
|
1400
|
+
const candidate = createRequire(base);
|
|
1401
|
+
candidate.resolve("tailwindcss/package.json");
|
|
1402
|
+
return candidate;
|
|
1403
|
+
} catch {
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
return null;
|
|
1407
|
+
}
|
|
1408
|
+
function loadEngine(options) {
|
|
1409
|
+
const req = projectRequire(options.projectRoot);
|
|
1410
|
+
if (!req) return null;
|
|
1411
|
+
try {
|
|
1412
|
+
const resolveConfig = req("tailwindcss/resolveConfig.js");
|
|
1413
|
+
const { createContext } = req("tailwindcss/lib/lib/setupContextUtils.js");
|
|
1414
|
+
const { generateRules } = req("tailwindcss/lib/lib/generateRules.js");
|
|
1415
|
+
const pkg = req("tailwindcss/package.json");
|
|
1416
|
+
let userConfig = options.config ?? { content: [{ raw: "" }] };
|
|
1417
|
+
if (options.configPath !== void 0) {
|
|
1418
|
+
const loadConfig = req("tailwindcss/loadConfig.js");
|
|
1419
|
+
userConfig = loadConfig(options.configPath);
|
|
1420
|
+
}
|
|
1421
|
+
const resolved = resolveConfig(userConfig);
|
|
1422
|
+
const context = createContext(resolved);
|
|
1423
|
+
return {
|
|
1424
|
+
version: pkg.version,
|
|
1425
|
+
context,
|
|
1426
|
+
generate(candidates) {
|
|
1427
|
+
const rules = generateRules(new Set(candidates), context);
|
|
1428
|
+
return rules.map(([, node]) => node);
|
|
1429
|
+
}
|
|
1430
|
+
};
|
|
1431
|
+
} catch {
|
|
1432
|
+
return null;
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
var LEGACY_PSEUDO_ELEMENTS = /* @__PURE__ */ new Set([
|
|
1436
|
+
":before",
|
|
1437
|
+
":after",
|
|
1438
|
+
":first-line",
|
|
1439
|
+
":first-letter"
|
|
1440
|
+
]);
|
|
1441
|
+
function parseSelector(selector) {
|
|
1442
|
+
const sel = selector.trim();
|
|
1443
|
+
if (sel.length === 0 || sel[0] !== ".") return { kind: "complex" };
|
|
1444
|
+
let i = 1;
|
|
1445
|
+
for (; i < sel.length; i += 1) {
|
|
1446
|
+
const c = sel[i];
|
|
1447
|
+
if (c === "\\") {
|
|
1448
|
+
i += 1;
|
|
1449
|
+
continue;
|
|
1450
|
+
}
|
|
1451
|
+
if (c === ":" || c === "." || c === "[" || c === " " || c === ">" || c === "+" || c === "~" || c === ",") {
|
|
1452
|
+
break;
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
const remainder = sel.slice(i);
|
|
1456
|
+
if (remainder.length === 0) {
|
|
1457
|
+
return { kind: "simple", states: [], pseudoElement: "" };
|
|
1458
|
+
}
|
|
1459
|
+
if (!/^(?:::?[-a-z]+(?:\([^()]*\))?)+$/i.test(remainder)) {
|
|
1460
|
+
return { kind: "complex" };
|
|
1461
|
+
}
|
|
1462
|
+
const parts = remainder.match(/::?[-a-z]+(?:\([^()]*\))?/gi) ?? [];
|
|
1463
|
+
const states = [];
|
|
1464
|
+
let pseudoElement = "";
|
|
1465
|
+
for (const part of parts) {
|
|
1466
|
+
if (part.startsWith("::") || LEGACY_PSEUDO_ELEMENTS.has(part)) {
|
|
1467
|
+
pseudoElement = part.startsWith("::") ? part : `:${part}`;
|
|
1468
|
+
} else {
|
|
1469
|
+
states.push(part);
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
return { kind: "simple", states, pseudoElement };
|
|
1473
|
+
}
|
|
1474
|
+
function makeCondition(media, states, pseudoElement) {
|
|
1475
|
+
return {
|
|
1476
|
+
media,
|
|
1477
|
+
states: [...new Set(states)].sort(),
|
|
1478
|
+
pseudoElement
|
|
1479
|
+
};
|
|
1480
|
+
}
|
|
1481
|
+
function collectRules(node, mediaStack, inUnsupportedAtRule, out) {
|
|
1482
|
+
if (node.type === "rule") {
|
|
1483
|
+
out.push({ rule: node, media: mediaStack, unsupported: inUnsupportedAtRule });
|
|
1484
|
+
return;
|
|
1485
|
+
}
|
|
1486
|
+
if (node.type === "atrule") {
|
|
1487
|
+
const at = node;
|
|
1488
|
+
const children = at.nodes ?? [];
|
|
1489
|
+
if (at.name === "media") {
|
|
1490
|
+
const nextStack = at.params ? [...mediaStack, at.params] : mediaStack;
|
|
1491
|
+
for (const child of children) collectRules(child, nextStack, inUnsupportedAtRule, out);
|
|
1492
|
+
} else {
|
|
1493
|
+
for (const child of children) collectRules(child, mediaStack, true, out);
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
function extractToken(token, nodes) {
|
|
1498
|
+
if (nodes.length === 0) return { blocks: [], produced: false };
|
|
1499
|
+
const leaves = [];
|
|
1500
|
+
for (const node of nodes) collectRules(node, [], false, leaves);
|
|
1501
|
+
const blocks = [];
|
|
1502
|
+
let sawComplex = false;
|
|
1503
|
+
for (const { rule, media, unsupported } of leaves) {
|
|
1504
|
+
const parsed = parseSelector(rule.selector);
|
|
1505
|
+
if (parsed.kind === "complex" || unsupported) {
|
|
1506
|
+
sawComplex = true;
|
|
1507
|
+
continue;
|
|
1508
|
+
}
|
|
1509
|
+
const decls = [];
|
|
1510
|
+
for (const child of rule.nodes ?? []) {
|
|
1511
|
+
if (child.type !== "decl") continue;
|
|
1512
|
+
const d = child;
|
|
1513
|
+
if (typeof d.value !== "string") continue;
|
|
1514
|
+
decls.push([d.prop, d.value, d.important === true]);
|
|
1515
|
+
}
|
|
1516
|
+
if (decls.length === 0) continue;
|
|
1517
|
+
const mediaQuery = media.join(" and ");
|
|
1518
|
+
blocks.push({ condition: makeCondition(mediaQuery, parsed.states, parsed.pseudoElement), decls });
|
|
1519
|
+
}
|
|
1520
|
+
const opaque = sawComplex && blocks.length === 0 ? { token, reason: "combinator-variant", detail: "utility targets descendants/siblings, not its own box" } : void 0;
|
|
1521
|
+
return { blocks, produced: true, opaque };
|
|
1522
|
+
}
|
|
1523
|
+
function buildStyleMap(blockMaps) {
|
|
1524
|
+
if (blockMaps.size === 0) return emptyStyleMap();
|
|
1525
|
+
const blocks = /* @__PURE__ */ new Map();
|
|
1526
|
+
for (const { condition, decls } of blockMaps.values()) {
|
|
1527
|
+
if (decls.size === 0) continue;
|
|
1528
|
+
blocks.set(conditionKey(condition), { condition, decls });
|
|
1529
|
+
}
|
|
1530
|
+
if (blocks.size === 0) return emptyStyleMap();
|
|
1531
|
+
return normalizer.normalizeStyleMap({ blocks });
|
|
1532
|
+
}
|
|
1533
|
+
function shadowedBy(prev) {
|
|
1534
|
+
const out = [];
|
|
1535
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1536
|
+
const add = (o) => {
|
|
1537
|
+
if (!o || o.kind !== "class" || seen.has(o.className)) return;
|
|
1538
|
+
seen.add(o.className);
|
|
1539
|
+
out.push(o);
|
|
1540
|
+
};
|
|
1541
|
+
for (const o of prev.shadowed ?? []) add(o);
|
|
1542
|
+
add(prev.origin);
|
|
1543
|
+
return out.length > 0 ? out : void 0;
|
|
1544
|
+
}
|
|
1545
|
+
var OPAQUE_USAGE = {
|
|
1546
|
+
asSubject: true,
|
|
1547
|
+
asAncestor: true,
|
|
1548
|
+
asCompound: true,
|
|
1549
|
+
asSibling: true,
|
|
1550
|
+
asHasArgument: true,
|
|
1551
|
+
asStructural: true,
|
|
1552
|
+
droppable: false
|
|
1553
|
+
};
|
|
1554
|
+
var DROPPABLE_USAGE = {
|
|
1555
|
+
asSubject: true,
|
|
1556
|
+
asAncestor: false,
|
|
1557
|
+
asCompound: false,
|
|
1558
|
+
asSibling: false,
|
|
1559
|
+
asHasArgument: false,
|
|
1560
|
+
asStructural: false,
|
|
1561
|
+
droppable: true
|
|
1562
|
+
};
|
|
1563
|
+
function fnv1a(input) {
|
|
1564
|
+
let h = 2166136261;
|
|
1565
|
+
for (let i = 0; i < input.length; i += 1) {
|
|
1566
|
+
h ^= input.charCodeAt(i);
|
|
1567
|
+
h = Math.imul(h, 16777619);
|
|
1568
|
+
}
|
|
1569
|
+
return (h >>> 0).toString(16).padStart(8, "0");
|
|
1570
|
+
}
|
|
1571
|
+
var TailwindResolver = class {
|
|
1572
|
+
id = "tailwind";
|
|
1573
|
+
provider;
|
|
1574
|
+
fingerprint;
|
|
1575
|
+
#engine;
|
|
1576
|
+
/** Per-token extraction cache (engine output is pure for a fixed config). */
|
|
1577
|
+
#tokenCache = /* @__PURE__ */ new Map();
|
|
1578
|
+
/** Per-class-set forward-resolution cache. */
|
|
1579
|
+
#resolveCache = /* @__PURE__ */ new Map();
|
|
1580
|
+
/** Lazily built reverse index for {@link emit}. */
|
|
1581
|
+
#reverseIndex = null;
|
|
1582
|
+
constructor(config = {}) {
|
|
1583
|
+
this.#engine = loadEngine(config);
|
|
1584
|
+
this.provider = config.provider ?? (this.#engine ? `tailwindcss@${this.#engine.version}` : "tailwindcss");
|
|
1585
|
+
const seed = JSON.stringify(config.config ?? {}) + (config.configPath ?? "");
|
|
1586
|
+
this.fingerprint = config.fingerprint ?? `${this.provider}/${fnv1a(seed)}`;
|
|
1587
|
+
}
|
|
1588
|
+
/** Engine-backed, cached single-token extraction. */
|
|
1589
|
+
#extract(token) {
|
|
1590
|
+
const cached = this.#tokenCache.get(token);
|
|
1591
|
+
if (cached) return cached;
|
|
1592
|
+
let result;
|
|
1593
|
+
if (!this.#engine) {
|
|
1594
|
+
result = { blocks: [], produced: false };
|
|
1595
|
+
} else {
|
|
1596
|
+
try {
|
|
1597
|
+
result = extractToken(token, this.#engine.generate([token]));
|
|
1598
|
+
} catch {
|
|
1599
|
+
result = { blocks: [], produced: false };
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
this.#tokenCache.set(token, result);
|
|
1603
|
+
return result;
|
|
1604
|
+
}
|
|
1605
|
+
owns(token) {
|
|
1606
|
+
if (token.length === 0) return false;
|
|
1607
|
+
return this.#extract(token).produced;
|
|
1608
|
+
}
|
|
1609
|
+
resolve(input) {
|
|
1610
|
+
const key = JSON.stringify(input.classes);
|
|
1611
|
+
const cached = this.#resolveCache.get(key);
|
|
1612
|
+
if (cached) return cached;
|
|
1613
|
+
const blockMaps = /* @__PURE__ */ new Map();
|
|
1614
|
+
const resolved = [];
|
|
1615
|
+
const unknown = [];
|
|
1616
|
+
const opaque = [];
|
|
1617
|
+
input.classes.forEach((token, tokenIndex) => {
|
|
1618
|
+
const extracted = this.#extract(token);
|
|
1619
|
+
if (!extracted.produced) {
|
|
1620
|
+
unknown.push(token);
|
|
1621
|
+
return;
|
|
1622
|
+
}
|
|
1623
|
+
if (extracted.opaque) opaque.push(extracted.opaque);
|
|
1624
|
+
if (extracted.blocks.length === 0) return;
|
|
1625
|
+
const origin = { kind: "class", tokenIndex, className: token };
|
|
1626
|
+
let contributed = false;
|
|
1627
|
+
for (const block of extracted.blocks) {
|
|
1628
|
+
const ck = conditionKey(block.condition);
|
|
1629
|
+
let bucket = blockMaps.get(ck);
|
|
1630
|
+
if (!bucket) {
|
|
1631
|
+
bucket = { condition: block.condition, decls: /* @__PURE__ */ new Map() };
|
|
1632
|
+
blockMaps.set(ck, bucket);
|
|
1633
|
+
}
|
|
1634
|
+
for (const [prop, value, important] of block.decls) {
|
|
1635
|
+
for (const decl of normalizer.normalizeDeclaration(prop, value, important)) {
|
|
1636
|
+
const prev = bucket.decls.get(decl.property);
|
|
1637
|
+
const shadowed = prev ? shadowedBy(prev) : void 0;
|
|
1638
|
+
bucket.decls.set(decl.property, shadowed ? { ...decl, origin, shadowed } : { ...decl, origin });
|
|
1639
|
+
contributed = true;
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
if (contributed) resolved.push(token);
|
|
1644
|
+
});
|
|
1645
|
+
const result = {
|
|
1646
|
+
styles: buildStyleMap(blockMaps),
|
|
1647
|
+
resolved,
|
|
1648
|
+
unknown,
|
|
1649
|
+
opaque,
|
|
1650
|
+
warnings: []
|
|
1651
|
+
};
|
|
1652
|
+
this.#resolveCache.set(key, result);
|
|
1653
|
+
return result;
|
|
1654
|
+
}
|
|
1655
|
+
/**
|
|
1656
|
+
* Lazily build the reverse index from the engine's own enumerable class list. Each indexable
|
|
1657
|
+
* utility maps to its NORMALIZED BASE longhand declarations (property → canonical value). Utilities
|
|
1658
|
+
* with variant conditions, combinator selectors, or no BASE declarations are skipped. Sorted by
|
|
1659
|
+
* declaration count (desc) so greedier (shorthand-like) utilities are tried first.
|
|
1660
|
+
*/
|
|
1661
|
+
#buildReverseIndex() {
|
|
1662
|
+
if (this.#reverseIndex) return this.#reverseIndex;
|
|
1663
|
+
const index = [];
|
|
1664
|
+
if (this.#engine) {
|
|
1665
|
+
try {
|
|
1666
|
+
const classes = this.#engine.context.getClassList().filter((c) => typeof c === "string");
|
|
1667
|
+
const nodes = this.#engine.generate(classes);
|
|
1668
|
+
for (const node of nodes) {
|
|
1669
|
+
if (node.type !== "rule") continue;
|
|
1670
|
+
const rule = node;
|
|
1671
|
+
const parsed = parseSelector(rule.selector);
|
|
1672
|
+
if (parsed.kind !== "simple" || parsed.states.length > 0 || parsed.pseudoElement !== "") {
|
|
1673
|
+
continue;
|
|
1674
|
+
}
|
|
1675
|
+
const className = unescapeClass(rule.selector);
|
|
1676
|
+
if (className === null) continue;
|
|
1677
|
+
const decls = /* @__PURE__ */ new Map();
|
|
1678
|
+
for (const child of rule.nodes ?? []) {
|
|
1679
|
+
if (child.type !== "decl") continue;
|
|
1680
|
+
const d = child;
|
|
1681
|
+
if (typeof d.value !== "string") continue;
|
|
1682
|
+
for (const decl of normalizer.normalizeDeclaration(d.prop, d.value, d.important === true)) {
|
|
1683
|
+
decls.set(decl.property, String(decl.value));
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
if (decls.size > 0) index.push([className, decls]);
|
|
1687
|
+
}
|
|
1688
|
+
} catch {
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
index.sort((a, b) => b[1].size - a[1].size || (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0));
|
|
1692
|
+
this.#reverseIndex = index;
|
|
1693
|
+
return index;
|
|
1694
|
+
}
|
|
1695
|
+
emit(styles, ctx) {
|
|
1696
|
+
const norm = ctx.normalizer ?? normalizer;
|
|
1697
|
+
const normalized = norm.normalizeStyleMap(styles);
|
|
1698
|
+
const base = normalized.blocks.get(conditionKey(BASE_CONDITION));
|
|
1699
|
+
if (!base || base.decls.size === 0) return { classes: [], exact: true, warnings: [] };
|
|
1700
|
+
const hasNonBase = normalized.blocks.size > 1;
|
|
1701
|
+
const target = /* @__PURE__ */ new Map();
|
|
1702
|
+
for (const [prop, decl] of base.decls) {
|
|
1703
|
+
for (const [lp, lv] of expandForEmit(norm, String(prop), String(decl.value), decl.important)) {
|
|
1704
|
+
target.set(lp, lv);
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
const candidates = [];
|
|
1708
|
+
for (const entry of this.#buildReverseIndex()) {
|
|
1709
|
+
const [, declMap] = entry;
|
|
1710
|
+
if (declMap.size === 0 || declMap.size > target.size) continue;
|
|
1711
|
+
let fits = true;
|
|
1712
|
+
for (const [prop, value] of declMap) {
|
|
1713
|
+
if (target.get(prop) !== value) {
|
|
1714
|
+
fits = false;
|
|
1715
|
+
break;
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
if (fits) candidates.push(entry);
|
|
1719
|
+
}
|
|
1720
|
+
const remaining = new Map(target);
|
|
1721
|
+
const classes = [];
|
|
1722
|
+
while (remaining.size > 0) {
|
|
1723
|
+
let best = null;
|
|
1724
|
+
let bestCover = 0;
|
|
1725
|
+
for (const entry of candidates) {
|
|
1726
|
+
const [token, declMap] = entry;
|
|
1727
|
+
let cover = 0;
|
|
1728
|
+
for (const prop of declMap.keys()) if (remaining.has(prop)) cover += 1;
|
|
1729
|
+
if (cover === 0) continue;
|
|
1730
|
+
const better = best === null || cover > bestCover || cover === bestCover && declMap.size < best[1].size || cover === bestCover && declMap.size === best[1].size && token < best[0];
|
|
1731
|
+
if (better) {
|
|
1732
|
+
best = entry;
|
|
1733
|
+
bestCover = cover;
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
if (!best) break;
|
|
1737
|
+
classes.push(best[0]);
|
|
1738
|
+
for (const prop of best[1].keys()) remaining.delete(prop);
|
|
1739
|
+
}
|
|
1740
|
+
const exact = remaining.size === 0 && !hasNonBase;
|
|
1741
|
+
if (remaining.size === 0) return { classes, exact, warnings: [] };
|
|
1742
|
+
const residual = synthesizeResidual(remaining, ctx);
|
|
1743
|
+
return residual ? { classes, residual, exact, warnings: [] } : { classes, exact, warnings: [] };
|
|
1744
|
+
}
|
|
1745
|
+
selectorUsage(token) {
|
|
1746
|
+
const ex = this.#extract(token);
|
|
1747
|
+
if (!ex.produced || ex.opaque) return OPAQUE_USAGE;
|
|
1748
|
+
const baseOnly = ex.blocks.length > 0 && ex.blocks.every((b) => conditionKey(b.condition) === conditionKey(BASE_CONDITION));
|
|
1749
|
+
if (!baseOnly) return OPAQUE_USAGE;
|
|
1750
|
+
return DROPPABLE_USAGE;
|
|
1751
|
+
}
|
|
1752
|
+
};
|
|
1753
|
+
function expandForEmit(norm, prop, value, important) {
|
|
1754
|
+
const pairsFor = (p, v) => norm.normalizeDeclaration(p, v, important).map((d) => [d.property, String(d.value)]);
|
|
1755
|
+
if (prop === "size") {
|
|
1756
|
+
return [...pairsFor("width", value), ...pairsFor("height", value)];
|
|
1757
|
+
}
|
|
1758
|
+
if (prop === "inset-block" || prop === "inset-inline") {
|
|
1759
|
+
const parts = value.split(/\s+/).filter((s) => s.length > 0);
|
|
1760
|
+
const a = parts[0] ?? value;
|
|
1761
|
+
const b = parts[1] ?? a;
|
|
1762
|
+
const sides = prop === "inset-block" ? ["top", "bottom"] : ["left", "right"];
|
|
1763
|
+
return [...pairsFor(sides[0], a), ...pairsFor(sides[1], b)];
|
|
1764
|
+
}
|
|
1765
|
+
return pairsFor(prop, value);
|
|
1766
|
+
}
|
|
1767
|
+
function synthesizeResidual(remaining, ctx) {
|
|
1768
|
+
if (remaining.size === 0) return void 0;
|
|
1769
|
+
const norm = ctx.normalizer ?? normalizer;
|
|
1770
|
+
const decls = /* @__PURE__ */ new Map();
|
|
1771
|
+
for (const [prop, value] of remaining) {
|
|
1772
|
+
for (const decl of norm.normalizeDeclaration(String(prop), value, false)) {
|
|
1773
|
+
decls.set(decl.property, decl);
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
const block = { condition: BASE_CONDITION, decls };
|
|
1777
|
+
const styleMap = { blocks: /* @__PURE__ */ new Map([[conditionKey(BASE_CONDITION), block]]) };
|
|
1778
|
+
const css = [...remaining].map(([p, v]) => `${p}:${v}`).join(";");
|
|
1779
|
+
const className = `df-${fnv1a(css)}`;
|
|
1780
|
+
const synthetic = { className, decls: styleMap, css: `.${className}{${css}}` };
|
|
1781
|
+
try {
|
|
1782
|
+
ctx.sink.register(synthetic);
|
|
1783
|
+
} catch {
|
|
1784
|
+
}
|
|
1785
|
+
return synthetic;
|
|
1786
|
+
}
|
|
1787
|
+
function unescapeClass(selector) {
|
|
1788
|
+
const sel = selector.trim();
|
|
1789
|
+
if (sel[0] !== ".") return null;
|
|
1790
|
+
let out = "";
|
|
1791
|
+
for (let i = 1; i < sel.length; i += 1) {
|
|
1792
|
+
const c = sel[i];
|
|
1793
|
+
if (c === "\\") {
|
|
1794
|
+
i += 1;
|
|
1795
|
+
if (i < sel.length) out += sel[i];
|
|
1796
|
+
continue;
|
|
1797
|
+
}
|
|
1798
|
+
if (c === ":" || c === "." || c === "[" || c === " " || c === ">" || c === "+" || c === "~" || c === ",") {
|
|
1799
|
+
return null;
|
|
1800
|
+
}
|
|
1801
|
+
out += c;
|
|
1802
|
+
}
|
|
1803
|
+
return out.length > 0 ? out : null;
|
|
1804
|
+
}
|
|
1805
|
+
function createTailwindResolver(config) {
|
|
1806
|
+
return new TailwindResolver(config);
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1809
|
+
// ../resolver-css/src/index.ts
|
|
1810
|
+
init_esm_shims();
|
|
1811
|
+
import { readFileSync } from "fs";
|
|
1812
|
+
import { createRequire as createRequire2 } from "module";
|
|
1813
|
+
import * as path2 from "path";
|
|
1814
|
+
function moduleBase2() {
|
|
1815
|
+
return typeof __filename === "string" ? __filename : import.meta.url;
|
|
1816
|
+
}
|
|
1817
|
+
function loadPostcssEngine(projectRoot) {
|
|
1818
|
+
const bases = [];
|
|
1819
|
+
if (projectRoot) bases.push(path2.join(projectRoot, "__domflax__.js"));
|
|
1820
|
+
bases.push(path2.join(process.cwd(), "__domflax__.js"));
|
|
1821
|
+
bases.push(moduleBase2());
|
|
1822
|
+
for (const base of bases) {
|
|
1823
|
+
try {
|
|
1824
|
+
const req = createRequire2(base);
|
|
1825
|
+
req.resolve("postcss");
|
|
1826
|
+
req.resolve("postcss-selector-parser");
|
|
1827
|
+
const postcss = req("postcss");
|
|
1828
|
+
const raw = req("postcss-selector-parser");
|
|
1829
|
+
const selector = raw.default ?? raw;
|
|
1830
|
+
return { parse: postcss.parse, selectorParser: selector };
|
|
1831
|
+
} catch {
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
return null;
|
|
1835
|
+
}
|
|
1836
|
+
var pc = null;
|
|
1837
|
+
var sp = null;
|
|
1838
|
+
function ensurePostcss(projectRoot) {
|
|
1839
|
+
if (pc && sp) return;
|
|
1840
|
+
const engine = loadPostcssEngine(projectRoot);
|
|
1841
|
+
if (!engine) {
|
|
1842
|
+
throw new Error(
|
|
1843
|
+
'@domflax/resolver-css requires "postcss" and "postcss-selector-parser" to be installed in your project (they are optional peer dependencies of domflax, loaded only when the custom-CSS provider is used). Install them with: npm install postcss postcss-selector-parser'
|
|
1844
|
+
);
|
|
1845
|
+
}
|
|
1846
|
+
pc = engine.parse;
|
|
1847
|
+
sp = engine.selectorParser;
|
|
1848
|
+
}
|
|
1849
|
+
var CSS_RESOLVER_ID = "css";
|
|
1850
|
+
var CSS_RESOLVER_PROVIDER = "custom-css";
|
|
1851
|
+
var ENGINE_VERSION = "css-index@1";
|
|
1852
|
+
var STRUCTURAL_PSEUDOS = /* @__PURE__ */ new Set([
|
|
1853
|
+
":nth-child",
|
|
1854
|
+
":nth-last-child",
|
|
1855
|
+
":first-child",
|
|
1856
|
+
":last-child",
|
|
1857
|
+
":only-child",
|
|
1858
|
+
":nth-of-type",
|
|
1859
|
+
":nth-last-of-type",
|
|
1860
|
+
":first-of-type",
|
|
1861
|
+
":last-of-type",
|
|
1862
|
+
":only-of-type"
|
|
1863
|
+
]);
|
|
1864
|
+
var FUNCTIONAL_PSEUDOS = /* @__PURE__ */ new Set([
|
|
1865
|
+
":not",
|
|
1866
|
+
":is",
|
|
1867
|
+
":where",
|
|
1868
|
+
":has",
|
|
1869
|
+
":matches"
|
|
1870
|
+
]);
|
|
1871
|
+
var LEGACY_PSEUDO_ELEMENTS2 = /* @__PURE__ */ new Set([
|
|
1872
|
+
":before",
|
|
1873
|
+
":after",
|
|
1874
|
+
":first-line",
|
|
1875
|
+
":first-letter"
|
|
1876
|
+
]);
|
|
1877
|
+
var CustomCSSResolver = class {
|
|
1878
|
+
id = CSS_RESOLVER_ID;
|
|
1879
|
+
provider = CSS_RESOLVER_PROVIDER;
|
|
1880
|
+
fingerprint;
|
|
1881
|
+
#files;
|
|
1882
|
+
/** Forward map: class token → simple-`.class` rule contributions (source order). */
|
|
1883
|
+
#classIndex = /* @__PURE__ */ new Map();
|
|
1884
|
+
/** Selector-participation facts per class token. */
|
|
1885
|
+
#usage = /* @__PURE__ */ new Map();
|
|
1886
|
+
/** Every class referenced anywhere in the stylesheets (forward-resolvable or not). */
|
|
1887
|
+
#known = /* @__PURE__ */ new Set();
|
|
1888
|
+
/** Distinct COMPLEX selectors (combinator or structural pseudo), sorted. */
|
|
1889
|
+
#complex;
|
|
1890
|
+
#reverse = null;
|
|
1891
|
+
constructor(cssFiles = [], options = {}) {
|
|
1892
|
+
ensurePostcss(options.projectRoot);
|
|
1893
|
+
const fromDisk = (options.files ?? []).map(readCssPath);
|
|
1894
|
+
this.#files = [...cssFiles, ...fromDisk];
|
|
1895
|
+
this.fingerprint = options.fingerprint ?? deriveFingerprint(this.provider, this.#files);
|
|
1896
|
+
const complex = /* @__PURE__ */ new Set();
|
|
1897
|
+
let order = 0;
|
|
1898
|
+
for (const file of this.#files) {
|
|
1899
|
+
order = this.#indexFile(file, order, complex);
|
|
1900
|
+
}
|
|
1901
|
+
this.#complex = [...complex].sort();
|
|
1902
|
+
}
|
|
1903
|
+
/** The stylesheets this resolver was constructed with (raw sources + any read from disk). */
|
|
1904
|
+
get files() {
|
|
1905
|
+
return this.#files;
|
|
1906
|
+
}
|
|
1907
|
+
/** Owns any plain class token referenced by one of {@link files}. */
|
|
1908
|
+
owns(token) {
|
|
1909
|
+
return isPlainClassToken(token) && this.#known.has(token);
|
|
1910
|
+
}
|
|
1911
|
+
resolve(input) {
|
|
1912
|
+
const styles = this.#resolveTokens(input.classes, input.classes);
|
|
1913
|
+
const resolved = [];
|
|
1914
|
+
const unknown = [];
|
|
1915
|
+
for (const token of input.classes) {
|
|
1916
|
+
if (this.#classIndex.has(token)) resolved.push(token);
|
|
1917
|
+
else unknown.push(token);
|
|
1918
|
+
}
|
|
1919
|
+
return { styles, resolved, unknown, opaque: [], warnings: [] };
|
|
1920
|
+
}
|
|
1921
|
+
emit(styles, ctx) {
|
|
1922
|
+
const norm = ctx.normalizer ?? normalizer;
|
|
1923
|
+
const remaining = /* @__PURE__ */ new Map();
|
|
1924
|
+
for (const [ck, block] of norm.normalizeStyleMap(styles).blocks) {
|
|
1925
|
+
for (const [prop, decl] of block.decls) {
|
|
1926
|
+
remaining.set(`${ck}\0${prop}`, String(decl.value));
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
if (remaining.size === 0) return { classes: [], exact: true, warnings: [] };
|
|
1930
|
+
const classes = [];
|
|
1931
|
+
for (const { token, keyed } of this.#reverseIndex()) {
|
|
1932
|
+
let matches = true;
|
|
1933
|
+
for (const [key, value] of keyed) {
|
|
1934
|
+
if (remaining.get(key) !== value) {
|
|
1935
|
+
matches = false;
|
|
1936
|
+
break;
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
if (!matches) continue;
|
|
1940
|
+
classes.push(token);
|
|
1941
|
+
for (const key of keyed.keys()) remaining.delete(key);
|
|
1942
|
+
if (remaining.size === 0) break;
|
|
1943
|
+
}
|
|
1944
|
+
return { classes, exact: remaining.size === 0, warnings: [] };
|
|
1945
|
+
}
|
|
1946
|
+
selectorUsage(token) {
|
|
1947
|
+
const u = this.#usage.get(token);
|
|
1948
|
+
if (!u) {
|
|
1949
|
+
return {
|
|
1950
|
+
asSubject: false,
|
|
1951
|
+
asAncestor: false,
|
|
1952
|
+
asCompound: false,
|
|
1953
|
+
asSibling: false,
|
|
1954
|
+
asHasArgument: false,
|
|
1955
|
+
asStructural: false,
|
|
1956
|
+
droppable: true
|
|
1957
|
+
};
|
|
1958
|
+
}
|
|
1959
|
+
return {
|
|
1960
|
+
asSubject: u.asSubject,
|
|
1961
|
+
asAncestor: u.asAncestor,
|
|
1962
|
+
asCompound: u.asCompound,
|
|
1963
|
+
asSibling: u.asSibling,
|
|
1964
|
+
asHasArgument: u.asHasArgument,
|
|
1965
|
+
asStructural: u.asStructural,
|
|
1966
|
+
// Safe to drop/rename only when every reference is the lone subject of a bare `.x {}`.
|
|
1967
|
+
droppable: u.referenced && !u.loadBearing
|
|
1968
|
+
};
|
|
1969
|
+
}
|
|
1970
|
+
/**
|
|
1971
|
+
* The distinct COMPLEX selectors found across all stylesheets — anything containing a combinator
|
|
1972
|
+
* (descendant / `>` / `+` / `~`) or a structural pseudo (`:nth-child`, `:first-child`, …). Feeds
|
|
1973
|
+
* domflax's CSS-selector-safety guard.
|
|
1974
|
+
*/
|
|
1975
|
+
complexSelectors() {
|
|
1976
|
+
return this.#complex;
|
|
1977
|
+
}
|
|
1978
|
+
/* ─────────────────────────── internals ─────────────────────────── */
|
|
1979
|
+
/** Parse one stylesheet and fold its rules into the indexes. Returns the advanced order counter. */
|
|
1980
|
+
#indexFile(file, startOrder, complex) {
|
|
1981
|
+
let order = startOrder;
|
|
1982
|
+
let root;
|
|
1983
|
+
try {
|
|
1984
|
+
root = pc(file.css, { from: file.id });
|
|
1985
|
+
} catch {
|
|
1986
|
+
return order;
|
|
1987
|
+
}
|
|
1988
|
+
root.walkRules((rule) => {
|
|
1989
|
+
const media = mediaContext(rule);
|
|
1990
|
+
if (media.skip) return;
|
|
1991
|
+
const decls = collectDecls(rule);
|
|
1992
|
+
let ast;
|
|
1993
|
+
try {
|
|
1994
|
+
ast = sp().astSync(rule.selector);
|
|
1995
|
+
} catch {
|
|
1996
|
+
return;
|
|
1997
|
+
}
|
|
1998
|
+
for (const sel of ast.nodes) {
|
|
1999
|
+
const thisOrder = order;
|
|
2000
|
+
this.#analyzeSelector(sel, media.media, decls, thisOrder, complex);
|
|
2001
|
+
}
|
|
2002
|
+
order += 1;
|
|
2003
|
+
});
|
|
2004
|
+
return order;
|
|
2005
|
+
}
|
|
2006
|
+
/** Analyze one comma-segment selector: forward indexing, usage facts, complex detection. */
|
|
2007
|
+
#analyzeSelector(selector, media, decls, order, complex) {
|
|
2008
|
+
const compounds = splitCompounds(selector);
|
|
2009
|
+
let hasCombinator = false;
|
|
2010
|
+
let hasStructural = false;
|
|
2011
|
+
compounds.forEach((compound, index) => {
|
|
2012
|
+
const isSubject = index === compounds.length - 1;
|
|
2013
|
+
const rightCombinator = index < compounds.length - 1 ? compounds[index + 1].leftCombinator : null;
|
|
2014
|
+
if (rightCombinator) hasCombinator = true;
|
|
2015
|
+
const classes = compound.nodes.filter((n) => sp.isClassName(n));
|
|
2016
|
+
const otherSimple = compound.nodes.some(
|
|
2017
|
+
(n) => sp.isTag(n) || sp.isIdentifier(n) || sp.isAttribute(n) || sp.isUniversal(n) || sp.isNesting(n)
|
|
2018
|
+
);
|
|
2019
|
+
const pseudos = compound.nodes.filter((n) => sp.isPseudo(n));
|
|
2020
|
+
const structuralPseudo = pseudos.some((p) => STRUCTURAL_PSEUDOS.has(pseudoName(p)));
|
|
2021
|
+
const functionalPseudo = pseudos.some((p) => FUNCTIONAL_PSEUDOS.has(pseudoName(p)));
|
|
2022
|
+
const statePseudos = pseudos.filter(
|
|
2023
|
+
(p) => sp.isPseudoClass(p) && !STRUCTURAL_PSEUDOS.has(pseudoName(p)) && !FUNCTIONAL_PSEUDOS.has(pseudoName(p))
|
|
2024
|
+
);
|
|
2025
|
+
const elementPseudos = pseudos.filter((p) => isPseudoElement(p));
|
|
2026
|
+
const qualified = classes.length > 1 || otherSimple || functionalPseudo || statePseudos.length > 0;
|
|
2027
|
+
if (structuralPseudo) hasStructural = true;
|
|
2028
|
+
for (const cls of classes) {
|
|
2029
|
+
const token = cls.value;
|
|
2030
|
+
this.#known.add(token);
|
|
2031
|
+
const u = this.#getUsage(token);
|
|
2032
|
+
u.referenced = true;
|
|
2033
|
+
if (isSubject) u.asSubject = true;
|
|
2034
|
+
if (rightCombinator === " " || rightCombinator === ">") u.asAncestor = true;
|
|
2035
|
+
if (rightCombinator === "+" || rightCombinator === "~") u.asSibling = true;
|
|
2036
|
+
if (qualified) u.asCompound = true;
|
|
2037
|
+
if (structuralPseudo) u.asStructural = true;
|
|
2038
|
+
if (rightCombinator !== null || qualified || structuralPseudo || elementPseudos.length > 0) {
|
|
2039
|
+
u.loadBearing = true;
|
|
2040
|
+
}
|
|
2041
|
+
const forwardEligible = compounds.length === 1 && classes.length === 1 && !otherSimple && !structuralPseudo && !functionalPseudo && elementPseudos.length <= 1;
|
|
2042
|
+
if (forwardEligible && decls.length > 0) {
|
|
2043
|
+
const condition = {
|
|
2044
|
+
media,
|
|
2045
|
+
states: statePseudos.map(pseudoName).sort(),
|
|
2046
|
+
pseudoElement: elementPseudos.length === 1 ? normalizePseudoElement(elementPseudos[0]) : ""
|
|
2047
|
+
};
|
|
2048
|
+
this.#addRuleEntry(token, { order, token, condition, decls });
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
for (const p of pseudos) {
|
|
2052
|
+
const isHas = pseudoName(p) === ":has";
|
|
2053
|
+
p.walkClasses((inner) => {
|
|
2054
|
+
const token = inner.value;
|
|
2055
|
+
this.#known.add(token);
|
|
2056
|
+
const u = this.#getUsage(token);
|
|
2057
|
+
u.referenced = true;
|
|
2058
|
+
u.loadBearing = true;
|
|
2059
|
+
if (isHas) u.asHasArgument = true;
|
|
2060
|
+
});
|
|
2061
|
+
}
|
|
2062
|
+
});
|
|
2063
|
+
if (hasCombinator || hasStructural) {
|
|
2064
|
+
complex.add(selector.toString().trim());
|
|
2065
|
+
}
|
|
2066
|
+
}
|
|
2067
|
+
#addRuleEntry(token, entry) {
|
|
2068
|
+
const list = this.#classIndex.get(token);
|
|
2069
|
+
if (list) list.push(entry);
|
|
2070
|
+
else this.#classIndex.set(token, [entry]);
|
|
2071
|
+
}
|
|
2072
|
+
#getUsage(token) {
|
|
2073
|
+
let u = this.#usage.get(token);
|
|
2074
|
+
if (!u) {
|
|
2075
|
+
u = {
|
|
2076
|
+
referenced: false,
|
|
2077
|
+
asSubject: false,
|
|
2078
|
+
asAncestor: false,
|
|
2079
|
+
asCompound: false,
|
|
2080
|
+
asSibling: false,
|
|
2081
|
+
asHasArgument: false,
|
|
2082
|
+
asStructural: false,
|
|
2083
|
+
loadBearing: false
|
|
2084
|
+
};
|
|
2085
|
+
this.#usage.set(token, u);
|
|
2086
|
+
}
|
|
2087
|
+
return u;
|
|
2088
|
+
}
|
|
2089
|
+
/**
|
|
2090
|
+
* Resolve a set of tokens into a normalized condition-keyed StyleMap. `tokenList` is the original
|
|
2091
|
+
* class list (for per-declaration `tokenIndex` provenance); `request` is the set being resolved.
|
|
2092
|
+
*/
|
|
2093
|
+
#resolveTokens(request, tokenList) {
|
|
2094
|
+
const entries = [];
|
|
2095
|
+
for (const token of new Set(request)) {
|
|
2096
|
+
const list = this.#classIndex.get(token);
|
|
2097
|
+
if (list) entries.push(...list);
|
|
2098
|
+
}
|
|
2099
|
+
if (entries.length === 0) return emptyStyleMap();
|
|
2100
|
+
entries.sort((a, b) => a.order - b.order);
|
|
2101
|
+
const acc = /* @__PURE__ */ new Map();
|
|
2102
|
+
for (const entry of entries) {
|
|
2103
|
+
const key = conditionKey(entry.condition);
|
|
2104
|
+
let block = acc.get(key);
|
|
2105
|
+
if (!block) {
|
|
2106
|
+
block = { condition: entry.condition, decls: /* @__PURE__ */ new Map() };
|
|
2107
|
+
acc.set(key, block);
|
|
2108
|
+
}
|
|
2109
|
+
const tokenIndex = tokenList.indexOf(entry.token);
|
|
2110
|
+
const origin = { kind: "class", tokenIndex, className: entry.token };
|
|
2111
|
+
for (const [prop, value, important] of entry.decls) {
|
|
2112
|
+
for (const decl of normalizer.normalizeDeclaration(prop, value, important)) {
|
|
2113
|
+
block.decls.set(decl.property, { ...decl, origin });
|
|
2114
|
+
}
|
|
2115
|
+
}
|
|
2116
|
+
}
|
|
2117
|
+
const rawBlocks = /* @__PURE__ */ new Map();
|
|
2118
|
+
for (const [key, block] of acc) {
|
|
2119
|
+
if (block.decls.size === 0) continue;
|
|
2120
|
+
rawBlocks.set(key, { condition: block.condition, decls: block.decls });
|
|
2121
|
+
}
|
|
2122
|
+
if (rawBlocks.size === 0) return emptyStyleMap();
|
|
2123
|
+
return normalizer.normalizeStyleMap({ blocks: rawBlocks });
|
|
2124
|
+
}
|
|
2125
|
+
/** Build (once) the reverse index used by {@link emit}. */
|
|
2126
|
+
#reverseIndex() {
|
|
2127
|
+
if (this.#reverse) return this.#reverse;
|
|
2128
|
+
const out = [];
|
|
2129
|
+
for (const token of this.#classIndex.keys()) {
|
|
2130
|
+
const styles = this.#resolveTokens([token], [token]);
|
|
2131
|
+
const keyed = /* @__PURE__ */ new Map();
|
|
2132
|
+
for (const [ck, block] of styles.blocks) {
|
|
2133
|
+
for (const [prop, decl] of block.decls) keyed.set(`${ck}\0${prop}`, String(decl.value));
|
|
2134
|
+
}
|
|
2135
|
+
if (keyed.size > 0) out.push({ token, keyed });
|
|
2136
|
+
}
|
|
2137
|
+
out.sort((a, b) => b.keyed.size - a.keyed.size);
|
|
2138
|
+
this.#reverse = out;
|
|
2139
|
+
return out;
|
|
2140
|
+
}
|
|
2141
|
+
};
|
|
2142
|
+
function createCssResolver(cssFiles = [], options) {
|
|
2143
|
+
return new CustomCSSResolver(cssFiles, options);
|
|
2144
|
+
}
|
|
2145
|
+
function splitCompounds(selector) {
|
|
2146
|
+
const compounds = [];
|
|
2147
|
+
let current = [];
|
|
2148
|
+
let leftCombinator = null;
|
|
2149
|
+
for (const node of selector.nodes) {
|
|
2150
|
+
if (sp.isCombinator(node)) {
|
|
2151
|
+
compounds.push({ leftCombinator, nodes: current });
|
|
2152
|
+
current = [];
|
|
2153
|
+
leftCombinator = combinatorValue(node);
|
|
2154
|
+
} else {
|
|
2155
|
+
current.push(node);
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
2158
|
+
compounds.push({ leftCombinator, nodes: current });
|
|
2159
|
+
return compounds;
|
|
2160
|
+
}
|
|
2161
|
+
function combinatorValue(node) {
|
|
2162
|
+
const v = node.value;
|
|
2163
|
+
return v.trim() === "" ? " " : v.trim();
|
|
2164
|
+
}
|
|
2165
|
+
function pseudoName(node) {
|
|
2166
|
+
return node.value.toLowerCase();
|
|
2167
|
+
}
|
|
2168
|
+
function isPseudoElement(node) {
|
|
2169
|
+
return sp.isPseudoElement(node) || LEGACY_PSEUDO_ELEMENTS2.has(pseudoName(node));
|
|
2170
|
+
}
|
|
2171
|
+
function normalizePseudoElement(node) {
|
|
2172
|
+
const name = pseudoName(node);
|
|
2173
|
+
return name.startsWith("::") ? name : `::${name.replace(/^:/, "")}`;
|
|
2174
|
+
}
|
|
2175
|
+
function mediaContext(rule) {
|
|
2176
|
+
const parts = [];
|
|
2177
|
+
let skip = false;
|
|
2178
|
+
let parent = rule.parent;
|
|
2179
|
+
while (parent && parent.type === "atrule") {
|
|
2180
|
+
const at = parent;
|
|
2181
|
+
const name = at.name.toLowerCase();
|
|
2182
|
+
if (name === "media") parts.unshift(at.params.trim().replace(/\s+/g, " "));
|
|
2183
|
+
else if (name === "keyframes" || name.endsWith("keyframes") || name === "font-face") skip = true;
|
|
2184
|
+
parent = parent.parent;
|
|
2185
|
+
}
|
|
2186
|
+
return { media: parts.join(" and "), skip };
|
|
2187
|
+
}
|
|
2188
|
+
function collectDecls(rule) {
|
|
2189
|
+
const out = [];
|
|
2190
|
+
for (const node of rule.nodes) {
|
|
2191
|
+
if (node.type === "decl") out.push([node.prop, node.value, node.important === true]);
|
|
2192
|
+
}
|
|
2193
|
+
return out;
|
|
2194
|
+
}
|
|
2195
|
+
function isPlainClassToken(token) {
|
|
2196
|
+
return token.length > 0 && !/[\s.#>+~:[\]()]/.test(token);
|
|
2197
|
+
}
|
|
2198
|
+
function readCssPath(path3) {
|
|
2199
|
+
try {
|
|
2200
|
+
return { id: path3, css: readFileSync(path3, "utf8") };
|
|
2201
|
+
} catch (cause) {
|
|
2202
|
+
throw new Error(`resolver-css: cannot read CSS file "${path3}"`, { cause });
|
|
2203
|
+
}
|
|
2204
|
+
}
|
|
2205
|
+
function deriveFingerprint(provider, files) {
|
|
2206
|
+
const parts = files.map((f) => `${f.id}:${f.css.length}`).sort();
|
|
2207
|
+
return `${provider}/${ENGINE_VERSION}::${parts.join("|")}`;
|
|
2208
|
+
}
|
|
2209
|
+
|
|
2210
|
+
export {
|
|
2211
|
+
emptyStyleDiv,
|
|
2212
|
+
flexCenterWrapper,
|
|
2213
|
+
nestedFlexMerge,
|
|
2214
|
+
passthroughWrapper,
|
|
2215
|
+
redundantFragment,
|
|
2216
|
+
dedupeClasses,
|
|
2217
|
+
insetShorthand,
|
|
2218
|
+
marginShorthand,
|
|
2219
|
+
paddingShorthand,
|
|
2220
|
+
sizeShorthand,
|
|
2221
|
+
builtinPatterns,
|
|
2222
|
+
createJsxFrontend,
|
|
2223
|
+
createJsxBackend,
|
|
2224
|
+
createTailwindResolver,
|
|
2225
|
+
createCssResolver
|
|
2226
|
+
};
|
|
2227
|
+
//# sourceMappingURL=chunk-4HHISSMR.js.map
|