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.
Files changed (42) hide show
  1. package/README.md +47 -29
  2. package/dist/{chunk-EVENAJYI.js → chunk-EYQXQQQH.js} +3 -3
  3. package/dist/{chunk-3Z5ZWLXX.js → chunk-FPT4EJ6Q.js} +805 -1612
  4. package/dist/chunk-FPT4EJ6Q.js.map +1 -0
  5. package/dist/{chunk-5FWENSD2.js → chunk-JBM3MJRM.js} +149 -10
  6. package/dist/chunk-JBM3MJRM.js.map +1 -0
  7. package/dist/{chunk-H5KTGI3A.js → chunk-TTJEXWAC.js} +172 -5
  8. package/dist/chunk-TTJEXWAC.js.map +1 -0
  9. package/dist/cli.cjs +1032 -1640
  10. package/dist/cli.cjs.map +1 -1
  11. package/dist/cli.js +30 -10
  12. package/dist/cli.js.map +1 -1
  13. package/dist/index.cjs +1116 -1627
  14. package/dist/index.cjs.map +1 -1
  15. package/dist/index.d.cts +226 -485
  16. package/dist/index.d.ts +226 -485
  17. package/dist/index.js +16 -36
  18. package/dist/{pattern-CP9_HpVK.d.cts → pattern-DotR_dHs.d.cts} +1 -1
  19. package/dist/pattern-kit.cjs +60 -1
  20. package/dist/pattern-kit.cjs.map +1 -1
  21. package/dist/pattern-kit.d.cts +2 -2
  22. package/dist/pattern-kit.d.ts +2 -2
  23. package/dist/pattern-kit.js +1 -1
  24. package/dist/{pattern-CYgsv-jO.d.ts → pattern-urm5uuwj.d.ts} +1 -1
  25. package/dist/{resolve-ops-Ci7LgYHC.d.ts → resolve-ops-D8aQina5.d.cts} +11 -0
  26. package/dist/{resolve-ops-Ci7LgYHC.d.cts → resolve-ops-D8aQina5.d.ts} +11 -0
  27. package/dist/verify.d.cts +1 -1
  28. package/dist/verify.d.ts +1 -1
  29. package/dist/webpack-loader.cjs +1014 -1578
  30. package/dist/webpack-loader.cjs.map +1 -1
  31. package/dist/webpack-loader.d.cts +8 -2
  32. package/dist/webpack-loader.d.ts +8 -2
  33. package/dist/webpack-loader.js +7 -4
  34. package/dist/webpack-loader.js.map +1 -1
  35. package/dist/worker.cjs +983 -1601
  36. package/dist/worker.cjs.map +1 -1
  37. package/dist/worker.js +3 -3
  38. package/package.json +1 -1
  39. package/dist/chunk-3Z5ZWLXX.js.map +0 -1
  40. package/dist/chunk-5FWENSD2.js.map +0 -1
  41. package/dist/chunk-H5KTGI3A.js.map +0 -1
  42. /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-3Z5ZWLXX.js";
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-H5KTGI3A.js";
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
- return finishPipeline(optimized, id, resolver);
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
- return finishHtmlPipeline(optimized, id, resolver);
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
- return out.code === code ? null : out;
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-5FWENSD2.js.map
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
- const retained = tokens.filter((t) => !resolver.selectorUsage(t).droppable);
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) || !resolver.selectorUsage(t).droppable;
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
- "border-radius",
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-H5KTGI3A.js.map
2525
+ //# sourceMappingURL=chunk-TTJEXWAC.js.map