domflax 0.1.4 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -29
- package/dist/{chunk-EVENAJYI.js → chunk-EYQXQQQH.js} +3 -3
- package/dist/{chunk-3Z5ZWLXX.js → chunk-FPT4EJ6Q.js} +805 -1612
- package/dist/chunk-FPT4EJ6Q.js.map +1 -0
- package/dist/{chunk-5FWENSD2.js → chunk-JBM3MJRM.js} +149 -10
- package/dist/chunk-JBM3MJRM.js.map +1 -0
- package/dist/{chunk-H5KTGI3A.js → chunk-TTJEXWAC.js} +172 -5
- package/dist/chunk-TTJEXWAC.js.map +1 -0
- package/dist/cli.cjs +1032 -1640
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +30 -10
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +1116 -1627
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +226 -485
- package/dist/index.d.ts +226 -485
- package/dist/index.js +16 -36
- package/dist/{pattern-CP9_HpVK.d.cts → pattern-DotR_dHs.d.cts} +1 -1
- package/dist/pattern-kit.cjs +60 -1
- package/dist/pattern-kit.cjs.map +1 -1
- package/dist/pattern-kit.d.cts +2 -2
- package/dist/pattern-kit.d.ts +2 -2
- package/dist/pattern-kit.js +1 -1
- package/dist/{pattern-CYgsv-jO.d.ts → pattern-urm5uuwj.d.ts} +1 -1
- package/dist/{resolve-ops-Ci7LgYHC.d.ts → resolve-ops-D8aQina5.d.cts} +11 -0
- package/dist/{resolve-ops-Ci7LgYHC.d.cts → resolve-ops-D8aQina5.d.ts} +11 -0
- package/dist/verify.d.cts +1 -1
- package/dist/verify.d.ts +1 -1
- package/dist/webpack-loader.cjs +1014 -1578
- package/dist/webpack-loader.cjs.map +1 -1
- package/dist/webpack-loader.d.cts +8 -2
- package/dist/webpack-loader.d.ts +8 -2
- package/dist/webpack-loader.js +7 -4
- package/dist/webpack-loader.js.map +1 -1
- package/dist/worker.cjs +983 -1601
- package/dist/worker.cjs.map +1 -1
- package/dist/worker.js +3 -3
- package/package.json +1 -1
- package/dist/chunk-3Z5ZWLXX.js.map +0 -1
- package/dist/chunk-5FWENSD2.js.map +0 -1
- package/dist/chunk-H5KTGI3A.js.map +0 -1
- /package/dist/{chunk-EVENAJYI.js.map → chunk-EYQXQQQH.js.map} +0 -0
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
createJsxBackend,
|
|
7
7
|
createJsxFrontend,
|
|
8
8
|
createTailwindResolver
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-FPT4EJ6Q.js";
|
|
10
10
|
import {
|
|
11
11
|
buildSelectorIndex,
|
|
12
12
|
createPipeline,
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
normalizer,
|
|
15
15
|
runPasses,
|
|
16
16
|
syncClassesFromComputed
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-TTJEXWAC.js";
|
|
18
18
|
import {
|
|
19
19
|
init_esm_shims
|
|
20
20
|
} from "./chunk-U5GOONKV.js";
|
|
@@ -26,6 +26,27 @@ import { fileURLToPath } from "url";
|
|
|
26
26
|
|
|
27
27
|
// src/pipeline-run.ts
|
|
28
28
|
init_esm_shims();
|
|
29
|
+
function bytes(s) {
|
|
30
|
+
return Buffer.byteLength(s, "utf8");
|
|
31
|
+
}
|
|
32
|
+
function countClassTokens(code) {
|
|
33
|
+
let total = 0;
|
|
34
|
+
const re = /\b(?:className|class)\s*=\s*"([^"]*)"/g;
|
|
35
|
+
let m;
|
|
36
|
+
while ((m = re.exec(code)) !== null) {
|
|
37
|
+
total += m[1].split(/\s+/).filter((t) => t.length > 0).length;
|
|
38
|
+
}
|
|
39
|
+
return total;
|
|
40
|
+
}
|
|
41
|
+
function computeStats(code, out, nodesIn, nodesOut) {
|
|
42
|
+
const classesBefore = countClassTokens(code);
|
|
43
|
+
const classesAfter = countClassTokens(out);
|
|
44
|
+
return {
|
|
45
|
+
nodesRemoved: Math.max(0, nodesIn - nodesOut),
|
|
46
|
+
classesSaved: Math.max(0, classesBefore - classesAfter),
|
|
47
|
+
bytesSaved: bytes(code) - bytes(out)
|
|
48
|
+
};
|
|
49
|
+
}
|
|
29
50
|
function jsxKindOf(id) {
|
|
30
51
|
const clean = id.split("?", 1)[0] ?? id;
|
|
31
52
|
if (clean.endsWith(".tsx")) return "tsx";
|
|
@@ -101,8 +122,10 @@ function finishPipeline(optimized, id, resolver) {
|
|
|
101
122
|
}
|
|
102
123
|
function runJsxPipeline(code, id, kind, resolver, patterns, safety) {
|
|
103
124
|
const { doc, ctx, passes } = preparePipeline(code, id, kind, resolver, patterns, safety, "provably-safe");
|
|
125
|
+
const nodesIn = doc.nodes.size;
|
|
104
126
|
const { doc: optimized } = runPasses(doc, passes, ctx);
|
|
105
|
-
|
|
127
|
+
const out = finishPipeline(optimized, id, resolver);
|
|
128
|
+
return { code: out, stats: computeStats(code, out, nodesIn, optimized.nodes.size) };
|
|
106
129
|
}
|
|
107
130
|
function prepareHtml(code, id, resolver, patterns, safety, gate) {
|
|
108
131
|
const parsed = createHtmlFrontend().parse(code, {
|
|
@@ -143,8 +166,85 @@ function finishHtmlPipeline(optimized, id, resolver) {
|
|
|
143
166
|
}
|
|
144
167
|
function runHtmlPipeline(code, id, resolver, patterns, safety) {
|
|
145
168
|
const { doc, ctx, passes } = prepareHtml(code, id, resolver, patterns, safety, "provably-safe");
|
|
169
|
+
const nodesIn = doc.nodes.size;
|
|
146
170
|
const { doc: optimized } = runPasses(doc, passes, ctx);
|
|
147
|
-
|
|
171
|
+
const out = finishHtmlPipeline(optimized, id, resolver);
|
|
172
|
+
return { code: out, stats: computeStats(code, out, nodesIn, optimized.nodes.size) };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// src/summary.ts
|
|
176
|
+
init_esm_shims();
|
|
177
|
+
function zeroStats() {
|
|
178
|
+
return { nodesRemoved: 0, classesSaved: 0, bytesSaved: 0 };
|
|
179
|
+
}
|
|
180
|
+
function emptyTotals() {
|
|
181
|
+
return { files: 0, nodesRemoved: 0, classesCompressed: 0, bytesSaved: 0 };
|
|
182
|
+
}
|
|
183
|
+
function resetTotals(t) {
|
|
184
|
+
t.files = 0;
|
|
185
|
+
t.nodesRemoved = 0;
|
|
186
|
+
t.classesCompressed = 0;
|
|
187
|
+
t.bytesSaved = 0;
|
|
188
|
+
}
|
|
189
|
+
function addStats(t, s, changed) {
|
|
190
|
+
if (!changed) return;
|
|
191
|
+
t.files += 1;
|
|
192
|
+
t.nodesRemoved += s.nodesRemoved;
|
|
193
|
+
t.classesCompressed += s.classesSaved;
|
|
194
|
+
t.bytesSaved += s.bytesSaved;
|
|
195
|
+
}
|
|
196
|
+
var BYTE_UNITS = ["KB", "MB", "GB", "TB"];
|
|
197
|
+
function formatBytes(n) {
|
|
198
|
+
const abs = Math.abs(n);
|
|
199
|
+
if (abs < 1024) return `${n} B`;
|
|
200
|
+
let value = n / 1024;
|
|
201
|
+
let unit = 0;
|
|
202
|
+
while (Math.abs(value) >= 1024 && unit < BYTE_UNITS.length - 1) {
|
|
203
|
+
value /= 1024;
|
|
204
|
+
unit += 1;
|
|
205
|
+
}
|
|
206
|
+
return `${value.toFixed(1)} ${BYTE_UNITS[unit]}`;
|
|
207
|
+
}
|
|
208
|
+
function formatCount(n) {
|
|
209
|
+
return Math.trunc(n).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
210
|
+
}
|
|
211
|
+
var LABEL_WIDTH = 20;
|
|
212
|
+
var RULE = ` ${"\u2500".repeat(32)}`;
|
|
213
|
+
function row(label, value) {
|
|
214
|
+
return ` ${label.padEnd(LABEL_WIDTH)}${value}`;
|
|
215
|
+
}
|
|
216
|
+
function renderSummary(totals) {
|
|
217
|
+
return [
|
|
218
|
+
"",
|
|
219
|
+
" \u25B2 domflax",
|
|
220
|
+
RULE,
|
|
221
|
+
row("files optimized", formatCount(totals.files)),
|
|
222
|
+
row("DOM nodes removed", formatCount(totals.nodesRemoved)),
|
|
223
|
+
row("classes compressed", formatCount(totals.classesCompressed)),
|
|
224
|
+
row("size saved", formatBytes(totals.bytesSaved)),
|
|
225
|
+
RULE,
|
|
226
|
+
""
|
|
227
|
+
].join("\n");
|
|
228
|
+
}
|
|
229
|
+
var TOTALS_KEY = /* @__PURE__ */ Symbol.for("domflax.buildTotals");
|
|
230
|
+
var PRINTED_KEY = /* @__PURE__ */ Symbol.for("domflax.summaryPrinted");
|
|
231
|
+
function accumulateOnCompilation(compilation, stats, changed) {
|
|
232
|
+
if (compilation === null || typeof compilation !== "object") return;
|
|
233
|
+
const bag = compilation;
|
|
234
|
+
let totals = bag[TOTALS_KEY];
|
|
235
|
+
if (!totals) {
|
|
236
|
+
totals = emptyTotals();
|
|
237
|
+
bag[TOTALS_KEY] = totals;
|
|
238
|
+
}
|
|
239
|
+
addStats(totals, stats, changed);
|
|
240
|
+
}
|
|
241
|
+
function printCompilationSummary(compilation) {
|
|
242
|
+
if (compilation === null || typeof compilation !== "object") return;
|
|
243
|
+
const bag = compilation;
|
|
244
|
+
if (bag[PRINTED_KEY]) return;
|
|
245
|
+
bag[PRINTED_KEY] = true;
|
|
246
|
+
const totals = bag[TOTALS_KEY];
|
|
247
|
+
if (totals && totals.files > 0) process.stdout.write(renderSummary(totals));
|
|
148
248
|
}
|
|
149
249
|
|
|
150
250
|
// src/index.ts
|
|
@@ -182,29 +282,48 @@ function createDomflax(options = {}) {
|
|
|
182
282
|
},
|
|
183
283
|
patterns,
|
|
184
284
|
transform(code, id) {
|
|
185
|
-
if (!isSupported(id, resolved.include)) return { code, map: null };
|
|
285
|
+
if (!isSupported(id, resolved.include)) return { code, map: null, stats: zeroStats() };
|
|
186
286
|
const kind = jsxKindOf(id);
|
|
187
287
|
if (kind !== null) {
|
|
188
288
|
const out = runJsxPipeline(code, id, kind, getResolver(), patterns, resolved.safety);
|
|
189
|
-
return { code: out, map: null };
|
|
289
|
+
return { code: out.code, map: null, stats: out.stats };
|
|
190
290
|
}
|
|
191
291
|
if (htmlKindOf(id) !== null) {
|
|
192
292
|
const out = runHtmlPipeline(code, id, getResolver(), patterns, resolved.safety);
|
|
193
|
-
return { code: out, map: null };
|
|
293
|
+
return { code: out.code, map: null, stats: out.stats };
|
|
194
294
|
}
|
|
195
|
-
return { code, map: null };
|
|
295
|
+
return { code, map: null, stats: zeroStats() };
|
|
196
296
|
}
|
|
197
297
|
};
|
|
198
298
|
}
|
|
199
299
|
function vite(options = {}) {
|
|
200
300
|
const engine = createDomflax(options);
|
|
301
|
+
const totals = emptyTotals();
|
|
302
|
+
let printed = false;
|
|
303
|
+
const printSummary = () => {
|
|
304
|
+
if (printed) return;
|
|
305
|
+
printed = true;
|
|
306
|
+
if (totals.files > 0) process.stdout.write(renderSummary(totals));
|
|
307
|
+
};
|
|
201
308
|
return {
|
|
202
309
|
name: "domflax",
|
|
203
310
|
enforce: "pre",
|
|
311
|
+
buildStart() {
|
|
312
|
+
resetTotals(totals);
|
|
313
|
+
printed = false;
|
|
314
|
+
},
|
|
204
315
|
transform(code, id) {
|
|
205
316
|
if (!isSupported(id, engine.options.include)) return null;
|
|
206
317
|
const out = engine.transform(code, id);
|
|
207
|
-
|
|
318
|
+
const changed = out.code !== code;
|
|
319
|
+
addStats(totals, out.stats, changed);
|
|
320
|
+
return changed ? out : null;
|
|
321
|
+
},
|
|
322
|
+
buildEnd() {
|
|
323
|
+
printSummary();
|
|
324
|
+
},
|
|
325
|
+
closeBundle() {
|
|
326
|
+
printSummary();
|
|
208
327
|
}
|
|
209
328
|
};
|
|
210
329
|
}
|
|
@@ -213,6 +332,24 @@ function webpackLoaderPath() {
|
|
|
213
332
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
214
333
|
return join(here, "webpack-loader.cjs");
|
|
215
334
|
}
|
|
335
|
+
function tapWebpackSummary(compiler) {
|
|
336
|
+
const done = compiler.hooks?.done;
|
|
337
|
+
if (typeof done?.tap !== "function") return;
|
|
338
|
+
done.tap("domflax", (stats) => {
|
|
339
|
+
const compilation = stats?.compilation ?? stats;
|
|
340
|
+
printCompilationSummary(compilation);
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
function installWebpackSummary(compiler, host) {
|
|
344
|
+
if (typeof compiler.hooks?.done?.tap === "function") {
|
|
345
|
+
tapWebpackSummary(compiler);
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
const plugins = host.plugins ??= [];
|
|
349
|
+
if (Array.isArray(plugins)) {
|
|
350
|
+
plugins.push({ apply: (real) => tapWebpackSummary(real) });
|
|
351
|
+
}
|
|
352
|
+
}
|
|
216
353
|
function webpack(options = {}) {
|
|
217
354
|
createDomflax(options);
|
|
218
355
|
return {
|
|
@@ -228,6 +365,7 @@ function webpack(options = {}) {
|
|
|
228
365
|
use: [{ loader: webpackLoaderPath(), options }]
|
|
229
366
|
};
|
|
230
367
|
rules.push(rule);
|
|
368
|
+
installWebpackSummary(compiler, host);
|
|
231
369
|
}
|
|
232
370
|
};
|
|
233
371
|
}
|
|
@@ -235,9 +373,10 @@ var domflax = { createDomflax, vite, webpack };
|
|
|
235
373
|
var src_default = domflax;
|
|
236
374
|
|
|
237
375
|
export {
|
|
376
|
+
accumulateOnCompilation,
|
|
238
377
|
createDomflax,
|
|
239
378
|
vite,
|
|
240
379
|
webpack,
|
|
241
380
|
src_default
|
|
242
381
|
};
|
|
243
|
-
//# sourceMappingURL=chunk-
|
|
382
|
+
//# sourceMappingURL=chunk-JBM3MJRM.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/pipeline-run.ts","../src/summary.ts"],"sourcesContent":["/**\n * domflax — public meta package.\n *\n * Re-exports the entire `@domflax/core` public API (types + reference runtime) and the built-in\n * `@domflax/patterns` library, then layers thin, framework-agnostic build adapters on top\n * (`vite()` / `webpack()`) plus a programmatic `createDomflax()` factory.\n *\n * Each adapter runs the SAME single-file engine as {@link createDomflax} (JSX/TSX + HTML frontends +\n * lazy Tailwind/CSS resolver → core pass manager → reverse-emit → surgical backend). The adapters are\n * structurally typed against their bundlers — they never hard-depend on `vite` or `webpack`.\n *\n * `.jsx`/`.tsx` route to `@domflax/frontend-jsx` (Babel); `.html`/`.htm` route to\n * `@domflax/frontend-html` (parse5). Both emit via SURGICAL span edits over the original source.\n */\n\nimport { createPipeline } from '@domflax/core';\nimport type {\n EncodedSourceMap,\n Pattern,\n Pipeline,\n SafetyLevel,\n StyleResolver,\n} from '@domflax/core';\nimport { builtinPatterns } from '@domflax/patterns';\nimport { createTailwindResolver } from '@domflax/resolver-tailwind';\nimport { createCssResolver } from '@domflax/resolver-css';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport { htmlKindOf, jsxKindOf, runHtmlPipeline, runJsxPipeline } from './pipeline-run';\nimport {\n addStats,\n emptyTotals,\n printCompilationSummary,\n renderSummary,\n resetTotals,\n zeroStats,\n type FileStatDelta,\n type Totals,\n} from './summary';\n\n// ── Re-export the public surface ──────────────────────────────────────────────────────────────\nexport * from '@domflax/core';\nexport * from '@domflax/patterns';\n\n/* ────────────────────────────────────────────────────────────────────────── *\n * Options\n * ────────────────────────────────────────────────────────────────────────── */\n\n/** How class names resolve to computed styles. */\nexport type DomflaxProvider = 'auto' | 'tailwind' | 'custom';\n\n/** Public adapter/factory options (mirrors the documented `domflax({...})` surface). */\nexport interface DomflaxOptions {\n /** Resolution strategy. Defaults to `'auto'`. */\n readonly provider?: DomflaxProvider;\n /** Stylesheets to parse when `provider` is `'custom'`. */\n readonly cssFiles?: readonly string[];\n /** Preview changes without rewriting source. */\n readonly dryRun?: boolean;\n /** Optimization aggressiveness handed to the pass manager (0 lint … 3 aggressive). */\n readonly safety?: SafetyLevel;\n /** File globs/extensions the adapters should consider. Defaults to jsx/tsx/html. */\n readonly include?: readonly string[];\n}\n\n/** Fully-resolved options with defaults applied. */\nexport interface ResolvedDomflaxOptions {\n readonly provider: DomflaxProvider;\n readonly cssFiles: readonly string[];\n readonly dryRun: boolean;\n readonly safety: SafetyLevel;\n readonly include: readonly string[];\n}\n\nconst DEFAULT_INCLUDE: readonly string[] = ['.jsx', '.tsx', '.html', '.htm'];\n\nfunction resolveOptions(options: DomflaxOptions): ResolvedDomflaxOptions {\n return {\n provider: options.provider ?? 'auto',\n cssFiles: options.cssFiles ?? [],\n dryRun: options.dryRun ?? false,\n safety: options.safety ?? 2,\n include: options.include ?? DEFAULT_INCLUDE,\n };\n}\n\n/** True when `id` is a file domflax knows how to transform. */\nfunction isSupported(id: string, include: readonly string[]): boolean {\n // Strip query suffixes bundlers append (e.g. `App.tsx?used`).\n const clean = id.split('?', 1)[0] ?? id;\n return include.some((ext) => clean.endsWith(ext));\n}\n\n/* ────────────────────────────────────────────────────────────────────────── *\n * Programmatic instance\n * ────────────────────────────────────────────────────────────────────────── */\n\n/** Result of a single-file transform. `map` is null until codegen lands. */\nexport interface DomflaxTransformResult {\n readonly code: string;\n readonly map: EncodedSourceMap | null;\n /**\n * Per-file optimization delta (nodes removed / classes saved / bytes saved). Zeroed for\n * unsupported or unchanged files. Consumed by the build adapters to accumulate the build-end\n * {@link renderSummary summary}.\n */\n readonly stats: FileStatDelta;\n}\n\n/**\n * A configured domflax engine. Holds the wired core {@link Pipeline}, the passthrough\n * {@link StyleResolver}, and the built-in {@link Pattern} set, and exposes a single-file\n * `transform`.\n */\nexport interface Domflax {\n readonly options: ResolvedDomflaxOptions;\n readonly pipeline: Pipeline;\n readonly resolver: StyleResolver;\n readonly patterns: readonly Pattern[];\n /**\n * Transform one file (SYNCHRONOUS, fully static, never launches a browser). For `.jsx`/`.tsx` this\n * runs the full pipeline (parse → resolve → flatten[provably-safe only] → reverse-emit → print);\n * every other (or unsupported) file is returned unchanged. Only provably layout-neutral flattens are\n * applied — domflax never changes rendering.\n */\n transform(code: string, id: string): DomflaxTransformResult;\n}\n\n/**\n * Build a configured domflax engine.\n *\n * Wires a real single-file pipeline: the JSX/TSX frontend + a Tailwind resolver feed the core pass\n * manager (running {@link builtinPatterns}), whose output is reverse-emitted back to class tokens\n * and re-printed by the JSX backend. Non-jsx/tsx files pass through unchanged.\n */\n/**\n * Build the {@link StyleResolver} for the chosen provider. The heavy engine each resolver wraps\n * (Tailwind v3 / postcss) is loaded LAZILY — at the moment this factory runs — and resolved from the\n * CONSUMER'S project, NOT from domflax's (possibly bundled) location. Both engines are OPTIONAL peer\n * dependencies of the published `domflax`: a Tailwind-only user never triggers a postcss load, and a\n * custom-CSS-only user never triggers a Tailwind load, because only the selected branch constructs.\n */\nfunction createResolver(resolved: ResolvedDomflaxOptions): StyleResolver {\n if (resolved.provider === 'custom') {\n return createCssResolver([], { files: resolved.cssFiles });\n }\n // 'auto' and 'tailwind' both resolve against the project's Tailwind engine.\n return createTailwindResolver();\n}\n\nexport function createDomflax(options: DomflaxOptions = {}): Domflax {\n const resolved = resolveOptions(options);\n const pipeline = createPipeline();\n const patterns = builtinPatterns;\n\n // Construct the resolver lazily so neither optional engine (Tailwind / postcss) is loaded until a\n // file is actually transformed (and only the engine for the selected provider is ever loaded).\n let cachedResolver: StyleResolver | null = null;\n const getResolver = (): StyleResolver => (cachedResolver ??= createResolver(resolved));\n\n return {\n options: resolved,\n pipeline,\n get resolver(): StyleResolver {\n return getResolver();\n },\n patterns,\n transform(code: string, id: string): DomflaxTransformResult {\n if (!isSupported(id, resolved.include)) return { code, map: null, stats: zeroStats() };\n const kind = jsxKindOf(id);\n if (kind !== null) {\n const out = runJsxPipeline(code, id, kind, getResolver(), patterns, resolved.safety);\n return { code: out.code, map: null, stats: out.stats };\n }\n // `.html`/`.htm` route to the parse5 HTML frontend/backend (surgical span edits).\n if (htmlKindOf(id) !== null) {\n const out = runHtmlPipeline(code, id, getResolver(), patterns, resolved.safety);\n return { code: out.code, map: null, stats: out.stats };\n }\n return { code, map: null, stats: zeroStats() };\n },\n };\n}\n\n/* ────────────────────────────────────────────────────────────────────────── *\n * Build adapters (framework-agnostic, structurally-typed shapes)\n * ────────────────────────────────────────────────────────────────────────── */\n\n/**\n * Minimal Vite-plugin shape. Declared locally so this adapter does NOT depend on `vite`'s types\n * (an optional, type-only peer). Structurally compatible with Vite's `Plugin` for the hooks domflax\n * uses: `enforce: 'pre'` runs domflax before Vite's JSX→`createElement` transform, and `transform`\n * is Vite's per-file source hook. Returning `null` is Vite's \"no change\" signal.\n */\nexport interface DomflaxVitePlugin {\n readonly name: string;\n readonly enforce: 'pre';\n /** Vite's per-file source hook. Fully synchronous and browser-free. */\n transform(code: string, id: string): DomflaxTransformResult | null;\n /** Vite build-start hook — resets the per-build summary accumulator (watch/serve safe). */\n buildStart(): void;\n /** Vite build-end hook — prints the aggregate {@link renderSummary} once (if anything changed). */\n buildEnd(): void;\n /** Vite close-bundle hook — prints the summary as a backstop if `buildEnd` did not fire. */\n closeBundle(): void;\n}\n\n/**\n * Vite adapter. Returns a real Vite `Plugin` (`enforce: 'pre'`) whose `transform` runs the domflax\n * engine on `.jsx`/`.tsx` modules — strips any bundler query suffix (e.g. `App.tsx?used`) before\n * matching, returns `{ code, map }` when the source changed, and `null` (Vite's unchanged signal)\n * for unchanged sources and for any non-jsx/tsx module.\n *\n * @example\n * ```js\n * // vite.config.js\n * import domflax from 'domflax';\n * export default { plugins: [domflax.vite({ provider: 'tailwind' })] };\n * ```\n */\nexport function vite(options: DomflaxOptions = {}): DomflaxVitePlugin {\n const engine = createDomflax(options);\n\n // Aggregate across every `transform` call in this plugin instance. `buildStart` resets it so\n // watch/serve rebuilds each get their own summary; a `printed` latch guards the double-fire of\n // `buildEnd` + `closeBundle`.\n const totals: Totals = emptyTotals();\n let printed = false;\n\n const printSummary = (): void => {\n if (printed) return;\n printed = true;\n if (totals.files > 0) process.stdout.write(renderSummary(totals));\n };\n\n return {\n name: 'domflax',\n enforce: 'pre',\n buildStart(): void {\n resetTotals(totals);\n printed = false;\n },\n transform(code: string, id: string): DomflaxTransformResult | null {\n if (!isSupported(id, engine.options.include)) return null;\n const out = engine.transform(code, id);\n const changed = out.code !== code;\n addStats(totals, out.stats, changed);\n // Signal \"no change\" to Vite when the source round-tripped unchanged.\n return changed ? out : null;\n },\n buildEnd(): void {\n printSummary();\n },\n closeBundle(): void {\n printSummary();\n },\n };\n}\n\n/* ── webpack / Next.js ──────────────────────────────────────────────────────────────────────── */\n\n/** A `module.rule` `use` entry: an absolute loader path plus the options forwarded to it. */\ninterface DomflaxRuleUse {\n readonly loader: string;\n readonly options: DomflaxOptions;\n}\n\n/** The slice of a webpack `module.rule` domflax appends. */\ninterface DomflaxModuleRule {\n readonly test: RegExp;\n readonly enforce: 'pre';\n readonly exclude: RegExp;\n readonly use: readonly DomflaxRuleUse[];\n}\n\n/** Anything carrying a `module.rules` array — both a webpack `Compiler.options` and Next's bare config. */\ninterface DomflaxWebpackModuleHost {\n module?: { rules?: unknown[] };\n /** webpack's plugin list (present on both a real `Compiler.options` and Next's bare config). */\n plugins?: unknown[];\n}\n\n/** A tappable webpack hook (only the `tap` arm domflax uses). */\ninterface DomflaxWebpackHook {\n tap(name: string, fn: (arg: unknown) => void): void;\n}\n\n/**\n * Minimal webpack-compiler shape. Declared locally so this adapter does NOT depend on `webpack`'s\n * types. domflax only needs to push a rule onto the host's `module.rules`.\n *\n * `apply` accepts BOTH shapes: a real webpack `Compiler` (rules live under `compiler.options.module`)\n * AND the bare `config` object Next.js hands you from `webpack(config)` (rules live directly under\n * `config.module`). It duck-types `compiler.options ?? compiler` to find the right host.\n */\nexport interface DomflaxWebpackCompiler extends DomflaxWebpackModuleHost {\n options?: DomflaxWebpackModuleHost;\n /** Present only on a REAL webpack `Compiler` (not on Next's bare config). Used for the summary. */\n hooks?: { done?: DomflaxWebpackHook };\n}\n\n/**\n * Minimal webpack-plugin shape. `apply(compiler)` is the webpack plugin entry point.\n */\nexport interface DomflaxWebpackPlugin {\n readonly name: string;\n apply(compiler: DomflaxWebpackCompiler): void;\n}\n\n/** `.jsx`/`.tsx` modules only (combinator-free with the JSX frontend; `.js`/`.ts` are skipped). */\nconst WEBPACK_JSX_TEST = /\\.[jt]sx$/;\n\n/**\n * Absolute path to the bundled webpack loader (`./webpack-loader`). Resolved lazily against THIS\n * module's location so it works whether `domflax` is loaded as ESM (`dist/index.js`) or CJS\n * (`dist/index.cjs`) — both sit beside `dist/webpack-loader.cjs`. webpack requires loaders via\n * CommonJS, so we always point at the `.cjs` output.\n */\nfunction webpackLoaderPath(): string {\n const here = dirname(fileURLToPath(import.meta.url));\n return join(here, 'webpack-loader.cjs');\n}\n\n/**\n * webpack adapter (also the Next.js path). Returns a plugin whose `apply(compiler)` injects a\n * pre-enforced `module.rule` that invokes the domflax {@link ./webpack-loader loader} on every\n * `.jsx`/`.tsx` module. The loader runs the SAME lazy engine as {@link createDomflax} (no eager\n * Tailwind/postcss load).\n *\n * Next.js wiring (`next.config.js`) — Next exposes the underlying webpack config via `webpack(config)`:\n * ```js\n * // next.config.js\n * const domflax = require('domflax');\n * module.exports = {\n * webpack(config) {\n * domflax.webpack({ provider: 'tailwind' }).apply(config);\n * return config;\n * },\n * };\n * ```\n * `apply(compiler)` is intentionally duck-typed on `compiler.options.module.rules`, so it accepts\n * both a real webpack `Compiler` and the bare `config` object Next.js hands you.\n *\n * Caveat: this targets the webpack builder only. **Turbopack is not yet supported** — it does not\n * accept arbitrary webpack loaders, so the `next.config.js` wiring above is a no-op under\n * `next dev --turbopack`. Run domflax through the webpack builder until Turbopack exposes a loader API.\n */\n/**\n * Tap a REAL webpack `Compiler`'s `done` hook to print the build-end summary. The per-file stats were\n * stashed on the `compilation` by the loader (separate bundle) under a shared `Symbol.for` key; here\n * we read + print them once. No-op if `compiler` has no `done` hook (e.g. a bare config or a stub).\n */\nfunction tapWebpackSummary(compiler: DomflaxWebpackCompiler): void {\n const done = compiler.hooks?.done;\n if (typeof done?.tap !== 'function') return;\n done.tap('domflax', (stats: unknown) => {\n // `done` receives a `Stats` whose `.compilation` is the object the loader wrote to; some stubs\n // pass the compilation directly.\n const compilation = (stats as { compilation?: unknown } | null)?.compilation ?? stats;\n printCompilationSummary(compilation);\n });\n}\n\n/**\n * Wire the summary printer. On a real `Compiler` we tap `done` directly. On Next's bare config (no\n * `hooks`) we push a child plugin onto `config.plugins`; webpack later calls its `apply(compiler)`\n * with the real `Compiler`, at which point we tap `done`.\n */\nfunction installWebpackSummary(compiler: DomflaxWebpackCompiler, host: DomflaxWebpackModuleHost): void {\n if (typeof compiler.hooks?.done?.tap === 'function') {\n tapWebpackSummary(compiler);\n return;\n }\n const plugins = (host.plugins ??= []);\n if (Array.isArray(plugins)) {\n plugins.push({ apply: (real: DomflaxWebpackCompiler) => tapWebpackSummary(real) });\n }\n}\n\nexport function webpack(options: DomflaxOptions = {}): DomflaxWebpackPlugin {\n // Validate options eagerly (parity with the other adapters); the resolver stays lazy.\n createDomflax(options);\n return {\n name: 'domflax',\n apply(compiler: DomflaxWebpackCompiler): void {\n // Real webpack passes a `Compiler` (rules under `.options.module`); Next's `webpack(config)`\n // passes the bare config (rules under `.module`). Duck-type to the right host.\n const host: DomflaxWebpackModuleHost = compiler.options ?? compiler;\n const mod = (host.module ??= {});\n const rules = (mod.rules ??= []);\n const rule: DomflaxModuleRule = {\n test: WEBPACK_JSX_TEST,\n enforce: 'pre',\n exclude: /node_modules/,\n use: [{ loader: webpackLoaderPath(), options }],\n };\n rules.push(rule);\n // Print the aggregate summary at build end (loader ↔ plugin bridge over the compilation).\n installWebpackSummary(compiler, host);\n },\n };\n}\n\n/**\n * The default-export namespace. Exposes the build adapters and the programmatic factory as an OBJECT\n * so the documented `import domflax from 'domflax'; domflax.vite()` / `domflax.webpack()` works (and a\n * CommonJS `const domflax = require('domflax'); domflax.vite()` too). The named exports\n * (`createDomflax`, `vite`, `webpack`, …) remain available for direct import.\n */\nexport interface DomflaxDefault {\n createDomflax(options?: DomflaxOptions): Domflax;\n vite(options?: DomflaxOptions): DomflaxVitePlugin;\n webpack(options?: DomflaxOptions): DomflaxWebpackPlugin;\n}\n\n/** Default export: an object exposing `vite`, `webpack`, and the programmatic `createDomflax`. */\nconst domflax: DomflaxDefault = { createDomflax, vite, webpack };\nexport default domflax;\n","/**\n * domflax — the single-file JSX/TSX pipeline runner (parse → resolve → flatten → reverse-emit →\n * print), split out of `index.ts` so the meta package's barrel + adapters stay focused.\n *\n * {@link runJsxPipeline} is SYNC and fully static (gate `'provably-safe'`): it never changes rendering\n * and never launches a browser.\n */\n\nimport {\n buildSelectorIndex,\n createSyntheticSink,\n runPasses,\n syncClassesFromComputed,\n} from '@domflax/core';\nimport type {\n ApplyContext,\n FileKind,\n FlattenGate,\n IRDocument,\n Pass,\n PassCategory,\n PassPhase,\n Pattern,\n SafetyLevel,\n StyleResolver,\n} from '@domflax/core';\nimport { createHtmlBackend, createHtmlFrontend } from '@domflax/frontend-html';\nimport { createJsxBackend, createJsxFrontend } from '@domflax/frontend-jsx';\nimport { normalizer } from '@domflax/pattern-kit';\n\nimport type { FileStatDelta } from './summary';\n\n/** Output of a pipeline run: the printed code plus the per-file optimization delta. */\nexport interface PipelineOutput {\n readonly code: string;\n readonly stats: FileStatDelta;\n}\n\n/** UTF-8 byte length (matches the CLI's `bytes()` — bytesSaved is measured in real bytes). */\nfunction bytes(s: string): number {\n return Buffer.byteLength(s, 'utf8');\n}\n\n/**\n * Rough class-token count (provider-independent, string-level) — identical to the CLI's\n * `countClassTokens`, so both surfaces report the same \"classes compressed\" figure.\n */\nfunction countClassTokens(code: string): number {\n let total = 0;\n const re = /\\b(?:className|class)\\s*=\\s*\"([^\"]*)\"/g;\n let m: RegExpExecArray | null;\n while ((m = re.exec(code)) !== null) {\n total += m[1]!.split(/\\s+/).filter((t) => t.length > 0).length;\n }\n return total;\n}\n\n/**\n * Compute the per-file stat delta the same way the CLI's `finish()` does: nodes from the IR\n * node-count delta, classes from the class-token delta, bytes from the UTF-8 byte-length delta.\n */\nfunction computeStats(code: string, out: string, nodesIn: number, nodesOut: number): FileStatDelta {\n const classesBefore = countClassTokens(code);\n const classesAfter = countClassTokens(out);\n return {\n nodesRemoved: Math.max(0, nodesIn - nodesOut),\n classesSaved: Math.max(0, classesBefore - classesAfter),\n bytesSaved: bytes(code) - bytes(out),\n };\n}\n\n/** `.tsx`/`.jsx` ⇒ the matching {@link FileKind}; anything else ⇒ null (no JSX frontend). */\nexport function jsxKindOf(id: string): FileKind | null {\n const clean = id.split('?', 1)[0] ?? id;\n if (clean.endsWith('.tsx')) return 'tsx';\n if (clean.endsWith('.jsx')) return 'jsx';\n return null;\n}\n\n/** `.html`/`.htm` ⇒ `'html'`; anything else ⇒ null (no HTML frontend). */\nexport function htmlKindOf(id: string): FileKind | null {\n const clean = (id.split('?', 1)[0] ?? id).toLowerCase();\n if (clean.endsWith('.html') || clean.endsWith('.htm')) return 'html';\n return null;\n}\n\n/** First registered source's EOL, defaulting to `\\n`. */\nfunction eolOf(doc: IRDocument): '\\n' | '\\r\\n' {\n for (const src of doc.sources.values()) return src.eol;\n return '\\n';\n}\n\n/** Group the flat pattern list into one {@link Pass} per {@link PassPhase} (derived from category). */\nfunction buildPasses(patterns: readonly Pattern[]): Pass[] {\n const byPhase = new Map<PassPhase, Pattern[]>();\n for (const p of patterns) {\n const phase = (p.category.split('/', 1)[0] ?? 'flatten') as PassPhase;\n let bucket = byPhase.get(phase);\n if (!bucket) {\n bucket = [];\n byPhase.set(phase, bucket);\n }\n bucket.push(p);\n }\n const passes: Pass[] = [];\n for (const [phase, pats] of byPhase) {\n passes.push({ phase, category: `${phase}/builtin` as PassCategory, patterns: pats });\n }\n return passes;\n}\n\n/** The parsed, authorized doc + the apply context + grouped passes, shared by sync + async runs. */\ninterface PreparedRun {\n readonly doc: IRDocument;\n readonly ctx: ApplyContext;\n readonly passes: readonly Pass[];\n}\n\n/** PARSE (JSX → IR, resolving classes onto `computed`) + AUTHORIZE + build the apply context. */\nfunction preparePipeline(\n code: string,\n id: string,\n kind: FileKind,\n resolver: StyleResolver,\n patterns: readonly Pattern[],\n safety: SafetyLevel,\n gate: FlattenGate,\n): PreparedRun {\n const parsed = createJsxFrontend().parse(code, {\n id,\n kind,\n resolver,\n normalizer,\n config: {},\n onDiagnostic: () => {},\n });\n const doc = parsed.doc;\n\n // AUTHORIZE — the JSX frontend defaults every node's safety floor to 0. The orchestrator opens the\n // floor to the max; the configured ceiling + each pattern's opacity predicates are the real gate.\n for (const node of doc.nodes.values()) node.meta.safetyFloor = 3;\n\n const ctx: ApplyContext = {\n doc,\n safetyCeiling: safety,\n normalizer,\n // Real CSS-selector-safety index from the active resolver: a wrapper a combinator/structural\n // selector depends on is flagged so the flatten guards refuse to flatten it. Tailwind (no\n // complexSelectors) degrades to the null index — behaviour unchanged.\n selectors: buildSelectorIndex(doc, resolver),\n resolver,\n gate,\n };\n return { doc, ctx, passes: buildPasses(patterns) };\n}\n\n/** REVERSE-EMIT optimized computed styles back into class tokens, then PRINT IR → JSX/TSX text. */\nfunction finishPipeline(optimized: IRDocument, id: string, resolver: StyleResolver): string {\n syncClassesFromComputed(optimized, resolver, normalizer);\n const printed = createJsxBackend().print(\n optimized,\n { moduleId: id, ops: [], provenance: new Map() },\n {\n normalizer,\n resolver,\n sink: createSyntheticSink(),\n eol: eolOf(optimized),\n onDiagnostic: () => {},\n },\n );\n return printed.code;\n}\n\n/** SYNC full pipeline (gate `'provably-safe'` — never changes rendering, never launches a browser). */\nexport function runJsxPipeline(\n code: string,\n id: string,\n kind: FileKind,\n resolver: StyleResolver,\n patterns: readonly Pattern[],\n safety: SafetyLevel,\n): PipelineOutput {\n const { doc, ctx, passes } = preparePipeline(code, id, kind, resolver, patterns, safety, 'provably-safe');\n const nodesIn = doc.nodes.size;\n const { doc: optimized } = runPasses(doc, passes, ctx);\n const out = finishPipeline(optimized, id, resolver);\n return { code: out, stats: computeStats(code, out, nodesIn, optimized.nodes.size) };\n}\n\n/* ───────────────────────── HTML pipeline (parse5 frontend/backend) ───────────────────────── */\n\n/**\n * PARSE (HTML → IR, resolving classes onto `computed`) + AUTHORIZE + build the apply context. Unlike\n * the JSX path, the HTML frontend sets per-node `safetyFloor` itself (opaque nodes → 0), so we must\n * NOT blanket-open every node to 3 (that would strip the opacity floors).\n */\nfunction prepareHtml(\n code: string,\n id: string,\n resolver: StyleResolver,\n patterns: readonly Pattern[],\n safety: SafetyLevel,\n gate: FlattenGate,\n): PreparedRun {\n const parsed = createHtmlFrontend().parse(code, {\n id,\n kind: 'html',\n resolver,\n normalizer,\n config: {},\n onDiagnostic: () => {},\n });\n const doc = parsed.doc;\n const ctx: ApplyContext = {\n doc,\n safetyCeiling: safety,\n normalizer,\n selectors: buildSelectorIndex(doc, resolver),\n resolver,\n gate,\n };\n return { doc, ctx, passes: buildPasses(patterns) };\n}\n\n/** REVERSE-EMIT optimized computed styles back into class tokens, then PRINT IR → HTML text. */\nfunction finishHtmlPipeline(optimized: IRDocument, id: string, resolver: StyleResolver): string {\n syncClassesFromComputed(optimized, resolver, normalizer);\n const printed = createHtmlBackend().print(\n optimized,\n { moduleId: id, ops: [], provenance: new Map() },\n {\n normalizer,\n resolver,\n sink: createSyntheticSink(),\n eol: eolOf(optimized),\n onDiagnostic: () => {},\n },\n );\n return printed.code;\n}\n\n/** SYNC full HTML pipeline (gate `'provably-safe'` — surgical span edits over verbatim source). */\nexport function runHtmlPipeline(\n code: string,\n id: string,\n resolver: StyleResolver,\n patterns: readonly Pattern[],\n safety: SafetyLevel,\n): PipelineOutput {\n const { doc, ctx, passes } = prepareHtml(code, id, resolver, patterns, safety, 'provably-safe');\n const nodesIn = doc.nodes.size;\n const { doc: optimized } = runPasses(doc, passes, ctx);\n const out = finishHtmlPipeline(optimized, id, resolver);\n return { code: out, stats: computeStats(code, out, nodesIn, optimized.nodes.size) };\n}\n","/**\n * domflax — build-end optimization SUMMARY.\n *\n * A tiny, dependency-free formatter shared by the Vite and webpack/Next adapters. Each adapter\n * accumulates {@link FileStatDelta} numbers across the build into a {@link Totals}, then prints ONE\n * boxed {@link renderSummary} block at build end — so the user sees the aggregate payoff without any\n * per-file spam in between.\n *\n * ```\n * ▲ domflax\n * ────────────────────────────────\n * files optimized 42\n * DOM nodes removed 318\n * classes compressed 1,204\n * size saved 18.7 KB\n * ────────────────────────────────\n * ```\n */\n\n/** Per-file optimization delta (from a single {@link Domflax.transform}). */\nexport interface FileStatDelta {\n /** DOM/IR nodes removed by provably-safe flattens. */\n readonly nodesRemoved: number;\n /** Class tokens eliminated by semantic compression. */\n readonly classesSaved: number;\n /** Bytes saved = original byte length − output byte length (may be negative in edge cases). */\n readonly bytesSaved: number;\n}\n\n/** All-zero delta, for unsupported / unchanged files. */\nexport function zeroStats(): FileStatDelta {\n return { nodesRemoved: 0, classesSaved: 0, bytesSaved: 0 };\n}\n\n/** Aggregate accumulator across a whole build. `files` counts only files that actually changed. */\nexport interface Totals {\n files: number;\n nodesRemoved: number;\n classesCompressed: number;\n bytesSaved: number;\n}\n\n/** A fresh, zeroed {@link Totals}. */\nexport function emptyTotals(): Totals {\n return { files: 0, nodesRemoved: 0, classesCompressed: 0, bytesSaved: 0 };\n}\n\n/** Reset a {@link Totals} in place (used to clear per rebuild in watch/serve mode). */\nexport function resetTotals(t: Totals): void {\n t.files = 0;\n t.nodesRemoved = 0;\n t.classesCompressed = 0;\n t.bytesSaved = 0;\n}\n\n/** Fold one file's delta into the running totals. Only `changed` files count toward `files`. */\nexport function addStats(t: Totals, s: FileStatDelta, changed: boolean): void {\n if (!changed) return;\n t.files += 1;\n t.nodesRemoved += s.nodesRemoved;\n t.classesCompressed += s.classesSaved;\n t.bytesSaved += s.bytesSaved;\n}\n\n/* ────────────────────────────────────────────────────────────────────────── *\n * Formatting\n * ────────────────────────────────────────────────────────────────────────── */\n\nconst BYTE_UNITS = ['KB', 'MB', 'GB', 'TB'] as const;\n\n/** Human byte size: `< 1 KiB` stays `B`, otherwise KB/MB/GB/TB with one decimal (1024-based). */\nexport function formatBytes(n: number): string {\n const abs = Math.abs(n);\n if (abs < 1024) return `${n} B`;\n let value = n / 1024;\n let unit = 0;\n while (Math.abs(value) >= 1024 && unit < BYTE_UNITS.length - 1) {\n value /= 1024;\n unit += 1;\n }\n return `${value.toFixed(1)} ${BYTE_UNITS[unit]}`;\n}\n\n/** Integer with thousands separators, e.g. `1204` → `1,204` (locale-independent). */\nexport function formatCount(n: number): string {\n return Math.trunc(n)\n .toString()\n .replace(/\\B(?=(\\d{3})+(?!\\d))/g, ',');\n}\n\n/** Width of the label column (values align to this offset within the 3-space row indent). */\nconst LABEL_WIDTH = 20;\n/** The horizontal rule inside the box. */\nconst RULE = ` ${'─'.repeat(32)}`;\n\nfunction row(label: string, value: string): string {\n return ` ${label.padEnd(LABEL_WIDTH)}${value}`;\n}\n\n/**\n * Render the boxed build-end summary. Callers should only invoke this when `totals.files > 0`\n * (i.e. at least one file changed) so a no-op build stays silent.\n */\nexport function renderSummary(totals: Totals): string {\n return [\n '',\n ' ▲ domflax',\n RULE,\n row('files optimized', formatCount(totals.files)),\n row('DOM nodes removed', formatCount(totals.nodesRemoved)),\n row('classes compressed', formatCount(totals.classesCompressed)),\n row('size saved', formatBytes(totals.bytesSaved)),\n RULE,\n '',\n ].join('\\n');\n}\n\n/* ────────────────────────────────────────────────────────────────────────── *\n * webpack loader ↔ plugin bridge\n *\n * The webpack loader (`webpack-loader.cjs`) and the plugin (`index.cjs`) ship as SEPARATE bundles,\n * so a module-level accumulator would not be shared between them. Instead the loader stashes the\n * running {@link Totals} directly on the webpack `compilation` object under a GLOBAL-REGISTRY symbol\n * (`Symbol.for`, shared process-wide across both bundles); the plugin reads the same key from the\n * compilation in its `done` hook. A fresh compilation per (re)build gives per-build totals for free.\n * ────────────────────────────────────────────────────────────────────────── */\n\n/** Global-registry keys — identical string ⇒ identical symbol across the separately-bundled files. */\nconst TOTALS_KEY = Symbol.for('domflax.buildTotals');\nconst PRINTED_KEY = Symbol.for('domflax.summaryPrinted');\n\n/** Accumulate one file's delta onto a webpack `compilation` (called from the loader). Defensive. */\nexport function accumulateOnCompilation(compilation: unknown, stats: FileStatDelta, changed: boolean): void {\n if (compilation === null || typeof compilation !== 'object') return;\n const bag = compilation as Record<symbol, unknown>;\n let totals = bag[TOTALS_KEY] as Totals | undefined;\n if (!totals) {\n totals = emptyTotals();\n bag[TOTALS_KEY] = totals;\n }\n addStats(totals, stats, changed);\n}\n\n/**\n * Print the summary stashed on a `compilation` exactly once (called from the plugin's `done` hook).\n * Silent when nothing was stashed or nothing changed. The once-latch guards a double-tap.\n */\nexport function printCompilationSummary(compilation: unknown): void {\n if (compilation === null || typeof compilation !== 'object') return;\n const bag = compilation as Record<symbol, unknown>;\n if (bag[PRINTED_KEY]) return;\n bag[PRINTED_KEY] = true;\n const totals = bag[TOTALS_KEY] as Totals | undefined;\n if (totals && totals.files > 0) process.stdout.write(renderSummary(totals));\n}\n"],"mappings":"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","names":[]}
|
|
@@ -61,6 +61,7 @@ function defaultMeta(safetyFloor = 0) {
|
|
|
61
61
|
hasDynamicChildren: false,
|
|
62
62
|
isComponent: false,
|
|
63
63
|
hasDangerousHtml: false,
|
|
64
|
+
hasUnresolvedClasses: false,
|
|
64
65
|
targetedByCombinator: false,
|
|
65
66
|
targetedByStructuralPseudo: false,
|
|
66
67
|
selectorDependents: 0,
|
|
@@ -1211,6 +1212,9 @@ function classifyFlattenOps(before, after, ops, norm) {
|
|
|
1211
1212
|
if (!wrapper || wrapper.kind !== "element") {
|
|
1212
1213
|
return { kind: "provably-safe", wrapperId: null, childId: null };
|
|
1213
1214
|
}
|
|
1215
|
+
if (wrapper.meta.hasUnresolvedClasses) {
|
|
1216
|
+
return { kind: "needs-verification", wrapperId, childId: survivingChildOf(ops, wrapper, before) };
|
|
1217
|
+
}
|
|
1214
1218
|
const childId = survivingChildOf(ops, wrapper, before);
|
|
1215
1219
|
const wrapperComputed = norm.normalizeStyleMap(wrapper.computed);
|
|
1216
1220
|
const childAfter = childId != null ? getElement(after, childId)?.computed ?? null : null;
|
|
@@ -1671,15 +1675,26 @@ function residualStyle(computed2, covered, norm) {
|
|
|
1671
1675
|
}
|
|
1672
1676
|
return { blocks };
|
|
1673
1677
|
}
|
|
1678
|
+
function joinedLength(tokens) {
|
|
1679
|
+
if (tokens.length === 0) return 0;
|
|
1680
|
+
let len = tokens.length - 1;
|
|
1681
|
+
for (const t of tokens) len += t.length;
|
|
1682
|
+
return len;
|
|
1683
|
+
}
|
|
1684
|
+
var COMPRESS_FLOOR = 1;
|
|
1674
1685
|
function syncClassesFromComputed(doc, resolver, norm) {
|
|
1675
1686
|
const sink = createSyntheticSink();
|
|
1687
|
+
const isDroppable = (t) => resolver.owns(t) && resolver.selectorUsage(t).droppable;
|
|
1676
1688
|
for (const id of elementIds(doc)) {
|
|
1677
1689
|
const el = getElement(doc, id);
|
|
1678
1690
|
if (!el) continue;
|
|
1679
|
-
if (!el.meta.styleDirty) continue;
|
|
1680
1691
|
if (el.classes.opaque || el.classes.hasDynamic) continue;
|
|
1692
|
+
const compressOnly = !el.meta.styleDirty;
|
|
1693
|
+
if (compressOnly && el.meta.safetyFloor < COMPRESS_FLOOR) continue;
|
|
1681
1694
|
const tokens = staticTokensOf(el.classes);
|
|
1682
|
-
|
|
1695
|
+
if (tokens.length === 0) continue;
|
|
1696
|
+
const retained = tokens.filter((t) => !isDroppable(t));
|
|
1697
|
+
if (compressOnly && retained.length === tokens.length) continue;
|
|
1683
1698
|
const covered = retained.length > 0 ? resolver.resolve({ classes: retained }).styles : null;
|
|
1684
1699
|
const target = covered ? residualStyle(el.computed, covered, norm) : el.computed;
|
|
1685
1700
|
const ctx = { normalizer: norm, sink };
|
|
@@ -1690,7 +1705,7 @@ function syncClassesFromComputed(doc, resolver, norm) {
|
|
|
1690
1705
|
const seen = /* @__PURE__ */ new Set();
|
|
1691
1706
|
for (const t of tokens) {
|
|
1692
1707
|
if (seen.has(t)) continue;
|
|
1693
|
-
const keep = emittedSet.has(t) || !
|
|
1708
|
+
const keep = emittedSet.has(t) || !isDroppable(t);
|
|
1694
1709
|
if (keep) {
|
|
1695
1710
|
next.push(t);
|
|
1696
1711
|
seen.add(t);
|
|
@@ -1702,10 +1717,99 @@ function syncClassesFromComputed(doc, resolver, norm) {
|
|
|
1702
1717
|
seen.add(c);
|
|
1703
1718
|
}
|
|
1704
1719
|
if (sameTokens(next, tokens)) continue;
|
|
1720
|
+
if (compressOnly) {
|
|
1721
|
+
if (!norm.equals(resolver.resolve({ classes: next }).styles, el.computed)) continue;
|
|
1722
|
+
if (joinedLength(next) > joinedLength(tokens)) continue;
|
|
1723
|
+
}
|
|
1705
1724
|
el.classes = staticClassList(el.classes, next);
|
|
1706
1725
|
}
|
|
1707
1726
|
}
|
|
1708
1727
|
|
|
1728
|
+
// ../core/src/compress-engine.ts
|
|
1729
|
+
init_esm_shims();
|
|
1730
|
+
var SEP = "";
|
|
1731
|
+
function tupleKey(condition, property, value, important) {
|
|
1732
|
+
return `${condition}${SEP}${property}${SEP}${value}${SEP}${important ? "1" : "0"}`;
|
|
1733
|
+
}
|
|
1734
|
+
function styleMapTuples(map, norm) {
|
|
1735
|
+
const out = [];
|
|
1736
|
+
const normalized = norm.normalizeStyleMap(map);
|
|
1737
|
+
for (const [ck, block] of normalized.blocks) {
|
|
1738
|
+
for (const [prop, decl] of block.decls) {
|
|
1739
|
+
out.push(tupleKey(String(ck), String(prop), String(decl.value), decl.important));
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
return out;
|
|
1743
|
+
}
|
|
1744
|
+
var DEFAULT_MAX_UNIVERSE = 20;
|
|
1745
|
+
function minStringCover(universe, vocabulary, options = {}) {
|
|
1746
|
+
const uniq = [...new Set(universe)];
|
|
1747
|
+
if (uniq.length === 0) return [];
|
|
1748
|
+
const n = uniq.length;
|
|
1749
|
+
const max = options.maxUniverse ?? DEFAULT_MAX_UNIVERSE;
|
|
1750
|
+
if (n > max) return null;
|
|
1751
|
+
const bitOf = /* @__PURE__ */ new Map();
|
|
1752
|
+
uniq.forEach((t, i) => bitOf.set(t, i));
|
|
1753
|
+
const byMask = /* @__PURE__ */ new Map();
|
|
1754
|
+
for (const entry of vocabulary) {
|
|
1755
|
+
if (entry.tuples.length === 0) continue;
|
|
1756
|
+
let mask = 0;
|
|
1757
|
+
let ok = true;
|
|
1758
|
+
for (const t of entry.tuples) {
|
|
1759
|
+
const b = bitOf.get(t);
|
|
1760
|
+
if (b === void 0) {
|
|
1761
|
+
ok = false;
|
|
1762
|
+
break;
|
|
1763
|
+
}
|
|
1764
|
+
mask |= 1 << b;
|
|
1765
|
+
}
|
|
1766
|
+
if (!ok || mask === 0) continue;
|
|
1767
|
+
const cost = entry.token.length + 1;
|
|
1768
|
+
const prev = byMask.get(mask);
|
|
1769
|
+
if (!prev || cost < prev.cost || cost === prev.cost && entry.token < prev.token) {
|
|
1770
|
+
byMask.set(mask, { token: entry.token, mask, cost });
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
const cands = [...byMask.values()];
|
|
1774
|
+
if (cands.length === 0) return null;
|
|
1775
|
+
const full = (1 << n) - 1;
|
|
1776
|
+
const byBit = Array.from({ length: n }, () => []);
|
|
1777
|
+
cands.forEach((c, ci) => {
|
|
1778
|
+
for (let b = 0; b < n; b += 1) if (c.mask & 1 << b) byBit[b].push(ci);
|
|
1779
|
+
});
|
|
1780
|
+
const size = full + 1;
|
|
1781
|
+
const dp = new Float64Array(size).fill(Infinity);
|
|
1782
|
+
const fromCand = new Int32Array(size).fill(-1);
|
|
1783
|
+
const fromMask = new Int32Array(size).fill(-1);
|
|
1784
|
+
dp[0] = 0;
|
|
1785
|
+
for (let mask = 0; mask < full; mask += 1) {
|
|
1786
|
+
const cur = dp[mask];
|
|
1787
|
+
if (!Number.isFinite(cur)) continue;
|
|
1788
|
+
let b = 0;
|
|
1789
|
+
while (b < n && mask & 1 << b) b += 1;
|
|
1790
|
+
for (const ci of byBit[b]) {
|
|
1791
|
+
const c = cands[ci];
|
|
1792
|
+
const nm = mask | c.mask;
|
|
1793
|
+
const cost = cur + c.cost;
|
|
1794
|
+
if (cost < dp[nm]) {
|
|
1795
|
+
dp[nm] = cost;
|
|
1796
|
+
fromCand[nm] = ci;
|
|
1797
|
+
fromMask[nm] = mask;
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
if (!Number.isFinite(dp[full])) return null;
|
|
1802
|
+
const chosen = [];
|
|
1803
|
+
let m = full;
|
|
1804
|
+
while (m !== 0) {
|
|
1805
|
+
const ci = fromCand[m];
|
|
1806
|
+
if (ci < 0) return null;
|
|
1807
|
+
chosen.push(cands[ci].token);
|
|
1808
|
+
m = fromMask[m];
|
|
1809
|
+
}
|
|
1810
|
+
return [...new Set(chosen)].sort();
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1709
1813
|
// ../core/src/index.ts
|
|
1710
1814
|
init_esm_shims();
|
|
1711
1815
|
|
|
@@ -1794,6 +1898,18 @@ var BOX_SIDES = {
|
|
|
1794
1898
|
padding: ["padding-top", "padding-right", "padding-bottom", "padding-left"],
|
|
1795
1899
|
margin: ["margin-top", "margin-right", "margin-bottom", "margin-left"],
|
|
1796
1900
|
inset: ["top", "right", "bottom", "left"],
|
|
1901
|
+
"scroll-margin": [
|
|
1902
|
+
"scroll-margin-top",
|
|
1903
|
+
"scroll-margin-right",
|
|
1904
|
+
"scroll-margin-bottom",
|
|
1905
|
+
"scroll-margin-left"
|
|
1906
|
+
],
|
|
1907
|
+
"scroll-padding": [
|
|
1908
|
+
"scroll-padding-top",
|
|
1909
|
+
"scroll-padding-right",
|
|
1910
|
+
"scroll-padding-bottom",
|
|
1911
|
+
"scroll-padding-left"
|
|
1912
|
+
],
|
|
1797
1913
|
"border-width": [
|
|
1798
1914
|
"border-top-width",
|
|
1799
1915
|
"border-right-width",
|
|
@@ -1811,8 +1927,35 @@ var BOX_SIDES = {
|
|
|
1811
1927
|
"border-right-color",
|
|
1812
1928
|
"border-bottom-color",
|
|
1813
1929
|
"border-left-color"
|
|
1930
|
+
],
|
|
1931
|
+
// `border-radius` 1–4 value form maps to the four CORNERS (TL, TR, BR, BL) — the same positional
|
|
1932
|
+
// pattern boxFourSides implements. Only the slash-free form is expanded (see expandShorthand).
|
|
1933
|
+
"border-radius": [
|
|
1934
|
+
"border-top-left-radius",
|
|
1935
|
+
"border-top-right-radius",
|
|
1936
|
+
"border-bottom-right-radius",
|
|
1937
|
+
"border-bottom-left-radius"
|
|
1814
1938
|
]
|
|
1815
1939
|
};
|
|
1940
|
+
var AXIS_PAIRS = {
|
|
1941
|
+
overflow: ["overflow-x", "overflow-y"],
|
|
1942
|
+
"overscroll-behavior": ["overscroll-behavior-x", "overscroll-behavior-y"],
|
|
1943
|
+
"place-items": ["align-items", "justify-items"],
|
|
1944
|
+
"place-content": ["align-content", "justify-content"],
|
|
1945
|
+
"place-self": ["align-self", "justify-self"]
|
|
1946
|
+
};
|
|
1947
|
+
var LOGICAL_PAIRS = {
|
|
1948
|
+
"padding-inline": ["padding-left", "padding-right"],
|
|
1949
|
+
"padding-block": ["padding-top", "padding-bottom"],
|
|
1950
|
+
"margin-inline": ["margin-left", "margin-right"],
|
|
1951
|
+
"margin-block": ["margin-top", "margin-bottom"],
|
|
1952
|
+
"inset-inline": ["left", "right"],
|
|
1953
|
+
"inset-block": ["top", "bottom"],
|
|
1954
|
+
"scroll-padding-inline": ["scroll-padding-left", "scroll-padding-right"],
|
|
1955
|
+
"scroll-padding-block": ["scroll-padding-top", "scroll-padding-bottom"],
|
|
1956
|
+
"scroll-margin-inline": ["scroll-margin-left", "scroll-margin-right"],
|
|
1957
|
+
"scroll-margin-block": ["scroll-margin-top", "scroll-margin-bottom"]
|
|
1958
|
+
};
|
|
1816
1959
|
function splitTopLevel(value) {
|
|
1817
1960
|
const out = [];
|
|
1818
1961
|
let depth = 0;
|
|
@@ -1846,6 +1989,7 @@ function boxFourSides(values) {
|
|
|
1846
1989
|
}
|
|
1847
1990
|
}
|
|
1848
1991
|
function expandShorthand(prop, value) {
|
|
1992
|
+
if (prop === "border-radius" && value.includes("/")) return [[prop, value]];
|
|
1849
1993
|
const box = BOX_SIDES[prop];
|
|
1850
1994
|
if (box) {
|
|
1851
1995
|
const parts = splitTopLevel(value);
|
|
@@ -1855,6 +1999,19 @@ function expandShorthand(prop, value) {
|
|
|
1855
1999
|
}
|
|
1856
2000
|
return [[prop, value]];
|
|
1857
2001
|
}
|
|
2002
|
+
const axis = AXIS_PAIRS[prop];
|
|
2003
|
+
if (axis) {
|
|
2004
|
+
const parts = splitTopLevel(value);
|
|
2005
|
+
if (parts.length === 1) return [[axis[0], parts[0]], [axis[1], parts[0]]];
|
|
2006
|
+
if (parts.length === 2) return [[axis[0], parts[0]], [axis[1], parts[1]]];
|
|
2007
|
+
return [[prop, value]];
|
|
2008
|
+
}
|
|
2009
|
+
const logical = LOGICAL_PAIRS[prop];
|
|
2010
|
+
if (logical) {
|
|
2011
|
+
const parts = splitTopLevel(value);
|
|
2012
|
+
if (parts.length === 1) return [[logical[0], parts[0]], [logical[1], parts[0]]];
|
|
2013
|
+
return [[prop, value]];
|
|
2014
|
+
}
|
|
1858
2015
|
if (prop === "gap" || prop === "grid-gap") {
|
|
1859
2016
|
const parts = splitTopLevel(value);
|
|
1860
2017
|
if (parts.length === 1) {
|
|
@@ -2016,7 +2173,12 @@ var VISUAL_PROPERTIES = /* @__PURE__ */ new Set([
|
|
|
2016
2173
|
"border-right-color",
|
|
2017
2174
|
"border-bottom-color",
|
|
2018
2175
|
"border-left-color",
|
|
2019
|
-
|
|
2176
|
+
// `border-radius` is expanded to its four corner longhands by the shared normalizer, so the
|
|
2177
|
+
// paint-establishing check must match those (a rounded wrapper still clips its background).
|
|
2178
|
+
"border-top-left-radius",
|
|
2179
|
+
"border-top-right-radius",
|
|
2180
|
+
"border-bottom-right-radius",
|
|
2181
|
+
"border-bottom-left-radius",
|
|
2020
2182
|
"box-shadow",
|
|
2021
2183
|
"outline",
|
|
2022
2184
|
"outline-width",
|
|
@@ -2042,6 +2204,7 @@ var hasOwnVisualStyle = (node, ctx) => {
|
|
|
2042
2204
|
const el = asElement(node);
|
|
2043
2205
|
if (!el) return false;
|
|
2044
2206
|
if (el.meta.hasOwnVisualStyle) return true;
|
|
2207
|
+
if (el.meta.hasUnresolvedClasses) return true;
|
|
2045
2208
|
const computedMap = ctx.computedOf(el) ?? el.computed;
|
|
2046
2209
|
const norm = normalizer.normalizeStyleMap(computedMap);
|
|
2047
2210
|
for (const block of norm.blocks.values()) {
|
|
@@ -2332,6 +2495,10 @@ export {
|
|
|
2332
2495
|
createSyntheticSink,
|
|
2333
2496
|
createPipeline,
|
|
2334
2497
|
syncClassesFromComputed,
|
|
2498
|
+
tupleKey,
|
|
2499
|
+
styleMapTuples,
|
|
2500
|
+
DEFAULT_MAX_UNIVERSE,
|
|
2501
|
+
minStringCover,
|
|
2335
2502
|
createNormalizer,
|
|
2336
2503
|
normalizer,
|
|
2337
2504
|
isStyleSuperset,
|
|
@@ -2355,4 +2522,4 @@ export {
|
|
|
2355
2522
|
definePattern,
|
|
2356
2523
|
pattern
|
|
2357
2524
|
};
|
|
2358
|
-
//# sourceMappingURL=chunk-
|
|
2525
|
+
//# sourceMappingURL=chunk-TTJEXWAC.js.map
|