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 +38 -32
- package/dist/htmlminifier.cjs +36 -3
- package/dist/htmlminifier.esm.bundle.js +36 -3
- package/dist/types/htmlminifier.d.ts.map +1 -1
- package/dist/types/lib/options.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/htmlminifier.js +2 -3
- package/src/lib/options.js +34 -0
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
|
|
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>[](https://socket.dev/npm/package/html-minifier-next) | [
|
|
359
|
-
| --- | --- | --- | --- | --- | --- | --- | --- |
|
|
360
|
-
| [A List Apart](https://alistapart.com/) | 59 | **50** |
|
|
361
|
-
| [Apple](https://www.apple.com/) | 266 | **206** |
|
|
362
|
-
| [BBC](https://www.bbc.co.uk/) |
|
|
363
|
-
| [CERN](https://home.cern/) | 152 | **83** |
|
|
364
|
-
| [CSS-Tricks](https://css-tricks.com/) | 162 | **119** |
|
|
365
|
-
| [ECMAScript](https://tc39.es/ecma262/) | 7250 |
|
|
366
|
-
| [EDRi](https://edri.org/) | 80 | **59** |
|
|
367
|
-
| [EFF](https://www.eff.org/) | 55 | **46** |
|
|
368
|
-
| [European Alternatives](https://european-alternatives.eu/) | 48 | **30** |
|
|
369
|
-
| [FAZ](https://www.faz.net/aktuell/) |
|
|
370
|
-
| [French Tech](https://lafrenchtech.gouv.fr/) |
|
|
371
|
-
| [Frontend Dogma](https://frontenddogma.com/) | 225 | **217** |
|
|
372
|
-
| [Google](https://www.google.com/) |
|
|
373
|
-
| [Ground News](https://ground.news/) |
|
|
374
|
-
| [HTML Living Standard](https://html.spec.whatwg.org/multipage/) | 149 | 148 |
|
|
375
|
-
| [Igalia](https://www.igalia.com/) | 50 | **
|
|
376
|
-
| [Leanpub](https://leanpub.com/) | 229 |
|
|
377
|
-
| [Mastodon](https://mastodon.social/explore) | 37 | **28** |
|
|
378
|
-
| [MDN](https://developer.mozilla.org/en-US/) |
|
|
379
|
-
| [Middle East Eye](https://www.middleeasteye.net/) |
|
|
380
|
-
| [
|
|
381
|
-
| [
|
|
382
|
-
| [
|
|
383
|
-
| [
|
|
384
|
-
| [
|
|
385
|
-
| [
|
|
386
|
-
|
|
|
387
|
-
|
|
388
|
-
(
|
|
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>[](https://socket.dev/npm/package/html-minifier-next) | [htmlnano](https://github.com/posthtml/htmlnano)<br>[](https://socket.dev/npm/package/htmlnano) | [@swc/html](https://github.com/swc-project/swc)<br>[](https://socket.dev/npm/package/@swc/html) | [minify-html](https://github.com/wilsonzlin/minify-html)<br>[](https://socket.dev/npm/package/@minify-html/node) | [minimize](https://github.com/Swaagie/minimize)<br>[](https://socket.dev/npm/package/minimize) | [htmlcompressor.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
|
package/dist/htmlminifier.cjs
CHANGED
|
@@ -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 (
|
|
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 (
|
|
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":"
|
|
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,
|
|
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
package/src/htmlminifier.js
CHANGED
|
@@ -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 (
|
|
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);
|
package/src/lib/options.js
CHANGED
|
@@ -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
|
}
|