html-minifier-next 8.2.2 → 8.3.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 CHANGED
@@ -145,12 +145,12 @@ Options can be used in config files (camelCase) or via CLI flags (kebab-case wit
145
145
  | `customAttrAssign`<br>`--custom-attr-assign` | Array of regexes that allow to support custom attribute assign expressions (e.g., `<div flex?="{{mode != cover}}"></div>`) | `[]` |
146
146
  | `customAttrCollapse`<br>`--custom-attr-collapse` | Regex that specifies custom attribute to strip newlines from (e.g., `/ng-class/`) | `undefined` |
147
147
  | `customAttrSurround`<br>`--custom-attr-surround` | Array of regexes that allow to support custom attribute surround expressions (e.g., `<input {{#if value}}checked="checked"{{/if}}>`) | `[]` |
148
- | `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,}$/ ]` |
148
+ | `customEventAttributes`<br>`--custom-event-attributes` | Array of regexes that allow to support custom event attributes (e.g., `ng-click`)—use with `minifyJS` | `[ /^on[a-z]{3,}$/ ]` |
149
149
  | `decodeEntities`<br>`--decode-entities` | Use direct Unicode characters whenever possible | `false` |
150
150
  | `ignoreCustomComments`<br>`--ignore-custom-comments` | Array of regexes that allow to ignore matching comments | `[ /^!/, /^\s*#/ ]` |
151
151
  | `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
152
  | `includeAutoGeneratedTags`<br>`--include-auto-generated-tags` | Insert elements generated by HTML parser | `false` |
153
- | `inlineCustomElements`<br>`--inline-custom-elements` | Array of names of custom elements which are inline, for whitespace handling | `[]` |
153
+ | `inlineCustomElements`<br>`--inline-custom-elements` | Array of names of custom elements which are inline, for whitespace handling—use with `collapseWhitespace` | `[]` |
154
154
  | `keepClosingSlash`<br>`--keep-closing-slash` | Keep the trailing slash on void elements and other start tags that carry one, and read it as closing the element | `false` |
155
155
  | `maxInputLength`<br>`--max-input-length` | Maximum input length to prevent ReDoS attacks (disabled by default) | `undefined` |
156
156
  | `maxLineLength`<br>`--max-line-length` | Specify a maximum line length; compressed output will be split by newlines at valid HTML split-points | `undefined` |
@@ -159,7 +159,7 @@ Options can be used in config files (camelCase) or via CLI flags (kebab-case wit
159
159
  | `minifyJS`<br>`--minify-js` | Minify JavaScript in `script` elements and event attributes (uses [Terser](https://terser.org/) or [SWC](https://swc.rs/)) | `false` (could be `true`, `Object`, `Function(text, inline)`) |
160
160
  | `minifySVG`<br>`--minify-svg` | Minify SVG elements (uses [SVGO](https://svgo.dev/)) | `false` (could be `true`, `Object`) |
161
161
  | `minifyURLs`<br>`--minify-urls` | Minify URLs in various attributes | `false` (could be `true`, `String`, `Object`, `Function(text)`) |
162
- | `noNewlinesBeforeTagClose`<br>`--no-newlines-before-tag-close` | Never add a newline before a tag that closes an element | `false` |
162
+ | `noNewlinesBeforeTagClose`<br>`--no-newlines-before-tag-close` | Never add a newline before a tag that closes an element—use with `maxLineLength` | `false` |
163
163
  | `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
164
  | `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`](#combining-whitespace-options) | `false` |
165
165
  | `preventAttributesEscaping`<br>`--prevent-attributes-escaping` | Prevents the escaping of the values of attributes | `false` |
@@ -170,7 +170,7 @@ Options can be used in config files (camelCase) or via CLI flags (kebab-case wit
170
170
  | `removeDefaultTypeAttributes`<br>`--remove-default-type-attributes` | Remove default `type` attributes from `style`/`link` (e.g., `type="text/css"`) and `script` (e.g., `type="text/javascript"`) elements; other `type` attribute values are left intact | `false` |
171
171
  | `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)`) |
172
172
  | `removeEmptyElements`<br>`--remove-empty-elements` | [Remove all elements with empty contents](https://perfectionkills.com/experimenting-with-html-minifier/#remove_empty_elements) | `false` |
173
- | `removeEmptyElementsExcept`<br>`--remove-empty-elements-except` | Array of elements to preserve when `removeEmptyElements` is enabled; accepts simple tag names (e.g., `["td"]`) or HTML-like markup with attributes (e.g., `["<span aria-hidden='true'>"]`); supports double quotes, single quotes, and unquoted attribute values | `[]` |
173
+ | `removeEmptyElementsExcept`<br>`--remove-empty-elements-except` | Array of elements to preserve—use with `removeEmptyElements`; accepts simple tag names (e.g., `["td"]`) or HTML-like markup with attributes (e.g., `["<span aria-hidden='true'>"]`); supports double quotes, single quotes, and unquoted attribute values | `[]` |
174
174
  | `removeOptionalTags`<br>`--remove-optional-tags` | [Remove optional tags](https://perfectionkills.com/experimenting-with-html-minifier/#remove_optional_tags) | `false` |
175
175
  | `removeRedundantAttributes`<br>`--remove-redundant-attributes` | [Remove attributes when value matches default](https://meiert.com/blog/optional-html/#toc-attribute-values) | `false` |
176
176
  | `removeTagWhitespace`<br>`--remove-tag-whitespace` | Remove space between attributes whenever possible; **note that this will result in invalid HTML** | `false` |
@@ -178,7 +178,7 @@ Options can be used in config files (camelCase) or via CLI flags (kebab-case wit
178
178
  | `sortAttributes`<br>`--sort-attributes` | [Sort attributes by frequency](#sorting-attributes-and-style-classes) | `false` |
179
179
  | `sortClassNames`<br>`--sort-class-names` | [Sort style classes by frequency](#sorting-attributes-and-style-classes) | `false` |
180
180
  | `strictCustomFragments`<br>`--strict-custom-fragments` | [Reject `ignoreCustomFragments` patterns that risk catastrophic backtracking](#redos-protection) (rather than warning about them) | `false` |
181
- | `trimCustomFragments`<br>`--trim-custom-fragments` | Trim whitespace around custom fragments (`ignoreCustomFragments`) | `false` |
181
+ | `trimCustomFragments`<br>`--trim-custom-fragments` | Trim whitespace around custom fragments (`ignoreCustomFragments`)—use with `collapseWhitespace` | `false` |
182
182
  | `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
183
 
184
184
  ### API-only options
@@ -191,6 +191,28 @@ A few options take functions and are therefore only available programmatically,
191
191
  | `canTrimWhitespace` | `Function(tag, attrs, defaultFn)` that determines whether leading and trailing whitespace around an element may be trimmed | Built-in handling |
192
192
  | `log` | `Function(message)` called with warnings and errors, including minification errors swallowed by `continueOnMinifyError` (e.g., pass `console.error` to surface them); the CLI wires this up under `--verbose` and `--dry` | No-op (errors are silent) |
193
193
 
194
+ ### Options that rely on another option
195
+
196
+ Some options modify what another option does, and do nothing when that other option is off. Setting one on its own is reported through [the `log` hook](#api-only-options) (and, in the CLI, on STDERR), once per message per run:
197
+
198
+ ```text
199
+ HTML Minifier Next: Ignoring `conservativeCollapse`—use with `collapseWhitespace` (`--collapse-whitespace`)
200
+ ```
201
+
202
+ | Option | Needs |
203
+ | --- | --- |
204
+ | `collapseInlineTagWhitespace` | `collapseWhitespace` |
205
+ | `conservativeCollapse` | `collapseWhitespace` |
206
+ | `customEventAttributes` | `minifyJS` |
207
+ | `inlineCustomElements` | `collapseWhitespace` |
208
+ | `noNewlinesBeforeTagClose` | `maxLineLength` |
209
+ | `preserveLineBreaks` | `collapseWhitespace` |
210
+ | `removeEmptyElementsExcept` | `removeEmptyElements` |
211
+ | `removeUnusedCSS` | `minifyCSS`, and not [a function of your own](#unused-css-removal) |
212
+ | `trimCustomFragments` | `collapseWhitespace` |
213
+
214
+ Passing the option `false`, or an empty array, asks for nothing and is not reported. `cacheCSS`, `cacheJS`, and `cacheSVG` are not listed: They size a cache rather than transform markup, and don’t change output.
215
+
194
216
  ### Combining whitespace options
195
217
 
196
218
  `collapseInlineTagWhitespace`, `conservativeCollapse`, and `preserveLineBreaks` are modifiers: They do nothing on their own, and only take effect when `collapseWhitespace` is enabled.
@@ -673,7 +695,7 @@ For CLI usage, using a config file is strongly recommended to avoid complex shel
673
695
 
674
696
  **[Web demo:](https://j9t.github.io/html-minifier-next/)**
675
697
 
676
- ```
698
+ ```text
677
699
  \{%[\s\S]{0,1000}?%\} \{\{[\s\S]{0,500}?\}\}
678
700
  ```
679
701
 
package/cli.js CHANGED
@@ -35,9 +35,6 @@ import os from 'os';
35
35
  import readline from 'readline';
36
36
  import { Command, Option } from 'commander';
37
37
 
38
- // Simple case conversion for CLI option names (ASCII-only, no Unicode needed)
39
- /** @param {string} str */
40
- const paramCase = (str) => str.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
41
38
  /** @param {string} str */
42
39
  const camelCase = (str) => paramCase(str).replace(/-([a-z])/g, (_, c) => c.toUpperCase());
43
40
 
@@ -54,7 +51,7 @@ const commanderOptionKey = (key) => {
54
51
 
55
52
  // Lazy-load HMN to reduce CLI cold-start overhead
56
53
  import { getPreset, getPresetNames } from './src/presets.js';
57
- import { parseRegExp } from './src/lib/utils.js';
54
+ import { paramCase, parseRegExp } from './src/lib/utils.js';
58
55
  import { optionDefinitions } from './src/lib/option-definitions.js';
59
56
 
60
57
  const pkg = JSON.parse(fs.readFileSync(new URL('./package.json', import.meta.url), 'utf8'));
@@ -71,6 +71,32 @@ export type ProcessedOptions = Omit<MinifierOptions, 'preset' | 'canCollapseWhit
71
71
  */
72
72
  /** @param {ProcessedOptions} options */
73
73
  declare function shouldMinifyInnerHTML(options: ProcessedOptions): boolean;
74
+ export type OptionDependency = OptionDependency[];
75
+ /**
76
+ * Options that do nothing on their own, and the option each one needs to take effect
77
+ *
78
+ * Cache sizes are deliberately absent: They never change output, so they are
79
+ * configuration for a subsystem rather than a transform that silently fails.
80
+ *
81
+ * @typedef {object} OptionDependency
82
+ * @property {string} option
83
+ * @property {string} requires
84
+ * @property {(input: Record<string, any>) => string | false} [unusable] Clause saying why the prerequisite does not count even though it is set
85
+ * @property {boolean} [clear] Whether to null the option out, where leaving it set would cost work downstream
86
+ *
87
+ * @type {OptionDependency[]}
88
+ */
89
+ declare const optionDependencies: ({
90
+ option: string;
91
+ requires: string;
92
+ unusable?: never;
93
+ clear?: never;
94
+ } | {
95
+ option: string;
96
+ requires: string;
97
+ unusable: (/** @type {Record<string, any>} */ input: Record<string, any>) => "which a function of your own replaces" | false;
98
+ clear: boolean;
99
+ })[];
74
100
  /**
75
101
  * @param {MinifierOptions} inputOptions - User-provided options
76
102
  * @param {{getLightningCSS?: Function | undefined, getTerser?: Function | undefined, getSwc?: Function | undefined, getSvgo?: Function | undefined, cssMinifyCache?: LRU | undefined, jsMinifyCache?: LRU | undefined, svgMinifyCache?: LRU | undefined}} [deps] - Dependencies from htmlminifier.js
@@ -85,5 +111,5 @@ declare const processOptions: (inputOptions: MinifierOptions, { getLightningCSS,
85
111
  jsMinifyCache?: LRU | undefined;
86
112
  svgMinifyCache?: LRU | undefined;
87
113
  }) => ProcessedOptions;
88
- export { shouldMinifyInnerHTML, processOptions };
114
+ export { optionDependencies, shouldMinifyInnerHTML, processOptions };
89
115
  //# sourceMappingURL=options.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../../../src/lib/options.js"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../../../src/lib/options.js"],"names":[],"mappings":"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"}
@@ -41,6 +41,8 @@ declare function identity(value: string): string;
41
41
  declare function isThenable(value: unknown): value is PromiseLike<any>;
42
42
  /** @param {string} value */
43
43
  declare function lowercase(value: string): string;
44
+ /** @param {string} value */
45
+ declare function paramCase(value: string): string;
44
46
  /**
45
47
  * Asynchronously replace matches in a string.
46
48
  * @param {string} str - Input string
@@ -93,5 +95,5 @@ declare function describeQuantifierRisk(pattern: RegExp | string): string | null
93
95
  * @returns {number} Index of the closing `>`, or -1 if not found
94
96
  */
95
97
  declare function findTagEnd(html: string, pos: number, limit?: number): number;
96
- export { stableStringify, findTagEnd, LRU, MAX_CACHE_ENTRY_SIZE, hashContent, uniqueId, identity, isThenable, lowercase, replaceAsync, parseRegExp, embedSource, lostFlag, describeQuantifierRisk };
98
+ export { stableStringify, findTagEnd, LRU, MAX_CACHE_ENTRY_SIZE, hashContent, uniqueId, identity, isThenable, lowercase, paramCase, replaceAsync, parseRegExp, embedSource, lostFlag, describeQuantifierRisk };
97
99
  //# sourceMappingURL=utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/lib/utils.js"],"names":[],"mappings":"AAEA;;;GAGG;AACH,iBAAS,eAAe,CAAC,GAAG,EAHjB,OAGiB,GAFf,MAAM,CAYlB;AAID,cAAM,GAAG;IAEA,KAAK;IACL,IAAI;IACJ,IAAI;IACT,mCAAmC;IAC9B,GAAG,EADG,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC;IAJjC,YAAY,KAAK,SAAM,EAMtB;IACD,0BAA0B;IAC1B,GAAG,CAAC,GAAG,EADK,MACL,WAUN;IACD;;;OAGG;IACH,GAAG,CAAC,GAAG,EAHI,MAGJ,EAAE,KAAK,EAFH,OAEG,QAOb;IACD,0BAA0B;IAC1B,MAAM,CAAC,GAAG,EADE,MACF,QAA2B;IACrC,6EAA6E;IAC7E,KAAK,IADS;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAGxE;CACF;AAKD,QAAA,MAAM,oBAAoB,QAAc,CAAC;AAIzC,0BAA0B;AAC1B,iBAAS,WAAW,CAAC,GAAG,EADZ,MACY,UAOvB;AAID,4BAA4B;AAC5B,iBAAS,QAAQ,CAAC,KAAK,EADX,MACW,UAMtB;AAID,4BAA4B;AAC5B,iBAAS,QAAQ,CAAC,KAAK,EADX,MACW,UAEtB;AAED;;;GAGG;AACH,iBAAS,UAAU,CAAC,KAAK,EAHd,OAGc,GAFZ,KAAK,IAAI,WAAW,CAAC,GAAG,CAAC,CAIrC;AAED,4BAA4B;AAC5B,iBAAS,SAAS,CAAC,KAAK,EADZ,MACY,UAEvB;AAID;;;;;;GAMG;AACH,iBAAe,YAAY,CAAC,GAAG,EALpB,MAKoB,EAAE,KAAK,EAJ3B,MAI2B,EAAE,OAAO,UAAA,GAFlC,OAAO,CAAC,MAAM,CAAC,CAe3B;AAID,qCAAqC;AACrC,iBAAS,WAAW,CAAC,KAAK,EADd,MAAM,GAAG,MACK,sBAUzB;AAyYD;;;;;;;;;GASG;AACH,iBAAS,QAAQ,CAAC,OAAO,EAJd,MAIc,GAHZ,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,IAAI,CAqDlC;AAED;;;;;;;;;GASG;AACH,iBAAS,WAAW,CAAC,OAAO,EAHjB,MAGiB,GAFf,MAAM,CAOlB;AAGE,YAA6D,MAAM,GAAzD;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,IAAI,CAAA;CAAC,CAE7D;AAqKD;;;;;GAKG;AACH,iBAAS,sBAAsB,CAAC,OAAO,EAL5B,MAAM,GAAG,MAKmB,GAH1B,MAAM,GAAG,IAAI,CAqBzB;AAED;;;;;;;GAOG;AACH,iBAAS,UAAU,CAAC,IAAI,EALb,MAKa,EAAE,GAAG,EAJlB,MAIkB,EAAE,KAAK,AAHjC,CACA,EADQ,MAGuC,GAFrC,MAAM,CAelB;AAID,OAAO,EACL,eAAe,EACf,UAAU,EACV,GAAG,EACH,oBAAoB,EACpB,WAAW,EACX,QAAQ,EACR,QAAQ,EACR,UAAU,EACV,SAAS,EACT,YAAY,EACZ,WAAW,EACX,WAAW,EACX,QAAQ,EACR,sBAAsB,EACvB,CAAC"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/lib/utils.js"],"names":[],"mappings":"AAEA;;;GAGG;AACH,iBAAS,eAAe,CAAC,GAAG,EAHjB,OAGiB,GAFf,MAAM,CAYlB;AAID,cAAM,GAAG;IAEA,KAAK;IACL,IAAI;IACJ,IAAI;IACT,mCAAmC;IAC9B,GAAG,EADG,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC;IAJjC,YAAY,KAAK,SAAM,EAMtB;IACD,0BAA0B;IAC1B,GAAG,CAAC,GAAG,EADK,MACL,WAUN;IACD;;;OAGG;IACH,GAAG,CAAC,GAAG,EAHI,MAGJ,EAAE,KAAK,EAFH,OAEG,QAOb;IACD,0BAA0B;IAC1B,MAAM,CAAC,GAAG,EADE,MACF,QAA2B;IACrC,6EAA6E;IAC7E,KAAK,IADS;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAGxE;CACF;AAKD,QAAA,MAAM,oBAAoB,QAAc,CAAC;AAIzC,0BAA0B;AAC1B,iBAAS,WAAW,CAAC,GAAG,EADZ,MACY,UAOvB;AAID,4BAA4B;AAC5B,iBAAS,QAAQ,CAAC,KAAK,EADX,MACW,UAMtB;AAID,4BAA4B;AAC5B,iBAAS,QAAQ,CAAC,KAAK,EADX,MACW,UAEtB;AAED;;;GAGG;AACH,iBAAS,UAAU,CAAC,KAAK,EAHd,OAGc,GAFZ,KAAK,IAAI,WAAW,CAAC,GAAG,CAAC,CAIrC;AAED,4BAA4B;AAC5B,iBAAS,SAAS,CAAC,KAAK,EADZ,MACY,UAEvB;AAGD,4BAA4B;AAC5B,iBAAS,SAAS,CAAC,KAAK,EADZ,MACY,UAEvB;AAID;;;;;;GAMG;AACH,iBAAe,YAAY,CAAC,GAAG,EALpB,MAKoB,EAAE,KAAK,EAJ3B,MAI2B,EAAE,OAAO,UAAA,GAFlC,OAAO,CAAC,MAAM,CAAC,CAe3B;AAID,qCAAqC;AACrC,iBAAS,WAAW,CAAC,KAAK,EADd,MAAM,GAAG,MACK,sBAUzB;AAyYD;;;;;;;;;GASG;AACH,iBAAS,QAAQ,CAAC,OAAO,EAJd,MAIc,GAHZ,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,IAAI,CAqDlC;AAED;;;;;;;;;GASG;AACH,iBAAS,WAAW,CAAC,OAAO,EAHjB,MAGiB,GAFf,MAAM,CAOlB;AAGE,YAA6D,MAAM,GAAzD;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,IAAI,CAAA;CAAC,CAE7D;AAqKD;;;;;GAKG;AACH,iBAAS,sBAAsB,CAAC,OAAO,EAL5B,MAAM,GAAG,MAKmB,GAH1B,MAAM,GAAG,IAAI,CAqBzB;AAED;;;;;;;GAOG;AACH,iBAAS,UAAU,CAAC,IAAI,EALb,MAKa,EAAE,GAAG,EAJlB,MAIkB,EAAE,KAAK,AAHjC,CACA,EADQ,MAGuC,GAFrC,MAAM,CAelB;AAID,OAAO,EACL,eAAe,EACf,UAAU,EACV,GAAG,EACH,oBAAoB,EACpB,WAAW,EACX,QAAQ,EACR,QAAQ,EACR,UAAU,EACV,SAAS,EACT,SAAS,EACT,YAAY,EACZ,WAAW,EACX,WAAW,EACX,QAAQ,EACR,sBAAsB,EACvB,CAAC"}
@@ -108,7 +108,7 @@
108
108
  }
109
109
  },
110
110
  "customEventAttributes": {
111
- "description": "Array of regexes that allow to support custom event attributes for minifyJS (e.g., `ng-click`)",
111
+ "description": "Array of regexes that allow to support custom event attributes (e.g., `ng-click`)—use with `--minify-js`",
112
112
  "type": [
113
113
  "string",
114
114
  "array"
@@ -146,7 +146,7 @@
146
146
  "type": "boolean"
147
147
  },
148
148
  "inlineCustomElements": {
149
- "description": "Array of names of custom elements which are inline, for whitespace handling",
149
+ "description": "Array of names of custom elements which are inline, for whitespace handling—use with `--collapse-whitespace`",
150
150
  "type": [
151
151
  "string",
152
152
  "array"
@@ -203,7 +203,7 @@
203
203
  ]
204
204
  },
205
205
  "noNewlinesBeforeTagClose": {
206
- "description": "Never add a newline before a tag that closes an element",
206
+ "description": "Never add a newline before a tag that closes an element—use with `--max-line-length`",
207
207
  "type": "boolean"
208
208
  },
209
209
  "partialMarkup": {
@@ -253,7 +253,7 @@
253
253
  "type": "boolean"
254
254
  },
255
255
  "removeEmptyElementsExcept": {
256
- "description": "Array of elements to preserve when `--remove-empty-elements` is enabled (e.g., `td`, `<span aria-hidden=\"true\">`)",
256
+ "description": "Array of elements to preserve (e.g., `td`, `<span aria-hidden=\"true\">`)—use with `--remove-empty-elements`",
257
257
  "type": [
258
258
  "string",
259
259
  "array"
@@ -290,7 +290,7 @@
290
290
  "type": "boolean"
291
291
  },
292
292
  "trimCustomFragments": {
293
- "description": "Trim whitespace around custom fragments (`--ignore-custom-fragments`)",
293
+ "description": "Trim whitespace around custom fragments (`--ignore-custom-fragments`)—use with `--collapse-whitespace`",
294
294
  "type": "boolean"
295
295
  },
296
296
  "strictCustomFragments": {
package/package.json CHANGED
@@ -95,5 +95,5 @@
95
95
  },
96
96
  "type": "module",
97
97
  "types": "dist/types/htmlminifier.d.ts",
98
- "version": "8.2.2"
98
+ "version": "8.3.0"
99
99
  }
@@ -67,7 +67,7 @@ const optionDefinitions = {
67
67
  type: 'regexpArray'
68
68
  },
69
69
  customEventAttributes: {
70
- description: 'Array of regexes that allow to support custom event attributes for minifyJS (e.g., `ng-click`)',
70
+ description: 'Array of regexes that allow to support custom event attributes (e.g., `ng-click`)—use with `--minify-js`',
71
71
  type: 'regexpArray'
72
72
  },
73
73
  decodeEntities: {
@@ -87,7 +87,7 @@ const optionDefinitions = {
87
87
  type: 'boolean'
88
88
  },
89
89
  inlineCustomElements: {
90
- description: 'Array of names of custom elements which are inline, for whitespace handling',
90
+ description: 'Array of names of custom elements which are inline, for whitespace handling—use with `--collapse-whitespace`',
91
91
  type: 'jsonArray'
92
92
  },
93
93
  keepClosingSlash: {
@@ -123,7 +123,7 @@ const optionDefinitions = {
123
123
  type: 'json'
124
124
  },
125
125
  noNewlinesBeforeTagClose: {
126
- description: 'Never add a newline before a tag that closes an element',
126
+ description: 'Never add a newline before a tag that closes an element—use with `--max-line-length`',
127
127
  type: 'boolean'
128
128
  },
129
129
  partialMarkup: {
@@ -167,7 +167,7 @@ const optionDefinitions = {
167
167
  type: 'boolean'
168
168
  },
169
169
  removeEmptyElementsExcept: {
170
- description: 'Array of elements to preserve when `--remove-empty-elements` is enabled (e.g., `td`, `<span aria-hidden="true">`)',
170
+ description: 'Array of elements to preserve (e.g., `td`, `<span aria-hidden="true">`)—use with `--remove-empty-elements`',
171
171
  type: 'jsonArray'
172
172
  },
173
173
  removeOptionalTags: {
@@ -195,7 +195,7 @@ const optionDefinitions = {
195
195
  type: 'boolean'
196
196
  },
197
197
  trimCustomFragments: {
198
- description: 'Trim whitespace around custom fragments (`--ignore-custom-fragments`)',
198
+ description: 'Trim whitespace around custom fragments (`--ignore-custom-fragments`)—use with `--collapse-whitespace`',
199
199
  type: 'boolean'
200
200
  },
201
201
  strictCustomFragments: {
@@ -1,5 +1,5 @@
1
1
  import { createUrlMinifier } from './urls.js';
2
- import { LRU, MAX_CACHE_ENTRY_SIZE, stableStringify, hashContent, identity, lowercase, replaceAsync, parseRegExp, describeQuantifierRisk, lostFlag } from './utils.js';
2
+ import { LRU, MAX_CACHE_ENTRY_SIZE, stableStringify, hashContent, identity, lowercase, paramCase, replaceAsync, parseRegExp, describeQuantifierRisk, lostFlag } from './utils.js';
3
3
  import { RE_TRAILING_SEMICOLON } from './constants.js';
4
4
  import { canCollapseWhitespace, canTrimWhitespace } from './whitespace.js';
5
5
  import { wrapCSS, unwrapCSS } from './content.js';
@@ -100,10 +100,53 @@ const optionKeysWarned = new Set();
100
100
  const presetNamesWarned = new Set();
101
101
  // Custom fragments whose shape risks ReDoS, warned about once per pattern per process
102
102
  const customFragmentsWarned = new Set();
103
+ // Options set without the option they need, warned about once per message per process
104
+ const dependenciesWarned = new Set();
105
+ // Unused-CSS configuration that does not hold up, warned about once per message per process
103
106
  const unusedCSSWarned = new Set();
104
107
  // Object-valued options handed a string, warned about once per distinct value
105
108
  const stringValuesWarned = new Set();
106
109
 
110
+ /**
111
+ * Options that do nothing on their own, and the option each one needs to take effect
112
+ *
113
+ * Cache sizes are deliberately absent: They never change output, so they are
114
+ * configuration for a subsystem rather than a transform that silently fails.
115
+ *
116
+ * @typedef {object} OptionDependency
117
+ * @property {string} option
118
+ * @property {string} requires
119
+ * @property {(input: Record<string, any>) => string | false} [unusable] Clause saying why the prerequisite does not count even though it is set
120
+ * @property {boolean} [clear] Whether to null the option out, where leaving it set would cost work downstream
121
+ *
122
+ * @type {OptionDependency[]}
123
+ */
124
+ const optionDependencies = [
125
+ { option: 'collapseInlineTagWhitespace', requires: 'collapseWhitespace' },
126
+ { option: 'conservativeCollapse', requires: 'collapseWhitespace' },
127
+ { option: 'customEventAttributes', requires: 'minifyJS' },
128
+ { option: 'inlineCustomElements', requires: 'collapseWhitespace' },
129
+ { option: 'noNewlinesBeforeTagClose', requires: 'maxLineLength' },
130
+ { option: 'preserveLineBreaks', requires: 'collapseWhitespace' },
131
+ { option: 'removeEmptyElementsExcept', requires: 'removeEmptyElements' },
132
+ {
133
+ option: 'removeUnusedCSS',
134
+ requires: 'minifyCSS',
135
+ // Removal rides along with Lightning CSS, which a function of one’s own replaces
136
+ unusable: (/** @type {Record<string, any>} */ input) => typeof input.minifyCSS === 'function' && 'which a function of your own replaces',
137
+ clear: true
138
+ },
139
+ { option: 'trimCustomFragments', requires: 'collapseWhitespace' }
140
+ ];
141
+
142
+ // An empty array is a value the user supplied but did not populate, and so asks for
143
+ // nothing; `identity` is what a minifier that could not be loaded resolves to
144
+ /** @param {unknown} value */
145
+ function isRequested(value) {
146
+ if (value === identity) return false;
147
+ return Array.isArray(value) ? value.length > 0 : Boolean(value);
148
+ }
149
+
107
150
  // Main options processor
108
151
 
109
152
  /**
@@ -642,19 +685,24 @@ const processOptions = (inputOptions, { getLightningCSS, getTerser, getSwc, getS
642
685
  }
643
686
  }
644
687
 
645
- // Unused-CSS removal rides along with Lightning CSS, so it silently does nothing
646
- // when `minifyCSS` is off or replaced by a function—say so rather than let it pass
647
- if (options.removeUnusedCSS) {
648
- const cssOption = /** @type {Record<string, any>} */ (effectiveInput).minifyCSS;
649
- const reason = typeof cssOption === 'function'
650
- ? 'it does not apply when `minifyCSS` is a function'
651
- : (options.minifyCSS === identity ? 'it requires `minifyCSS` (`--minify-css`)' : '');
652
- if (reason) {
653
- if (!unusedCSSWarned.has(reason)) {
654
- unusedCSSWarned.add(reason);
655
- warn(`HTML Minifier Next: Ignoring \`removeUnusedCSS\`—${reason}`);
656
- }
657
- options.removeUnusedCSS = null;
688
+ // Options that silently do nothing without the option they build on say so rather
689
+ // than let them pass. The option is read from `effectiveInput` (user options
690
+ // over preset), so that a default filled in above doesn’t count as the user asking
691
+ // for it; the option it needs is read from the settled options, so that a minifier
692
+ // asked for but not loadable counts as absent rather than as present.
693
+ const requestedOptions = /** @type {Record<string, any>} */ (effectiveInput);
694
+ const settledOptions = /** @type {Record<string, any>} */ (options);
695
+ for (const { option, requires, unusable, clear } of optionDependencies) {
696
+ if (!isRequested(requestedOptions[option])) continue;
697
+ const aside = unusable && unusable(requestedOptions);
698
+ if (!aside && isRequested(settledOptions[requires])) continue;
699
+ const message = `HTML Minifier Next: Ignoring \`${option}\`—use with \`${requires}\` (\`--${paramCase(requires)}\`)${aside ? `, ${aside}` : ''}`;
700
+ if (!dependenciesWarned.has(message)) {
701
+ dependenciesWarned.add(message);
702
+ warn(message);
703
+ }
704
+ if (clear) {
705
+ settledOptions[option] = null;
658
706
  }
659
707
  }
660
708
 
@@ -664,6 +712,7 @@ const processOptions = (inputOptions, { getLightningCSS, getTerser, getSwc, getS
664
712
  // Exports
665
713
 
666
714
  export {
715
+ optionDependencies,
667
716
  shouldMinifyInnerHTML,
668
717
  processOptions
669
718
  };
package/src/lib/utils.js CHANGED
@@ -106,6 +106,12 @@ function lowercase(value) {
106
106
  return value.toLowerCase();
107
107
  }
108
108
 
109
+ // Option name to CLI flag name (ASCII-only, no Unicode needed)
110
+ /** @param {string} value */
111
+ function paramCase(value) {
112
+ return value.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
113
+ }
114
+
109
115
  // Replace async helper
110
116
 
111
117
  /**
@@ -844,6 +850,7 @@ export {
844
850
  identity,
845
851
  isThenable,
846
852
  lowercase,
853
+ paramCase,
847
854
  replaceAsync,
848
855
  parseRegExp,
849
856
  embedSource,