domflax 0.1.1 → 0.1.2

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 CHANGED
@@ -27,7 +27,7 @@ It rewrites only the **static shape** of your markup. Dynamic class lists (`clas
27
27
  - **Flattening is conservative.** A wrapper is removed only when removal is *provably* render-neutral — it establishes no layout context and has no style to reproduce on its child. It never drops a style it can't reproduce, and never touches a wrapper a CSS selector depends on (`.list > .item h3`).
28
28
  - domflax runs as a **purely static** source transform. It never launches a browser, so builds stay fast and deterministic.
29
29
 
30
- > **Status: v0.1.1.** Works end-to-end on real `.jsx`/`.tsx` — in component-return position **and inside `.map()` / expressions (list rows)** — via Vite, Next.js (webpack), and the CLI, with Tailwind and custom-CSS providers. 22 patterns. Wrappers that establish a layout context (e.g. `flex`/`grid` centering) are **conservatively preserved** — proving those render-identical needs context a static pass can't see; recovering them safely is on the Roadmap. APIs may change before 1.0.
30
+ > **Status: v0.1.2.** Works end-to-end on real `.jsx`/`.tsx` — in component-return position **and inside `.map()` / expressions (list rows)** — via Vite, Next.js (webpack), and the CLI, with Tailwind and custom-CSS providers. 22 patterns. Wrappers that establish a layout context (e.g. `flex`/`grid` centering) are **conservatively preserved** — proving those render-identical needs context a static pass can't see; recovering them safely is on the Roadmap. APIs may change before 1.0.
31
31
 
32
32
  ## Install
33
33
 
package/dist/cli.cjs CHANGED
@@ -5636,24 +5636,26 @@ function createTransform(options) {
5636
5636
  }
5637
5637
  };
5638
5638
  }
5639
- function builtinPatternNames() {
5640
- return builtinPatterns.map((p2) => p2.name);
5641
- }
5642
5639
 
5643
5640
  // ../cli/src/walk.ts
5644
5641
  init_cjs_shims();
5645
5642
  var fs = __toESM(require("fs"), 1);
5646
5643
  var path4 = __toESM(require("path"), 1);
5647
- var SUPPORTED_EXTS = [".jsx", ".tsx", ".html", ".htm"];
5644
+ var SUPPORTED_EXTS = [".jsx", ".tsx"];
5645
+ var HTML_EXTS = [".html", ".htm"];
5648
5646
  var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "domflax-out"]);
5649
5647
  function isSupported(file) {
5650
5648
  const lower = file.toLowerCase();
5651
5649
  return SUPPORTED_EXTS.some((ext) => lower.endsWith(ext));
5652
5650
  }
5651
+ function isHtml(file) {
5652
+ const lower = file.toLowerCase();
5653
+ return HTML_EXTS.some((ext) => lower.endsWith(ext));
5654
+ }
5653
5655
  function hasGlobMagic(p2) {
5654
5656
  return /[*?[\]{}]/.test(p2);
5655
5657
  }
5656
- function walkDir(dir, out) {
5658
+ function walkDir(dir, out, counts) {
5657
5659
  let entries;
5658
5660
  try {
5659
5661
  entries = fs.readdirSync(dir, { withFileTypes: true });
@@ -5664,9 +5666,10 @@ function walkDir(dir, out) {
5664
5666
  const full = path4.join(dir, entry.name);
5665
5667
  if (entry.isDirectory()) {
5666
5668
  if (SKIP_DIRS.has(entry.name)) continue;
5667
- walkDir(full, out);
5668
- } else if (entry.isFile() && isSupported(entry.name)) {
5669
- out.push(full);
5669
+ walkDir(full, out, counts);
5670
+ } else if (entry.isFile()) {
5671
+ if (isSupported(entry.name)) out.push(full);
5672
+ else if (isHtml(entry.name)) counts.html += 1;
5670
5673
  }
5671
5674
  }
5672
5675
  }
@@ -5677,6 +5680,7 @@ function globSyncMaybe() {
5677
5680
  function discoverInputs(paths) {
5678
5681
  const files = [];
5679
5682
  const warnings = [];
5683
+ const counts = { html: 0 };
5680
5684
  const seen = /* @__PURE__ */ new Set();
5681
5685
  const push = (f) => {
5682
5686
  const abs = path4.resolve(f);
@@ -5700,11 +5704,13 @@ function discoverInputs(paths) {
5700
5704
  stat = null;
5701
5705
  }
5702
5706
  if (stat?.isDirectory()) {
5703
- walkDir(path4.resolve(p2), files);
5707
+ walkDir(path4.resolve(p2), files, counts);
5704
5708
  continue;
5705
5709
  }
5706
5710
  if (stat?.isFile()) {
5707
- push(p2);
5711
+ if (isSupported(p2)) push(p2);
5712
+ else if (isHtml(p2)) counts.html += 1;
5713
+ else warnings.push(`unsupported file type, skipped: ${p2}`);
5708
5714
  continue;
5709
5715
  }
5710
5716
  if (hasGlobMagic(p2)) {
@@ -5713,9 +5719,11 @@ function discoverInputs(paths) {
5713
5719
  warnings.push(`glob not supported on this Node version, skipped: ${p2}`);
5714
5720
  continue;
5715
5721
  }
5716
- const matches = glob(p2).filter(isSupported);
5717
- if (matches.length === 0) warnings.push(`no files matched: ${p2}`);
5718
- for (const m2 of matches) push(m2);
5722
+ const matched = glob(p2);
5723
+ const supported = matched.filter(isSupported);
5724
+ counts.html += matched.filter(isHtml).length;
5725
+ if (supported.length === 0) warnings.push(`no .jsx/.tsx files matched: ${p2}`);
5726
+ for (const m2 of supported) push(m2);
5719
5727
  continue;
5720
5728
  }
5721
5729
  warnings.push(`no such file or directory: ${p2}`);
@@ -5729,6 +5737,11 @@ function discoverInputs(paths) {
5729
5737
  deduped.push(abs);
5730
5738
  }
5731
5739
  }
5740
+ if (deduped.length === 0 && counts.html > 0) {
5741
+ warnings.push(
5742
+ `found ${counts.html} .html file${counts.html === 1 ? "" : "s"} but HTML optimization isn't supported yet (domflax currently optimizes .jsx/.tsx source; HTML is on the roadmap: https://github.com/Krishnesh-Mishra/domflax#roadmap).`
5743
+ );
5744
+ }
5732
5745
  return { files: deduped, inputRoot, warnings };
