html-minifier-next 3.2.2 → 4.0.2
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 +65 -26
- package/cli.js +1 -1
- package/dist/htmlminifier.cjs +36 -35
- package/dist/htmlminifier.esm.bundle.js +3873 -25021
- package/package.json +5 -5
- package/src/htmlminifier.js +36 -35
- package/dist/htmlminifier.umd.bundle.js +0 -62195
- package/dist/htmlminifier.umd.bundle.min.js +0 -9
package/README.md
CHANGED
|
@@ -97,7 +97,7 @@ const { minify } = require('html-minifier-next');
|
|
|
97
97
|
})();
|
|
98
98
|
```
|
|
99
99
|
|
|
100
|
-
See [the original blog post](
|
|
100
|
+
See [the original blog post](https://perfectionkills.com/experimenting-with-html-minifier) for details of [how it works](https://perfectionkills.com/experimenting-with-html-minifier#how_it_works), [description of each option](https://perfectionkills.com/experimenting-with-html-minifier#options), [testing results](https://perfectionkills.com/experimenting-with-html-minifier#field_testing), and [conclusions](https://perfectionkills.com/experimenting-with-html-minifier#cost_and_benefits).
|
|
101
101
|
|
|
102
102
|
For lint-like capabilities, take a look at [HTMLLint](https://github.com/kangax/html-lint).
|
|
103
103
|
|
|
@@ -110,9 +110,9 @@ Options can be used in config files (camelCase) or via CLI flags (kebab-case wit
|
|
|
110
110
|
| Option (config/CLI) | Description | Default |
|
|
111
111
|
| --- | --- | --- |
|
|
112
112
|
| `caseSensitive`<br>`--case-sensitive` | Treat attributes in case-sensitive manner (useful for custom HTML elements) | `false` |
|
|
113
|
-
| `collapseBooleanAttributes`<br>`--collapse-boolean-attributes` | [Omit attribute values from boolean attributes](
|
|
113
|
+
| `collapseBooleanAttributes`<br>`--collapse-boolean-attributes` | [Omit attribute values from boolean attributes](https://perfectionkills.com/experimenting-with-html-minifier#collapse_boolean_attributes) | `false` |
|
|
114
114
|
| `collapseInlineTagWhitespace`<br>`--collapse-inline-tag-whitespace` | Don’t leave any spaces between `display: inline;` elements when collapsing—use with `collapseWhitespace: true` | `false` |
|
|
115
|
-
| `collapseWhitespace`<br>`--collapse-whitespace` | [Collapse whitespace that contributes to text nodes in a document tree](
|
|
115
|
+
| `collapseWhitespace`<br>`--collapse-whitespace` | [Collapse whitespace that contributes to text nodes in a document tree](https://perfectionkills.com/experimenting-with-html-minifier#collapse_whitespace) | `false` |
|
|
116
116
|
| `conservativeCollapse`<br>`--conservative-collapse` | Always collapse to 1 space (never remove it entirely)—use with `collapseWhitespace: true` | `false` |
|
|
117
117
|
| `continueOnParseError`<br>`--continue-on-parse-error` | [Handle parse errors](https://html.spec.whatwg.org/multipage/parsing.html#parse-errors) instead of aborting | `false` |
|
|
118
118
|
| `customAttrAssign`<br>`--custom-attr-assign` | Arrays of regexes that allow to support custom attribute assign expressions (e.g., `<div flex?="{{mode != cover}}"></div>`) | `[]` |
|
|
@@ -129,7 +129,7 @@ Options can be used in config files (camelCase) or via CLI flags (kebab-case wit
|
|
|
129
129
|
| `keepClosingSlash`<br>`--keep-closing-slash` | Keep the trailing slash on void elements | `false` |
|
|
130
130
|
| `maxInputLength`<br>`--max-input-length` | Maximum input length to prevent ReDoS attacks (disabled by default) | `undefined` |
|
|
131
131
|
| `maxLineLength`<br>`--max-line-length` | Specify a maximum line length; compressed output will be split by newlines at valid HTML split-points | |
|
|
132
|
-
| `minifyCSS`<br>`--minify-css` | Minify CSS in `style` elements and `style` attributes (uses [
|
|
132
|
+
| `minifyCSS`<br>`--minify-css` | Minify CSS in `style` elements and `style` attributes (uses [Lightning CSS](https://lightningcss.dev/)) | `false` (could be `true`, `Object`, `Function(text, type)`) |
|
|
133
133
|
| `minifyJS`<br>`--minify-js` | Minify JavaScript in `script` elements and event attributes (uses [Terser](https://github.com/terser/terser)) | `false` (could be `true`, `Object`, `Function(text, inline)`) |
|
|
134
134
|
| `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)`) |
|
|
135
135
|
| `noNewlinesBeforeTagClose`<br>`--no-newlines-before-tag-close` | Never add a newline before a tag that closes an element | `false` |
|
|
@@ -138,11 +138,11 @@ Options can be used in config files (camelCase) or via CLI flags (kebab-case wit
|
|
|
138
138
|
| `processConditionalComments`<br>`--process-conditional-comments` | Process contents of conditional comments through minifier | `false` |
|
|
139
139
|
| `processScripts`<br>`--process-scripts` | Array of strings corresponding to types of `script` elements to process through minifier (e.g., `text/ng-template`, `text/x-handlebars-template`, etc.) | `[]` |
|
|
140
140
|
| `quoteCharacter`<br>`--quote-character` | Type of quote to use for attribute values (`'` or `"`) | |
|
|
141
|
-
| `removeAttributeQuotes`<br>`--remove-attribute-quotes` | [Remove quotes around attributes when possible](
|
|
142
|
-
| `removeComments`<br>`--remove-comments` | [Strip HTML comments](
|
|
143
|
-
| `removeEmptyAttributes`<br>`--remove-empty-attributes` | [Remove all attributes with whitespace-only values](
|
|
144
|
-
| `removeEmptyElements`<br>`--remove-empty-elements` | [Remove all elements with empty contents](
|
|
145
|
-
| `removeOptionalTags`<br>`--remove-optional-tags` | [Remove optional tags](
|
|
141
|
+
| `removeAttributeQuotes`<br>`--remove-attribute-quotes` | [Remove quotes around attributes when possible](https://perfectionkills.com/experimenting-with-html-minifier#remove_attribute_quotes) | `false` |
|
|
142
|
+
| `removeComments`<br>`--remove-comments` | [Strip HTML comments](https://perfectionkills.com/experimenting-with-html-minifier#remove_comments) | `false` |
|
|
143
|
+
| `removeEmptyAttributes`<br>`--remove-empty-attributes` | [Remove all attributes with whitespace-only values](https://perfectionkills.com/experimenting-with-html-minifier#remove_empty_or_blank_attributes) | `false` (could be `true`, `Function(attrName, tag)`) |
|
|
144
|
+
| `removeEmptyElements`<br>`--remove-empty-elements` | [Remove all elements with empty contents](https://perfectionkills.com/experimenting-with-html-minifier#remove_empty_elements) | `false` |
|
|
145
|
+
| `removeOptionalTags`<br>`--remove-optional-tags` | [Remove optional tags](https://perfectionkills.com/experimenting-with-html-minifier#remove_optional_tags) | `false` |
|
|
146
146
|
| `removeRedundantAttributes`<br>`--remove-redundant-attributes` | [Remove attributes when value matches default](https://meiert.com/blog/optional-html/#toc-attribute-values) | `false` |
|
|
147
147
|
| `removeScriptTypeAttributes`<br>`--remove-script-type-attributes` | Remove `type="text/javascript"` from `script` elements; other `type` attribute values are left intact | `false` |
|
|
148
148
|
| `removeStyleLinkTypeAttributes`<br>`--remove-style-link-type-attributes` | Remove `type="text/css"` from `style` and `link` elements; other `type` attribute values are left intact | `false` |
|
|
@@ -150,11 +150,50 @@ Options can be used in config files (camelCase) or via CLI flags (kebab-case wit
|
|
|
150
150
|
| `sortAttributes`<br>`--sort-attributes` | [Sort attributes by frequency](#sorting-attributes-and-style-classes) | `false` |
|
|
151
151
|
| `sortClassName`<br>`--sort-class-name` | [Sort style classes by frequency](#sorting-attributes-and-style-classes) | `false` |
|
|
152
152
|
| `trimCustomFragments`<br>`--trim-custom-fragments` | Trim whitespace around `ignoreCustomFragments` | `false` |
|
|
153
|
-
| `useShortDoctype`<br>`--use-short-doctype` | [Replaces the doctype with the short (HTML) doctype](
|
|
153
|
+
| `useShortDoctype`<br>`--use-short-doctype` | [Replaces the doctype with the short (HTML) doctype](https://perfectionkills.com/experimenting-with-html-minifier#use_short_doctype) | `false` |
|
|
154
154
|
|
|
155
155
|
### Sorting attributes and style classes
|
|
156
156
|
|
|
157
|
-
Minifier options like `sortAttributes` and `sortClassName` won
|
|
157
|
+
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.
|
|
158
|
+
|
|
159
|
+
### CSS minification with Lightning CSS
|
|
160
|
+
|
|
161
|
+
When `minifyCSS` is set to `true`, HTML Minifier Next uses [Lightning CSS](https://lightningcss.dev/) to minify CSS in `<style>` elements and `style` attributes. Lightning CSS provides excellent minification by default.
|
|
162
|
+
|
|
163
|
+
You can pass Lightning CSS configuration options by providing an object:
|
|
164
|
+
|
|
165
|
+
```js
|
|
166
|
+
const result = await minify(html, {
|
|
167
|
+
minifyCSS: {
|
|
168
|
+
targets: {
|
|
169
|
+
// Browser targets for vendor prefix handling
|
|
170
|
+
chrome: 95,
|
|
171
|
+
firefox: 90,
|
|
172
|
+
safari: 14
|
|
173
|
+
},
|
|
174
|
+
unusedSymbols: ['unused-class', 'old-animation']
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Available Lightning CSS options when passed as an object:
|
|
180
|
+
|
|
181
|
+
* `targets`: Browser targets for vendor prefix optimization (e.g., `{ chrome: 95, firefox: 90 }`)
|
|
182
|
+
* `unusedSymbols`: Array of class names, IDs, keyframe names, and CSS variables to remove
|
|
183
|
+
* `errorRecovery`: Boolean to skip invalid rules instead of throwing errors (disabled in Lightning CSS, but enabled by default in HMN)
|
|
184
|
+
* `sourceMap`: Boolean to generate source maps
|
|
185
|
+
|
|
186
|
+
For advanced usage, you can also pass a function:
|
|
187
|
+
|
|
188
|
+
```js
|
|
189
|
+
const result = await minify(html, {
|
|
190
|
+
minifyCSS: function(text, type) {
|
|
191
|
+
// `text`: CSS string to minify
|
|
192
|
+
// `type`: `inline` for style attributes, `media` for media queries, `undefined` for `<style>` elements
|
|
193
|
+
return yourCustomMinifier(text);
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
```
|
|
158
197
|
|
|
159
198
|
## Minification comparison
|
|
160
199
|
|
|
@@ -162,25 +201,25 @@ How does HTML Minifier Next compare to other solutions, like [minimize](https://
|
|
|
162
201
|
|
|
163
202
|
| Site | Original Size (KB) | HTML Minifier Next | minimize | htmlcompressor.com | htmlnano | minify-html |
|
|
164
203
|
| --- | --- | --- | --- | --- | --- | --- |
|
|
165
|
-
| [A List Apart](https://alistapart.com/) | 62 | **
|
|
204
|
+
| [A List Apart](https://alistapart.com/) | 62 | **52** | 58 | 56 | 54 | 55 |
|
|
166
205
|
| [Amazon](https://www.amazon.com/) | 206 | **195** | 203 | 200 | 196 | n/a |
|
|
167
|
-
| [Apple](https://www.apple.com/) |
|
|
168
|
-
| [BBC](https://www.bbc.co.uk/) |
|
|
169
|
-
| [CSS-Tricks](https://css-tricks.com/) |
|
|
170
|
-
| [ECMAScript](https://tc39.es/ecma262/) |
|
|
171
|
-
| [EFF](https://www.eff.org/) | 57 | **48** | 52 |
|
|
172
|
-
| [FAZ](https://www.faz.net/aktuell/) |
|
|
173
|
-
| [Frontend Dogma](https://frontenddogma.com/) |
|
|
206
|
+
| [Apple](https://www.apple.com/) | 153 | **116** | 140 | 140 | 133 | 138 |
|
|
207
|
+
| [BBC](https://www.bbc.co.uk/) | 590 | **534** | 585 | n/a | 553 | 555 |
|
|
208
|
+
| [CSS-Tricks](https://css-tricks.com/) | 165 | **124** | 151 | 147 | 129 | 146 |
|
|
209
|
+
| [ECMAScript](https://tc39.es/ecma262/) | 7238 | **6342** | 6614 | n/a | 6561 | 6567 |
|
|
210
|
+
| [EFF](https://www.eff.org/) | 57 | **48** | 52 | 51 | 51 | 49 |
|
|
211
|
+
| [FAZ](https://www.faz.net/aktuell/) | 1847 | 1722 | 1762 | n/a | **1628** | n/a |
|
|
212
|
+
| [Frontend Dogma](https://frontenddogma.com/) | 219 | **211** | 237 | 218 | 232 | 218 |
|
|
174
213
|
| [Google](https://www.google.com/) | 18 | **16** | 18 | 18 | 17 | n/a |
|
|
175
|
-
| [Ground News](https://ground.news/) |
|
|
214
|
+
| [Ground News](https://ground.news/) | 1709 | **1463** | 1696 | n/a | 1567 | n/a |
|
|
176
215
|
| [HTML](https://html.spec.whatwg.org/multipage/) | 149 | **147** | 155 | 148 | 153 | 149 |
|
|
177
|
-
| [Leanpub](https://leanpub.com/) |
|
|
216
|
+
| [Leanpub](https://leanpub.com/) | 1424 | **1190** | 1418 | n/a | 1197 | n/a |
|
|
178
217
|
| [Mastodon](https://mastodon.social/explore) | 35 | **26** | 34 | 34 | 30 | 33 |
|
|
179
|
-
| [MDN](https://developer.mozilla.org/en-US/) | 107 | **
|
|
180
|
-
| [Middle East Eye](https://www.middleeasteye.net/) | 224 | **197** |
|
|
181
|
-
| [SitePoint](https://www.sitepoint.com/) |
|
|
182
|
-
| [United Nations](https://www.un.org/en/) | 151 | **
|
|
183
|
-
| [W3C](https://www.w3.org/) | 50 | **36** | 41 | 39 |
|
|
218
|
+
| [MDN](https://developer.mozilla.org/en-US/) | 107 | **62** | 67 | 68 | 64 | n/a |
|
|
219
|
+
| [Middle East Eye](https://www.middleeasteye.net/) | 224 | **197** | 203 | 204 | 204 | 201 |
|
|
220
|
+
| [SitePoint](https://www.sitepoint.com/) | 536 | **405** | 532 | n/a | 474 | 516 |
|
|
221
|
+
| [United Nations](https://www.un.org/en/) | 151 | **113** | 130 | 123 | 121 | 125 |
|
|
222
|
+
| [W3C](https://www.w3.org/) | 50 | **36** | 41 | 39 | 39 | 39 |
|
|
184
223
|
|
|
185
224
|
## Examples
|
|
186
225
|
|
package/cli.js
CHANGED
|
@@ -135,7 +135,7 @@ const mainOptions = {
|
|
|
135
135
|
keepClosingSlash: 'Keep the trailing slash on void elements',
|
|
136
136
|
maxInputLength: ['Maximum input length to prevent ReDoS attacks', parseValidInt('maxInputLength')],
|
|
137
137
|
maxLineLength: ['Specify a maximum line length; compressed output will be split by newlines at valid HTML split-points', parseValidInt('maxLineLength')],
|
|
138
|
-
minifyCSS: ['Minify CSS in “style” elements and “style” attributes (uses
|
|
138
|
+
minifyCSS: ['Minify CSS in “style” elements and “style” attributes (uses Lightning CSS)', parseJSON],
|
|
139
139
|
minifyJS: ['Minify JavaScript in “script” elements and event attributes (uses Terser)', parseJSON],
|
|
140
140
|
minifyURLs: ['Minify URLs in various attributes (uses relateurl)', parseJSON],
|
|
141
141
|
noNewlinesBeforeTagClose: 'Never add a newline before a tag that closes an element',
|
package/dist/htmlminifier.cjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
|
-
var
|
|
5
|
+
var lightningcss = require('lightningcss');
|
|
6
6
|
var entities = require('entities');
|
|
7
7
|
var RelateURL = require('relateurl');
|
|
8
8
|
var terser = require('terser');
|
|
@@ -920,12 +920,8 @@ function isContentSecurityPolicy(tag, attrs) {
|
|
|
920
920
|
}
|
|
921
921
|
}
|
|
922
922
|
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
// Wrap CSS declarations for CleanCSS > 3.x
|
|
928
|
-
// See https://github.com/jakubpawlowicz/clean-css/issues/418
|
|
923
|
+
// Wrap CSS declarations for inline styles and media queries
|
|
924
|
+
// This ensures proper context for CSS minification
|
|
929
925
|
function wrapCSS(text, type) {
|
|
930
926
|
switch (type) {
|
|
931
927
|
case 'inline':
|
|
@@ -1257,7 +1253,7 @@ const processOptions = (inputOptions) => {
|
|
|
1257
1253
|
return;
|
|
1258
1254
|
}
|
|
1259
1255
|
|
|
1260
|
-
const
|
|
1256
|
+
const lightningCssOptions = typeof option === 'object' ? option : {};
|
|
1261
1257
|
|
|
1262
1258
|
options.minifyCSS = async function (text, type) {
|
|
1263
1259
|
text = await replaceAsync(
|
|
@@ -1278,17 +1274,38 @@ const processOptions = (inputOptions) => {
|
|
|
1278
1274
|
|
|
1279
1275
|
const inputCSS = wrapCSS(text, type);
|
|
1280
1276
|
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
const outputCSS = unwrapCSS(output.styles, type);
|
|
1289
|
-
resolve(outputCSS);
|
|
1277
|
+
try {
|
|
1278
|
+
const result = lightningcss.transform({
|
|
1279
|
+
filename: 'input.css',
|
|
1280
|
+
code: Buffer.from(inputCSS),
|
|
1281
|
+
minify: true,
|
|
1282
|
+
errorRecovery: true,
|
|
1283
|
+
...lightningCssOptions
|
|
1290
1284
|
});
|
|
1291
|
-
|
|
1285
|
+
|
|
1286
|
+
const outputCSS = unwrapCSS(result.code.toString(), type);
|
|
1287
|
+
|
|
1288
|
+
// If Lightning CSS removed significant content that looks like template syntax or UIDs, return original
|
|
1289
|
+
// This preserves:
|
|
1290
|
+
// 1. Template code like `<?php ?>`, `<%= %>`, `{{ }}`, etc. (contain `<` or `>` but not `CDATA`)
|
|
1291
|
+
// 2. UIDs representing custom fragments (only lowercase letters and digits, no spaces)
|
|
1292
|
+
// CDATA sections, HTML entities, and other invalid CSS are allowed to be removed
|
|
1293
|
+
const isCDATA = text.includes('<![CDATA[');
|
|
1294
|
+
const uidPattern = /[a-z0-9]{10,}/; // UIDs are long alphanumeric strings
|
|
1295
|
+
const hasUID = uidPattern.test(text) && !isCDATA; // Exclude CDATA from UID detection
|
|
1296
|
+
const looksLikeTemplate = (text.includes('<') || text.includes('>')) && !isCDATA;
|
|
1297
|
+
|
|
1298
|
+
// Preserve if output is empty and input had template syntax or UIDs
|
|
1299
|
+
// This catches cases where Lightning CSS removed content that should be preserved
|
|
1300
|
+
if (text.trim() && !outputCSS.trim() && (looksLikeTemplate || hasUID)) {
|
|
1301
|
+
return text;
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
return outputCSS;
|
|
1305
|
+
} catch (err) {
|
|
1306
|
+
options.log && options.log(err);
|
|
1307
|
+
return text;
|
|
1308
|
+
}
|
|
1292
1309
|
};
|
|
1293
1310
|
} else if (key === 'minifyJS' && typeof option !== 'function') {
|
|
1294
1311
|
if (!option) {
|
|
@@ -1526,23 +1543,7 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
1526
1543
|
return chunks[1] + uidAttr + index + uidAttr + chunks[2];
|
|
1527
1544
|
});
|
|
1528
1545
|
|
|
1529
|
-
|
|
1530
|
-
new CleanCSS().minify(wrapCSS(text, type)).warnings.forEach(function (warning) {
|
|
1531
|
-
const match = uidPattern.exec(warning);
|
|
1532
|
-
if (match) {
|
|
1533
|
-
const id = uidAttr + match[2] + uidAttr;
|
|
1534
|
-
text = text.replace(id, ignoreCSS(id));
|
|
1535
|
-
ids.push(id);
|
|
1536
|
-
}
|
|
1537
|
-
});
|
|
1538
|
-
|
|
1539
|
-
return fn(text, type).then(chunk => {
|
|
1540
|
-
ids.forEach(function (id) {
|
|
1541
|
-
chunk = chunk.replace(ignoreCSS(id), id);
|
|
1542
|
-
});
|
|
1543
|
-
|
|
1544
|
-
return chunk;
|
|
1545
|
-
});
|
|
1546
|
+
return fn(text, type);
|
|
1546
1547
|
};
|
|
1547
1548
|
})(options.minifyCSS);
|
|
1548
1549
|
}
|