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 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>[![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
359
  | --- | --- | --- | --- | --- | --- | --- | --- |
360
360
  | [A List Apart](https://alistapart.com/) | 59 | **50** | 51 | 52 | 51 | 54 | 52 |
361
- | [Apple](https://www.apple.com/) | 260 | **203** | 231 | 235 | 236 | 238 | 238 |
362
- | [BBC](https://www.bbc.co.uk/) | 704 | **641** | 661 | 661 | 662 | 698 | n/a |
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** | 128 | 143 | 143 | 148 | 145 |
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** | 48 | 47 | 48 | 49 | 49 |
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/) | 1537 | 1429 | **1378** | 1462 | 1473 | 1484 | n/a |
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 | 224 | 225 | 244 | 225 |
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/) | 233 | **203** | 217 | 217 | 218 | 228 | 230 |
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** | | 132 ms (29/29) | 169 ms (28/29) | 54 ms (29/29) | **14 ms (29/29)** | 293 ms (29/29) | 1355 ms (23/29) |
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 -->
@@ -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
- // Build position map for this token to avoid repeated `indexOf`
628
- const positions = [];
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
- positions.push(j);
633
+ matchCount++;
634
+ } else {
635
+ others.push(tokens[j]);
632
636
  }
633
637
  }
634
638
 
635
- if (positions.length > 0) {
636
- // Build new array with tokens in sorted order instead of splicing
637
- const result = [];
638
-
639
- // Add all instances of the current token first
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
- // Copy sorted portion back to tokens array
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 + positions.length;
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
- attrValue = attrValue.replace(/[ \n\r\t\f]+/g, ' ').replace(/^[ \n\r\t\f]+|[ \n\r\t\f]+$/g, '');
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
- // Build position map for this token to avoid repeated `indexOf`
3240
- const positions = [];
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
- positions.push(j);
3245
+ matchCount++;
3246
+ } else {
3247
+ others.push(tokens[j]);
3244
3248
  }
3245
3249
  }
3246
3250
 
3247
- if (positions.length > 0) {
3248
- // Build new array with tokens in sorted order instead of splicing
3249
- const result = [];
3250
-
3251
- // Add all instances of the current token first
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
- // Copy sorted portion back to tokens array
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 + positions.length;
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
- attrValue = attrValue.replace(/[ \n\r\t\f]+/g, ' ').replace(/^[ \n\r\t\f]+|[ \n\r\t\f]+$/g, '');
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,0IA0IC;AAsBD;;;;GAwCC;AAED,6GA4EC"}
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":";AA2CA;IAGI,mBAAoB;IAGtB,uBAOC;IAED,uBAiDC;CACF;AA5GD;IACE,2CAuCC;CACF"}
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
@@ -93,5 +93,5 @@
93
93
  "test:watch": "node --test --watch tests/*.spec.js"
94
94
  },
95
95
  "type": "module",
96
- "version": "4.16.0"
96
+ "version": "4.16.1"
97
97
  }
@@ -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
- attrValue = attrValue.replace(/[ \n\r\t\f]+/g, ' ').replace(/^[ \n\r\t\f]+|[ \n\r\t\f]+$/g, '');
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
- // Build position map for this token to avoid repeated `indexOf`
7
- const positions = [];
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
- positions.push(j);
12
+ matchCount++;
13
+ } else {
14
+ others.push(tokens[j]);
11
15
  }
12
16
  }
13
17
 
14
- if (positions.length > 0) {
15
- // Build new array with tokens in sorted order instead of splicing
16
- const result = [];
17
-
18
- // Add all instances of the current token first
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
- // Copy sorted portion back to tokens array
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 + positions.length;
28
+ const newFromIndex = fromIndex + matchCount;
37
29
  return this.sorterMap.get(token).sort(tokens, newFromIndex);
38
30
  }
39
31
  }