html-minifier-next 4.15.1 → 4.15.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
@@ -352,42 +352,48 @@ Available options:
352
352
 
353
353
  ## Minification comparison
354
354
 
355
- How does HTML Minifier Next compare to other minifiers? (All minification with the most aggressive settings—though without [hyper-optimization](https://meiert.com/blog/the-ways-of-writing-html/#toc-hyper-optimized)—and against a wide range of pages. Note that HTML Minifier Terser may handle whitespace too aggressively and doesn’t currently—Dec 2025—support modern CSS syntax like CSS nesting. Minimize does not minify CSS and JS.)
355
+ How does HTML Minifier Next compare to other minifiers? (All minification with the most aggressive settings—though without [hyper-optimization](https://meiert.com/blog/the-ways-of-writing-html/#toc-hyper-optimized)—and against a wide range of pages.)
356
356
 
357
357
  <!-- Auto-generated benchmarks, don’t edit -->
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) | [HTML Minifier Terser](https://github.com/terser/html-minifier-terser)<br>[![npm last update](https://img.shields.io/npm/last-update/html-minifier-terser)](https://socket.dev/npm/package/html-minifier-terser) | [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
- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
360
- | [A List Apart](https://alistapart.com/) | 59 | **50** | **50** | 51 | 52 | 51 | 54 | 52 |
361
- | [Apple](https://www.apple.com/) | 266 | **206** | 207 | 236 | 239 | 240 | 242 | 243 |
362
- | [BBC](https://www.bbc.co.uk/) | 645 | **587** | 597 | 606 | 606 | 607 | 640 | n/a |
363
- | [CERN](https://home.cern/) | 152 | **83** | 84 | 91 | 91 | 92 | 93 | 96 |
364
- | [CSS-Tricks](https://css-tricks.com/) | 162 | **119** | 120 | 127 | 143 | 143 | 148 | 144 |
365
- | [ECMAScript](https://tc39.es/ecma262/) | 7250 | 6401 | **6354** | 6573 | 6455 | 6578 | 6626 | n/a |
366
- | [EDRi](https://edri.org/) | 80 | **59** | 60 | 70 | 70 | 71 | 75 | 73 |
367
- | [EFF](https://www.eff.org/) | 55 | **46** | 47 | 49 | 48 | 48 | 50 | 50 |
368
- | [European Alternatives](https://european-alternatives.eu/) | 48 | **30** | **30** | 32 | 32 | 32 | 32 | 32 |
369
- | [FAZ](https://www.faz.net/aktuell/) | 1575 | 1467 | 1476 | **1413** | 1500 | 1511 | 1522 | n/a |
370
- | [French Tech](https://lafrenchtech.gouv.fr/) | 152 | **122** | **122** | 126 | 125 | 125 | 132 | 127 |
371
- | [Frontend Dogma](https://frontenddogma.com/) | 225 | **217** | **217** | 238 | 223 | 225 | 243 | 224 |
372
- | [Google](https://www.google.com/) | 18 | **16** | 17 | 17 | 17 | 17 | 18 | 18 |
373
- | [Ground News](https://ground.news/) | 2106 | **1837** | 1838 | 1934 | 1962 | 1967 | 2093 | n/a |
374
- | [HTML Living Standard](https://html.spec.whatwg.org/multipage/) | 149 | 148 | **147** | 153 | **147** | 149 | 155 | 149 |
375
- | [Igalia](https://www.igalia.com/) | 50 | **33** | **33** | 36 | 36 | 36 | 37 | 36 |
376
- | [Leanpub](https://leanpub.com/) | 225 | 196 | **194** | 210 | 210 | 211 | 221 | 223 |
377
- | [Mastodon](https://mastodon.social/explore) | 37 | **28** | **28** | 32 | 35 | 35 | 36 | 36 |
378
- | [MDN](https://developer.mozilla.org/en-US/) | 109 | **62** | **62** | 64 | 65 | 65 | 68 | 68 |
379
- | [Middle East Eye](https://www.middleeasteye.net/) | 223 | **196** | **196** | 202 | 201 | 200 | 202 | 203 |
380
- | [Nielsen Norman Group](https://www.nngroup.com/) | 86 | 68 | 74 | **55** | 74 | 75 | 77 | 76 |
381
- | [SitePoint](https://www.sitepoint.com/) | 491 | **360** | **360** | 431 | 465 | 470 | 488 | n/a |
382
- | [TetraLogical](https://tetralogical.com/) | 44 | 39 | 38 | **35** | 38 | 39 | 39 | 39 |
383
- | [TPGi](https://www.tpgi.com/) | 175 | **159** | 161 | 160 | 164 | 166 | 172 | 172 |
384
- | [United Nations](https://www.un.org/en/) | 152 | **113** | 115 | 121 | 125 | 125 | 131 | 124 |
385
- | [W3C](https://www.w3.org/) | 50 | **36** | **36** | 39 | 38 | 38 | 41 | 39 |
386
- | **Average processing time** | | 267 ms (26/26) | 359 ms (26/26) | 160 ms (26/26) | 54 ms (26/26) | **16 ms (26/26)** | 313 ms (26/26) | 1572 ms (21/26) |
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
+ | --- | --- | --- | --- | --- | --- | --- | --- |
360
+ | [A List Apart](https://alistapart.com/) | 59 | **50** | 51 | 52 | 51 | 54 | 52 |
361
+ | [Apple](https://www.apple.com/) | 266 | **206** | 236 | 239 | 240 | 242 | 243 |
362
+ | [BBC](https://www.bbc.co.uk/) | 643 | **584** | 603 | 604 | 605 | 638 | n/a |
363
+ | [CERN](https://home.cern/) | 152 | **83** | 91 | 91 | 91 | 93 | 96 |
364
+ | [CSS-Tricks](https://css-tricks.com/) | 162 | **119** | 127 | 143 | 143 | 148 | 144 |
365
+ | [ECMAScript](https://tc39.es/ecma262/) | 7250 | **6401** | 6573 | 6455 | 6578 | 6626 | n/a |
366
+ | [EDRi](https://edri.org/) | 80 | **59** | 70 | 70 | 71 | 75 | 73 |
367
+ | [EFF](https://www.eff.org/) | 55 | **46** | 49 | 48 | 49 | 50 | 50 |
368
+ | [European Alternatives](https://european-alternatives.eu/) | 48 | **30** | 32 | 32 | 32 | 32 | 32 |
369
+ | [FAZ](https://www.faz.net/aktuell/) | 1564 | 1457 | **1404** | 1489 | 1500 | 1511 | n/a |
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 | 243 | 224 |
372
+ | [Google](https://www.google.com/) | 76 | **71** | n/a | 72 | 73 | 75 | 75 |
373
+ | [Ground News](https://ground.news/) | 2373 | **2089** | 2185 | 2211 | 2213 | 2360 | n/a |
374
+ | [HTML Living Standard](https://html.spec.whatwg.org/multipage/) | 149 | 148 | 153 | **147** | 149 | 155 | 149 |
375
+ | [Igalia](https://www.igalia.com/) | 50 | **33** | 36 | 36 | 36 | 37 | 36 |
376
+ | [Leanpub](https://leanpub.com/) | 229 | **199** | 214 | 213 | 214 | 224 | 226 |
377
+ | [Mastodon](https://mastodon.social/explore) | 37 | **28** | 32 | 35 | 35 | 36 | 36 |
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 | 200 | 202 | 203 |
380
+ | [Mistral AI](https://mistral.ai/) | 360 | **319** | 323 | 326 | 327 | 357 | n/a |
381
+ | [Mozilla](https://www.mozilla.org/) | 45 | **31** | 34 | 34 | 34 | 35 | 35 |
382
+ | [Nielsen Norman Group](https://www.nngroup.com/) | 86 | 68 | **55** | 74 | 75 | 77 | 76 |
383
+ | [SitePoint](https://www.sitepoint.com/) | 491 | **360** | 431 | 465 | 470 | 488 | n/a |
384
+ | [Startup-Verband](https://startupverband.de/) | 42 | **29** | 30 | 30 | 30 | 31 | 30 |
385
+ | [TetraLogical](https://tetralogical.com/) | 44 | 39 | **35** | 38 | 39 | 39 | 39 |
386
+ | [TPGi](https://www.tpgi.com/) | 175 | **159** | 160 | 164 | 166 | 172 | 172 |
387
+ | [United Nations](https://www.un.org/en/) | 151 | **112** | 121 | 125 | 125 | 130 | 123 |
388
+ | [Vivaldi](https://vivaldi.com/) | 92 | **74** | n/a | 79 | 81 | 83 | 81 |
389
+ | [W3C](https://www.w3.org/) | 50 | **36** | 39 | 38 | 38 | 41 | 39 |
390
+ | **Average processing time** | | 121 ms (30/30) | 648 ms (28/30) | 50 ms (30/30) | **14 ms (30/30)** | 275 ms (30/30) | 1409 ms (24/30) |
387
391
 
388
392
  (Last updated: Dec 23, 2025)
389
393
  <!-- End auto-generated -->
390
394
 
395
+ Notes: htmlnano runs in an isolated process for crash protection, adding ~50–100ms overhead per test. 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.
396
+
391
397
  ## Examples
392
398
 
393
399
  ### CLI
@@ -1581,6 +1581,31 @@ const processOptions = (inputOptions, { getLightningCSS, getTerser, getSwc, cssM
1581
1581
  minifySVG: null
1582
1582
  };
1583
1583
 
1584
+ // Helper to convert string patterns to RegExp (for JSON config support)
1585
+ const parseRegExp = (value) => {
1586
+ if (typeof value === 'string') {
1587
+ return new RegExp(value.replace(/^\/(.*)\/$/, '$1'));
1588
+ }
1589
+ return value; // Already a RegExp or other type
1590
+ };
1591
+
1592
+ const parseRegExpArray = (arr) => {
1593
+ return Array.isArray(arr) ? arr.map(parseRegExp) : arr;
1594
+ };
1595
+
1596
+ // Helper for nested arrays (e.g., `customAttrSurround: [[start, end], …]`)
1597
+ const parseNestedRegExpArray = (arr) => {
1598
+ if (!Array.isArray(arr)) return arr;
1599
+ return arr.map(item => {
1600
+ // If item is an array (a pair), recursively convert each element
1601
+ if (Array.isArray(item)) {
1602
+ return item.map(parseRegExp);
1603
+ }
1604
+ // Otherwise, convert single item
1605
+ return parseRegExp(item);
1606
+ });
1607
+ };
1608
+
1584
1609
  Object.keys(inputOptions).forEach(function (key) {
1585
1610
  const option = inputOptions[key];
1586
1611
 
@@ -1816,6 +1841,15 @@ const processOptions = (inputOptions, { getLightningCSS, getTerser, getSwc, cssM
1816
1841
  // Unlike minifyCSS/minifyJS, this is a simple options object, not a function
1817
1842
  // The actual minification is applied inline during attribute processing
1818
1843
  options.minifySVG = getSVGMinifierOptions(option);
1844
+ } else if (key === 'customAttrCollapse') {
1845
+ // Single RegExp pattern
1846
+ options[key] = parseRegExp(option);
1847
+ } else if (key === 'customAttrSurround') {
1848
+ // Nested array of RegExp pairs: `[[openRegExp, closeRegExp], …]`
1849
+ options[key] = parseNestedRegExpArray(option);
1850
+ } else if (['customAttrAssign', 'customEventAttributes', 'ignoreCustomComments', 'ignoreCustomFragments'].includes(key)) {
1851
+ // Array of RegExp patterns
1852
+ options[key] = parseRegExpArray(option);
1819
1853
  } else {
1820
1854
  options[key] = option;
1821
1855
  }
@@ -6723,6 +6723,31 @@ const processOptions = (inputOptions, { getLightningCSS, getTerser, getSwc, cssM
6723
6723
  minifySVG: null
6724
6724
  };
6725
6725
 
6726
+ // Helper to convert string patterns to RegExp (for JSON config support)
6727
+ const parseRegExp = (value) => {
6728
+ if (typeof value === 'string') {
6729
+ return new RegExp(value.replace(/^\/(.*)\/$/, '$1'));
6730
+ }
6731
+ return value; // Already a RegExp or other type
6732
+ };
6733
+
6734
+ const parseRegExpArray = (arr) => {
6735
+ return Array.isArray(arr) ? arr.map(parseRegExp) : arr;
6736
+ };
6737
+
6738
+ // Helper for nested arrays (e.g., `customAttrSurround: [[start, end], …]`)
6739
+ const parseNestedRegExpArray = (arr) => {
6740
+ if (!Array.isArray(arr)) return arr;
6741
+ return arr.map(item => {
6742
+ // If item is an array (a pair), recursively convert each element
6743
+ if (Array.isArray(item)) {
6744
+ return item.map(parseRegExp);
6745
+ }
6746
+ // Otherwise, convert single item
6747
+ return parseRegExp(item);
6748
+ });
6749
+ };
6750
+
6726
6751
  Object.keys(inputOptions).forEach(function (key) {
6727
6752
  const option = inputOptions[key];
6728
6753
 
@@ -6958,6 +6983,15 @@ const processOptions = (inputOptions, { getLightningCSS, getTerser, getSwc, cssM
6958
6983
  // Unlike minifyCSS/minifyJS, this is a simple options object, not a function
6959
6984
  // The actual minification is applied inline during attribute processing
6960
6985
  options.minifySVG = getSVGMinifierOptions(option);
6986
+ } else if (key === 'customAttrCollapse') {
6987
+ // Single RegExp pattern
6988
+ options[key] = parseRegExp(option);
6989
+ } else if (key === 'customAttrSurround') {
6990
+ // Nested array of RegExp pairs: `[[openRegExp, closeRegExp], …]`
6991
+ options[key] = parseNestedRegExpArray(option);
6992
+ } else if (['customAttrAssign', 'customEventAttributes', 'ignoreCustomComments', 'ignoreCustomFragments'].includes(key)) {
6993
+ // Array of RegExp patterns
6994
+ options[key] = parseRegExpArray(option);
6961
6995
  } else {
6962
6996
  options[key] = option;
6963
6997
  }
@@ -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,CA2Q3B"}
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"}
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.15.1"
96
+ "version": "4.15.2"
97
97
  }
@@ -58,6 +58,31 @@ const processOptions = (inputOptions, { getLightningCSS, getTerser, getSwc, cssM
58
58
  minifySVG: null
59
59
  };
60
60
 
61
+ // Helper to convert string patterns to RegExp (for JSON config support)
62
+ const parseRegExp = (value) => {
63
+ if (typeof value === 'string') {
64
+ return new RegExp(value.replace(/^\/(.*)\/$/, '$1'));
65
+ }
66
+ return value; // Already a RegExp or other type
67
+ };
68
+
69
+ const parseRegExpArray = (arr) => {
70
+ return Array.isArray(arr) ? arr.map(parseRegExp) : arr;
71
+ };
72
+
73
+ // Helper for nested arrays (e.g., `customAttrSurround: [[start, end], …]`)
74
+ const parseNestedRegExpArray = (arr) => {
75
+ if (!Array.isArray(arr)) return arr;
76
+ return arr.map(item => {
77
+ // If item is an array (a pair), recursively convert each element
78
+ if (Array.isArray(item)) {
79
+ return item.map(parseRegExp);
80
+ }
81
+ // Otherwise, convert single item
82
+ return parseRegExp(item);
83
+ });
84
+ };
85
+
61
86
  Object.keys(inputOptions).forEach(function (key) {
62
87
  const option = inputOptions[key];
63
88
 
@@ -293,6 +318,15 @@ const processOptions = (inputOptions, { getLightningCSS, getTerser, getSwc, cssM
293
318
  // Unlike minifyCSS/minifyJS, this is a simple options object, not a function
294
319
  // The actual minification is applied inline during attribute processing
295
320
  options.minifySVG = getSVGMinifierOptions(option);
321
+ } else if (key === 'customAttrCollapse') {
322
+ // Single RegExp pattern
323
+ options[key] = parseRegExp(option);
324
+ } else if (key === 'customAttrSurround') {
325
+ // Nested array of RegExp pairs: `[[openRegExp, closeRegExp], …]`
326
+ options[key] = parseNestedRegExpArray(option);
327
+ } else if (['customAttrAssign', 'customEventAttributes', 'ignoreCustomComments', 'ignoreCustomFragments'].includes(key)) {
328
+ // Array of RegExp patterns
329
+ options[key] = parseRegExpArray(option);
296
330
  } else {
297
331
  options[key] = option;
298
332
  }