html-minifier-next 1.3.2 → 1.4.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/README.md +49 -23
- package/cli.js +14 -11
- package/package.json +1 -1
- package/src/htmlminifier.js +20 -2
package/README.md
CHANGED
|
@@ -40,14 +40,16 @@ html-minifier-next --collapse-whitespace --input-dir=src --output-dir=dist --fil
|
|
|
40
40
|
# Process multiple file extensions (CLI method)
|
|
41
41
|
html-minifier-next --collapse-whitespace --input-dir=src --output-dir=dist --file-ext=html,htm,php
|
|
42
42
|
|
|
43
|
-
# Using configuration file
|
|
43
|
+
# Using configuration file that sets `fileExt` (e.g., `"fileExt": "html,htm"`)
|
|
44
44
|
html-minifier-next --config-file=html-minifier.json --input-dir=src --output-dir=dist
|
|
45
45
|
|
|
46
46
|
# Process all files (default behavior)
|
|
47
47
|
html-minifier-next --collapse-whitespace --input-dir=src --output-dir=dist
|
|
48
|
+
# Note: When processing all files, non-HTML files will also be read as UTF‑8 and passed to the minifier.
|
|
49
|
+
# Consider restricting with “--file-ext” to avoid touching binaries (e.g., images, archives).
|
|
48
50
|
```
|
|
49
51
|
|
|
50
|
-
### CLI
|
|
52
|
+
### CLI options
|
|
51
53
|
|
|
52
54
|
Use `html-minifier-next --help` to check all available options:
|
|
53
55
|
|
|
@@ -59,9 +61,9 @@ Use `html-minifier-next --help` to check all available options:
|
|
|
59
61
|
| `-o --output <file>` | Specify output file (single file mode) | `-o minified.html` |
|
|
60
62
|
| `-c --config-file <file>` | Use a configuration file | `--config-file=html-minifier.json` |
|
|
61
63
|
|
|
62
|
-
### Configuration
|
|
64
|
+
### Configuration file
|
|
63
65
|
|
|
64
|
-
You can also use a configuration file to specify options:
|
|
66
|
+
You can also use a configuration file to specify options. The file can be either JSON format or a JavaScript module that exports the configuration object:
|
|
65
67
|
|
|
66
68
|
**JSON configuration example:**
|
|
67
69
|
|
|
@@ -73,6 +75,16 @@ You can also use a configuration file to specify options:
|
|
|
73
75
|
}
|
|
74
76
|
```
|
|
75
77
|
|
|
78
|
+
**JavaScript module configuration example:**
|
|
79
|
+
|
|
80
|
+
```js
|
|
81
|
+
module.exports = {
|
|
82
|
+
collapseWhitespace: true,
|
|
83
|
+
removeComments: true,
|
|
84
|
+
fileExt: "html,htm"
|
|
85
|
+
};
|
|
86
|
+
```
|
|
87
|
+
|
|
76
88
|
**Using a configuration file:**
|
|
77
89
|
|
|
78
90
|
```bash
|
|
@@ -85,13 +97,28 @@ html-minifier-next --config-file=html-minifier.json --file-ext=xml --input-dir=s
|
|
|
85
97
|
|
|
86
98
|
### Node.js
|
|
87
99
|
|
|
100
|
+
ESM with Node.js ≥16.14:
|
|
101
|
+
|
|
88
102
|
```js
|
|
89
|
-
|
|
103
|
+
import { minify } from 'html-minifier-next';
|
|
90
104
|
|
|
91
105
|
const result = await minify('<p title="blah" id="moo">foo</p>', {
|
|
92
106
|
removeAttributeQuotes: true,
|
|
93
107
|
});
|
|
94
|
-
result; // “<p title=blah id=moo>foo</p>”
|
|
108
|
+
console.log(result); // “<p title=blah id=moo>foo</p>”
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
CommonJS:
|
|
112
|
+
|
|
113
|
+
```js
|
|
114
|
+
const { minify } = require('html-minifier-next');
|
|
115
|
+
|
|
116
|
+
(async () => {
|
|
117
|
+
const result = await minify('<p title="blah" id="moo">foo</p>', {
|
|
118
|
+
removeAttributeQuotes: true,
|
|
119
|
+
});
|
|
120
|
+
console.log(result);
|
|
121
|
+
})();
|
|
95
122
|
```
|
|
96
123
|
|
|
97
124
|
See [the original blog post](http://perfectionkills.com/experimenting-with-html-minifier) for details of [how it works](http://perfectionkills.com/experimenting-with-html-minifier#how_it_works), [description of each option](http://perfectionkills.com/experimenting-with-html-minifier#options), [testing results](http://perfectionkills.com/experimenting-with-html-minifier#field_testing), and [conclusions](http://perfectionkills.com/experimenting-with-html-minifier#cost_and_benefits).
|
|
@@ -102,24 +129,24 @@ For lint-like capabilities take a look at [HTMLLint](https://github.com/kangax/h
|
|
|
102
129
|
|
|
103
130
|
How does HTML Minifier compare to other solutions, like [minimize](https://github.com/Swaagie/minimize) or [htmlcompressor.com](http://htmlcompressor.com/)?
|
|
104
131
|
|
|
105
|
-
| Site | Original size (KB) |
|
|
106
|
-
| --- | ---
|
|
132
|
+
| Site | Original size (KB) | HTMLMinifier | minimize | htmlcompressor.com |
|
|
133
|
+
| --- | --- | --- | --- | --- |
|
|
107
134
|
| [A List Apart](https://alistapart.com/) | 64 | **54** | 59 | 57 |
|
|
108
|
-
| [Amazon](https://www.amazon.com/) |
|
|
109
|
-
| [BBC](https://www.bbc.co.uk/) |
|
|
110
|
-
| [CSS-Tricks](https://css-tricks.com/) |
|
|
111
|
-
| [ECMAScript](https://tc39.es/ecma262/) |
|
|
112
|
-
| [EFF](https://www.eff.org/) |
|
|
113
|
-
| [
|
|
114
|
-
| [
|
|
115
|
-
| [
|
|
116
|
-
| [
|
|
117
|
-
| [
|
|
118
|
-
| [
|
|
119
|
-
| [
|
|
135
|
+
| [Amazon](https://www.amazon.com/) | 707 | **635** | 693 | n/a |
|
|
136
|
+
| [BBC](https://www.bbc.co.uk/) | 700 | **642** | 694 | n/a |
|
|
137
|
+
| [CSS-Tricks](https://css-tricks.com/) | 167 | **124** | 153 | 149 |
|
|
138
|
+
| [ECMAScript](https://tc39.es/ecma262/) | 7205 | **6365** | 6585 | n/a |
|
|
139
|
+
| [EFF](https://www.eff.org/) | 58 | **49** | 52 | 52 |
|
|
140
|
+
| [Eloquent JavaScript](https://eloquentjavascript.net/) | 6 | **5** | 6 | 5 |
|
|
141
|
+
| [FAZ](https://www.faz.net/aktuell/) | 1848 | **1727** | 1763 | n/a |
|
|
142
|
+
| [Frontend Dogma](https://frontenddogma.com/) | 118 | **113** | 127 | 117 |
|
|
143
|
+
| [Google](https://www.google.com/) | 50 | **46** | 50 | 50 |
|
|
144
|
+
| [HTMLMinifier](https://github.com/kangax/html-minifier) | 371 | **249** | 347 | n/a |
|
|
145
|
+
| [Mastodon](https://mastodon.social/explore) | 35 | **26** | 34 | 34 |
|
|
146
|
+
| [NBC](https://www.nbc.com/) | 579 | **528** | 572 | n/a |
|
|
147
|
+
| [New York Times](https://www.nytimes.com/) | 733 | **625** | 722 | n/a |
|
|
120
148
|
| [United Nations](https://www.un.org/) | 9 | **7** | 8 | 8 |
|
|
121
|
-
| [W3C](https://www.w3.org/) |
|
|
122
|
-
| [Wikipedia](https://en.wikipedia.org/wiki/Main_Page) | 225 | **204** | 215 | 215 |
|
|
149
|
+
| [W3C](https://www.w3.org/) | 51 | **36** | 42 | 40 |
|
|
123
150
|
|
|
124
151
|
## Options quick reference
|
|
125
152
|
|
|
@@ -139,7 +166,6 @@ Most of the options are disabled by default.
|
|
|
139
166
|
| `customAttrSurround` | Arrays of regexes that allow to support custom attribute surround expressions (e.g. `<input {{#if value}}checked="checked"{{/if}}>`) | `[]` |
|
|
140
167
|
| `customEventAttributes` | Arrays of regexes that allow to support custom event attributes for `minifyJS` (e.g. `ng-click`) | `[ /^on[a-z]{3,}$/ ]` |
|
|
141
168
|
| `decodeEntities` | Use direct Unicode characters whenever possible | `false` |
|
|
142
|
-
| `fileExt` | File extensions to process | `[]` (process all files) |
|
|
143
169
|
| `html5` | Parse input according to HTML5 specifications | `true` |
|
|
144
170
|
| `ignoreCustomComments` | Array of regexes that allow to ignore certain comments, when matched | `[ /^!/, /^\s*#/ ]` |
|
|
145
171
|
| `ignoreCustomFragments` | Array of regexes that allow to ignore certain fragments, when matched (e.g. `<?php ... ?>`, `{{ ... }}`, etc.) | `[ /<%[\s\S]*?%>/, /<\?[\s\S]*?\?>/ ]` |
|
package/cli.js
CHANGED
|
@@ -197,7 +197,7 @@ program.option('-c --config-file <file>', 'Use config file', function (configPat
|
|
|
197
197
|
});
|
|
198
198
|
program.option('--input-dir <dir>', 'Specify an input directory');
|
|
199
199
|
program.option('--output-dir <dir>', 'Specify an output directory');
|
|
200
|
-
program.option('--file-ext <
|
|
200
|
+
program.option('--file-ext <extensions>', 'Specify file extension(s) to process (comma-separated), e.g., “html” or “html,htm,php”');
|
|
201
201
|
|
|
202
202
|
let content;
|
|
203
203
|
program.arguments('[files...]').action(function (files) {
|
|
@@ -250,17 +250,16 @@ function processFile(inputFile, outputFile) {
|
|
|
250
250
|
}
|
|
251
251
|
|
|
252
252
|
function parseFileExtensions(fileExt) {
|
|
253
|
-
if (!fileExt)
|
|
254
|
-
|
|
255
|
-
}
|
|
256
|
-
return fileExt
|
|
253
|
+
if (!fileExt) return [];
|
|
254
|
+
const list = fileExt
|
|
257
255
|
.split(',')
|
|
258
256
|
.map(ext => ext.trim().replace(/^\.+/, '').toLowerCase())
|
|
259
257
|
.filter(ext => ext.length > 0);
|
|
258
|
+
return [...new Set(list)];
|
|
260
259
|
}
|
|
261
260
|
|
|
262
261
|
function shouldProcessFile(filename, fileExtensions) {
|
|
263
|
-
if (fileExtensions.length === 0) {
|
|
262
|
+
if (!fileExtensions || fileExtensions.length === 0) {
|
|
264
263
|
return true; // No extensions specified, process all files
|
|
265
264
|
}
|
|
266
265
|
|
|
@@ -268,8 +267,11 @@ function shouldProcessFile(filename, fileExtensions) {
|
|
|
268
267
|
return fileExtensions.includes(fileExt);
|
|
269
268
|
}
|
|
270
269
|
|
|
271
|
-
function processDirectory(inputDir, outputDir,
|
|
272
|
-
|
|
270
|
+
function processDirectory(inputDir, outputDir, extensions) {
|
|
271
|
+
// If first call provided a string, normalize once; otherwise assume pre-parsed array
|
|
272
|
+
if (typeof extensions === 'string') {
|
|
273
|
+
extensions = parseFileExtensions(extensions);
|
|
274
|
+
}
|
|
273
275
|
|
|
274
276
|
fs.readdir(inputDir, function (err, files) {
|
|
275
277
|
if (err) {
|
|
@@ -284,7 +286,7 @@ function processDirectory(inputDir, outputDir, fileExt) {
|
|
|
284
286
|
if (err) {
|
|
285
287
|
fatal('Cannot read ' + inputFile + '\n' + err.message);
|
|
286
288
|
} else if (stat.isDirectory()) {
|
|
287
|
-
processDirectory(inputFile, outputFile,
|
|
289
|
+
processDirectory(inputFile, outputFile, extensions);
|
|
288
290
|
} else if (shouldProcessFile(file, extensions)) {
|
|
289
291
|
mkdir(outputDir, function () {
|
|
290
292
|
processFile(inputFile, outputFile);
|
|
@@ -319,8 +321,9 @@ const writeMinify = async () => {
|
|
|
319
321
|
|
|
320
322
|
const { inputDir, outputDir, fileExt } = programOptions;
|
|
321
323
|
|
|
322
|
-
// Resolve file extensions: CLI argument takes priority over config file
|
|
323
|
-
const
|
|
324
|
+
// Resolve file extensions: CLI argument takes priority over config file, even if empty string
|
|
325
|
+
const hasCliFileExt = program.getOptionValueSource('fileExt') === 'cli';
|
|
326
|
+
const resolvedFileExt = hasCliFileExt ? fileExt : config.fileExt;
|
|
324
327
|
|
|
325
328
|
if (inputDir || outputDir) {
|
|
326
329
|
if (!inputDir) {
|
package/package.json
CHANGED
package/src/htmlminifier.js
CHANGED
|
@@ -260,7 +260,7 @@ function isSrcset(attrName, tag) {
|
|
|
260
260
|
return attrName === 'srcset' && srcsetTags.has(tag);
|
|
261
261
|
}
|
|
262
262
|
|
|
263
|
-
async function cleanAttributeValue(tag, attrName, attrValue, options, attrs) {
|
|
263
|
+
async function cleanAttributeValue(tag, attrName, attrValue, options, attrs, minifyHTMLSelf) {
|
|
264
264
|
if (isEventAttribute(attrName, options)) {
|
|
265
265
|
attrValue = trimWhitespace(attrValue).replace(/^javascript:\s*/i, '');
|
|
266
266
|
return options.minifyJS(attrValue, true);
|
|
@@ -318,6 +318,13 @@ async function cleanAttributeValue(tag, attrName, attrValue, options, attrs) {
|
|
|
318
318
|
} else if (isMediaQuery(tag, attrs, attrName)) {
|
|
319
319
|
attrValue = trimWhitespace(attrValue);
|
|
320
320
|
return options.minifyCSS(attrValue, 'media');
|
|
321
|
+
} else if (tag === 'iframe' && attrName === 'srcdoc') {
|
|
322
|
+
// Recursively minify HTML content within srcdoc attribute
|
|
323
|
+
// Fast-path: skip if nothing would change
|
|
324
|
+
if (!shouldMinifyInnerHTML(options)) {
|
|
325
|
+
return attrValue;
|
|
326
|
+
}
|
|
327
|
+
return minifyHTMLSelf(attrValue, options, true);
|
|
321
328
|
}
|
|
322
329
|
return attrValue;
|
|
323
330
|
}
|
|
@@ -557,7 +564,7 @@ async function normalizeAttr(attr, attrs, tag, options) {
|
|
|
557
564
|
}
|
|
558
565
|
|
|
559
566
|
if (attrValue) {
|
|
560
|
-
attrValue = await cleanAttributeValue(tag, attrName, attrValue, options, attrs);
|
|
567
|
+
attrValue = await cleanAttributeValue(tag, attrName, attrValue, options, attrs, minifyHTML);
|
|
561
568
|
}
|
|
562
569
|
|
|
563
570
|
if (options.removeEmptyAttributes &&
|
|
@@ -632,6 +639,17 @@ function identityAsync(value) {
|
|
|
632
639
|
return Promise.resolve(value);
|
|
633
640
|
}
|
|
634
641
|
|
|
642
|
+
function shouldMinifyInnerHTML(options) {
|
|
643
|
+
return Boolean(
|
|
644
|
+
options.collapseWhitespace ||
|
|
645
|
+
options.removeComments ||
|
|
646
|
+
options.removeOptionalTags ||
|
|
647
|
+
options.minifyJS !== identity ||
|
|
648
|
+
options.minifyCSS !== identityAsync ||
|
|
649
|
+
options.minifyURLs !== identity
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
|
|
635
653
|
const processOptions = (inputOptions) => {
|
|
636
654
|
const options = {
|
|
637
655
|
name: function (name) {
|