html-minifier-next 4.16.1 → 4.16.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
@@ -358,36 +358,38 @@ How does HTML Minifier Next compare to other minifiers? (All minification with t
358
358
  | Site | Original Size (KB) | [HTML Minifier Next](https://github.com/j9t/html-minifier-next) ([config](https://github.com/j9t/html-minifier-next/blob/main/benchmarks/html-minifier.json))<br>[![npm last update](https://img.shields.io/npm/last-update/html-minifier-next)](https://socket.dev/npm/package/html-minifier-next) | [htmlnano](https://github.com/posthtml/htmlnano)<br>[![npm last update](https://img.shields.io/npm/last-update/htmlnano)](https://socket.dev/npm/package/htmlnano) | [@swc/html](https://github.com/swc-project/swc)<br>[![npm last update](https://img.shields.io/npm/last-update/@swc/html)](https://socket.dev/npm/package/@swc/html) | [minify-html](https://github.com/wilsonzlin/minify-html)<br>[![npm last update](https://img.shields.io/npm/last-update/@minify-html/node)](https://socket.dev/npm/package/@minify-html/node) | [minimize](https://github.com/Swaagie/minimize)<br>[![npm last update](https://img.shields.io/npm/last-update/minimize)](https://socket.dev/npm/package/minimize) | [html­com­pressor.­com](https://htmlcompressor.com/) |
359
359
  | --- | --- | --- | --- | --- | --- | --- | --- |
360
360
  | [A List Apart](https://alistapart.com/) | 59 | **50** | 51 | 52 | 51 | 54 | 52 |
361
- | [Apple](https://www.apple.com/) | 211 | **177** | 188 | 190 | 191 | 192 | 193 |
362
- | [BBC](https://www.bbc.co.uk/) | 675 | **614** | 633 | 634 | 635 | 669 | n/a |
361
+ | [Apple](https://www.apple.com/) | 211 | **176** | 187 | 189 | 190 | 191 | 192 |
362
+ | [BBC](https://www.bbc.co.uk/) | 674 | **613** | 633 | 633 | 634 | 669 | n/a |
363
363
  | [CERN](https://home.cern/) | 152 | **83** | 91 | 91 | 91 | 93 | 96 |
364
364
  | [CSS-Tricks](https://css-tricks.com/) | 162 | **119** | 127 | 143 | 143 | 148 | 144 |
365
365
  | [ECMAScript](https://tc39.es/ecma262/) | 7250 | **6401** | 6573 | 6455 | 6578 | 6626 | n/a |
366
366
  | [EDRi](https://edri.org/) | 80 | **59** | 70 | 70 | 71 | 75 | 73 |
367
367
  | [EFF](https://www.eff.org/) | 54 | **45** | 49 | 47 | 48 | 49 | 49 |
368
368
  | [European Alternatives](https://european-alternatives.eu/) | 48 | **30** | 32 | 32 | 32 | 32 | 32 |
369
- | [FAZ](https://www.faz.net/aktuell/) | 1579 | 1468 | **1414** | 1503 | 1514 | 1525 | n/a |
369
+ | [FAZ](https://www.faz.net/aktuell/) | 1570 | 1458 | **1405** | 1494 | 1505 | 1516 | n/a |
370
370
  | [French Tech](https://lafrenchtech.gouv.fr/) | 153 | **122** | 126 | 126 | 126 | 132 | 127 |
371
- | [Frontend Dogma](https://frontenddogma.com/) | 225 | **217** | 238 | 223 | 225 | 244 | 225 |
371
+ | [Frontend Dogma](https://frontenddogma.com/) | 225 | **217** | 238 | 224 | 225 | 244 | 225 |
372
372
  | [Google](https://www.google.com/) | 18 | **16** | 17 | 17 | 17 | 18 | 18 |
373
+ | [Ground News](https://ground.news/) | 2259 | **1978** | 2077 | 2107 | 2109 | 2246 | n/a |
373
374
  | [HTML Living Standard](https://html.spec.whatwg.org/multipage/) | 149 | 148 | 153 | **147** | 149 | 155 | 149 |
374
- | [Igalia](https://www.igalia.com/) | 50 | **33** | 36 | 36 | 36 | 37 | 36 |
375
- | [Leanpub](https://leanpub.com/) | 231 | **202** | 216 | 216 | 217 | 227 | 229 |
375
+ | [Igalia](https://www.igalia.com/) | 50 | **33** | 36 | 36 | 36 | 37 | 37 |
376
+ | [Leanpub](https://leanpub.com/) | 235 | **205** | 220 | 219 | 220 | 230 | 232 |
376
377
  | [Mastodon](https://mastodon.social/explore) | 37 | **28** | 32 | 35 | 35 | 36 | 36 |
377
- | [MDN](https://developer.mozilla.org/en-US/) | 106 | **60** | 62 | 63 | 63 | 66 | 66 |
378
- | [Middle East Eye](https://www.middleeasteye.net/) | 223 | **197** | 203 | 201 | 201 | 203 | 204 |
378
+ | [MDN](https://developer.mozilla.org/en-US/) | 109 | **62** | 64 | 65 | 65 | 68 | 68 |
379
+ | [Middle East Eye](https://www.middleeasteye.net/) | 223 | **196** | 203 | 201 | 201 | 202 | 203 |
379
380
  | [Mistral AI](https://mistral.ai/) | 361 | **319** | 324 | 326 | 327 | 357 | n/a |
380
381
  | [Mozilla](https://www.mozilla.org/) | 45 | **31** | 34 | 34 | 34 | 35 | 35 |
381
382
  | [Nielsen Norman Group](https://www.nngroup.com/) | 86 | 68 | **55** | 74 | 75 | 77 | 76 |
383
+ | [SitePoint](https://www.sitepoint.com/) | 482 | **351** | 422 | 456 | 460 | 478 | n/a |
382
384
  | [Startup-Verband](https://startupverband.de/) | 42 | **29** | 30 | 30 | 30 | 31 | 30 |
383
385
  | [TetraLogical](https://tetralogical.com/) | 44 | 38 | **35** | 38 | 39 | 39 | 39 |
384
- | [TPGi](https://www.tpgi.com/) | 175 | **159** | 160 | 164 | 166 | 172 | 172 |
386
+ | [TPGi](https://www.tpgi.com/) | 174 | **158** | 159 | 163 | 165 | 171 | 171 |
385
387
  | [United Nations](https://www.un.org/en/) | 152 | **112** | 121 | 125 | 125 | 130 | 123 |
386
388
  | [Vivaldi](https://vivaldi.com/) | 92 | **74** | n/a | 79 | 81 | 83 | 81 |
387
389
  | [W3C](https://www.w3.org/) | 50 | **36** | 39 | 38 | 38 | 41 | 39 |
388
- | **Average processing time** | | 120 ms (28/28) | 138 ms (27/28) | 41 ms (28/28) | **14 ms (28/28)** | 283 ms (28/28) | 1349 ms (24/28) |
390
+ | **Average processing time** | | 120 ms (30/30) | 143 ms (29/30) | 49 ms (30/30) | **13 ms (30/30)** | 270 ms (30/30) | 1252 ms (24/30) |
389
391
 
390
- (Last updated: Dec 26, 2025)
392
+ (Last updated: Dec 27, 2025)
391
393
  <!-- End auto-generated -->
392
394
 
393
395
  Notes: Minimize does not minify CSS and JS. [HTML Minifier Terser](https://github.com/terser/html-minifier-terser) is currently not included due to issues around whitespace collapsing and removal of code using modern CSS features, issues which appeared to distort the data.
@@ -1764,24 +1764,30 @@ const processOptions = (inputOptions, { getLightningCSS, getTerser, getSwc, cssM
1764
1764
  if (!text || !text.trim()) {
1765
1765
  return text;
1766
1766
  }
1767
- text = await replaceAsync(
1768
- text,
1769
- /(url\s*\(\s*)(?:"([^"]*)"|'([^']*)'|([^\s)]+))(\s*\))/ig,
1770
- async function (match, prefix, dq, sq, unq, suffix) {
1771
- const quote = dq != null ? '"' : (sq != null ? "'" : '');
1772
- const url = dq ?? sq ?? unq ?? '';
1773
- try {
1774
- const out = await options.minifyURLs(url);
1775
- return prefix + quote + (typeof out === 'string' ? out : url) + quote + suffix;
1776
- } catch (err) {
1777
- if (!options.continueOnMinifyError) {
1778
- throw err;
1767
+
1768
+ // Optimization: Only process URLs if minification is enabled (not identity function)
1769
+ // This avoids expensive `replaceAsync` when URL minification is disabled
1770
+ if (options.minifyURLs !== identity) {
1771
+ text = await replaceAsync(
1772
+ text,
1773
+ /(url\s*\(\s*)(?:"([^"]*)"|'([^']*)'|([^\s)]+))(\s*\))/ig,
1774
+ async function (match, prefix, dq, sq, unq, suffix) {
1775
+ const quote = dq != null ? '"' : (sq != null ? "'" : '');
1776
+ const url = dq ?? sq ?? unq ?? '';
1777
+ try {
1778
+ const out = await options.minifyURLs(url);
1779
+ return prefix + quote + (typeof out === 'string' ? out : url) + quote + suffix;
1780
+ } catch (err) {
1781
+ if (!options.continueOnMinifyError) {
1782
+ throw err;
1783
+ }
1784
+ options.log && options.log(err);
1785
+ return match;
1779
1786
  }
1780
- options.log && options.log(err);
1781
- return match;
1782
1787
  }
1783
- }
1784
- );
1788
+ );
1789
+ }
1790
+
1785
1791
  // Cache key: Wrapped content, type, options signature
1786
1792
  const inputCSS = wrapCSS(text, type);
1787
1793
  const cssSig = stableStringify({ type, opts: lightningCssOptions, cont: !!options.continueOnMinifyError });
@@ -1793,36 +1799,44 @@ const processOptions = (inputOptions, { getLightningCSS, getTerser, getSwc, cssM
1793
1799
  try {
1794
1800
  const cached = cssMinifyCache.get(cssKey);
1795
1801
  if (cached) {
1796
- return cached;
1802
+ // Support both resolved values and in-flight promises
1803
+ return await cached;
1797
1804
  }
1798
1805
 
1799
- const transformCSS = await getLightningCSS();
1800
- const result = transformCSS({
1801
- filename: 'input.css',
1802
- code: Buffer.from(inputCSS),
1803
- minify: true,
1804
- errorRecovery: !!options.continueOnMinifyError,
1805
- ...lightningCssOptions
1806
- });
1807
-
1808
- const outputCSS = unwrapCSS(result.code.toString(), type);
1809
-
1810
- // If Lightning CSS removed significant content that looks like template syntax or UIDs, return original
1811
- // This preserves:
1812
- // 1. Template code like `<?php ?>`, `<%= %>`, `{{ }}`, etc. (contain `<` or `>` but not `CDATA`)
1813
- // 2. UIDs representing custom fragments (only lowercase letters and digits, no spaces)
1814
- // CDATA sections, HTML entities, and other invalid CSS are allowed to be removed
1815
- const isCDATA = text.includes('<![CDATA[');
1816
- const uidPattern = /[a-z0-9]{10,}/; // UIDs are long alphanumeric strings
1817
- const hasUID = uidPattern.test(text) && !isCDATA; // Exclude CDATA from UID detection
1818
- const looksLikeTemplate = (text.includes('<') || text.includes('>')) && !isCDATA;
1819
-
1820
- // Preserve if output is empty and input had template syntax or UIDs
1821
- // This catches cases where Lightning CSS removed content that should be preserved
1822
- const finalOutput = (text.trim() && !outputCSS.trim() && (looksLikeTemplate || hasUID)) ? text : outputCSS;
1806
+ // In-flight promise caching: Prevent duplicate concurrent minifications
1807
+ // of the same CSS content (same pattern as JS minification)
1808
+ const inFlight = (async () => {
1809
+ const transformCSS = await getLightningCSS();
1810
+ // Note: `Buffer.from()` is required—Lightning CSS API expects Uint8Array
1811
+ const result = transformCSS({
1812
+ filename: 'input.css',
1813
+ code: Buffer.from(inputCSS),
1814
+ minify: true,
1815
+ errorRecovery: !!options.continueOnMinifyError,
1816
+ ...lightningCssOptions
1817
+ });
1818
+
1819
+ const outputCSS = unwrapCSS(result.code.toString(), type);
1820
+
1821
+ // If Lightning CSS removed significant content that looks like template syntax or UIDs, return original
1822
+ // This preserves:
1823
+ // 1. Template code like `<?php ?>`, `<%= ?>`, `{{ }}`, etc. (contain `<` or `>` but not `CDATA`)
1824
+ // 2. UIDs representing custom fragments (only lowercase letters and digits, no spaces)
1825
+ // CDATA sections, HTML entities, and other invalid CSS are allowed to be removed
1826
+ const isCDATA = text.includes('<![CDATA[');
1827
+ const uidPattern = /[a-z0-9]{10,}/; // UIDs are long alphanumeric strings
1828
+ const hasUID = uidPattern.test(text) && !isCDATA; // Exclude CDATA from UID detection
1829
+ const looksLikeTemplate = (text.includes('<') || text.includes('>')) && !isCDATA;
1830
+
1831
+ // Preserve if output is empty and input had template syntax or UIDs
1832
+ // This catches cases where Lightning CSS removed content that should be preserved
1833
+ return (text.trim() && !outputCSS.trim() && (looksLikeTemplate || hasUID)) ? text : outputCSS;
1834
+ })();
1823
1835
 
1824
- cssMinifyCache.set(cssKey, finalOutput);
1825
- return finalOutput;
1836
+ cssMinifyCache.set(cssKey, inFlight);
1837
+ const resolved = await inFlight;
1838
+ cssMinifyCache.set(cssKey, resolved);
1839
+ return resolved;
1826
1840
  } catch (err) {
1827
1841
  cssMinifyCache.delete(cssKey);
1828
1842
  if (!options.continueOnMinifyError) {
@@ -2740,8 +2754,8 @@ async function getSwc() {
2740
2754
 
2741
2755
  // Minification caches
2742
2756
 
2743
- const cssMinifyCache = new LRU(200);
2744
- const jsMinifyCache = new LRU(200);
2757
+ const cssMinifyCache = new LRU(500);
2758
+ const jsMinifyCache = new LRU(500);
2745
2759
 
2746
2760
  // Type definitions
2747
2761
 
@@ -3284,7 +3298,7 @@ async function createSortFns(value, options, uidIgnore, uidAttr, ignoredMarkupCh
3284
3298
  attrSorters[tag] = attrChains[tag].createSorter();
3285
3299
  }
3286
3300
  // Memoize sorted attribute orders—attribute sets often repeat in templates
3287
- const attrOrderCache = new LRU(200);
3301
+ const attrOrderCache = new LRU(500);
3288
3302
 
3289
3303
  options.sortAttributes = function (tag, attrs) {
3290
3304
  const sorter = attrSorters[tag];
@@ -3315,7 +3329,7 @@ async function createSortFns(value, options, uidIgnore, uidAttr, ignoredMarkupCh
3315
3329
  if (classChain) {
3316
3330
  const sorter = classChain.createSorter();
3317
3331
  // Memoize `sortClassName` results—class lists often repeat in templates
3318
- const classNameCache = new LRU(200);
3332
+ const classNameCache = new LRU(500);
3319
3333
 
3320
3334
  options.sortClassName = function (value) {
3321
3335
  // Fast path: Single class (no spaces) needs no sorting
@@ -6906,24 +6906,30 @@ const processOptions = (inputOptions, { getLightningCSS, getTerser, getSwc, cssM
6906
6906
  if (!text || !text.trim()) {
6907
6907
  return text;
6908
6908
  }
6909
- text = await replaceAsync(
6910
- text,
6911
- /(url\s*\(\s*)(?:"([^"]*)"|'([^']*)'|([^\s)]+))(\s*\))/ig,
6912
- async function (match, prefix, dq, sq, unq, suffix) {
6913
- const quote = dq != null ? '"' : (sq != null ? "'" : '');
6914
- const url = dq ?? sq ?? unq ?? '';
6915
- try {
6916
- const out = await options.minifyURLs(url);
6917
- return prefix + quote + (typeof out === 'string' ? out : url) + quote + suffix;
6918
- } catch (err) {
6919
- if (!options.continueOnMinifyError) {
6920
- throw err;
6909
+
6910
+ // Optimization: Only process URLs if minification is enabled (not identity function)
6911
+ // This avoids expensive `replaceAsync` when URL minification is disabled
6912
+ if (options.minifyURLs !== identity) {
6913
+ text = await replaceAsync(
6914
+ text,
6915
+ /(url\s*\(\s*)(?:"([^"]*)"|'([^']*)'|([^\s)]+))(\s*\))/ig,
6916
+ async function (match, prefix, dq, sq, unq, suffix) {
6917
+ const quote = dq != null ? '"' : (sq != null ? "'" : '');
6918
+ const url = dq ?? sq ?? unq ?? '';
6919
+ try {
6920
+ const out = await options.minifyURLs(url);
6921
+ return prefix + quote + (typeof out === 'string' ? out : url) + quote + suffix;
6922
+ } catch (err) {
6923
+ if (!options.continueOnMinifyError) {
6924
+ throw err;
6925
+ }
6926
+ options.log && options.log(err);
6927
+ return match;
6921
6928
  }
6922
- options.log && options.log(err);
6923
- return match;
6924
6929
  }
6925
- }
6926
- );
6930
+ );
6931
+ }
6932
+
6927
6933
  // Cache key: Wrapped content, type, options signature
6928
6934
  const inputCSS = wrapCSS(text, type);
6929
6935
  const cssSig = stableStringify({ type, opts: lightningCssOptions, cont: !!options.continueOnMinifyError });
@@ -6935,36 +6941,44 @@ const processOptions = (inputOptions, { getLightningCSS, getTerser, getSwc, cssM
6935
6941
  try {
6936
6942
  const cached = cssMinifyCache.get(cssKey);
6937
6943
  if (cached) {
6938
- return cached;
6944
+ // Support both resolved values and in-flight promises
6945
+ return await cached;
6939
6946
  }
6940
6947
 
6941
- const transformCSS = await getLightningCSS();
6942
- const result = transformCSS({
6943
- filename: 'input.css',
6944
- code: Buffer.from(inputCSS),
6945
- minify: true,
6946
- errorRecovery: !!options.continueOnMinifyError,
6947
- ...lightningCssOptions
6948
- });
6949
-
6950
- const outputCSS = unwrapCSS(result.code.toString(), type);
6951
-
6952
- // If Lightning CSS removed significant content that looks like template syntax or UIDs, return original
6953
- // This preserves:
6954
- // 1. Template code like `<?php ?>`, `<%= %>`, `{{ }}`, etc. (contain `<` or `>` but not `CDATA`)
6955
- // 2. UIDs representing custom fragments (only lowercase letters and digits, no spaces)
6956
- // CDATA sections, HTML entities, and other invalid CSS are allowed to be removed
6957
- const isCDATA = text.includes('<![CDATA[');
6958
- const uidPattern = /[a-z0-9]{10,}/; // UIDs are long alphanumeric strings
6959
- const hasUID = uidPattern.test(text) && !isCDATA; // Exclude CDATA from UID detection
6960
- const looksLikeTemplate = (text.includes('<') || text.includes('>')) && !isCDATA;
6948
+ // In-flight promise caching: Prevent duplicate concurrent minifications
6949
+ // of the same CSS content (same pattern as JS minification)
6950
+ const inFlight = (async () => {
6951
+ const transformCSS = await getLightningCSS();
6952
+ // Note: `Buffer.from()` is required—Lightning CSS API expects Uint8Array
6953
+ const result = transformCSS({
6954
+ filename: 'input.css',
6955
+ code: Buffer.from(inputCSS),
6956
+ minify: true,
6957
+ errorRecovery: !!options.continueOnMinifyError,
6958
+ ...lightningCssOptions
6959
+ });
6961
6960
 
6962
- // Preserve if output is empty and input had template syntax or UIDs
6963
- // This catches cases where Lightning CSS removed content that should be preserved
6964
- const finalOutput = (text.trim() && !outputCSS.trim() && (looksLikeTemplate || hasUID)) ? text : outputCSS;
6961
+ const outputCSS = unwrapCSS(result.code.toString(), type);
6962
+
6963
+ // If Lightning CSS removed significant content that looks like template syntax or UIDs, return original
6964
+ // This preserves:
6965
+ // 1. Template code like `<?php ?>`, `<%= ?>`, `{{ }}`, etc. (contain `<` or `>` but not `CDATA`)
6966
+ // 2. UIDs representing custom fragments (only lowercase letters and digits, no spaces)
6967
+ // CDATA sections, HTML entities, and other invalid CSS are allowed to be removed
6968
+ const isCDATA = text.includes('<![CDATA[');
6969
+ const uidPattern = /[a-z0-9]{10,}/; // UIDs are long alphanumeric strings
6970
+ const hasUID = uidPattern.test(text) && !isCDATA; // Exclude CDATA from UID detection
6971
+ const looksLikeTemplate = (text.includes('<') || text.includes('>')) && !isCDATA;
6972
+
6973
+ // Preserve if output is empty and input had template syntax or UIDs
6974
+ // This catches cases where Lightning CSS removed content that should be preserved
6975
+ return (text.trim() && !outputCSS.trim() && (looksLikeTemplate || hasUID)) ? text : outputCSS;
6976
+ })();
6965
6977
 
6966
- cssMinifyCache.set(cssKey, finalOutput);
6967
- return finalOutput;
6978
+ cssMinifyCache.set(cssKey, inFlight);
6979
+ const resolved = await inFlight;
6980
+ cssMinifyCache.set(cssKey, resolved);
6981
+ return resolved;
6968
6982
  } catch (err) {
6969
6983
  cssMinifyCache.delete(cssKey);
6970
6984
  if (!options.continueOnMinifyError) {
@@ -7882,8 +7896,8 @@ async function getSwc() {
7882
7896
 
7883
7897
  // Minification caches
7884
7898
 
7885
- const cssMinifyCache = new LRU(200);
7886
- const jsMinifyCache = new LRU(200);
7899
+ const cssMinifyCache = new LRU(500);
7900
+ const jsMinifyCache = new LRU(500);
7887
7901
 
7888
7902
  // Type definitions
7889
7903
 
@@ -8426,7 +8440,7 @@ async function createSortFns(value, options, uidIgnore, uidAttr, ignoredMarkupCh
8426
8440
  attrSorters[tag] = attrChains[tag].createSorter();
8427
8441
  }
8428
8442
  // Memoize sorted attribute orders—attribute sets often repeat in templates
8429
- const attrOrderCache = new LRU(200);
8443
+ const attrOrderCache = new LRU(500);
8430
8444
 
8431
8445
  options.sortAttributes = function (tag, attrs) {
8432
8446
  const sorter = attrSorters[tag];
@@ -8457,7 +8471,7 @@ async function createSortFns(value, options, uidIgnore, uidAttr, ignoredMarkupCh
8457
8471
  if (classChain) {
8458
8472
  const sorter = classChain.createSorter();
8459
8473
  // Memoize `sortClassName` results—class lists often repeat in templates
8460
- const classNameCache = new LRU(200);
8474
+ const classNameCache = new LRU(500);
8461
8475
 
8462
8476
  options.sortClassName = function (value) {
8463
8477
  // Fast path: Single class (no spaces) needs no sorting
@@ -1 +1 @@
1
- {"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../../../src/lib/options.js"],"names":[],"mappings":"AAWA,6DAUC;AAID;;;;;;;;;GASG;AACH,6CATW,OAAO,CAAC,eAAe,CAAC,0EAEhC;IAAuB,eAAe;IACf,SAAS;IACT,MAAM;CAA2B,GAG9C,eAAe,CA6S3B"}
1
+ {"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../../../src/lib/options.js"],"names":[],"mappings":"AAWA,6DAUC;AAID;;;;;;;;;GASG;AACH,6CATW,OAAO,CAAC,eAAe,CAAC,0EAEhC;IAAuB,eAAe;IACf,SAAS;IACT,MAAM;CAA2B,GAG9C,eAAe,CA2T3B"}
package/package.json CHANGED
@@ -93,5 +93,5 @@
93
93
  "test:watch": "node --test --watch tests/*.spec.js"
94
94
  },
95
95
  "type": "module",
96
- "version": "4.16.1"
96
+ "version": "4.16.2"
97
97
  }
@@ -91,8 +91,8 @@ async function getSwc() {
91
91
 
92
92
  // Minification caches
93
93
 
94
- const cssMinifyCache = new LRU(200);
95
- const jsMinifyCache = new LRU(200);
94
+ const cssMinifyCache = new LRU(500);
95
+ const jsMinifyCache = new LRU(500);
96
96
 
97
97
  // Type definitions
98
98
 
@@ -635,7 +635,7 @@ async function createSortFns(value, options, uidIgnore, uidAttr, ignoredMarkupCh
635
635
  attrSorters[tag] = attrChains[tag].createSorter();
636
636
  }
637
637
  // Memoize sorted attribute orders—attribute sets often repeat in templates
638
- const attrOrderCache = new LRU(200);
638
+ const attrOrderCache = new LRU(500);
639
639
 
640
640
  options.sortAttributes = function (tag, attrs) {
641
641
  const sorter = attrSorters[tag];
@@ -666,7 +666,7 @@ async function createSortFns(value, options, uidIgnore, uidAttr, ignoredMarkupCh
666
666
  if (classChain) {
667
667
  const sorter = classChain.createSorter();
668
668
  // Memoize `sortClassName` results—class lists often repeat in templates
669
- const classNameCache = new LRU(200);
669
+ const classNameCache = new LRU(500);
670
670
 
671
671
  options.sortClassName = function (value) {
672
672
  // Fast path: Single class (no spaces) needs no sorting
@@ -106,24 +106,30 @@ const processOptions = (inputOptions, { getLightningCSS, getTerser, getSwc, cssM
106
106
  if (!text || !text.trim()) {
107
107
  return text;
108
108
  }
109
- text = await replaceAsync(
110
- text,
111
- /(url\s*\(\s*)(?:"([^"]*)"|'([^']*)'|([^\s)]+))(\s*\))/ig,
112
- async function (match, prefix, dq, sq, unq, suffix) {
113
- const quote = dq != null ? '"' : (sq != null ? "'" : '');
114
- const url = dq ?? sq ?? unq ?? '';
115
- try {
116
- const out = await options.minifyURLs(url);
117
- return prefix + quote + (typeof out === 'string' ? out : url) + quote + suffix;
118
- } catch (err) {
119
- if (!options.continueOnMinifyError) {
120
- throw err;
109
+
110
+ // Optimization: Only process URLs if minification is enabled (not identity function)
111
+ // This avoids expensive `replaceAsync` when URL minification is disabled
112
+ if (options.minifyURLs !== identity) {
113
+ text = await replaceAsync(
114
+ text,
115
+ /(url\s*\(\s*)(?:"([^"]*)"|'([^']*)'|([^\s)]+))(\s*\))/ig,
116
+ async function (match, prefix, dq, sq, unq, suffix) {
117
+ const quote = dq != null ? '"' : (sq != null ? "'" : '');
118
+ const url = dq ?? sq ?? unq ?? '';
119
+ try {
120
+ const out = await options.minifyURLs(url);
121
+ return prefix + quote + (typeof out === 'string' ? out : url) + quote + suffix;
122
+ } catch (err) {
123
+ if (!options.continueOnMinifyError) {
124
+ throw err;
125
+ }
126
+ options.log && options.log(err);
127
+ return match;
121
128
  }
122
- options.log && options.log(err);
123
- return match;
124
129
  }
125
- }
126
- );
130
+ );
131
+ }
132
+
127
133
  // Cache key: Wrapped content, type, options signature
128
134
  const inputCSS = wrapCSS(text, type);
129
135
  const cssSig = stableStringify({ type, opts: lightningCssOptions, cont: !!options.continueOnMinifyError });
@@ -135,36 +141,44 @@ const processOptions = (inputOptions, { getLightningCSS, getTerser, getSwc, cssM
135
141
  try {
136
142
  const cached = cssMinifyCache.get(cssKey);
137
143
  if (cached) {
138
- return cached;
144
+ // Support both resolved values and in-flight promises
145
+ return await cached;
139
146
  }
140
147
 
141
- const transformCSS = await getLightningCSS();
142
- const result = transformCSS({
143
- filename: 'input.css',
144
- code: Buffer.from(inputCSS),
145
- minify: true,
146
- errorRecovery: !!options.continueOnMinifyError,
147
- ...lightningCssOptions
148
- });
149
-
150
- const outputCSS = unwrapCSS(result.code.toString(), type);
151
-
152
- // If Lightning CSS removed significant content that looks like template syntax or UIDs, return original
153
- // This preserves:
154
- // 1. Template code like `<?php ?>`, `<%= %>`, `{{ }}`, etc. (contain `<` or `>` but not `CDATA`)
155
- // 2. UIDs representing custom fragments (only lowercase letters and digits, no spaces)
156
- // CDATA sections, HTML entities, and other invalid CSS are allowed to be removed
157
- const isCDATA = text.includes('<![CDATA[');
158
- const uidPattern = /[a-z0-9]{10,}/; // UIDs are long alphanumeric strings
159
- const hasUID = uidPattern.test(text) && !isCDATA; // Exclude CDATA from UID detection
160
- const looksLikeTemplate = (text.includes('<') || text.includes('>')) && !isCDATA;
161
-
162
- // Preserve if output is empty and input had template syntax or UIDs
163
- // This catches cases where Lightning CSS removed content that should be preserved
164
- const finalOutput = (text.trim() && !outputCSS.trim() && (looksLikeTemplate || hasUID)) ? text : outputCSS;
165
-
166
- cssMinifyCache.set(cssKey, finalOutput);
167
- return finalOutput;
148
+ // In-flight promise caching: Prevent duplicate concurrent minifications
149
+ // of the same CSS content (same pattern as JS minification)
150
+ const inFlight = (async () => {
151
+ const transformCSS = await getLightningCSS();
152
+ // Note: `Buffer.from()` is required—Lightning CSS API expects Uint8Array
153
+ const result = transformCSS({
154
+ filename: 'input.css',
155
+ code: Buffer.from(inputCSS),
156
+ minify: true,
157
+ errorRecovery: !!options.continueOnMinifyError,
158
+ ...lightningCssOptions
159
+ });
160
+
161
+ const outputCSS = unwrapCSS(result.code.toString(), type);
162
+
163
+ // If Lightning CSS removed significant content that looks like template syntax or UIDs, return original
164
+ // This preserves:
165
+ // 1. Template code like `<?php ?>`, `<%= ?>`, `{{ }}`, etc. (contain `<` or `>` but not `CDATA`)
166
+ // 2. UIDs representing custom fragments (only lowercase letters and digits, no spaces)
167
+ // CDATA sections, HTML entities, and other invalid CSS are allowed to be removed
168
+ const isCDATA = text.includes('<![CDATA[');
169
+ const uidPattern = /[a-z0-9]{10,}/; // UIDs are long alphanumeric strings
170
+ const hasUID = uidPattern.test(text) && !isCDATA; // Exclude CDATA from UID detection
171
+ const looksLikeTemplate = (text.includes('<') || text.includes('>')) && !isCDATA;
172
+
173
+ // Preserve if output is empty and input had template syntax or UIDs
174
+ // This catches cases where Lightning CSS removed content that should be preserved
175
+ return (text.trim() && !outputCSS.trim() && (looksLikeTemplate || hasUID)) ? text : outputCSS;
176
+ })();
177
+
178
+ cssMinifyCache.set(cssKey, inFlight);
179
+ const resolved = await inFlight;
180
+ cssMinifyCache.set(cssKey, resolved);
181
+ return resolved;
168
182
  } catch (err) {
169
183
  cssMinifyCache.delete(cssKey);
170
184
  if (!options.continueOnMinifyError) {