html-minifier-next 4.19.0 → 5.0.0

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/LICENSE CHANGED
@@ -1,9 +1,7 @@
1
1
  MIT License
2
2
 
3
3
  Copyright 2010–2019 Juriy “kangax” Zaytsev
4
-
5
4
  Copyright Terser
6
-
7
5
  Copyright 2025 Jens Oliver Meiert
8
6
 
9
7
  Permission is hereby granted, free of charge, to any person obtaining a copy
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/html-minifier-next.svg)](https://www.npmjs.com/package/html-minifier-next) [![Build status](https://github.com/j9t/html-minifier-next/workflows/Tests/badge.svg)](https://github.com/j9t/html-minifier-next/actions) [![Socket](https://badge.socket.dev/npm/package/html-minifier-next)](https://socket.dev/npm/package/html-minifier-next)
4
4
 
5
- HTML Minifier Next (HMN) is a **super-configurable, well-tested, JavaScript-based HTML minifier** that can also handle in-document CSS, JavaScript, and SVG minification.
5
+ Your HTML optimization precision tool: HTML Minifier Next (HMN) is a **super-configurable, well-tested, JavaScript-based HTML minifier** able to also handle in-document CSS, JavaScript, and SVG minification.
6
6
 
7
7
  The project was based on [HTML Minifier Terser (HMT)](https://github.com/terser/html-minifier-terser), which in turn had been based on [Juriy “kangax” Zaytsev’s HTML Minifier (HM)](https://github.com/kangax/html-minifier); as of 2025, both HTML Minifier Terser and HTML Minifier had been unmaintained for several years. HMN offers additional features and has been optimized for speed. While an independent project, it is still backwards-compatible with HMT and HM.
8
8
 
@@ -34,15 +34,15 @@ Use `html-minifier-next --help` to check all available options:
34
34
 
35
35
  | Option | Description | Example |
36
36
  | --- | --- | --- |
37
- | `--input-dir <dir>` | Specify an input directory (best restricted with `--file-ext`) | `--input-dir=src` |
38
- | `--ignore-dir <patterns>` | Exclude directories—relative to input directory—from processing (comma-separated, overrides config file setting) | `--ignore-dir=libs`, `--ignore-dir=libs,vendor,node_modules` |
39
- | `--output-dir <dir>` | Specify an output directory | `--output-dir=dist` |
40
- | `--file-ext <extensions>` | Specify file extension(s) to process (comma-separated, overrides config file setting) | `--file-ext=html`, `--file-ext=html,htm,php`, `--file-ext="html, htm, php"` |
41
- | `-o <file>`, `--output <file>` | Specify output file (reads from file arguments or STDIN) | File to file: `html-minifier-next input.html -o output.html`<br>Pipe to file: `cat input.html \| html-minifier-next -o output.html`<br>File to STDOUT: `html-minifier-next input.html` |
42
- | `--preset <name>` | Use a preset configuration (conservative or comprehensive) | `--preset=conservative` |
43
- | `-c <file>`, `--config-file <file>` | Use a configuration file | `--config-file=html-minifier.json` |
44
- | `-v`, `--verbose` | Show detailed processing information (active options, file statistics) | `html-minifier-next --input-dir=src --output-dir=dist --verbose --collapse-whitespace` |
45
- | `-d`, `--dry` | Dry run: Process and report statistics without writing output | `html-minifier-next input.html --dry --collapse-whitespace` |
37
+ | `--input-dir <dir>`, `-I <dir>` | Specify an input directory | `--input-dir=src` |
38
+ | `--ignore-dir <patterns>`, `-X <patterns>` | Exclude directories—relative to input directory—from processing (comma-separated, overrides config file setting) | `--ignore-dir=libs`, `--ignore-dir=libs,vendor,node_modules` |
39
+ | `--output-dir <dir>`, `-O <dir>` | Specify an output directory | `--output-dir=dist` |
40
+ | `--output <file>`, `-o <file>` | Specify output file (reads from file arguments or STDIN) | File to file: `html-minifier-next input.html -o output.html`<br>Pipe to file: `cat input.html \| html-minifier-next -o output.html`<br>File to STDOUT: `html-minifier-next input.html` |
41
+ | `--file-ext <extensions>`, `-f <extensions>` | Specify file extension(s) to process (comma-separated, overrides config file setting); defaults to `html,htm,xhtml,shtml`; use `*` for all files | `--file-ext=html,php`, `--file-ext='*'` |
42
+ | `--preset <name>`, `-p <name>` | Use a preset configuration (conservative or comprehensive) | `--preset=conservative` |
43
+ | `--config-file <file>`, `-c <file>` | Use a configuration file | `--config-file=html-minifier.json` |
44
+ | `--verbose`, `-v` | Show detailed processing information (active options, file statistics) | `html-minifier-next --input-dir=src --output-dir=dist --verbose --collapse-whitespace` |
45
+ | `--dry`, `-d` | Dry run: Process and report statistics without writing output | `html-minifier-next input.html --dry --collapse-whitespace` |
46
46
 
47
47
  ### Configuration file
48
48
 
@@ -54,7 +54,7 @@ You can use a configuration file to specify options. The file can be either JSON
54
54
  {
55
55
  "collapseWhitespace": true,
56
56
  "removeComments": true,
57
- "fileExt": "html,htm",
57
+ "fileExt": "html,php",
58
58
  "ignoreDir": "libs,vendor"
59
59
  }
60
60
  ```
@@ -65,7 +65,7 @@ You can use a configuration file to specify options. The file can be either JSON
65
65
  module.exports = {
66
66
  collapseWhitespace: true,
67
67
  removeComments: true,
68
- fileExt: "html,htm",
68
+ fileExt: "html,php",
69
69
  ignoreDir: ["libs", "vendor"]
70
70
  };
71
71
  ```
@@ -87,10 +87,10 @@ console.log(result); // “<p title=example id=moo>foo”
87
87
  CommonJS:
88
88
 
89
89
  ```javascript
90
- const { minify, getPreset } = require('html-minifier-next');
90
+ const { minify } = require('html-minifier-next');
91
91
 
92
92
  (async () => {
93
- const result = await minify('<p title="example" id="moo">foo</p>', getPreset('comprehensive'));
93
+ const result = await minify('<p title="example" id="moo">foo</p>', { preset: 'comprehensive' });
94
94
  console.log(result); // “<p id=moo title=example>foo”
95
95
  })();
96
96
  ```
@@ -101,8 +101,8 @@ See [the original blog post](https://perfectionkills.com/experimenting-with-html
101
101
 
102
102
  HTML Minifier Next provides presets for common use cases. Presets are pre-configured option sets that can be used as a starting point:
103
103
 
104
- * `conservative`: Safe minification suitable for most projects. Includes whitespace collapsing, comment removal, and doctype normalization.
105
- * `comprehensive`: More aggressive minification for better file size reduction. Includes relevant conservative options plus attribute quote removal, optional tag removal, and more.
104
+ * `conservative`: Basic minification with whitespace collapsing, comment removal, and removal of select attributes.
105
+ * `comprehensive`: More advanced minification for better file size reduction, including relevant conservative options plus attribute quote removal, optional tag removal, and more.
106
106
 
107
107
  To review the specific options set, [presets.js](https://github.com/j9t/html-minifier-next/blob/main/src/presets.js) lists them in an accessible manner.
108
108
 
@@ -130,8 +130,8 @@ Options can be used in config files (camelCase) or via CLI flags (kebab-case wit
130
130
 
131
131
  | Option (config/CLI) | Description | Default |
132
132
  | --- | --- | --- |
133
- | `cacheCSS`<br>`--cache-css` | Set CSS minification cache size; higher values improve performance for batch processing | `500` (or `1000` when `CI=true`) |
134
- | `cacheJS`<br>`--cache-js` | Set JavaScript minification cache size; higher values improve performance for batch processing | `500` (or `1000` when `CI=true`) |
133
+ | `cacheCSS`<br>`--cache-css` | Set CSS minification cache size; higher values improve performance for batch processing | `500` |
134
+ | `cacheJS`<br>`--cache-js` | Set JavaScript minification cache size; higher values improve performance for batch processing | `500` |
135
135
  | `caseSensitive`<br>`--case-sensitive` | Treat attributes in case-sensitive manner (useful for custom HTML elements) | `false` |
136
136
  | `collapseAttributeWhitespace`<br>`--collapse-attribute-whitespace` | Trim and collapse whitespace characters within attribute values | `false` |
137
137
  | `collapseBooleanAttributes`<br>`--collapse-boolean-attributes` | [Omit attribute values from boolean attributes](https://perfectionkills.com/experimenting-with-html-minifier/#collapse_boolean_attributes) | `false` |
@@ -146,11 +146,10 @@ Options can be used in config files (camelCase) or via CLI flags (kebab-case wit
146
146
  | `customEventAttributes`<br>`--custom-event-attributes` | Array of regexes that allow to support custom event attributes for `minifyJS` (e.g., `ng-click`) | `[ /^on[a-z]{3,}$/ ]` |
147
147
  | `customFragmentQuantifierLimit`<br>`--custom-fragment-quantifier-limit` | Set maximum quantifier limit for custom fragments to prevent ReDoS attacks | `200` |
148
148
  | `decodeEntities`<br>`--decode-entities` | Use direct Unicode characters whenever possible | `false` |
149
- | `html5`<br>`--no-html5` | Parse input according to the HTML specification; when `false`, enforces legacy inline/block nesting rules that may restructure modern HTML | `true` |
150
- | `ignoreCustomComments`<br>`--ignore-custom-comments` | Array of regexes that allow to ignore certain comments, when matched | `[ /^!/, /^\s*#/ ]` |
149
+ | `ignoreCustomComments`<br>`--ignore-custom-comments` | Array of regexes that allow to ignore matching comments | `[ /^!/, /^\s*#/ ]` |
151
150
  | `ignoreCustomFragments`<br>`--ignore-custom-fragments` | Array of regexes that allow to ignore certain fragments, when matched (e.g., `<?php … ?>`, `{{ … }}`, etc.) | `[ /<%[\s\S]*?%>/, /<\?[\s\S]*?\?>/ ]` |
152
- | `includeAutoGeneratedTags`<br>`--no-include-auto-generated-tags` | Insert elements generated by HTML parser; when `false`, omits auto-generated tags | `true` |
153
- | `inlineCustomElements`<br>`--inline-custom-elements` | Array of names of custom elements which are inline | `[]` |
151
+ | `includeAutoGeneratedTags`<br>`--include-auto-generated-tags` | Insert elements generated by HTML parser | `false` |
152
+ | `inlineCustomElements`<br>`--inline-custom-elements` | Array of names of custom elements which are inline, for whitespace handling | `[]` |
154
153
  | `keepClosingSlash`<br>`--keep-closing-slash` | Keep the trailing slash on void elements | `false` |
155
154
  | `maxInputLength`<br>`--max-input-length` | Maximum input length to prevent ReDoS attacks (disabled by default) | `undefined` |
156
155
  | `maxLineLength`<br>`--max-line-length` | Specify a maximum line length; compressed output will be split by newlines at valid HTML split-points | `undefined` |
@@ -158,7 +157,7 @@ Options can be used in config files (camelCase) or via CLI flags (kebab-case wit
158
157
  | `minifyCSS`<br>`--minify-css` | Minify CSS in `style` elements and attributes (uses [Lightning CSS](https://lightningcss.dev/)) | `false` (could be `true`, `Object`, `Function(text, type)`) |
159
158
  | `minifyJS`<br>`--minify-js` | Minify JavaScript in `script` elements and event attributes (uses [Terser](https://github.com/terser/terser) or [SWC](https://swc.rs/)) | `false` (could be `true`, `Object`, `Function(text, inline)`) |
160
159
  | `minifySVG`<br>`--minify-svg` | Minify SVG elements and attributes (numeric precision, default attributes, colors) | `false` (could be `true`, `Object`) |
161
- | `minifyURLs`<br>`--minify-urls` | Minify URLs in various attributes (uses [relateurl](https://github.com/stevenvachon/relateurl)) | `false` (could be `String`, `Object`, `Function(text)`, `async Function(text)`) |
160
+ | `minifyURLs`<br>`--minify-urls` | Minify URLs in various attributes | `false` (could be `true`, `String`, `Object`, `Function(text)`) |
162
161
  | `noNewlinesBeforeTagClose`<br>`--no-newlines-before-tag-close` | Never add a newline before a tag that closes an element | `false` |
163
162
  | `partialMarkup`<br>`--partial-markup` | Treat input as a partial HTML fragment, preserving stray end tags (closing tags without opening tags) and preventing auto-closing of unclosed tags at end of input | `false` |
164
163
  | `preserveLineBreaks`<br>`--preserve-line-breaks` | Always collapse to one line break (never remove it entirely) when whitespace between tags includes a line break—use with `collapseWhitespace: true` | `false` |
@@ -177,13 +176,13 @@ Options can be used in config files (camelCase) or via CLI flags (kebab-case wit
177
176
  | `removeStyleLinkTypeAttributes`<br>`--remove-style-link-type-attributes` | Remove `type="text/css"` from `style` and `link` elements; other `type` attribute values are left intact | `false` |
178
177
  | `removeTagWhitespace`<br>`--remove-tag-whitespace` | Remove space between attributes whenever possible; **note that this will result in invalid HTML** | `false` |
179
178
  | `sortAttributes`<br>`--sort-attributes` | [Sort attributes by frequency](#sorting-attributes-and-style-classes) | `false` |
180
- | `sortClassName`<br>`--sort-class-name` | [Sort style classes by frequency](#sorting-attributes-and-style-classes) | `false` |
179
+ | `sortClassNames`<br>`--sort-class-names` | [Sort style classes by frequency](#sorting-attributes-and-style-classes) | `false` |
181
180
  | `trimCustomFragments`<br>`--trim-custom-fragments` | Trim whitespace around custom fragments (`ignoreCustomFragments`) | `false` |
182
181
  | `useShortDoctype`<br>`--use-short-doctype` | [Replaces the doctype with the short HTML doctype](https://perfectionkills.com/experimenting-with-html-minifier/#use_short_doctype) | `false` |
183
182
 
184
183
  ### Sorting attributes and style classes
185
184
 
186
- Minifier options like `sortAttributes` and `sortClassName` won’t impact the plain‑text size of the output. However, using these options for more consistent ordering improves the compression ratio for gzip and Brotli used over HTTP.
185
+ Minifier options like `sortAttributes` and `sortClassNames` won’t impact the plain‑text size of the output. However, using these options for more consistent ordering improves the compression ratio for Gzip and Brotli used over HTTP.
187
186
 
188
187
  ### CSS minification
189
188
 
@@ -295,8 +294,8 @@ const result = await minify(html, {
295
294
  minifyCSS: true,
296
295
  minifyJS: true,
297
296
  // Configure cache sizes (in number of entries)
298
- cacheCSS: 750, // CSS cache size, default: 500 (1000 in CI mode)
299
- cacheJS: 250 // JS cache size, default: 500 (1000 in CI mode)
297
+ cacheCSS: 750, // CSS cache size, default: 500
298
+ cacheJS: 250 // JS cache size, default: 500
300
299
  });
301
300
  ```
302
301
 
@@ -325,26 +324,10 @@ html-minifier-next --minify-css --minify-js input.html
325
324
  }
326
325
  ```
327
326
 
328
- **For batch/CI processing:**
329
-
330
- Set `CI=true` to double default cache sizes (optimal for processing many files):
331
-
332
- ```shell
333
- # Single command
334
- CI=true html-minifier-next --minify-css --minify-js input.html
335
-
336
- # Or export for multiple commands
337
- export CI=true
338
- html-minifier-next --minify-css --minify-js file1.html
339
- html-minifier-next --minify-css --minify-js file2.html
340
- ```
341
-
342
- This is particularly useful in CI/CD pipelines where you’re processing multiple files and want better performance without manually tuning cache sizes.
343
-
344
327
  **When to adjust cache sizes:**
345
328
 
346
329
  * Single file processing: Default `500` is sufficient
347
- * Batch processing (CI/CD): Increase to `1000` or higher for better cache hit rates
330
+ * Batch processing: Increase to `1000` or higher for better cache hit rates
348
331
  * Memory-constrained environments: Reduce to `200`–`300` to save memory
349
332
  * Hundreds/thousands of files: Increase to `1000`–`2000` for optimal performance
350
333
 
@@ -383,6 +366,10 @@ What gets optimized:
383
366
  - `fill-opacity="1"` → removed
384
367
  - `stroke-linecap="butt"` → removed
385
368
 
369
+ 5. Leading and trailing zero removal: Unnecessary zeros are stripped from numeric values
370
+ - `0.5` → `.5`, `-0.3` → `-.3` (leading zeros)
371
+ - `1.0` → `1`, `10.500` → `10.5` (trailing zeros)
372
+
386
373
  You can customize the optimization behavior by providing an options object:
387
374
 
388
375
  ```javascript
@@ -415,39 +402,39 @@ How does HTML Minifier Next compare to other minifiers? (All minification with t
415
402
  <!-- Auto-generated benchmarks, don’t edit -->
416
403
  | 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/) |
417
404
  | --- | --- | --- | --- | --- | --- | --- | --- |
418
- | [A List Apart](https://alistapart.com/) | 59 | **49** | 51 | 52 | 51 | 54 | 52 |
419
- | [Apple](https://www.apple.com/) | 255 | **197** | 226 | 230 | 231 | 233 | 233 |
420
- | [BBC](https://www.bbc.co.uk/) | 620 | **562** | 582 | 583 | 584 | 615 | n/a |
421
- | [CERN](https://home.cern/) | 151 | **82** | 90 | 90 | 91 | 93 | 95 |
422
- | [CSS-Tricks](https://css-tricks.com/) | 161 | **119** | 126 | 142 | 142 | 147 | 143 |
423
- | [ECMAScript](https://tc39.es/ecma262/) | 7250 | **6401** | 6573 | 6455 | 6578 | 6626 | n/a |
424
- | [EDRi](https://edri.org/) | 80 | **59** | 70 | 70 | 71 | 75 | 73 |
425
- | [EFF](https://www.eff.org/) | 54 | **45** | 48 | 47 | 48 | 49 | 49 |
405
+ | [A List Apart](https://alistapart.com/) | 64 | **53** | 55 | 56 | 55 | 59 | 57 |
406
+ | [Apple](https://www.apple.com/) | 233 | **174** | 206 | 209 | 210 | 212 | 212 |
407
+ | [BBC](https://www.bbc.co.uk/) | 625 | **569** | 587 | 587 | 588 | 620 | n/a |
408
+ | [CERN](https://home.cern/) | 151 | **82** | 90 | 90 | 91 | 92 | 95 |
409
+ | [CSS-Tricks](https://css-tricks.com/) | 156 | **115** | 122 | 137 | 138 | 142 | 139 |
410
+ | [ECMAScript](https://tc39.es/ecma262/) | 7261 | **6411** | 6583 | 6465 | 6589 | 6637 | n/a |
411
+ | [EDRi](https://edri.org/) | 80 | **59** | 69 | 69 | 71 | 74 | 72 |
412
+ | [EFF](https://www.eff.org/) | 54 | **44** | 48 | 47 | 48 | 49 | 49 |
426
413
  | [European Alternatives](https://european-alternatives.eu/) | 48 | **30** | 32 | 32 | 32 | 32 | 32 |
427
- | [FAZ](https://www.faz.net/aktuell/) | 1596 | 1458 | **1430** | 1520 | 1531 | 1542 | n/a |
414
+ | [FAZ](https://www.faz.net/aktuell/) | 1488 | 1361 | **1337** | 1416 | 1427 | 1436 | n/a |
428
415
  | [French Tech](https://lafrenchtech.gouv.fr/) | 153 | **122** | 126 | 126 | 126 | 132 | 127 |
429
- | [Frontend Dogma](https://frontenddogma.com/) | 228 | **220** | 241 | 226 | 228 | 247 | 228 |
416
+ | [Frontend Dogma](https://frontenddogma.com/) | 227 | **219** | 241 | 226 | 227 | 246 | 227 |
430
417
  | [Google](https://www.google.com/) | 18 | **16** | 17 | 17 | 17 | 18 | 18 |
431
- | [Ground News](https://ground.news/) | 1658 | **1431** | 1526 | 1548 | 1553 | 1645 | n/a |
418
+ | [Ground News](https://ground.news/) | 2093 | **1829** | 1928 | 1954 | 1957 | 2080 | n/a |
432
419
  | [HTML Living Standard](https://html.spec.whatwg.org/multipage/) | 149 | 148 | 153 | **147** | 149 | 155 | 149 |
433
- | [Igalia](https://www.igalia.com/) | 49 | **33** | 36 | 35 | 36 | 36 | 36 |
434
- | [Leanpub](https://leanpub.com/) | 233 | **204** | 219 | 219 | 219 | 228 | 230 |
420
+ | [Igalia](https://www.igalia.com/) | 49 | **33** | 36 | 35 | 36 | 37 | 36 |
421
+ | [Leanpub](https://leanpub.com/) | 241 | **210** | 225 | 225 | 225 | 236 | 238 |
435
422
  | [Mastodon](https://mastodon.social/explore) | 38 | **28** | 32 | 35 | 35 | 36 | 36 |
436
423
  | [MDN](https://developer.mozilla.org/en-US/) | 109 | **62** | 64 | 65 | 65 | 68 | 68 |
437
- | [Middle East Eye](https://www.middleeasteye.net/) | 222 | **196** | 202 | 200 | 200 | 201 | 202 |
438
- | [Mistral AI](https://mistral.ai/) | 364 | **319** | 326 | 329 | 330 | 360 | n/a |
439
- | [Mozilla](https://www.mozilla.org/) | 45 | **31** | 35 | 34 | 34 | 35 | 35 |
440
- | [Nielsen Norman Group](https://www.nngroup.com/) | 93 | 70 | **57** | 76 | 77 | 78 | 78 |
441
- | [SitePoint](https://www.sitepoint.com/) | 522 | **391** | 463 | 496 | 501 | 519 | n/a |
424
+ | [Middle East Eye](https://www.middleeasteye.net/) | 219 | **194** | 199 | 198 | 198 | 199 | 200 |
425
+ | [Mistral AI](https://mistral.ai/) | 343 | **301** | 307 | 310 | 311 | 340 | n/a |
426
+ | [Mozilla](https://www.mozilla.org/) | 47 | **32** | 35 | 35 | 35 | 36 | 36 |
427
+ | [Nielsen Norman Group](https://www.nngroup.com/) | 97 | 72 | **59** | 78 | 80 | 81 | 80 |
428
+ | [SitePoint](https://www.sitepoint.com/) | 494 | **352** | 431 | 468 | 473 | 491 | n/a |
442
429
  | [Startup-Verband](https://startupverband.de/) | 43 | **30** | 31 | **30** | 31 | 31 | 31 |
443
- | [TetraLogical](https://tetralogical.com/) | 48 | **22** | 39 | 41 | 42 | 42 | 42 |
430
+ | [TetraLogical](https://tetralogical.com/) | 59 | **23** | 49 | 51 | 53 | 53 | 53 |
444
431
  | [TPGi](https://www.tpgi.com/) | 173 | **157** | 159 | 163 | 164 | 170 | 170 |
445
- | [United Nations](https://www.un.org/en/) | 151 | **112** | 121 | 125 | 125 | 130 | 123 |
432
+ | [United Nations](https://www.un.org/en/) | 151 | **113** | 121 | 125 | 125 | 130 | 123 |
446
433
  | [Vivaldi](https://vivaldi.com/) | 93 | **74** | n/a | 79 | 81 | 84 | 82 |
447
- | [W3C](https://www.w3.org/) | 51 | **36** | 39 | 39 | 39 | 41 | 39 |
448
- | **Average processing time** | | 95 ms (30/30) | 143 ms (29/30) | 46 ms (30/30) | **14 ms (30/30)** | 274 ms (30/30) | 1297 ms (24/30) |
434
+ | [W3C](https://www.w3.org/) | 50 | **36** | 39 | 38 | 38 | 41 | 39 |
435
+ | **Average processing time** | | 73 ms (30/30) | 153 ms (29/30) | 51 ms (30/30) | **14 ms (30/30)** | 289 ms (30/30) | 1214 ms (24/30) |
449
436
 
450
- (Last updated: Jan 20, 2026)
437
+ (Last updated: Feb 2, 2026)
451
438
  <!-- End auto-generated -->
452
439
 
453
440
  Notes: 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.
@@ -465,25 +452,23 @@ html-minifier-next --collapse-whitespace --remove-comments --minify-js --input-d
465
452
  Example using npx:
466
453
 
467
454
  ```shell
468
- npx html-minifier-next --input-dir=test --file-ext html --preset comprehensive --output-dir example
455
+ npx html-minifier-next --input-dir=test --preset comprehensive --output-dir example
469
456
  ```
470
457
 
471
458
  **Process specific files and directories:**
472
459
 
473
460
  ```shell
474
- # Process only HTML files
475
- html-minifier-next --collapse-whitespace --input-dir=src --output-dir=dist --file-ext=html
461
+ # Process default extensions (html, htm, xhtml, shtml)
462
+ html-minifier-next --collapse-whitespace --input-dir=src --output-dir=dist
476
463
 
477
- # Process multiple file extensions
478
- html-minifier-next --collapse-whitespace --input-dir=src --output-dir=dist --file-ext=html,htm,php
464
+ # Process only specific extensions
465
+ html-minifier-next --collapse-whitespace --input-dir=src --output-dir=dist --file-ext=html,php
479
466
 
480
- # Using configuration file that sets `fileExt` (e.g., `"fileExt": "html,htm"`)
467
+ # Using configuration file that sets `fileExt` (e.g., `"fileExt": "html,php"`)
481
468
  html-minifier-next --config-file=html-minifier.json --input-dir=src --output-dir=dist
482
469
 
483
- # Process all files (default behavior)
484
- html-minifier-next --collapse-whitespace --input-dir=src --output-dir=dist
485
- # When processing all files, non-HTML files will also be read as UTF‑8 and passed to the minifier
486
- # Consider restricting with `--file-ext` to avoid touching binaries (e.g., images, archives)
470
+ # Process all files (explicit wildcard)
471
+ html-minifier-next --collapse-whitespace --input-dir=src --output-dir=dist --file-ext='*'
487
472
  ```
488
473
 
489
474
  **Exclude directories from processing:**
@@ -519,7 +504,7 @@ html-minifier-next --input-dir=src --output-dir=dist --dry --collapse-whitespace
519
504
  ```shell
520
505
  # Show processing details while minifying
521
506
  html-minifier-next --input-dir=src --output-dir=dist --verbose --collapse-whitespace
522
- # Output: Options: collapseWhitespace, html5, includeAutoGeneratedTags
507
+ # Output: CLI options: collapseWhitespace
523
508
  # ✓ src/index.html: 1,234 → 892 bytes (-342, 27.7%)
524
509
  # ✓ src/about.html: 2,100 → 1,654 bytes (-446, 21.2%)
525
510
  # ---
package/cli.js CHANGED
@@ -6,9 +6,7 @@
6
6
  * The MIT License (MIT)
7
7
  *
8
8
  * Copyright 2014–2016 Zoltan Frombach
9
- *
10
9
  * Copyright Juriy “kangax” Zaytsev
11
- *
12
10
  * Copyright 2025 Jens Oliver Meiert
13
11
  *
14
12
  * Permission is hereby granted, free of charge, to any person obtaining a copy of
@@ -35,14 +33,20 @@ import path from 'path';
35
33
  import { pathToFileURL } from 'url';
36
34
  import os from 'os';
37
35
  import { createRequire } from 'module';
38
- import { camelCase, paramCase } from 'change-case';
39
36
  import { Command } from 'commander';
37
+
38
+ // Simple case conversion for CLI option names (ASCII-only, no Unicode needed)
39
+ const paramCase = (str) => str.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
40
+ const camelCase = (str) => paramCase(str).replace(/-([a-z])/g, (_, c) => c.toUpperCase());
41
+
40
42
  // Lazy-load HMN to reduce CLI cold-start overhead
41
43
  import { getPreset, getPresetNames } from './src/presets.js';
42
44
 
43
45
  const require = createRequire(import.meta.url);
44
46
  const pkg = require('./package.json');
45
47
 
48
+ const DEFAULT_FILE_EXTENSIONS = ['html', 'htm', 'xhtml', 'shtml'];
49
+
46
50
  const program = new Command();
47
51
  program.name(pkg.name);
48
52
  program.version(pkg.version);
@@ -136,11 +140,10 @@ const mainOptions = {
136
140
  customEventAttributes: ['Array of regexes that allow to support custom event attributes for minifyJS (e.g., `ng-click`)', parseJSONRegExpArray],
137
141
  customFragmentQuantifierLimit: ['Set maximum quantifier limit for custom fragments to prevent ReDoS attacks (default: 200)', parseValidInt('customFragmentQuantifierLimit')],
138
142
  decodeEntities: 'Use direct Unicode characters whenever possible',
139
- html5: 'Don’t parse input according to the HTML specification (not recommended for modern HTML)',
140
- ignoreCustomComments: ['Array of regexes that allow to ignore certain comments, when matched', parseJSONRegExpArray],
143
+ ignoreCustomComments: ['Array of regexes that allow to ignore matching comments', parseJSONRegExpArray],
141
144
  ignoreCustomFragments: ['Array of regexes that allow to ignore certain fragments, when matched (e.g., `<?php … ?>`, `{{ … }}`)', parseJSONRegExpArray],
142
- includeAutoGeneratedTags: 'Don’t insert elements generated by HTML parser',
143
- inlineCustomElements: ['Array of names of custom elements which are inline', parseJSONArray],
145
+ includeAutoGeneratedTags: 'Insert elements generated by HTML parser',
146
+ inlineCustomElements: ['Array of names of custom elements which are inline, for whitespace handling', parseJSONArray],
144
147
  keepClosingSlash: 'Keep the trailing slash on void elements',
145
148
  maxInputLength: ['Maximum input length to prevent ReDoS attacks', parseValidInt('maxInputLength')],
146
149
  maxLineLength: ['Specify a maximum line length; compressed output will be split by newlines at valid HTML split-points', parseValidInt('maxLineLength')],
@@ -148,7 +151,7 @@ const mainOptions = {
148
151
  minifyCSS: ['Minify CSS in `style` elements and attributes (uses Lightning CSS)', parseJSON],
149
152
  minifyJS: ['Minify JavaScript in `script` elements and event attributes (uses Terser or SWC; pass `{"engine": "swc"}` for SWC)', parseJSON],
150
153
  minifySVG: ['Minify SVG elements and attributes (numeric precision, default attributes, colors)', parseJSON],
151
- minifyURLs: ['Minify URLs in various attributes (uses relateurl)', parseJSON],
154
+ minifyURLs: ['Minify URLs in various attributes', parseJSON],
152
155
  noNewlinesBeforeTagClose: 'Never add a newline before a tag that closes an element',
153
156
  partialMarkup: 'Treat input as a partial HTML fragment, preserving stray end tags and unclosed tags',
154
157
  preserveLineBreaks: 'Always collapse to one line break (never remove it entirely) when whitespace between tags includes a line break—use with `--collapse-whitespace`',
@@ -167,7 +170,7 @@ const mainOptions = {
167
170
  removeStyleLinkTypeAttributes: 'Remove `type="text/css"` from `style` and `link` elements; other `type` attribute values are left intact',
168
171
  removeTagWhitespace: 'Remove space between attributes whenever possible; note that this will result in invalid HTML',
169
172
  sortAttributes: 'Sort attributes by frequency',
170
- sortClassName: 'Sort style classes by frequency',
173
+ sortClassNames: 'Sort style classes by frequency',
171
174
  trimCustomFragments: 'Trim whitespace around custom fragments (`--ignore-custom-fragments`)',
172
175
  useShortDoctype: 'Replaces the doctype with the short HTML doctype'
173
176
  };
@@ -177,10 +180,10 @@ const mainOptionKeys = Object.keys(mainOptions);
177
180
  mainOptionKeys.forEach(function (key) {
178
181
  const option = mainOptions[key];
179
182
  if (Array.isArray(option)) {
180
- key = key === 'minifyURLs' ? '--minify-urls' : '--' + paramCase(key);
183
+ key = '--' + paramCase(key);
181
184
  key += option[1] === parseJSON ? ' [value]' : ' <value>';
182
185
  program.option(key, option[0], option[1]);
183
- } else if (~['html5', 'includeAutoGeneratedTags', 'continueOnMinifyError'].indexOf(key)) {
186
+ } else if (key === 'continueOnMinifyError') {
184
187
  program.option('--no-' + paramCase(key), option);
185
188
  } else {
186
189
  program.option('--' + paramCase(key), option);
@@ -277,14 +280,14 @@ function normalizeConfig(config) {
277
280
  }
278
281
 
279
282
  let config = {};
283
+ program.option('-I --input-dir <dir>', 'Specify an input directory');
284
+ program.option('-X --ignore-dir <patterns>', 'Exclude directories—relative to input directory—from processing (comma-separated), e.g., “libs” or “libs,vendor,node_modules”');
285
+ program.option('-O --output-dir <dir>', 'Specify an output directory');
286
+ program.option('-f --file-ext <extensions>', 'Specify file extension(s) to process (comma-separated); defaults to “html,htm,xhtml,shtml”; use “*” for all files');
287
+ program.option('-p --preset <name>', `Use a preset configuration (${getPresetNames().join(', ')})`);
280
288
  program.option('-c --config-file <file>', 'Use config file');
281
- program.option('--preset <name>', `Use a preset configuration (${getPresetNames().join(', ')})`);
282
- program.option('--input-dir <dir>', 'Specify an input directory');
283
- program.option('--ignore-dir <patterns>', 'Exclude directories—relative to input directory—from processing (comma-separated), e.g., “libs” or “libs,vendor,node_modules”');
284
- program.option('--output-dir <dir>', 'Specify an output directory');
285
- program.option('--file-ext <extensions>', 'Specify file extension(s) to process (comma-separated), e.g., “html” or “html,htm,php”');
286
- program.option('--cache-css <size>', 'Set CSS minification cache size (number of entries, default: 500 [1000 in CI])', parseValidInt('cacheCSS'));
287
- program.option('--cache-js <size>', 'Set JavaScript minification cache size (number of entries, default: 500 [1000 in CI])', parseValidInt('cacheJS'));
289
+ program.option('--cache-css <size>', 'Set CSS minification cache size (number of entries, default: 500)', parseValidInt('cacheCSS'));
290
+ program.option('--cache-js <size>', 'Set JavaScript minification cache size (number of entries, default: 500)', parseValidInt('cacheJS'));
288
291
 
289
292
  (async () => {
290
293
  let content;
@@ -342,7 +345,7 @@ program.option('--cache-js <size>', 'Set JavaScript minification cache size (num
342
345
 
343
346
  // 3. Apply CLI options (overrides config and preset)
344
347
  mainOptionKeys.forEach(function (key) {
345
- const param = programOptions[key === 'minifyURLs' ? 'minifyUrls' : camelCase(key)];
348
+ const param = programOptions[camelCase(key)];
346
349
  if (typeof param !== 'undefined') {
347
350
  options[key] = param;
348
351
  }
@@ -357,7 +360,7 @@ program.option('--cache-js <size>', 'Set JavaScript minification cache size (num
357
360
  console.error(`Using preset: ${presetName}`);
358
361
  }
359
362
  const activeOptions = Object.entries(minifierOptions)
360
- .filter(([k]) => program.getOptionValueSource(k === 'minifyURLs' ? 'minifyUrls' : camelCase(k)) === 'cli')
363
+ .filter(([k]) => program.getOptionValueSource(camelCase(k)) === 'cli')
361
364
  .map(([k, v]) => (typeof v === 'boolean' ? (v ? k : `no-${k}`) : k));
362
365
  if (activeOptions.length > 0) {
363
366
  console.error('CLI options: ' + activeOptions.join(', '));
@@ -406,6 +409,7 @@ program.option('--cache-js <size>', 'Set JavaScript minification cache size (num
406
409
 
407
410
  function parseFileExtensions(fileExt) {
408
411
  if (!fileExt) return [];
412
+ if (fileExt.trim() === '*') return ['*'];
409
413
  const list = fileExt
410
414
  .split(',')
411
415
  .map(ext => ext.trim().replace(/^\.+/, '').toLowerCase())
@@ -414,8 +418,9 @@ program.option('--cache-js <size>', 'Set JavaScript minification cache size (num
414
418
  }
415
419
 
416
420
  function shouldProcessFile(filename, fileExtensions) {
417
- if (!fileExtensions || fileExtensions.length === 0) {
418
- return true; // No extensions specified, process all files
421
+ // Wildcard: process all files
422
+ if (fileExtensions.includes('*')) {
423
+ return true;
419
424
  }
420
425
 
421
426
  const fileExt = path.extname(filename).replace(/^\.+/, '').toLowerCase();
@@ -641,9 +646,9 @@ program.option('--cache-js <size>', 'Set JavaScript minification cache size (num
641
646
 
642
647
  const { inputDir, outputDir, fileExt, ignoreDir } = programOptions;
643
648
 
644
- // Resolve file extensions: CLI argument takes priority over config file, even if empty string
649
+ // Resolve file extensions: CLI argument > config file > defaults
645
650
  const hasCliFileExt = program.getOptionValueSource('fileExt') === 'cli';
646
- const resolvedFileExt = hasCliFileExt ? fileExt : config.fileExt;
651
+ const resolvedFileExt = hasCliFileExt ? (fileExt || '*') : (config.fileExt || DEFAULT_FILE_EXTENSIONS);
647
652
 
648
653
  // Resolve ignore patterns: CLI argument takes priority over config file
649
654
  const hasCliIgnoreDir = program.getOptionValueSource('ignoreDir') === 'cli';