html-minifier-next 1.0.1 → 1.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 +77 -14
- package/cli.js +3 -1
- package/dist/htmlminifier.cjs +23 -2
- package/dist/htmlminifier.esm.bundle.js +2950 -806
- package/dist/htmlminifier.umd.bundle.js +2950 -806
- package/dist/htmlminifier.umd.bundle.min.js +3 -3
- package/package.json +13 -17
- package/src/htmlminifier.js +23 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# HTML Minifier Next (HTMLMinifier)
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/html-minifier-next)
|
|
4
4
|
<!-- [](https://github.com/j9t/html-minifier-next/actions?workflow=CI) -->
|
|
5
5
|
|
|
6
6
|
(This project is based on [Terser’s html-minifier-terser](https://github.com/terser/html-minifier-terser), which in turn is based on [Juriy Zaytsev’s html-minifier](https://github.com/kangax/html-minifier). It was set up because as of May 2025, both html-minifier-terser and html-minifier seem unmaintained. **This project is currently under test.** If it seems maintainable to me, [Jens](https://meiert.com/), even without community support, the project will be updated and documented further. The following documentation largely matches the original project.)
|
|
@@ -54,23 +54,23 @@ How does HTMLMinifier compare to other solutions — [HTML Minifier from Will Pe
|
|
|
54
54
|
|
|
55
55
|
| Site | Original size (KB) | HTMLMinifier | minimize | htmlcompressor.com |
|
|
56
56
|
| --- | --- | --- | --- | --- |
|
|
57
|
-
| [Amazon](https://www.amazon.com/) |
|
|
58
|
-
| [BBC](https://www.bbc.co.uk/) |
|
|
59
|
-
| [ECMAScript](https://tc39.es/ecma262/) |
|
|
57
|
+
| [Amazon](https://www.amazon.com/) | 695 | **625** | 682 | n/a |
|
|
58
|
+
| [BBC](https://www.bbc.co.uk/) | 655 | **602** | 649 | n/a |
|
|
59
|
+
| [ECMAScript](https://tc39.es/ecma262/) | 7197 | **6353** | 6573 | n/a |
|
|
60
60
|
| [EFF](https://www.eff.org/) | 60 | **51** | 54 | n/a |
|
|
61
61
|
| [Eloquent JavaScript](https://eloquentjavascript.net/) | 6 | **5** | 6 | n/a |
|
|
62
|
-
| [FAZ](https://www.faz.net/aktuell/) |
|
|
63
|
-
| [Frontend Dogma](https://frontenddogma.com/) |
|
|
64
|
-
| [Google](https://www.google.com/) |
|
|
65
|
-
| [HTMLMinifier](https://github.com/kangax/html-minifier) |
|
|
66
|
-
| [Mastodon](https://mastodon.social/explore) |
|
|
67
|
-
| [NBC](https://www.nbc.com/) |
|
|
68
|
-
| [New York Times](https://www.nytimes.com/) |
|
|
69
|
-
| [United Nations](https://www.un.org/) |
|
|
62
|
+
| [FAZ](https://www.faz.net/aktuell/) | 1793 | **1667** | 1705 | n/a |
|
|
63
|
+
| [Frontend Dogma](https://frontenddogma.com/) | 116 | **112** | 125 | n/a |
|
|
64
|
+
| [Google](https://www.google.com/) | 51 | **46** | 51 | n/a |
|
|
65
|
+
| [HTMLMinifier](https://github.com/kangax/html-minifier) | 366 | **245** | 343 | n/a |
|
|
66
|
+
| [Mastodon](https://mastodon.social/explore) | 37 | **27** | 36 | n/a |
|
|
67
|
+
| [NBC](https://www.nbc.com/) | 1022 | **932** | 1010 | n/a |
|
|
68
|
+
| [New York Times](https://www.nytimes.com/) | 951 | **809** | 939 | n/a |
|
|
69
|
+
| [United Nations](https://www.un.org/) | 9 | **7** | 8 | n/a |
|
|
70
70
|
| [W3C](https://www.w3.org/) | 51 | **36** | 42 | n/a |
|
|
71
71
|
| [Wikipedia](https://en.wikipedia.org/wiki/Main_Page) | 114 | **100** | 107 | n/a |
|
|
72
72
|
|
|
73
|
-
## Options
|
|
73
|
+
## Options quick reference
|
|
74
74
|
|
|
75
75
|
Most of the options are disabled by default.
|
|
76
76
|
|
|
@@ -78,6 +78,7 @@ Most of the options are disabled by default.
|
|
|
78
78
|
| --- | --- | --- |
|
|
79
79
|
| `caseSensitive` | Treat attributes in case sensitive manner (useful for custom HTML tags) | `false` |
|
|
80
80
|
| `collapseBooleanAttributes` | [Omit attribute values from boolean attributes](http://perfectionkills.com/experimenting-with-html-minifier#collapse_boolean_attributes) | `false` |
|
|
81
|
+
| `customFragmentQuantifierLimit` | Set maximum quantifier limit for custom fragments to prevent ReDoS attacks | `200` |
|
|
81
82
|
| `collapseInlineTagWhitespace` | Don’t leave any spaces between `display:inline;` elements when collapsing. Must be used in conjunction with `collapseWhitespace=true` | `false` |
|
|
82
83
|
| `collapseWhitespace` | [Collapse white space that contributes to text nodes in a document tree](http://perfectionkills.com/experimenting-with-html-minifier#collapse_whitespace) | `false` |
|
|
83
84
|
| `conservativeCollapse` | Always collapse to 1 space (never remove it entirely). Must be used in conjunction with `collapseWhitespace=true` | `false` |
|
|
@@ -92,6 +93,7 @@ Most of the options are disabled by default.
|
|
|
92
93
|
| `ignoreCustomFragments` | Array of regexes that allow to ignore certain fragments, when matched (e.g. `<?php ... ?>`, `{{ ... }}`, etc.) | `[ /<%[\s\S]*?%>/, /<\?[\s\S]*?\?>/ ]` |
|
|
93
94
|
| `includeAutoGeneratedTags` | Insert tags generated by HTML parser | `true` |
|
|
94
95
|
| `keepClosingSlash` | Keep the trailing slash on singleton elements | `false` |
|
|
96
|
+
| `maxInputLength` | Maximum input length to prevent ReDoS attacks (disabled by default) | `undefined` |
|
|
95
97
|
| `maxLineLength` | Specify a maximum line length. Compressed output will be split by newlines at valid HTML split-points |
|
|
96
98
|
| `minifyCSS` | Minify CSS in style elements and style attributes (uses [clean-css](https://github.com/jakubpawlowicz/clean-css)) | `false` (could be `true`, `Object`, `Function(text, type)`) |
|
|
97
99
|
| `minifyJS` | Minify JavaScript in script elements and event attributes (uses [Terser](https://github.com/terser/terser)) | `false` (could be `true`, `Object`, `Function(text, inline)`) |
|
|
@@ -154,6 +156,63 @@ Output of resulting markup (e.g. `<p>foo</p>`)
|
|
|
154
156
|
|
|
155
157
|
HTMLMinifier can’t know that original markup was only half of the tree; it does its best to try to parse it as a full tree and it loses information about tree being malformed or partial in the beginning. As a result, it can’t create a partial/malformed tree at the time of the output.
|
|
156
158
|
|
|
159
|
+
## Security
|
|
160
|
+
|
|
161
|
+
### ReDoS protection
|
|
162
|
+
|
|
163
|
+
This minifier includes protection against regular expression denial of service (ReDoS) attacks:
|
|
164
|
+
|
|
165
|
+
* Custom fragment quantifier limits: The `customFragmentQuantifierLimit` option (default: 200) prevents exponential backtracking by replacing unlimited quantifiers (`*`, `+`) with bounded ones in regular expressions.
|
|
166
|
+
|
|
167
|
+
* Input length limits: The `maxInputLength` option allows you to set a maximum input size to prevent processing of excessively large inputs that could cause performance issues.
|
|
168
|
+
|
|
169
|
+
* Enhanced pattern detection: The minifier detects and warns about various ReDoS-prone patterns including nested quantifiers, alternation with quantifiers, and multiple unlimited quantifiers.
|
|
170
|
+
|
|
171
|
+
**Important:** When using custom `ignoreCustomFragments`, ensure your regular expressions don’t contain unlimited quantifiers (`*`, `+`) without bounds, as these can lead to ReDoS vulnerabilities.
|
|
172
|
+
|
|
173
|
+
(Further improvements are needed. Contributions welcome.)
|
|
174
|
+
|
|
175
|
+
#### Custom fragment examples
|
|
176
|
+
|
|
177
|
+
**Safe patterns** (recommended):
|
|
178
|
+
|
|
179
|
+
```javascript
|
|
180
|
+
ignoreCustomFragments: [
|
|
181
|
+
/<%[\s\S]{0,1000}?%>/, // JSP/ASP with explicit bounds
|
|
182
|
+
/<\?php[\s\S]{0,5000}?\?>/, // PHP with bounds
|
|
183
|
+
/\{\{[^}]{0,500}\}\}/ // Handlebars without nested braces
|
|
184
|
+
]
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
**Potentially unsafe patterns** (will trigger warnings):
|
|
188
|
+
|
|
189
|
+
```javascript
|
|
190
|
+
ignoreCustomFragments: [
|
|
191
|
+
/<%[\s\S]*?%>/, // Unlimited quantifiers
|
|
192
|
+
/<!--[\s\S]*?-->/, // Could cause issues with very long comments
|
|
193
|
+
/\{\{.*?\}\}/, // Nested unlimited quantifiers
|
|
194
|
+
/(script|style)[\s\S]*?/ // Multiple unlimited quantifiers
|
|
195
|
+
]
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
**Template engine configurations:**
|
|
199
|
+
|
|
200
|
+
```javascript
|
|
201
|
+
// Handlebars/Mustache
|
|
202
|
+
ignoreCustomFragments: [/\{\{[\s\S]{0,1000}?\}\}/]
|
|
203
|
+
|
|
204
|
+
// Liquid (Jekyll)
|
|
205
|
+
ignoreCustomFragments: [/\{%[\s\S]{0,500}?%\}/, /\{\{[\s\S]{0,500}?\}\}/]
|
|
206
|
+
|
|
207
|
+
// Angular
|
|
208
|
+
ignoreCustomFragments: [/\{\{[\s\S]{0,500}?\}\}/]
|
|
209
|
+
|
|
210
|
+
// Vue.js
|
|
211
|
+
ignoreCustomFragments: [/\{\{[\s\S]{0,500}?\}\}/]
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
**Important:** When using custom `ignoreCustomFragments`, the minifier automatically applies bounded quantifiers to prevent ReDoS attacks, but you can also write safer patterns yourself using explicit bounds.
|
|
215
|
+
|
|
157
216
|
## Running benchmarks
|
|
158
217
|
|
|
159
218
|
Benchmarks for minified HTML:
|
|
@@ -168,4 +227,8 @@ npm run benchmark
|
|
|
168
227
|
|
|
169
228
|
```shell
|
|
170
229
|
npm run serve
|
|
171
|
-
```
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Acknowledgements
|
|
233
|
+
|
|
234
|
+
With many thanks to all the previous authors of HTML Minifier, especially [Juriy Zaytsev](https://github.com/kangax), and to everyone who helped make this new edition better, like [Daniel Ruf](https://github.com/DanielRuf).
|
package/cli.js
CHANGED
|
@@ -101,6 +101,7 @@ function parseString(value) {
|
|
|
101
101
|
const mainOptions = {
|
|
102
102
|
caseSensitive: 'Treat attributes in case sensitive manner (useful for SVG; e.g. viewBox)',
|
|
103
103
|
collapseBooleanAttributes: 'Omit attribute values from boolean attributes',
|
|
104
|
+
customFragmentQuantifierLimit: ['Set maximum quantifier limit for custom fragments to prevent ReDoS attacks (default: 200)', parseInt],
|
|
104
105
|
collapseInlineTagWhitespace: 'Collapse white space around inline tag',
|
|
105
106
|
collapseWhitespace: 'Collapse white space that contributes to text nodes in a document tree.',
|
|
106
107
|
conservativeCollapse: 'Always collapse to 1 space (never remove it entirely)',
|
|
@@ -115,6 +116,7 @@ const mainOptions = {
|
|
|
115
116
|
ignoreCustomFragments: ['Array of regex\'es that allow to ignore certain fragments, when matched (e.g. <?php ... ?>, {{ ... }})', parseJSONRegExpArray],
|
|
116
117
|
includeAutoGeneratedTags: 'Insert tags generated by HTML parser',
|
|
117
118
|
keepClosingSlash: 'Keep the trailing slash on singleton elements',
|
|
119
|
+
maxInputLength: ['Maximum input length to prevent ReDoS attacks', parseInt],
|
|
118
120
|
maxLineLength: ['Max line length', parseInt],
|
|
119
121
|
minifyCSS: ['Minify CSS in style elements and style attributes (uses clean-css)', parseJSON],
|
|
120
122
|
minifyJS: ['Minify Javascript in script elements and on* attributes', parseJSON],
|
|
@@ -304,4 +306,4 @@ if (inputDir || outputDir) {
|
|
|
304
306
|
process.stdin.on('data', function (data) {
|
|
305
307
|
content += data;
|
|
306
308
|
}).on('end', writeMinify);
|
|
307
|
-
}
|
|
309
|
+
}
|
package/dist/htmlminifier.cjs
CHANGED
|
@@ -1333,6 +1333,11 @@ async function createSortFns(value, options, uidIgnore, uidAttr) {
|
|
|
1333
1333
|
}
|
|
1334
1334
|
|
|
1335
1335
|
async function minifyHTML(value, options, partialMarkup) {
|
|
1336
|
+
// Check input length limitation to prevent ReDoS attacks
|
|
1337
|
+
if (options.maxInputLength && value.length > options.maxInputLength) {
|
|
1338
|
+
throw new Error(`Input length (${value.length}) exceeds maximum allowed length (${options.maxInputLength})`);
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1336
1341
|
if (options.collapseWhitespace) {
|
|
1337
1342
|
value = collapseWhitespace(value, options, true, true);
|
|
1338
1343
|
}
|
|
@@ -1377,8 +1382,24 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
1377
1382
|
return re.source;
|
|
1378
1383
|
});
|
|
1379
1384
|
if (customFragments.length) {
|
|
1380
|
-
|
|
1381
|
-
|
|
1385
|
+
// Warn about potential ReDoS if custom fragments use unlimited quantifiers
|
|
1386
|
+
for (let i = 0; i < customFragments.length; i++) {
|
|
1387
|
+
if (/[*+]/.test(customFragments[i])) {
|
|
1388
|
+
options.log('Warning: Custom fragment contains unlimited quantifiers (* or +) which may cause ReDoS vulnerability');
|
|
1389
|
+
break;
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
// Safe approach: Use bounded quantifiers instead of unlimited ones to prevent ReDoS
|
|
1394
|
+
const maxQuantifier = options.customFragmentQuantifierLimit || 200;
|
|
1395
|
+
const whitespacePattern = `\\s{0,${maxQuantifier}}`;
|
|
1396
|
+
|
|
1397
|
+
// Use bounded quantifiers to prevent ReDoS - this approach prevents exponential backtracking
|
|
1398
|
+
const reCustomIgnore = new RegExp(
|
|
1399
|
+
whitespacePattern + '(?:' + customFragments.join('|') + '){1,' + maxQuantifier + '}' + whitespacePattern,
|
|
1400
|
+
'g'
|
|
1401
|
+
);
|
|
1402
|
+
// Temporarily replace custom ignored fragments with unique attributes
|
|
1382
1403
|
value = value.replace(reCustomIgnore, function (match) {
|
|
1383
1404
|
if (!uidAttr) {
|
|
1384
1405
|
uidAttr = uniqueId(value);
|