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 +0 -2
- package/README.md +63 -78
- package/cli.js +29 -24
- package/dist/htmlminifier.cjs +400 -175
- package/dist/htmlminifier.esm.bundle.js +611 -2722
- package/dist/types/htmlminifier.d.ts +12 -17
- package/dist/types/htmlminifier.d.ts.map +1 -1
- package/dist/types/htmlparser.d.ts.map +1 -1
- package/dist/types/lib/options.d.ts.map +1 -1
- package/dist/types/lib/urls.d.ts +7 -0
- package/dist/types/lib/urls.d.ts.map +1 -0
- package/dist/types/lib/whitespace.d.ts.map +1 -1
- package/dist/types/presets.d.ts +8 -16
- package/dist/types/presets.d.ts.map +1 -1
- package/package.json +6 -10
- package/src/htmlminifier.js +18 -24
- package/src/htmlparser.js +174 -119
- package/src/lib/attributes.js +3 -3
- package/src/lib/options.js +7 -9
- package/src/lib/urls.js +155 -0
- package/src/lib/whitespace.js +40 -12
- package/src/presets.js +3 -8
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/html-minifier-next) [](https://github.com/j9t/html-minifier-next/actions) [](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**
|
|
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
|
|
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
|
|
41
|
-
|
|
|
42
|
-
| `--preset <name>` | Use a preset configuration (conservative or comprehensive) | `--preset=conservative` |
|
|
43
|
-
|
|
|
44
|
-
| `-v
|
|
45
|
-
| `-d
|
|
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,
|
|
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,
|
|
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
|
|
90
|
+
const { minify } = require('html-minifier-next');
|
|
91
91
|
|
|
92
92
|
(async () => {
|
|
93
|
-
const result = await minify('<p title="example" id="moo">foo</p>',
|
|
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`:
|
|
105
|
-
* `comprehensive`: 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`
|
|
134
|
-
| `cacheJS`<br>`--cache-js` | Set JavaScript minification cache size; higher values improve performance for batch processing | `500`
|
|
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
|
-
| `
|
|
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>`--
|
|
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
|
|
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
|
-
| `
|
|
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 `
|
|
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
|
|
299
|
-
cacheJS: 250 // JS cache size, default: 500
|
|
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
|
|
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>[](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/) |
|
|
417
404
|
| --- | --- | --- | --- | --- | --- | --- | --- |
|
|
418
|
-
| [A List Apart](https://alistapart.com/) |
|
|
419
|
-
| [Apple](https://www.apple.com/) |
|
|
420
|
-
| [BBC](https://www.bbc.co.uk/) |
|
|
421
|
-
| [CERN](https://home.cern/) | 151 | **82** | 90 | 90 | 91 |
|
|
422
|
-
| [CSS-Tricks](https://css-tricks.com/) |
|
|
423
|
-
| [ECMAScript](https://tc39.es/ecma262/) |
|
|
424
|
-
| [EDRi](https://edri.org/) | 80 | **59** |
|
|
425
|
-
| [EFF](https://www.eff.org/) | 54 | **
|
|
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/) |
|
|
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/) |
|
|
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/) |
|
|
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 |
|
|
434
|
-
| [Leanpub](https://leanpub.com/) |
|
|
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/) |
|
|
438
|
-
| [Mistral AI](https://mistral.ai/) |
|
|
439
|
-
| [Mozilla](https://www.mozilla.org/) |
|
|
440
|
-
| [Nielsen Norman Group](https://www.nngroup.com/) |
|
|
441
|
-
| [SitePoint](https://www.sitepoint.com/) |
|
|
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/) |
|
|
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 | **
|
|
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/) |
|
|
448
|
-
| **Average processing time** | |
|
|
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:
|
|
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 --
|
|
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
|
|
475
|
-
html-minifier-next --collapse-whitespace --input-dir=src --output-dir=dist
|
|
461
|
+
# Process default extensions (html, htm, xhtml, shtml)
|
|
462
|
+
html-minifier-next --collapse-whitespace --input-dir=src --output-dir=dist
|
|
476
463
|
|
|
477
|
-
# Process
|
|
478
|
-
html-minifier-next --collapse-whitespace --input-dir=src --output-dir=dist --file-ext=html,
|
|
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,
|
|
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 (
|
|
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:
|
|
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
|
-
|
|
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: '
|
|
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
|
|
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
|
-
|
|
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 =
|
|
183
|
+
key = '--' + paramCase(key);
|
|
181
184
|
key += option[1] === parseJSON ? ' [value]' : ' <value>';
|
|
182
185
|
program.option(key, option[0], option[1]);
|
|
183
|
-
} else if (
|
|
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('--
|
|
282
|
-
program.option('--
|
|
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[
|
|
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(
|
|
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
|
-
|
|
418
|
-
|
|
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
|
|
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';
|