html-minifier-next 6.1.4 → 6.2.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 +2 -2
- package/cli.js +36 -10
- package/dist/htmlminifier.cjs +16 -3
- package/dist/types/htmlparser.d.ts.map +1 -1
- package/dist/types/lib/option-definitions.d.ts +1 -0
- package/package.json +1 -1
- package/src/htmlparser.js +16 -3
- package/src/lib/option-definitions.js +1 -0
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` |
|
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
|
@@ -611,10 +611,23 @@ class HTMLParser {
|
|
|
611
611
|
continue;
|
|
612
612
|
}
|
|
613
613
|
}
|
|
614
|
-
// Note: Unquoted attribute values are intentionally not handled here
|
|
614
|
+
// Note: Unquoted attribute values are intentionally not handled here
|
|
615
615
|
// Per HTML spec, unquoted values cannot contain spaces or special chars,
|
|
616
|
-
// making a 20 KB+ unquoted value practically impossible
|
|
617
|
-
// it’s malformed HTML and using the truncated regex match is acceptable
|
|
616
|
+
// making a 20 KB+ unquoted value practically impossible; if encountered,
|
|
617
|
+
// it’s malformed HTML and using the truncated regex match is acceptable
|
|
618
|
+
}
|
|
619
|
+
} else {
|
|
620
|
+
// If attr has no value assign but `=` follows in `fullHtml`,
|
|
621
|
+
// the value would be cut off—reset to trigger manual extraction below
|
|
622
|
+
const numCustomParts = handler.customAttrSurround
|
|
623
|
+
? handler.customAttrSurround.length * NCP
|
|
624
|
+
: 0;
|
|
625
|
+
const baseIndex = 1 + numCustomParts;
|
|
626
|
+
if (attr[baseIndex + 1] === undefined) {
|
|
627
|
+
const posAfterName = currentPos + attrEnd;
|
|
628
|
+
if (/^\s*=/.test(fullHtml.slice(posAfterName, posAfterName + 50))) {
|
|
629
|
+
attr = null;
|
|
630
|
+
}
|
|
618
631
|
}
|
|
619
632
|
}
|
|
620
633
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"htmlparser.d.ts","sourceRoot":"","sources":["../../src/htmlparser.js"],"names":[],"mappings":"AAqDA,4BAAkE;AA0GlE;IACE,qCAGC;IAFC,UAAgB;IAChB,aAAsB;IAGxB,
|
|
1
|
+
{"version":3,"file":"htmlparser.d.ts","sourceRoot":"","sources":["../../src/htmlparser.js"],"names":[],"mappings":"AAqDA,4BAAkE;AA0GlE;IACE,qCAGC;IAFC,UAAgB;IAChB,aAAsB;IAGxB,uBAgnBC;CACF"}
|
package/package.json
CHANGED
package/src/htmlparser.js
CHANGED
|
@@ -510,10 +510,23 @@ export class HTMLParser {
|
|
|
510
510
|
continue;
|
|
511
511
|
}
|
|
512
512
|
}
|
|
513
|
-
// Note: Unquoted attribute values are intentionally not handled here
|
|
513
|
+
// Note: Unquoted attribute values are intentionally not handled here
|
|
514
514
|
// Per HTML spec, unquoted values cannot contain spaces or special chars,
|
|
515
|
-
// making a 20 KB+ unquoted value practically impossible
|
|
516
|
-
// it’s malformed HTML and using the truncated regex match is acceptable
|
|
515
|
+
// making a 20 KB+ unquoted value practically impossible; if encountered,
|
|
516
|
+
// it’s malformed HTML and using the truncated regex match is acceptable
|
|
517
|
+
}
|
|
518
|
+
} else {
|
|
519
|
+
// If attr has no value assign but `=` follows in `fullHtml`,
|
|
520
|
+
// the value would be cut off—reset to trigger manual extraction below
|
|
521
|
+
const numCustomParts = handler.customAttrSurround
|
|
522
|
+
? handler.customAttrSurround.length * NCP
|
|
523
|
+
: 0;
|
|
524
|
+
const baseIndex = 1 + numCustomParts;
|
|
525
|
+
if (attr[baseIndex + 1] === undefined) {
|
|
526
|
+
const posAfterName = currentPos + attrEnd;
|
|
527
|
+
if (/^\s*=/.test(fullHtml.slice(posAfterName, posAfterName + 50))) {
|
|
528
|
+
attr = null;
|
|
529
|
+
}
|
|
517
530
|
}
|
|
518
531
|
}
|
|
519
532
|
}
|