html-minifier-next 4.15.0 → 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 some large documents. Note that HTML Minifier Terser cannot currently—Dec 2025—handle modern CSS syntax like CSS nesting and removes such code, which can make it appear more effective than it is. 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/) | 647 | **587** | 598 | 607 | 607 | 608 | 641 | 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 | 145 |
365
- | [ECMAScript](https://tc39.es/ecma262/) | 7250 | 6402 | **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 | 49 | 50 | 50 |
368
- | [European Alternatives](https://european-alternatives.eu/) | 48 | **30** | **30** | 32 | 32 | 32 | 32 | 32 |
369
- | [FAZ](https://www.faz.net/aktuell/) | 1559 | 1451 | 1460 | **1398** | 1484 | 1495 | 1506 | 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/) | 2104 | **1841** | 1843 | 1936 | 1959 | 1964 | 2091 | 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 | **34** | **34** | 36 | 36 | 36 | 37 | 37 |
376
- | [Leanpub](https://leanpub.com/) | 229 | 200 | **198** | 214 | 214 | 215 | 225 | 227 |
377
- | [Mastodon](https://mastodon.social/explore) | 37 | **28** | **28** | 32 | 35 | 35 | 36 | 36 |
378
- | [MDN](https://developer.mozilla.org/en-US/) | 108 | **62** | **62** | 64 | 64 | 65 | 67 | 67 |
379
- | [Middle East Eye](https://www.middleeasteye.net/) | 221 | **195** | **195** | 201 | 199 | 199 | 201 | 202 |
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 | 122 | 125 | 125 | 131 | 124 |
385
- | [W3C](https://www.w3.org/) | 50 | **36** | **36** | 39 | 38 | 38 | 41 | 39 |
386
- | **Average processing time** | | 246 ms (26/26) | 343 ms (26/26) | 154 ms (26/26) | 54 ms (26/26) | **15 ms (26/26)** | 312 ms (26/26) | 1379 ms (21/26) |
387
-
388
- (Last updated: Dec 22, 2025)
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) |
391
+
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
  }
@@ -3500,7 +3534,7 @@ async function minifyHTML(value, options, partialMarkup) {
3500
3534
  // `</head>` may be omitted if not followed by space or comment
3501
3535
  // `</p>` may be omitted if no more content in non-`</a>` parent
3502
3536
  // except for `</dt>` or `</thead>`, end tags may be omitted if no more content in parent element
3503
- if (htmlTags.has(tag) && optionalEndTag && !trailingTags.has(optionalEndTag) && (optionalEndTag !== 'p' || !pInlineTags.has(tag))) {
3537
+ if (tag && optionalEndTag && !trailingTags.has(optionalEndTag) && (optionalEndTag !== 'p' || !pInlineTags.has(tag))) {
3504
3538
  removeEndTag();
3505
3539
  }
3506
3540
  optionalEndTag = optionalEndTags.has(tag) ? tag : '';
@@ -3678,8 +3712,7 @@ async function minifyHTML(value, options, partialMarkup) {
3678
3712
  const prevComment = buffer[buffer.length - 2];
3679
3713
 
3680
3714
  // Check if previous item is whitespace-only and item before that is ignore-placeholder
3681
- if (prevText && /^\s+$/.test(prevText) &&
3682
- prevComment && uidIgnorePlaceholderPattern.test(prevComment)) {
3715
+ if (prevText && /^\s+$/.test(prevText) && prevComment && uidIgnorePlaceholderPattern.test(prevComment)) {
3683
3716
  // Extract the index from both placeholders to check their content
3684
3717
  const currentMatch = text.match(uidIgnorePlaceholderPattern);
3685
3718
  const prevMatch = prevComment.match(uidIgnorePlaceholderPattern);
@@ -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
  }
@@ -8642,7 +8676,7 @@ async function minifyHTML(value, options, partialMarkup) {
8642
8676
  // `</head>` may be omitted if not followed by space or comment
8643
8677
  // `</p>` may be omitted if no more content in non-`</a>` parent
8644
8678
  // except for `</dt>` or `</thead>`, end tags may be omitted if no more content in parent element
8645
- if (htmlTags.has(tag) && optionalEndTag && !trailingTags.has(optionalEndTag) && (optionalEndTag !== 'p' || !pInlineTags.has(tag))) {
8679
+ if (tag && optionalEndTag && !trailingTags.has(optionalEndTag) && (optionalEndTag !== 'p' || !pInlineTags.has(tag))) {
8646
8680
  removeEndTag();
8647
8681
  }
8648
8682
  optionalEndTag = optionalEndTags.has(tag) ? tag : '';
@@ -8820,8 +8854,7 @@ async function minifyHTML(value, options, partialMarkup) {
8820
8854
  const prevComment = buffer[buffer.length - 2];
8821
8855
 
8822
8856
  // Check if previous item is whitespace-only and item before that is ignore-placeholder
8823
- if (prevText && /^\s+$/.test(prevText) &&
8824
- prevComment && uidIgnorePlaceholderPattern.test(prevComment)) {
8857
+ if (prevText && /^\s+$/.test(prevText) && prevComment && uidIgnorePlaceholderPattern.test(prevComment)) {
8825
8858
  // Extract the index from both placeholders to check their content
8826
8859
  const currentMatch = text.match(uidIgnorePlaceholderPattern);
8827
8860
  const prevMatch = prevComment.match(uidIgnorePlaceholderPattern);
@@ -1 +1 @@
1
- {"version":3,"file":"htmlminifier.d.ts","sourceRoot":"","sources":["../../src/htmlminifier.js"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"htmlminifier.d.ts","sourceRoot":"","sources":["../../src/htmlminifier.js"],"names":[],"mappings":"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"}
@@ -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.0"
96
+ "version": "4.15.2"
97
97
  }
@@ -1028,7 +1028,7 @@ async function minifyHTML(value, options, partialMarkup) {
1028
1028
  // `</head>` may be omitted if not followed by space or comment
1029
1029
  // `</p>` may be omitted if no more content in non-`</a>` parent
1030
1030
  // except for `</dt>` or `</thead>`, end tags may be omitted if no more content in parent element
1031
- if (htmlTags.has(tag) && optionalEndTag && !trailingTags.has(optionalEndTag) && (optionalEndTag !== 'p' || !pInlineTags.has(tag))) {
1031
+ if (tag && optionalEndTag && !trailingTags.has(optionalEndTag) && (optionalEndTag !== 'p' || !pInlineTags.has(tag))) {
1032
1032
  removeEndTag();
1033
1033
  }
1034
1034
  optionalEndTag = optionalEndTags.has(tag) ? tag : '';
@@ -1206,8 +1206,7 @@ async function minifyHTML(value, options, partialMarkup) {
1206
1206
  const prevComment = buffer[buffer.length - 2];
1207
1207
 
1208
1208
  // Check if previous item is whitespace-only and item before that is ignore-placeholder
1209
- if (prevText && /^\s+$/.test(prevText) &&
1210
- prevComment && uidIgnorePlaceholderPattern.test(prevComment)) {
1209
+ if (prevText && /^\s+$/.test(prevText) && prevComment && uidIgnorePlaceholderPattern.test(prevComment)) {
1211
1210
  // Extract the index from both placeholders to check their content
1212
1211
  const currentMatch = text.match(uidIgnorePlaceholderPattern);
1213
1212
  const prevMatch = prevComment.match(uidIgnorePlaceholderPattern);
@@ -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
  }