html-minifier-next 4.16.0 → 4.16.1
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 +9 -10
- package/dist/htmlminifier.cjs +24 -24
- package/dist/htmlminifier.esm.bundle.js +24 -24
- package/dist/types/lib/attributes.d.ts.map +1 -1
- package/dist/types/tokenchain.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/lib/attributes.js +9 -1
- package/src/tokenchain.js +15 -23
package/README.md
CHANGED
|
@@ -358,35 +358,34 @@ 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>[](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
359
|
| --- | --- | --- | --- | --- | --- | --- | --- |
|
|
360
360
|
| [A List Apart](https://alistapart.com/) | 59 | **50** | 51 | 52 | 51 | 54 | 52 |
|
|
361
|
-
| [Apple](https://www.apple.com/) |
|
|
362
|
-
| [BBC](https://www.bbc.co.uk/) |
|
|
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 |
|
|
363
363
|
| [CERN](https://home.cern/) | 152 | **83** | 91 | 91 | 91 | 93 | 96 |
|
|
364
|
-
| [CSS-Tricks](https://css-tricks.com/) | 162 | **119** |
|
|
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
|
-
| [EFF](https://www.eff.org/) | 54 | **45** |
|
|
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/) |
|
|
369
|
+
| [FAZ](https://www.faz.net/aktuell/) | 1579 | 1468 | **1414** | 1503 | 1514 | 1525 | 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 |
|
|
371
|
+
| [Frontend Dogma](https://frontenddogma.com/) | 225 | **217** | 238 | 223 | 225 | 244 | 225 |
|
|
372
372
|
| [Google](https://www.google.com/) | 18 | **16** | 17 | 17 | 17 | 18 | 18 |
|
|
373
|
-
| [Ground News](https://ground.news/) | 2437 | **2150** | 2246 | 2272 | 2275 | 2424 | n/a |
|
|
374
373
|
| [HTML Living Standard](https://html.spec.whatwg.org/multipage/) | 149 | 148 | 153 | **147** | 149 | 155 | 149 |
|
|
375
374
|
| [Igalia](https://www.igalia.com/) | 50 | **33** | 36 | 36 | 36 | 37 | 36 |
|
|
376
|
-
| [Leanpub](https://leanpub.com/) |
|
|
375
|
+
| [Leanpub](https://leanpub.com/) | 231 | **202** | 216 | 216 | 217 | 227 | 229 |
|
|
377
376
|
| [Mastodon](https://mastodon.social/explore) | 37 | **28** | 32 | 35 | 35 | 36 | 36 |
|
|
378
377
|
| [MDN](https://developer.mozilla.org/en-US/) | 106 | **60** | 62 | 63 | 63 | 66 | 66 |
|
|
379
378
|
| [Middle East Eye](https://www.middleeasteye.net/) | 223 | **197** | 203 | 201 | 201 | 203 | 204 |
|
|
380
379
|
| [Mistral AI](https://mistral.ai/) | 361 | **319** | 324 | 326 | 327 | 357 | n/a |
|
|
381
380
|
| [Mozilla](https://www.mozilla.org/) | 45 | **31** | 34 | 34 | 34 | 35 | 35 |
|
|
382
381
|
| [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 |
|
|
384
382
|
| [Startup-Verband](https://startupverband.de/) | 42 | **29** | 30 | 30 | 30 | 31 | 30 |
|
|
383
|
+
| [TetraLogical](https://tetralogical.com/) | 44 | 38 | **35** | 38 | 39 | 39 | 39 |
|
|
385
384
|
| [TPGi](https://www.tpgi.com/) | 175 | **159** | 160 | 164 | 166 | 172 | 172 |
|
|
386
385
|
| [United Nations](https://www.un.org/en/) | 152 | **112** | 121 | 125 | 125 | 130 | 123 |
|
|
387
386
|
| [Vivaldi](https://vivaldi.com/) | 92 | **74** | n/a | 79 | 81 | 83 | 81 |
|
|
388
387
|
| [W3C](https://www.w3.org/) | 50 | **36** | 39 | 38 | 38 | 41 | 39 |
|
|
389
|
-
| **Average processing time** | |
|
|
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
389
|
|
|
391
390
|
(Last updated: Dec 26, 2025)
|
|
392
391
|
<!-- End auto-generated -->
|
package/dist/htmlminifier.cjs
CHANGED
|
@@ -624,37 +624,29 @@ class Sorter {
|
|
|
624
624
|
for (let i = 0, len = this.keys.length; i < len; i++) {
|
|
625
625
|
const token = this.keys[i];
|
|
626
626
|
|
|
627
|
-
//
|
|
628
|
-
|
|
627
|
+
// Single pass: Count matches and collect non-matches
|
|
628
|
+
let matchCount = 0;
|
|
629
|
+
const others = [];
|
|
630
|
+
|
|
629
631
|
for (let j = fromIndex; j < tokens.length; j++) {
|
|
630
632
|
if (tokens[j] === token) {
|
|
631
|
-
|
|
633
|
+
matchCount++;
|
|
634
|
+
} else {
|
|
635
|
+
others.push(tokens[j]);
|
|
632
636
|
}
|
|
633
637
|
}
|
|
634
638
|
|
|
635
|
-
if (
|
|
636
|
-
//
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
for (let j = 0; j < positions.length; j++) {
|
|
641
|
-
result.push(token);
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
// Add other tokens, skipping positions where current token was
|
|
645
|
-
const posSet = new Set(positions);
|
|
646
|
-
for (let j = fromIndex; j < tokens.length; j++) {
|
|
647
|
-
if (!posSet.has(j)) {
|
|
648
|
-
result.push(tokens[j]);
|
|
649
|
-
}
|
|
639
|
+
if (matchCount > 0) {
|
|
640
|
+
// Rebuild: `matchCount` instances of token first, then others
|
|
641
|
+
let writeIdx = fromIndex;
|
|
642
|
+
for (let j = 0; j < matchCount; j++) {
|
|
643
|
+
tokens[writeIdx++] = token;
|
|
650
644
|
}
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
for (let j = 0; j < result.length; j++) {
|
|
654
|
-
tokens[fromIndex + j] = result[j];
|
|
645
|
+
for (let j = 0; j < others.length; j++) {
|
|
646
|
+
tokens[writeIdx++] = others[j];
|
|
655
647
|
}
|
|
656
648
|
|
|
657
|
-
const newFromIndex = fromIndex +
|
|
649
|
+
const newFromIndex = fromIndex + matchCount;
|
|
658
650
|
return this.sorterMap.get(token).sort(tokens, newFromIndex);
|
|
659
651
|
}
|
|
660
652
|
}
|
|
@@ -2210,7 +2202,15 @@ async function cleanAttributeValue(tag, attrName, attrValue, options, attrs, min
|
|
|
2210
2202
|
// Apply early whitespace normalization if enabled
|
|
2211
2203
|
// Preserves special spaces (non-breaking space, hair space, etc.) for consistency with `collapseWhitespace`
|
|
2212
2204
|
if (options.collapseAttributeWhitespace) {
|
|
2213
|
-
|
|
2205
|
+
// Single-pass: Trim leading/trailing whitespace and collapse internal whitespace to single space
|
|
2206
|
+
attrValue = attrValue.replace(/^[ \n\r\t\f]+|[ \n\r\t\f]+$|[ \n\r\t\f]+/g, function(match, offset, str) {
|
|
2207
|
+
// Leading whitespace (`offset === 0`)
|
|
2208
|
+
if (offset === 0) return '';
|
|
2209
|
+
// Trailing whitespace (match ends at string end)
|
|
2210
|
+
if (offset + match.length === str.length) return '';
|
|
2211
|
+
// Internal whitespace
|
|
2212
|
+
return ' ';
|
|
2213
|
+
});
|
|
2214
2214
|
}
|
|
2215
2215
|
|
|
2216
2216
|
if (isEventAttribute(attrName, options)) {
|
|
@@ -3236,37 +3236,29 @@ class Sorter {
|
|
|
3236
3236
|
for (let i = 0, len = this.keys.length; i < len; i++) {
|
|
3237
3237
|
const token = this.keys[i];
|
|
3238
3238
|
|
|
3239
|
-
//
|
|
3240
|
-
|
|
3239
|
+
// Single pass: Count matches and collect non-matches
|
|
3240
|
+
let matchCount = 0;
|
|
3241
|
+
const others = [];
|
|
3242
|
+
|
|
3241
3243
|
for (let j = fromIndex; j < tokens.length; j++) {
|
|
3242
3244
|
if (tokens[j] === token) {
|
|
3243
|
-
|
|
3245
|
+
matchCount++;
|
|
3246
|
+
} else {
|
|
3247
|
+
others.push(tokens[j]);
|
|
3244
3248
|
}
|
|
3245
3249
|
}
|
|
3246
3250
|
|
|
3247
|
-
if (
|
|
3248
|
-
//
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
for (let j = 0; j < positions.length; j++) {
|
|
3253
|
-
result.push(token);
|
|
3254
|
-
}
|
|
3255
|
-
|
|
3256
|
-
// Add other tokens, skipping positions where current token was
|
|
3257
|
-
const posSet = new Set(positions);
|
|
3258
|
-
for (let j = fromIndex; j < tokens.length; j++) {
|
|
3259
|
-
if (!posSet.has(j)) {
|
|
3260
|
-
result.push(tokens[j]);
|
|
3261
|
-
}
|
|
3251
|
+
if (matchCount > 0) {
|
|
3252
|
+
// Rebuild: `matchCount` instances of token first, then others
|
|
3253
|
+
let writeIdx = fromIndex;
|
|
3254
|
+
for (let j = 0; j < matchCount; j++) {
|
|
3255
|
+
tokens[writeIdx++] = token;
|
|
3262
3256
|
}
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
for (let j = 0; j < result.length; j++) {
|
|
3266
|
-
tokens[fromIndex + j] = result[j];
|
|
3257
|
+
for (let j = 0; j < others.length; j++) {
|
|
3258
|
+
tokens[writeIdx++] = others[j];
|
|
3267
3259
|
}
|
|
3268
3260
|
|
|
3269
|
-
const newFromIndex = fromIndex +
|
|
3261
|
+
const newFromIndex = fromIndex + matchCount;
|
|
3270
3262
|
return this.sorterMap.get(token).sort(tokens, newFromIndex);
|
|
3271
3263
|
}
|
|
3272
3264
|
}
|
|
@@ -7352,7 +7344,15 @@ async function cleanAttributeValue(tag, attrName, attrValue, options, attrs, min
|
|
|
7352
7344
|
// Apply early whitespace normalization if enabled
|
|
7353
7345
|
// Preserves special spaces (non-breaking space, hair space, etc.) for consistency with `collapseWhitespace`
|
|
7354
7346
|
if (options.collapseAttributeWhitespace) {
|
|
7355
|
-
|
|
7347
|
+
// Single-pass: Trim leading/trailing whitespace and collapse internal whitespace to single space
|
|
7348
|
+
attrValue = attrValue.replace(/^[ \n\r\t\f]+|[ \n\r\t\f]+$|[ \n\r\t\f]+/g, function(match, offset, str) {
|
|
7349
|
+
// Leading whitespace (`offset === 0`)
|
|
7350
|
+
if (offset === 0) return '';
|
|
7351
|
+
// Trailing whitespace (match ends at string end)
|
|
7352
|
+
if (offset + match.length === str.length) return '';
|
|
7353
|
+
// Internal whitespace
|
|
7354
|
+
return ' ';
|
|
7355
|
+
});
|
|
7356
7356
|
}
|
|
7357
7357
|
|
|
7358
7358
|
if (isEventAttribute(attrName, options)) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attributes.d.ts","sourceRoot":"","sources":["../../../src/lib/attributes.js"],"names":[],"mappings":"AAuBA,yDAEC;AAED,mEAOC;AAED,uEAWC;AAED,8DAGC;AAED,4EAOC;AAED,mGAqBC;AAED,mEAGC;AAED,qEAGC;AAED,kEAWC;AAED,sEAGC;AAED,4DAWC;AAED,2EAEC;AAED,qEAaC;AAED,wEAUC;AAED,sEAUC;AAED,2EAEC;AAED,2DAEC;AAED,8DAUC;AAED,uEAUC;AAED,oGASC;AAED,4DAOC;AAID,
|
|
1
|
+
{"version":3,"file":"attributes.d.ts","sourceRoot":"","sources":["../../../src/lib/attributes.js"],"names":[],"mappings":"AAuBA,yDAEC;AAED,mEAOC;AAED,uEAWC;AAED,8DAGC;AAED,4EAOC;AAED,mGAqBC;AAED,mEAGC;AAED,qEAGC;AAED,kEAWC;AAED,sEAGC;AAED,4DAWC;AAED,2EAEC;AAED,qEAaC;AAED,wEAUC;AAED,sEAUC;AAED,2EAEC;AAED,2DAEC;AAED,8DAUC;AAED,uEAUC;AAED,oGASC;AAED,4DAOC;AAID,0IAkJC;AAsBD;;;;GAwCC;AAED,6GA4EC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tokenchain.d.ts","sourceRoot":"","sources":["../../src/tokenchain.js"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"tokenchain.d.ts","sourceRoot":"","sources":["../../src/tokenchain.js"],"names":[],"mappings":";AAmCA;IAGI,mBAAoB;IAGtB,uBAOC;IAED,uBAiDC;CACF;AApGD;IACE,2CA+BC;CACF"}
|
package/package.json
CHANGED
package/src/lib/attributes.js
CHANGED
|
@@ -226,7 +226,15 @@ async function cleanAttributeValue(tag, attrName, attrValue, options, attrs, min
|
|
|
226
226
|
// Apply early whitespace normalization if enabled
|
|
227
227
|
// Preserves special spaces (non-breaking space, hair space, etc.) for consistency with `collapseWhitespace`
|
|
228
228
|
if (options.collapseAttributeWhitespace) {
|
|
229
|
-
|
|
229
|
+
// Single-pass: Trim leading/trailing whitespace and collapse internal whitespace to single space
|
|
230
|
+
attrValue = attrValue.replace(/^[ \n\r\t\f]+|[ \n\r\t\f]+$|[ \n\r\t\f]+/g, function(match, offset, str) {
|
|
231
|
+
// Leading whitespace (`offset === 0`)
|
|
232
|
+
if (offset === 0) return '';
|
|
233
|
+
// Trailing whitespace (match ends at string end)
|
|
234
|
+
if (offset + match.length === str.length) return '';
|
|
235
|
+
// Internal whitespace
|
|
236
|
+
return ' ';
|
|
237
|
+
});
|
|
230
238
|
}
|
|
231
239
|
|
|
232
240
|
if (isEventAttribute(attrName, options)) {
|
package/src/tokenchain.js
CHANGED
|
@@ -3,37 +3,29 @@ class Sorter {
|
|
|
3
3
|
for (let i = 0, len = this.keys.length; i < len; i++) {
|
|
4
4
|
const token = this.keys[i];
|
|
5
5
|
|
|
6
|
-
//
|
|
7
|
-
|
|
6
|
+
// Single pass: Count matches and collect non-matches
|
|
7
|
+
let matchCount = 0;
|
|
8
|
+
const others = [];
|
|
9
|
+
|
|
8
10
|
for (let j = fromIndex; j < tokens.length; j++) {
|
|
9
11
|
if (tokens[j] === token) {
|
|
10
|
-
|
|
12
|
+
matchCount++;
|
|
13
|
+
} else {
|
|
14
|
+
others.push(tokens[j]);
|
|
11
15
|
}
|
|
12
16
|
}
|
|
13
17
|
|
|
14
|
-
if (
|
|
15
|
-
//
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
for (let j = 0; j < positions.length; j++) {
|
|
20
|
-
result.push(token);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// Add other tokens, skipping positions where current token was
|
|
24
|
-
const posSet = new Set(positions);
|
|
25
|
-
for (let j = fromIndex; j < tokens.length; j++) {
|
|
26
|
-
if (!posSet.has(j)) {
|
|
27
|
-
result.push(tokens[j]);
|
|
28
|
-
}
|
|
18
|
+
if (matchCount > 0) {
|
|
19
|
+
// Rebuild: `matchCount` instances of token first, then others
|
|
20
|
+
let writeIdx = fromIndex;
|
|
21
|
+
for (let j = 0; j < matchCount; j++) {
|
|
22
|
+
tokens[writeIdx++] = token;
|
|
29
23
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
for (let j = 0; j < result.length; j++) {
|
|
33
|
-
tokens[fromIndex + j] = result[j];
|
|
24
|
+
for (let j = 0; j < others.length; j++) {
|
|
25
|
+
tokens[writeIdx++] = others[j];
|
|
34
26
|
}
|
|
35
27
|
|
|
36
|
-
const newFromIndex = fromIndex +
|
|
28
|
+
const newFromIndex = fromIndex + matchCount;
|
|
37
29
|
return this.sorterMap.get(token).sort(tokens, newFromIndex);
|
|
38
30
|
}
|
|
39
31
|
}
|