5733
5746
  }
5734
5747
 
@@ -6329,15 +6342,17 @@ var SKIP_DIRS2 = /* @__PURE__ */ new Set([
6329
6342
  ]);
6330
6343
  var COMMON_INPUT_DIRS = ["src", "app", "components", "pages", "lib", "ui", "public"];
6331
6344
  var CSS_FILE_CAP = 200;
6345
+ var DEFAULT_CSS_DEPTH = 10;
6332
6346
  function toRelative(root, abs) {
6333
6347
  const rel = path5.relative(root, abs);
6334
6348
  return rel.split(path5.sep).join("/");
6335
6349
  }
6336
- function detectCssFiles(root) {
6337
- const found = [];
6350
+ function detectCssFiles(root, scanRoots = [], maxDepth = DEFAULT_CSS_DEPTH) {
6351
+ const base = path5.resolve(root);
6352
+ const found = /* @__PURE__ */ new Map();
6338
6353
  let capped = false;
6339
- const walk = (dir) => {
6340
- if (capped) return;
6354
+ const walk = (dir, depth) => {
6355
+ if (capped || depth > maxDepth) return;
6341
6356
  let entries;
6342
6357
  try {
6343
6358
  entries = fs2.readdirSync(dir, { withFileTypes: true });
@@ -6348,23 +6363,31 @@ function detectCssFiles(root) {
6348
6363
  const full = path5.join(dir, entry.name);
6349
6364
  if (entry.isDirectory()) {
6350
6365
  if (SKIP_DIRS2.has(entry.name)) continue;
6351
- walk(full);
6366
+ walk(full, depth + 1);
6352
6367
  if (capped) return;
6353
6368
  } else if (entry.isFile() && entry.name.toLowerCase().endsWith(".css")) {
6354
- found.push(toRelative(root, full));
6355
- if (found.length >= CSS_FILE_CAP) {
6356
- capped = true;
6357
- return;
6369
+ const abs = path5.resolve(full);
6370
+ if (!found.has(abs)) {
6371
+ found.set(abs, toRelative(base, abs));
6372
+ if (found.size >= CSS_FILE_CAP) {
6373
+ capped = true;
6374
+ return;
6375
+ }
6358
6376
  }
6359
6377
  }
6360
6378
  }
6361
6379
  };
6362
- walk(path5.resolve(root));
6363
- found.sort((a, b3) => a.localeCompare(b3));
6380
+ walk(base, 0);
6381
+ for (const r2 of scanRoots) {
6382
+ if (capped) break;
6383
+ const abs = path5.resolve(r2);
6384
+ if (abs !== base) walk(abs, 0);
6385
+ }
6386
+ const list = [...found.values()].sort((a, b3) => a.localeCompare(b3));
6364
6387
  if (capped) {
6365
6388
  console.error(`domflax: more than ${CSS_FILE_CAP} CSS files found; showing the first ${CSS_FILE_CAP}.`);
6366
6389
  }
6367
- return found;
6390
+ return list;
6368
6391
  }
6369
6392
  function detectInputDirs(root) {
6370
6393
  const resolved = path5.resolve(root);
@@ -6446,15 +6469,6 @@ async function runWizard(base) {
6446
6469
  } else if (outputMode === "overwrite") {
6447
6470
  dangerouslyOverwriteSource = true;
6448
6471
  }
6449
- const allPasses = builtinPatternNames();
6450
- const passSelection = await fe({
6451
- message: "Which optimization passes should run?",
6452
- options: allPasses.map((name) => ({ value: name, label: name })),
6453
- initialValues: [...allPasses],
6454
- required: true
6455
- });
6456
- if (cancelled(passSelection)) return done();
6457
- const passes = passSelection;
6458
6472
  const provider = await ve({
6459
6473
  message: "How should class names resolve to styles?",
6460
6474
  options: [
@@ -6467,7 +6481,7 @@ async function runWizard(base) {
6467
6481
  if (cancelled(provider)) return done();
6468
6482
  let css = base.css;
6469
6483
  if (provider === "custom") {
6470
- const detectedCss = detectCssFiles(root);
6484
+ const detectedCss = detectCssFiles(root, [inputPath]);
6471
6485
  if (detectedCss.length > 0) {
6472
6486
  const picked = await fe({
6473
6487
  message: "Which CSS files should resolve your classes? (all detected files are pre-selected)",
@@ -6495,7 +6509,7 @@ async function runWizard(base) {
6495
6509
  css,
6496
6510
  dryRun,
6497
6511
  dangerouslyOverwriteSource,
6498
- passes: passes.length === allPasses.length ? null : passes,
6512
+ passes: null,
6499
6513
  safety: base.safety ?? DEFAULT_SAFETY
6500
6514
  };
6501
6515
  function done() {
@@ -6525,7 +6539,7 @@ async function execute(options) {
6525
6539
  const { files, inputRoot, warnings } = discoverInputs(options.paths);
6526
6540
  for (const w2 of warnings) console.error(`domflax: ${w2}`);
6527
6541
  if (files.length === 0) {
6528
- console.error("domflax: no .jsx/.tsx/.html files found for the given paths");
6542
+ console.error("domflax: no .jsx/.tsx files found for the given paths");
6529
6543
  return { exitCode: 1 };
6530
6544
  }
6531
6545
  const projectRoot = options.projectRoot ?? process.cwd();
@@ -6572,8 +6586,20 @@ async function execute(options) {
6572
6586
  failures += 1;
6573
6587
  }
6574
6588
  }
6589
+ if (options.dryRun) {
6590
+ console.log("\ndomflax: dry run \u2014 no files were written.");
6591
+ } else if (totals.changed === 0) {
6592
+ console.log(
6593
+ `
6594
+ domflax: processed ${totals.files} file${totals.files === 1 ? "" : "s"} \u2014 nothing to optimize (0 changed).`
6595
+ );
6596
+ } else {
6597
+ console.log(
6598
+ `
6599
+ domflax: optimized ${totals.changed} of ${totals.files} file${totals.files === 1 ? "" : "s"} (${totals.nodesRemoved} nodes removed, ${totals.classesSaved} classes saved, ${totals.bytesSaved} bytes saved).`
6600
+ );
6601
+ }
6575
6602
  if (options.report) printReport(totals);
6576
- if (options.dryRun) console.log("\ndomflax: dry run \u2014 no files were written.");
6577
6603
  return { exitCode: failures > 0 ? 1 : 0 };
6578
6604
  }
6579
6605
  async function main(argv = process.argv.slice(2)) {