html-minifier-next 6.1.5 → 6.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/cli.js +36 -10
- package/dist/htmlminifier.cjs +12 -6
- package/dist/types/lib/attributes.d.ts.map +1 -1
- package/dist/types/lib/option-definitions.d.ts +1 -0
- package/package.json +4 -4
- package/src/lib/attributes.js +8 -2
- package/src/lib/option-definitions.js +1 -0
- package/src/lib/whitespace.js +4 -4
package/README.md
CHANGED
|
@@ -129,7 +129,7 @@ html-minifier-next --preset conservative --remove-empty-attributes input.html
|
|
|
129
129
|
|
|
130
130
|
Most of the options are disabled by default. Experiment and find what works best for you and your project.
|
|
131
131
|
|
|
132
|
-
Options can be used in config files (camelCase) or via CLI flags (kebab-case with `--` prefix).
|
|
132
|
+
Options can be used in config files (camelCase) or via CLI flags (kebab-case with `--` prefix). Boolean options generally support both `--option-name` to enable and `--no-option-name` to disable, so you can override a preset or config file from the command line. (Exception: Options whose name already starts with `no-`, such as `noNewlinesBeforeTagClose`, only expose the `--no-…` CLI flag.)
|
|
133
133
|
|
|
134
134
|
| Option (config/CLI) | Description | Default |
|
|
135
135
|
| --- | --- | --- |
|
|
@@ -142,7 +142,7 @@ Options can be used in config files (camelCase) or via CLI flags (kebab-case wit
|
|
|
142
142
|
| `collapseInlineTagWhitespace`<br>`--collapse-inline-tag-whitespace` | Collapse whitespace more aggressively between inline elements—use with `collapseWhitespace: true` | `false` |
|
|
143
143
|
| `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` |
|
|
144
144
|
| `conservativeCollapse`<br>`--conservative-collapse` | Always collapse to one space (never remove it entirely)—use with `collapseWhitespace: true` | `false` |
|
|
145
|
-
| `continueOnMinifyError`<br>`--no-continue-on-minify-error` | Continue on minification errors; when `false`, minification errors throw and abort processing | `true` |
|
|
145
|
+
| `continueOnMinifyError`<br>`--continue-on-minify-error`<br>`--no-continue-on-minify-error` | Continue on minification errors; when `false`, minification errors throw and abort processing | `true` |
|
|
146
146
|
| `continueOnParseError`<br>`--continue-on-parse-error` | [Handle parse errors](https://html.spec.whatwg.org/multipage/parsing.html#parse-errors) instead of aborting | `false` |
|
|
147
147
|
| `customAttrAssign`<br>`--custom-attr-assign` | Array of regexes that allow to support custom attribute assign expressions (e.g., `<div flex?="{{mode != cover}}"></div>`) | `[]` |
|
|
148
148
|
| `customAttrCollapse`<br>`--custom-attr-collapse` | Regex that specifies custom attribute to strip newlines from (e.g., `/ng-class/`) | `undefined` |
|
|
@@ -603,7 +603,7 @@ Parameters:
|
|
|
603
603
|
|
|
604
604
|
## Acknowledgements
|
|
605
605
|
|
|
606
|
-
With many thanks to
|
|
606
|
+
With many thanks to the previous authors of and contributors to HTML Minifier, especially [Juriy “kangax” Zaytsev](https://github.com/kangax), and to everyone who helped make this new edition better, particularly [Daniel Ruf](https://github.com/DanielRuf), [Jonas Geiler](https://github.com/jonasgeiler), and [Chris Morgan](https://github.com/chris-morgan)!
|
|
607
607
|
|
|
608
608
|
***
|
|
609
609
|
|
package/cli.js
CHANGED
|
@@ -34,12 +34,22 @@ import { pathToFileURL } from 'url';
|
|
|
34
34
|
import os from 'os';
|
|
35
35
|
import readline from 'readline';
|
|
36
36
|
import { createRequire } from 'module';
|
|
37
|
-
import { Command } from 'commander';
|
|
37
|
+
import { Command, Option } from 'commander';
|
|
38
38
|
|
|
39
39
|
// Simple case conversion for CLI option names (ASCII-only, no Unicode needed)
|
|
40
40
|
const paramCase = (str) => str.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
|
|
41
41
|
const camelCase = (str) => paramCase(str).replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
42
42
|
|
|
43
|
+
// Commander derives its internal option key by applying paramCase then camelCase to the flag name,
|
|
44
|
+
// stripping a leading `no-` first for negated flags (e.g., `--no-foo-bar` → `fooBar`);
|
|
45
|
+
// because option definition keys may differ from the result of that round-trip (e.g.,
|
|
46
|
+
// `minifyURLs` → Commander key `minifyUrls`, `noNewlinesBeforeTagClose` → `newlinesBeforeTagClose`),
|
|
47
|
+
// `commanderOptionKey` uses the same paramCase + camelCase path to compute the key Commander will use
|
|
48
|
+
const commanderOptionKey = (key) => {
|
|
49
|
+
const pc = paramCase(key);
|
|
50
|
+
return pc.startsWith('no-') ? camelCase(pc.slice(3)) : camelCase(pc);
|
|
51
|
+
};
|
|
52
|
+
|
|
43
53
|
// Lazy-load HMN to reduce CLI cold-start overhead
|
|
44
54
|
import { getPreset, getPresetNames } from './src/presets.js';
|
|
45
55
|
import { parseRegExp } from './src/lib/utils.js';
|
|
@@ -140,20 +150,31 @@ const typeParsers = {
|
|
|
140
150
|
// Configure command-line flags from shared option definitions
|
|
141
151
|
const mainOptionKeys = Object.keys(optionDefinitions);
|
|
142
152
|
mainOptionKeys.forEach(function (key) {
|
|
143
|
-
const { description, type } = optionDefinitions[key];
|
|
153
|
+
const { description, descriptionAffirmative, type } = optionDefinitions[key];
|
|
154
|
+
const flag = paramCase(key);
|
|
144
155
|
if (type === 'invertedBoolean') {
|
|
145
|
-
|
|
156
|
+
// The positive form (to re-enable after a preset/config disables it) is hidden from
|
|
157
|
+
// help—the footer note covers the convention; the negative form is the primary use case
|
|
158
|
+
program.addOption(new Option('--' + flag, descriptionAffirmative ?? 'Enable --' + flag).hideHelp());
|
|
159
|
+
program.option('--no-' + flag, description);
|
|
146
160
|
} else if (type === 'boolean') {
|
|
147
|
-
program.option('--' +
|
|
161
|
+
program.option('--' + flag, description);
|
|
162
|
+
// The negation form is hidden from help—the footer note covers the convention;
|
|
163
|
+
// skip options whose flag already starts with `no-` (currently only
|
|
164
|
+
// `noNewlinesBeforeTagClose`), as `--no-no-X` is not usable
|
|
165
|
+
if (!flag.startsWith('no-')) {
|
|
166
|
+
program.addOption(new Option('--no-' + flag, 'Disable --' + flag).hideHelp());
|
|
167
|
+
}
|
|
148
168
|
} else {
|
|
149
|
-
const
|
|
169
|
+
const cliFlag = '--' + flag + (type === 'json' ? ' [value]' : ' <value>');
|
|
150
170
|
const parser = type === 'int' ? typeParsers.int(key) : typeParsers[type];
|
|
151
|
-
program.option(
|
|
171
|
+
program.option(cliFlag, description, parser);
|
|
152
172
|
}
|
|
153
173
|
});
|
|
154
174
|
program.option('-o --output <file>', 'Specify output file (reads from file arguments or STDIN; outputs to STDOUT if not specified)');
|
|
155
175
|
program.option('-v --verbose', 'Show detailed processing information');
|
|
156
176
|
program.option('-d --dry', 'Dry run: Process and report statistics without writing output');
|
|
177
|
+
program.addHelpText('after', '\nBoolean options support a `--no-<flag>` form to disable them, overriding a preset or config file (e.g., `--preset=comprehensive --no-collapse-whitespace`).');
|
|
157
178
|
|
|
158
179
|
// Lazy import wrapper for HMN
|
|
159
180
|
let minifyFnPromise;
|
|
@@ -398,9 +419,14 @@ program.helpOption('-h, --help', 'Display help for command');
|
|
|
398
419
|
|
|
399
420
|
// 3. Apply CLI options (overrides config and preset)
|
|
400
421
|
mainOptionKeys.forEach(function (key) {
|
|
401
|
-
const
|
|
402
|
-
|
|
403
|
-
|
|
422
|
+
const { type } = optionDefinitions[key];
|
|
423
|
+
const ck = commanderOptionKey(key);
|
|
424
|
+
if (program.getOptionValueSource(ck) === 'cli') {
|
|
425
|
+
const val = programOptions[ck];
|
|
426
|
+
// For boolean options whose param-case name starts with `no-`, Commander treats
|
|
427
|
+
// the flag as a negation and stores the inverted value under the stripped key;
|
|
428
|
+
// invert back so the option definition key gets the intended value
|
|
429
|
+
options[key] = (type === 'boolean' && paramCase(key).startsWith('no-')) ? !val : val;
|
|
404
430
|
}
|
|
405
431
|
});
|
|
406
432
|
|
|
@@ -413,7 +439,7 @@ program.helpOption('-h, --help', 'Display help for command');
|
|
|
413
439
|
console.error(`Using preset: ${presetName}`);
|
|
414
440
|
}
|
|
415
441
|
const activeOptions = Object.entries(minifierOptions)
|
|
416
|
-
.filter(([k]) => program.getOptionValueSource(
|
|
442
|
+
.filter(([k]) => program.getOptionValueSource(commanderOptionKey(k)) === 'cli')
|
|
417
443
|
.map(([k, v]) => (typeof v === 'boolean' ? (v ? k : `no-${k}`) : k));
|
|
418
444
|
if (activeOptions.length > 0) {
|
|
419
445
|
console.error('CLI options: ' + activeOptions.join(', '));
|
package/dist/htmlminifier.cjs
CHANGED
|
@@ -1429,15 +1429,15 @@ function collapseWhitespaceSmart(str, prevTag, nextTag, prevAttrs, nextAttrs, op
|
|
|
1429
1429
|
|
|
1430
1430
|
// Collapse/trim whitespace for given tag
|
|
1431
1431
|
|
|
1432
|
-
const
|
|
1433
|
-
const
|
|
1432
|
+
const noCollapseWhitespaceTags = new Set(['script', 'style', 'pre', 'textarea']);
|
|
1433
|
+
const noTrimWhitespaceTags = new Set(['pre', 'textarea']);
|
|
1434
1434
|
|
|
1435
1435
|
function canCollapseWhitespace(tag) {
|
|
1436
|
-
return !
|
|
1436
|
+
return !noCollapseWhitespaceTags.has(tag);
|
|
1437
1437
|
}
|
|
1438
1438
|
|
|
1439
1439
|
function canTrimWhitespace(tag) {
|
|
1440
|
-
return !
|
|
1440
|
+
return !noTrimWhitespaceTags.has(tag);
|
|
1441
1441
|
}
|
|
1442
1442
|
|
|
1443
1443
|
/**
|
|
@@ -2366,12 +2366,18 @@ function hasAttrName(name, attrs) {
|
|
|
2366
2366
|
|
|
2367
2367
|
// Cleaners
|
|
2368
2368
|
|
|
2369
|
+
const collapseAttributeWhitespaceExempt = new Set(['pattern', 'placeholder', 'title']);
|
|
2370
|
+
// `value` whitespace matters only on form-submission and machine-readable elements
|
|
2371
|
+
const valueWhitespaceExemptElements = new Set(['button', 'data', 'input', 'option', 'param']);
|
|
2372
|
+
|
|
2369
2373
|
// Returns the cleaned attribute value directly (sync) or as a Promise (async);
|
|
2370
2374
|
// callers must handle both cases—use `isThenable()` to distinguish
|
|
2371
2375
|
function cleanAttributeValue(tag, attrName, attrValue, options, attrs, minifyHTMLSelf) {
|
|
2376
|
+
const isEventAttr = isEventAttribute(attrName, options);
|
|
2377
|
+
|
|
2372
2378
|
// Apply early whitespace normalization if enabled
|
|
2373
2379
|
// Preserves special spaces (no-break space, hair space, etc.) for consistency with `collapseWhitespace`
|
|
2374
|
-
if (options.collapseAttributeWhitespace) {
|
|
2380
|
+
if (options.collapseAttributeWhitespace && !collapseAttributeWhitespaceExempt.has(attrName) && !(attrName === 'value' && valueWhitespaceExemptElements.has(tag)) && !isEventAttr) {
|
|
2375
2381
|
// Fast path: Only process if whitespace exists (avoids regex overhead on clean values)
|
|
2376
2382
|
if (RE_ATTR_WS_CHECK.test(attrValue)) {
|
|
2377
2383
|
// Two-pass approach (faster than single-pass with callback)
|
|
@@ -2381,7 +2387,7 @@ function cleanAttributeValue(tag, attrName, attrValue, options, attrs, minifyHTM
|
|
|
2381
2387
|
}
|
|
2382
2388
|
}
|
|
2383
2389
|
|
|
2384
|
-
if (
|
|
2390
|
+
if (isEventAttr) {
|
|
2385
2391
|
attrValue = trimWhitespace(attrValue).replace(/^javascript:\s*/i, '');
|
|
2386
2392
|
const result = options.minifyJS(attrValue, true);
|
|
2387
2393
|
if (isThenable(result)) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attributes.d.ts","sourceRoot":"","sources":["../../../src/lib/attributes.js"],"names":[],"mappings":"AAmCA,mEAOC;AAED,uEAWC;AAED,8DAGC;AAED,4EAOC;AAgCD,mGAuCC;AAED,mEAGC;AAED,qEAGC;AAED,kEAWC;AAED,sEAGC;AAED,8DAWC;AAED,2EAIC;AAmBD,qEAGC;AAgBD,wEAGC;AAED,sEAUC;AAED,2EAEC;AAED,2DAEC;AAED,8DAUC;AAED,uEAUC;AAED,oGASC;AAED,4DAOC;
|
|
1
|
+
{"version":3,"file":"attributes.d.ts","sourceRoot":"","sources":["../../../src/lib/attributes.js"],"names":[],"mappings":"AAmCA,mEAOC;AAED,uEAWC;AAED,8DAGC;AAED,4EAOC;AAgCD,mGAuCC;AAED,mEAGC;AAED,qEAGC;AAED,kEAWC;AAED,sEAGC;AAED,8DAWC;AAED,2EAIC;AAmBD,qEAGC;AAgBD,wEAGC;AAED,sEAUC;AAED,2EAEC;AAED,2DAEC;AAED,8DAUC;AAED,uEAUC;AAED,oGASC;AAED,4DAOC;AAUD,iIA4KC;AAwBD,mGAYC;AA0CD,6GAuHC;AAxlBD;;;;;;;GAOG;AACH,mEAHW,OAAO,SAuBjB"}
|
package/package.json
CHANGED
|
@@ -18,11 +18,11 @@
|
|
|
18
18
|
"@rollup/plugin-commonjs": "^29.0.2",
|
|
19
19
|
"@rollup/plugin-json": "^6.1.0",
|
|
20
20
|
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
21
|
-
"@swc/core": "^1.15.
|
|
21
|
+
"@swc/core": "^1.15.30",
|
|
22
22
|
"eslint": "^10.2.0",
|
|
23
|
-
"rollup": "^4.60.
|
|
23
|
+
"rollup": "^4.60.2",
|
|
24
24
|
"rollup-plugin-polyfill-node": "^0.13.0",
|
|
25
|
-
"typescript": "^6.0.
|
|
25
|
+
"typescript": "^6.0.3",
|
|
26
26
|
"vite": "^8.0.8"
|
|
27
27
|
},
|
|
28
28
|
"exports": {
|
|
@@ -96,5 +96,5 @@
|
|
|
96
96
|
},
|
|
97
97
|
"type": "module",
|
|
98
98
|
"types": "./dist/types/htmlminifier.d.ts",
|
|
99
|
-
"version": "6.1
|
|
99
|
+
"version": "6.2.1"
|
|
100
100
|
}
|
package/src/lib/attributes.js
CHANGED
|
@@ -294,12 +294,18 @@ function hasAttrName(name, attrs) {
|
|
|
294
294
|
|
|
295
295
|
// Cleaners
|
|
296
296
|
|
|
297
|
+
const collapseAttributeWhitespaceExempt = new Set(['pattern', 'placeholder', 'title']);
|
|
298
|
+
// `value` whitespace matters only on form-submission and machine-readable elements
|
|
299
|
+
const valueWhitespaceExemptElements = new Set(['button', 'data', 'input', 'option', 'param']);
|
|
300
|
+
|
|
297
301
|
// Returns the cleaned attribute value directly (sync) or as a Promise (async);
|
|
298
302
|
// callers must handle both cases—use `isThenable()` to distinguish
|
|
299
303
|
function cleanAttributeValue(tag, attrName, attrValue, options, attrs, minifyHTMLSelf) {
|
|
304
|
+
const isEventAttr = isEventAttribute(attrName, options);
|
|
305
|
+
|
|
300
306
|
// Apply early whitespace normalization if enabled
|
|
301
307
|
// Preserves special spaces (no-break space, hair space, etc.) for consistency with `collapseWhitespace`
|
|
302
|
-
if (options.collapseAttributeWhitespace) {
|
|
308
|
+
if (options.collapseAttributeWhitespace && !collapseAttributeWhitespaceExempt.has(attrName) && !(attrName === 'value' && valueWhitespaceExemptElements.has(tag)) && !isEventAttr) {
|
|
303
309
|
// Fast path: Only process if whitespace exists (avoids regex overhead on clean values)
|
|
304
310
|
if (RE_ATTR_WS_CHECK.test(attrValue)) {
|
|
305
311
|
// Two-pass approach (faster than single-pass with callback)
|
|
@@ -309,7 +315,7 @@ function cleanAttributeValue(tag, attrName, attrValue, options, attrs, minifyHTM
|
|
|
309
315
|
}
|
|
310
316
|
}
|
|
311
317
|
|
|
312
|
-
if (
|
|
318
|
+
if (isEventAttr) {
|
|
313
319
|
attrValue = trimWhitespace(attrValue).replace(/^javascript:\s*/i, '');
|
|
314
320
|
const result = options.minifyJS(attrValue, true);
|
|
315
321
|
if (isThenable(result)) {
|
package/src/lib/whitespace.js
CHANGED
|
@@ -211,15 +211,15 @@ function collapseWhitespaceSmart(str, prevTag, nextTag, prevAttrs, nextAttrs, op
|
|
|
211
211
|
|
|
212
212
|
// Collapse/trim whitespace for given tag
|
|
213
213
|
|
|
214
|
-
const
|
|
215
|
-
const
|
|
214
|
+
const noCollapseWhitespaceTags = new Set(['script', 'style', 'pre', 'textarea']);
|
|
215
|
+
const noTrimWhitespaceTags = new Set(['pre', 'textarea']);
|
|
216
216
|
|
|
217
217
|
function canCollapseWhitespace(tag) {
|
|
218
|
-
return !
|
|
218
|
+
return !noCollapseWhitespaceTags.has(tag);
|
|
219
219
|
}
|
|
220
220
|
|
|
221
221
|
function canTrimWhitespace(tag) {
|
|
222
|
-
return !
|
|
222
|
+
return !noTrimWhitespaceTags.has(tag);
|
|
223
223
|
}
|
|
224
224
|
|
|
225
225
|
// Exports
|