html-minifier-next 8.2.1 → 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 +61 -12
- package/cli.js +1 -4
- package/dist/types/htmlparser.d.ts.map +1 -1
- package/dist/types/lib/options.d.ts +27 -1
- package/dist/types/lib/options.d.ts.map +1 -1
- package/dist/types/lib/utils.d.ts +3 -1
- package/dist/types/lib/utils.d.ts.map +1 -1
- package/dist/types/lib/whitespace.d.ts.map +1 -1
- package/html-minifier-next.schema.json +6 -6
- package/package.json +1 -1
- package/src/htmlparser.js +114 -77
- package/src/lib/option-definitions.js +6 -6
- package/src/lib/options.js +63 -14
- package/src/lib/utils.js +7 -0
- package/src/lib/whitespace.js +14 -3
package/README.md
CHANGED
|
@@ -94,6 +94,8 @@ const result = await minify('<p title="example" id="moo">foo</p>', {
|
|
|
94
94
|
console.log(result); // `<p title=example id=moo>foo`
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
+
(CommonJS consumers can still use dynamic import—`const { minify } = await import('html-minifier-next')`—as `minify()` is async.)
|
|
98
|
+
|
|
97
99
|
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), [descriptions of most options](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).
|
|
98
100
|
|
|
99
101
|
## Presets
|
|
@@ -135,20 +137,20 @@ Options can be used in config files (camelCase) or via CLI flags (kebab-case wit
|
|
|
135
137
|
| `caseSensitive`<br>`--case-sensitive` | Treat attributes in case-sensitive manner (useful for custom HTML elements) | `false` |
|
|
136
138
|
| `collapseAttributeWhitespace`<br>`--collapse-attribute-whitespace` | Trim and collapse whitespace characters within attribute values | `false` |
|
|
137
139
|
| `collapseBooleanAttributes`<br>`--collapse-boolean-attributes` | [Omit attribute values from boolean attributes](https://perfectionkills.com/experimenting-with-html-minifier/#collapse_boolean_attributes) | `false` |
|
|
138
|
-
| `collapseInlineTagWhitespace`<br>`--collapse-inline-tag-whitespace` | Collapse whitespace more aggressively between inline elements—use with `collapseWhitespace
|
|
139
|
-
| `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` |
|
|
140
|
-
| `conservativeCollapse`<br>`--conservative-collapse` | Always collapse to one space (never remove it entirely)—use with `collapseWhitespace
|
|
140
|
+
| `collapseInlineTagWhitespace`<br>`--collapse-inline-tag-whitespace` | Collapse whitespace more aggressively between inline elements—use with [`collapseWhitespace`](#combining-whitespace-options) | `false` |
|
|
141
|
+
| `collapseWhitespace`<br>`--collapse-whitespace` | [Collapse whitespace that contributes to text nodes in a document tree](https://perfectionkills.com/experimenting-with-html-minifier/#collapse_whitespace); [enable other whitespace options](#combining-whitespace-options) | `false` |
|
|
142
|
+
| `conservativeCollapse`<br>`--conservative-collapse` | Always collapse to one space (never remove it entirely)—use with [`collapseWhitespace`](#combining-whitespace-options) | `false` |
|
|
141
143
|
| `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` |
|
|
142
144
|
| `continueOnParseError`<br>`--continue-on-parse-error` | [Handle parse errors](https://html.spec.whatwg.org/multipage/parsing.html#parse-errors) instead of aborting | `false` |
|
|
143
145
|
| `customAttrAssign`<br>`--custom-attr-assign` | Array of regexes that allow to support custom attribute assign expressions (e.g., `<div flex?="{{mode != cover}}"></div>`) | `[]` |
|
|
144
146
|
| `customAttrCollapse`<br>`--custom-attr-collapse` | Regex that specifies custom attribute to strip newlines from (e.g., `/ng-class/`) | `undefined` |
|
|
145
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}}>`) | `[]` |
|
|
146
|
-
| `customEventAttributes`<br>`--custom-event-attributes` | Array of regexes that allow to support custom event attributes
|
|
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,}$/ ]` |
|
|
147
149
|
| `decodeEntities`<br>`--decode-entities` | Use direct Unicode characters whenever possible | `false` |
|
|
148
150
|
| `ignoreCustomComments`<br>`--ignore-custom-comments` | Array of regexes that allow to ignore matching comments | `[ /^!/, /^\s*#/ ]` |
|
|
149
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]*?\?>/ ]` |
|
|
150
152
|
| `includeAutoGeneratedTags`<br>`--include-auto-generated-tags` | Insert elements generated by HTML parser | `false` |
|
|
151
|
-
| `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` | `[]` |
|
|
152
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` |
|
|
153
155
|
| `maxInputLength`<br>`--max-input-length` | Maximum input length to prevent ReDoS attacks (disabled by default) | `undefined` |
|
|
154
156
|
| `maxLineLength`<br>`--max-line-length` | Specify a maximum line length; compressed output will be split by newlines at valid HTML split-points | `undefined` |
|
|
@@ -157,9 +159,9 @@ Options can be used in config files (camelCase) or via CLI flags (kebab-case wit
|
|
|
157
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)`) |
|
|
158
160
|
| `minifySVG`<br>`--minify-svg` | Minify SVG elements (uses [SVGO](https://svgo.dev/)) | `false` (could be `true`, `Object`) |
|
|
159
161
|
| `minifyURLs`<br>`--minify-urls` | Minify URLs in various attributes | `false` (could be `true`, `String`, `Object`, `Function(text)`) |
|
|
160
|
-
| `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` |
|
|
161
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` |
|
|
162
|
-
| `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
|
|
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` |
|
|
163
165
|
| `preventAttributesEscaping`<br>`--prevent-attributes-escaping` | Prevents the escaping of the values of attributes | `false` |
|
|
164
166
|
| `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.) | `[]` |
|
|
165
167
|
| `quoteCharacter`<br>`--quote-character` | Type of quote to use for attribute values (`'` or `"`) | Auto-detected (uses the quote requiring less escaping; defaults to `"` when equal) |
|
|
@@ -168,15 +170,15 @@ Options can be used in config files (camelCase) or via CLI flags (kebab-case wit
|
|
|
168
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` |
|
|
169
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)`) |
|
|
170
172
|
| `removeEmptyElements`<br>`--remove-empty-elements` | [Remove all elements with empty contents](https://perfectionkills.com/experimenting-with-html-minifier/#remove_empty_elements) | `false` |
|
|
171
|
-
| `removeEmptyElementsExcept`<br>`--remove-empty-elements-except` | Array of elements to preserve
|
|
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 | `[]` |
|
|
172
174
|
| `removeOptionalTags`<br>`--remove-optional-tags` | [Remove optional tags](https://perfectionkills.com/experimenting-with-html-minifier/#remove_optional_tags) | `false` |
|
|
173
175
|
| `removeRedundantAttributes`<br>`--remove-redundant-attributes` | [Remove attributes when value matches default](https://meiert.com/blog/optional-html/#toc-attribute-values) | `false` |
|
|
174
176
|
| `removeTagWhitespace`<br>`--remove-tag-whitespace` | Remove space between attributes whenever possible; **note that this will result in invalid HTML** | `false` |
|
|
175
|
-
| `removeUnusedCSS`<br>`--remove-unused-css` | [Remove unused CSS rules](#unused-css-removal) from `style` elements
|
|
177
|
+
| `removeUnusedCSS`<br>`--remove-unused-css` | [Remove unused CSS rules](#unused-css-removal) from `style` elements—use with `minifyCSS`; **note that this can change how a document renders** | `false` (could be `true`, `{ safelist, scripts }`) |
|
|
176
178
|
| `sortAttributes`<br>`--sort-attributes` | [Sort attributes by frequency](#sorting-attributes-and-style-classes) | `false` |
|
|
177
179
|
| `sortClassNames`<br>`--sort-class-names` | [Sort style classes by frequency](#sorting-attributes-and-style-classes) | `false` |
|
|
178
180
|
| `strictCustomFragments`<br>`--strict-custom-fragments` | [Reject `ignoreCustomFragments` patterns that risk catastrophic backtracking](#redos-protection) (rather than warning about them) | `false` |
|
|
179
|
-
| `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` |
|
|
180
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` |
|
|
181
183
|
|
|
182
184
|
### API-only options
|
|
@@ -189,6 +191,53 @@ A few options take functions and are therefore only available programmatically,
|
|
|
189
191
|
| `canTrimWhitespace` | `Function(tag, attrs, defaultFn)` that determines whether leading and trailing whitespace around an element may be trimmed | Built-in handling |
|
|
190
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) |
|
|
191
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
|
+
|
|
216
|
+
### Combining whitespace options
|
|
217
|
+
|
|
218
|
+
`collapseInlineTagWhitespace`, `conservativeCollapse`, and `preserveLineBreaks` are modifiers: They do nothing on their own, and only take effect when `collapseWhitespace` is enabled.
|
|
219
|
+
|
|
220
|
+
Given input
|
|
221
|
+
|
|
222
|
+
```html
|
|
223
|
+
<nav>
|
|
224
|
+
<button>A</button> <button>B</button>
|
|
225
|
+
</nav>
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
you get the following output (condensed, `\n` represents an actual line break):
|
|
229
|
+
|
|
230
|
+
| Options | Output |
|
|
231
|
+
| --- | --- |
|
|
232
|
+
| `collapseInlineTagWhitespace` | `<nav>\n <button>A</button> <button>B</button>\n</nav>` (unchanged) |
|
|
233
|
+
| `collapseWhitespace` | `<nav><button>A</button> <button>B</button></nav>` |
|
|
234
|
+
| `collapseWhitespace`, `collapseInlineTagWhitespace` | `<nav><button>A</button><button>B</button></nav>` |
|
|
235
|
+
| `collapseWhitespace`, `conservativeCollapse` | `<nav> <button>A</button> <button>B</button> </nav>` |
|
|
236
|
+
| `collapseWhitespace`, `preserveLineBreaks` | `<nav>\n<button>A</button> <button>B</button>\n</nav>` |
|
|
237
|
+
| `collapseWhitespace`, `preserveLineBreaks`, `collapseInlineTagWhitespace` | `<nav>\n<button>A</button><button>B</button>\n</nav>` |
|
|
238
|
+
|
|
239
|
+
Where the modifiers disagree, the preserving one wins—`conservativeCollapse` and `preserveLineBreaks` do not let `collapseInlineTagWhitespace` remove a space or line break entirely.
|
|
240
|
+
|
|
192
241
|
### Sorting attributes and style classes
|
|
193
242
|
|
|
194
243
|
Minifier options like `sortAttributes` and `sortClassNames` 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.
|
|
@@ -234,7 +283,7 @@ const result = await minify(html, {
|
|
|
234
283
|
|
|
235
284
|
### Unused CSS removal
|
|
236
285
|
|
|
237
|
-
`removeUnusedCSS` removes rules from `style` elements whose class or ID selectors the document doesn’t reference. It
|
|
286
|
+
`removeUnusedCSS` removes rules from `style` elements whose class or ID selectors the document doesn’t reference. It needs to be used with `minifyCSS`, because the removal runs through Lightning CSS—passing `minifyCSS` a function of your own replaces that step, so the removal does not apply, either. Both cases are reported through [the `log` hook](#api-only-options). It does not touch `style` or `media` attributes.
|
|
238
287
|
|
|
239
288
|
```js
|
|
240
289
|
const result = await minify(html, {
|
|
@@ -646,7 +695,7 @@ For CLI usage, using a config file is strongly recommended to avoid complex shel
|
|
|
646
695
|
|
|
647
696
|
**[Web demo:](https://j9t.github.io/html-minifier-next/)**
|
|
648
697
|
|
|
649
|
-
```
|
|
698
|
+
```text
|
|
650
699
|
\{%[\s\S]{0,1000}?%\} \{\{[\s\S]{0,500}?\}\}
|
|
651
700
|
```
|
|
652
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'));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"htmlparser.d.ts","sourceRoot":"","sources":["../../src/htmlparser.js"],"names":[],"mappings":"AAeG,YAYG,iBAAiB,GAZV;IACR,KAAK,CAAC,WAAW;IACjB,GAAG,CAAC,WAAW;IACf,KAAK,CAAC,WAAW;IACjB,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,WAAW;IACnB,oBAAoB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC3C,aAAa,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACpC,YAAY,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACnC,gBAAgB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACvC,kBAAkB,CAAC,EAAE,MAAM,EAAE,EAAE,GAAG,SAAS,CAAC;IAC5C,gBAAgB,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAA;CACxC,CAAmB;AA0CvB,eAAO,MAAM,MAAM,QAA8C,CAAC;
|
|
1
|
+
{"version":3,"file":"htmlparser.d.ts","sourceRoot":"","sources":["../../src/htmlparser.js"],"names":[],"mappings":"AAeG,YAYG,iBAAiB,GAZV;IACR,KAAK,CAAC,WAAW;IACjB,GAAG,CAAC,WAAW;IACf,KAAK,CAAC,WAAW;IACjB,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,WAAW;IACnB,oBAAoB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC3C,aAAa,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACpC,YAAY,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACnC,gBAAgB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACvC,kBAAkB,CAAC,EAAE,MAAM,EAAE,EAAE,GAAG,SAAS,CAAC;IAC5C,gBAAgB,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAA;CACxC,CAAmB;AA0CvB,eAAO,MAAM,MAAM,QAA8C,CAAC;AA2JlE,qBAAa,UAAU;IAMd,IAAI;IACJ,OAAO;IANd;;;OAGG;IACH,YAAY,IAAI,EAHL,MAGK,EAAE,OAAO,EAFd,iBAEc,EAGxB;IAEK,KAAK,kBAkxBV;CACF"}
|
|
@@ -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":"AACA,OAAO,EAAE,GAAG,
|
|
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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"whitespace.d.ts","sourceRoot":"","sources":["../../../src/lib/whitespace.js"],"names":[],"mappings":"AAeA,0BAA0B;AAC1B,QAAA,MAAM,cAAc,QADR,MAAM,WAQjB,CAAC;AAIF,0BAA0B;AAC1B,iBAAS,qBAAqB,CAAC,GAAG,EADtB,MACsB,UAgBjC;AAID;;;;;;GAMG;AACH,iBAAS,kBAAkB,CAAC,GAAG,EANpB,MAMoB,EAAE,OAAO,EAL7B;IAAC,kBAAkB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAAC,oBAAoB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;CAKxD,EAAE,QAAQ,EAJvC,OAIuC,EAAE,SAAS,EAHlD,OAGkD,EAAE,WAAW,AAFvE,CAA8B,EAAtB,OAEuE,UAqFjF;AAoBD;;;;;;;;;GASG;AACH,iBAAS,uBAAuB,CAAC,GAAG,EATzB,MASyB,EAAE,OAAO,EARlC,MAQkC,EAAE,OAAO,EAP3C,MAO2C,EAAE,SAAS,EANtD,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAC,CAMkB,EAAE,SAAS,EALjE,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAC,CAK6B,EAAE,OAAO,EAJ1E;IAAC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAAC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAAC,2BAA2B,CAAC,EAAE,OAAO,CAAA;CAI1B,EAAE,cAAc,EAH1F,GAAG,CAAC,MAAM,CAGgF,EAAE,aAAa,EAFzG,GAAG,CAAC,MAAM,CAE+F,
|
|
1
|
+
{"version":3,"file":"whitespace.d.ts","sourceRoot":"","sources":["../../../src/lib/whitespace.js"],"names":[],"mappings":"AAeA,0BAA0B;AAC1B,QAAA,MAAM,cAAc,QADR,MAAM,WAQjB,CAAC;AAIF,0BAA0B;AAC1B,iBAAS,qBAAqB,CAAC,GAAG,EADtB,MACsB,UAgBjC;AAID;;;;;;GAMG;AACH,iBAAS,kBAAkB,CAAC,GAAG,EANpB,MAMoB,EAAE,OAAO,EAL7B;IAAC,kBAAkB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAAC,oBAAoB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;CAKxD,EAAE,QAAQ,EAJvC,OAIuC,EAAE,SAAS,EAHlD,OAGkD,EAAE,WAAW,AAFvE,CAA8B,EAAtB,OAEuE,UAqFjF;AAoBD;;;;;;;;;GASG;AACH,iBAAS,uBAAuB,CAAC,GAAG,EATzB,MASyB,EAAE,OAAO,EARlC,MAQkC,EAAE,OAAO,EAP3C,MAO2C,EAAE,SAAS,EANtD,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAC,CAMkB,EAAE,SAAS,EALjE,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAC,CAK6B,EAAE,OAAO,EAJ1E;IAAC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAAC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAAC,2BAA2B,CAAC,EAAE,OAAO,CAAA;CAI1B,EAAE,cAAc,EAH1F,GAAG,CAAC,MAAM,CAGgF,EAAE,aAAa,EAFzG,GAAG,CAAC,MAAM,CAE+F,UA4EnH;AASD,0BAA0B;AAC1B,iBAAS,qBAAqB,CAAC,GAAG,EADtB,MACsB,WAEjC;AAED,0BAA0B;AAC1B,iBAAS,iBAAiB,CAAC,GAAG,EADlB,MACkB,WAE7B;AAID,OAAO,EACL,cAAc,EACd,qBAAqB,EACrB,kBAAkB,EAClB,uBAAuB,EACvB,qBAAqB,EACrB,iBAAiB,EAClB,CAAC"}
|
|
@@ -108,7 +108,7 @@
|
|
|
108
108
|
}
|
|
109
109
|
},
|
|
110
110
|
"customEventAttributes": {
|
|
111
|
-
"description": "Array of regexes that allow to support custom event attributes
|
|
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
|
|
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"
|
|
@@ -275,7 +275,7 @@
|
|
|
275
275
|
"type": "boolean"
|
|
276
276
|
},
|
|
277
277
|
"removeUnusedCSS": {
|
|
278
|
-
"description": "Remove rules from `style` elements whose class or ID selectors the document never references
|
|
278
|
+
"description": "Remove rules from `style` elements whose class or ID selectors the document never references—use with `--minify-css`; note that class names only applied by external scripts cannot be detected—use `{\"safelist\": […]}` for those",
|
|
279
279
|
"type": [
|
|
280
280
|
"boolean",
|
|
281
281
|
"object"
|
|
@@ -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
package/src/htmlparser.js
CHANGED
|
@@ -103,6 +103,13 @@ const nonPhrasing = new Set(['address', 'article', 'aside', 'base', 'blockquote'
|
|
|
103
103
|
const endsTagName = (/** @type {string} */ character) =>
|
|
104
104
|
character === '' || character === '/' || character === '>' || /\s/.test(character);
|
|
105
105
|
|
|
106
|
+
// The characters `\s` matches, by code: Tab through carriage return, space, and the
|
|
107
|
+
// Unicode whitespace and line terminators
|
|
108
|
+
const isWhitespaceCode = (/** @type {number} */ code) =>
|
|
109
|
+
(code >= 9 && code <= 13) || code === 32 || code === 0xA0 || code === 0x1680 ||
|
|
110
|
+
(code >= 0x2000 && code <= 0x200A) || code === 0x2028 || code === 0x2029 ||
|
|
111
|
+
code === 0x202F || code === 0x205F || code === 0x3000 || code === 0xFEFF;
|
|
112
|
+
|
|
106
113
|
/**
|
|
107
114
|
* @param {string} text - The input the tag stands in
|
|
108
115
|
* @param {number} nameEnd - Position right after the tag name
|
|
@@ -251,8 +258,6 @@ export class HTMLParser {
|
|
|
251
258
|
|
|
252
259
|
// Sticky regex versions for position-based matching (avoids string slicing)
|
|
253
260
|
const startTagOpenY = new RegExp(startTagOpen.source.slice(1), 'y');
|
|
254
|
-
// `\s*` with sticky flag is O(n) at worst—no retry from different positions possible
|
|
255
|
-
const startTagCloseY = /\s*(\/?)>/y;
|
|
256
261
|
const endTagOpenY = new RegExp(endTagOpen.source.slice(1), 'y');
|
|
257
262
|
const doctypeY = /<!DOCTYPE[^<>]+>/iy;
|
|
258
263
|
const commentTestY = /<!--/y;
|
|
@@ -386,77 +391,84 @@ export class HTMLParser {
|
|
|
386
391
|
cachedNextStartTag = null;
|
|
387
392
|
cachedNextEndTag = null;
|
|
388
393
|
|
|
389
|
-
//
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
394
|
+
// Dispatch on the character after `<`: Each branch below can only match
|
|
395
|
+
// with one leading character (`!` for markup declarations, `/` for end
|
|
396
|
+
// tags, a name character for start tags), so the matchers that require
|
|
397
|
+
// another one never run—a name lookup costs several regex executions
|
|
398
|
+
const nextCode = fullHtml.charCodeAt(pos + 1);
|
|
399
|
+
if (nextCode === 33) { // `!`
|
|
400
|
+
// Comment
|
|
401
|
+
commentTestY.lastIndex = pos;
|
|
402
|
+
if (commentTestY.test(fullHtml)) {
|
|
403
|
+
const commentEnd = commentEndAhead ? fullHtml.indexOf('-->', pos + 4) : -1;
|
|
404
|
+
if (commentEnd === -1) commentEndAhead = false;
|
|
405
|
+
|
|
406
|
+
if (commentEnd >= 0) {
|
|
407
|
+
if (handler.comment) {
|
|
408
|
+
const result = handler.comment(fullHtml.substring(pos + 4, commentEnd));
|
|
409
|
+
if (isThenable(result)) await result;
|
|
410
|
+
}
|
|
411
|
+
advance(commentEnd + 3 - pos);
|
|
412
|
+
prevTag = '';
|
|
413
|
+
prevAttrs = [];
|
|
414
|
+
continue;
|
|
399
415
|
}
|
|
400
|
-
advance(commentEnd + 3 - pos);
|
|
401
|
-
prevTag = '';
|
|
402
|
-
prevAttrs = [];
|
|
403
|
-
continue;
|
|
404
416
|
}
|
|
405
|
-
}
|
|
406
417
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
418
|
+
// https://web.archive.org/web/20241201212701/https://en.wikipedia.org/wiki/Conditional_comment#Downlevel-revealed_conditional_comment
|
|
419
|
+
conditionalTestY.lastIndex = pos;
|
|
420
|
+
if (conditionalTestY.test(fullHtml)) {
|
|
421
|
+
const conditionalEnd = conditionalEndAhead ? fullHtml.indexOf(']>', pos + 3) : -1;
|
|
422
|
+
if (conditionalEnd === -1) conditionalEndAhead = false;
|
|
423
|
+
|
|
424
|
+
if (conditionalEnd >= 0) {
|
|
425
|
+
if (handler.comment) {
|
|
426
|
+
const result = handler.comment(fullHtml.substring(pos + 2, conditionalEnd + 1), true /* Non-standard */);
|
|
427
|
+
if (isThenable(result)) await result;
|
|
428
|
+
}
|
|
429
|
+
advance(conditionalEnd + 2 - pos);
|
|
430
|
+
prevTag = '';
|
|
431
|
+
prevAttrs = [];
|
|
432
|
+
continue;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
412
435
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
436
|
+
// Doctype
|
|
437
|
+
doctypeY.lastIndex = pos;
|
|
438
|
+
const doctypeMatch = doctypeY.exec(fullHtml);
|
|
439
|
+
if (doctypeMatch) {
|
|
440
|
+
if (handler.doctype) {
|
|
441
|
+
handler.doctype(doctypeMatch[0]);
|
|
417
442
|
}
|
|
418
|
-
advance(
|
|
443
|
+
advance(doctypeMatch[0].length);
|
|
419
444
|
prevTag = '';
|
|
420
445
|
prevAttrs = [];
|
|
421
446
|
continue;
|
|
422
447
|
}
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
448
|
+
} else if (nextCode === 47) { // `/`
|
|
449
|
+
// End tag—skip when no `>` lies ahead: The tag cannot be complete, and matching
|
|
450
|
+
// its name at every position would cost O(n²) on a long unclosed name
|
|
451
|
+
if (hasCloseAtOrAfter(pos)) {
|
|
452
|
+
const endTagMatch = matchEndTag(pos);
|
|
453
|
+
if (endTagMatch) {
|
|
454
|
+
advance(endTagMatch.text.length);
|
|
455
|
+
await parseEndTag(endTagMatch.text, endTagMatch.name);
|
|
456
|
+
prevTag = '/' + endTagMatch.name.toLowerCase();
|
|
457
|
+
prevAttrs = [];
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
431
460
|
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
// its name at every position would cost O(n²) on a long unclosed name
|
|
440
|
-
if (hasCloseAtOrAfter(pos)) {
|
|
441
|
-
const endTagMatch = matchEndTag(pos);
|
|
442
|
-
if (endTagMatch) {
|
|
443
|
-
advance(endTagMatch.text.length);
|
|
444
|
-
await parseEndTag(endTagMatch.text, endTagMatch.name);
|
|
445
|
-
prevTag = '/' + endTagMatch.name.toLowerCase();
|
|
446
|
-
prevAttrs = [];
|
|
461
|
+
} else {
|
|
462
|
+
// Start tag
|
|
463
|
+
const startTagMatch = parseStartTag(pos);
|
|
464
|
+
if (startTagMatch) {
|
|
465
|
+
advance(startTagMatch.advance);
|
|
466
|
+
await handleStartTag(startTagMatch);
|
|
467
|
+
prevTag = startTagMatch.tagName.toLowerCase();
|
|
447
468
|
continue;
|
|
448
469
|
}
|
|
449
470
|
}
|
|
450
471
|
|
|
451
|
-
// Start tag
|
|
452
|
-
const startTagMatch = parseStartTag(pos);
|
|
453
|
-
if (startTagMatch) {
|
|
454
|
-
advance(startTagMatch.advance);
|
|
455
|
-
await handleStartTag(startTagMatch);
|
|
456
|
-
prevTag = startTagMatch.tagName.toLowerCase();
|
|
457
|
-
continue;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
472
|
// Treat `<` as text
|
|
461
473
|
if (handler.continueOnParseError) {
|
|
462
474
|
// Continue looking for next tag
|
|
@@ -474,13 +486,21 @@ export class HTMLParser {
|
|
|
474
486
|
|
|
475
487
|
// Next tag for whitespace processing context
|
|
476
488
|
if (handler.wantsNextTag) {
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
489
|
+
// Same dispatch as above: A start tag cannot follow `<` + `/` or `!`,
|
|
490
|
+
// an end tag requires `/`—the other matcher never runs
|
|
491
|
+
const nextCode = fullHtml.charCodeAt(pos + 1);
|
|
492
|
+
if (nextCode !== 47 && nextCode !== 33) {
|
|
493
|
+
const nextStartTagMatch = parseStartTag(pos);
|
|
494
|
+
if (nextStartTagMatch) {
|
|
495
|
+
nextTag = nextStartTagMatch.tagName;
|
|
496
|
+
// Extract minimal attribute info for whitespace logic (just name/value pairs)
|
|
497
|
+
nextAttrs = extractAttrInfo(nextStartTagMatch.attrs);
|
|
498
|
+
cachedNextStartTag = { match: nextStartTagMatch, pos };
|
|
499
|
+
} else {
|
|
500
|
+
nextTag = '';
|
|
501
|
+
nextAttrs = [];
|
|
502
|
+
}
|
|
503
|
+
} else if (nextCode === 47 && hasCloseAtOrAfter(pos)) {
|
|
484
504
|
const nextEndTagMatch = matchEndTag(pos);
|
|
485
505
|
if (nextEndTagMatch) {
|
|
486
506
|
nextTag = '/' + nextEndTagMatch.name;
|
|
@@ -603,6 +623,23 @@ export class HTMLParser {
|
|
|
603
623
|
return close === -1 ? null : { text: fullHtml.slice(startPos, close + 1), name: open[1] ?? '' };
|
|
604
624
|
}
|
|
605
625
|
|
|
626
|
+
// Where a tag ends: After optional whitespace, at `>` or `/>`. Scanned by hand in a
|
|
627
|
+
// single pass, where a regex did the same with one execution per attribute.
|
|
628
|
+
function matchTagClose(/** @type {number} */ currentPos) {
|
|
629
|
+
let scan = currentPos;
|
|
630
|
+
let code = fullHtml.charCodeAt(scan);
|
|
631
|
+
while (isWhitespaceCode(code)) {
|
|
632
|
+
code = fullHtml.charCodeAt(++scan);
|
|
633
|
+
}
|
|
634
|
+
if (code === 62) { // `>`
|
|
635
|
+
return { len: scan - currentPos + 1, slash: '' };
|
|
636
|
+
}
|
|
637
|
+
if (code === 47 && fullHtml.charCodeAt(scan + 1) === 62) { // `/>`
|
|
638
|
+
return { len: scan - currentPos + 2, slash: '/' };
|
|
639
|
+
}
|
|
640
|
+
return null;
|
|
641
|
+
}
|
|
642
|
+
|
|
606
643
|
function parseStartTag(/** @type {number} */ startPos) {
|
|
607
644
|
startTagOpenY.lastIndex = startPos;
|
|
608
645
|
const start = startTagOpenY.exec(fullHtml);
|
|
@@ -615,17 +652,16 @@ export class HTMLParser {
|
|
|
615
652
|
};
|
|
616
653
|
let consumed = start[0].length;
|
|
617
654
|
let currentPos = startPos + consumed;
|
|
618
|
-
let
|
|
655
|
+
let close, attr;
|
|
619
656
|
|
|
620
657
|
// Safety limit: Max length of input to check for attributes
|
|
621
658
|
// Protects against catastrophic backtracking on massive attribute values
|
|
622
659
|
const MAX_ATTR_PARSE_LENGTH = 20000; // 20 KB should be enough for any reasonable tag
|
|
623
660
|
|
|
624
661
|
while (true) {
|
|
625
|
-
// Check for closing tag first (
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
if (end) {
|
|
662
|
+
// Check for closing tag first (manual scan, regex only for the unusual)
|
|
663
|
+
close = matchTagClose(currentPos);
|
|
664
|
+
if (close) {
|
|
629
665
|
break;
|
|
630
666
|
}
|
|
631
667
|
|
|
@@ -748,12 +784,13 @@ export class HTMLParser {
|
|
|
748
784
|
match.attrs.push(attr);
|
|
749
785
|
}
|
|
750
786
|
|
|
751
|
-
// Check for closing tag (
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
787
|
+
// Check for closing tag (manual scan, regex only for the unusual)
|
|
788
|
+
if (!close) {
|
|
789
|
+
close = matchTagClose(currentPos);
|
|
790
|
+
}
|
|
791
|
+
if (close) {
|
|
792
|
+
match.unarySlash = close.slash;
|
|
793
|
+
consumed += close.len;
|
|
757
794
|
match.advance = consumed;
|
|
758
795
|
return match;
|
|
759
796
|
}
|
|
@@ -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
|
|
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
|
|
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: {
|
|
@@ -183,7 +183,7 @@ const optionDefinitions = {
|
|
|
183
183
|
type: 'boolean'
|
|
184
184
|
},
|
|
185
185
|
removeUnusedCSS: {
|
|
186
|
-
description: 'Remove rules from `style` elements whose class or ID selectors the document never references
|
|
186
|
+
description: 'Remove rules from `style` elements whose class or ID selectors the document never references—use with `--minify-css`; note that class names only applied by external scripts cannot be detected—use `{"safelist": […]}` for those',
|
|
187
187
|
type: 'jsonObject'
|
|
188
188
|
},
|
|
189
189
|
sortAttributes: {
|
|
@@ -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: {
|
package/src/lib/options.js
CHANGED
|
@@ -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
|
-
//
|
|
646
|
-
//
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
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,
|
package/src/lib/whitespace.js
CHANGED
|
@@ -169,6 +169,14 @@ function isHiddenInput(tagName, attrs) {
|
|
|
169
169
|
* @param {Set<string>} inlineTextSet
|
|
170
170
|
*/
|
|
171
171
|
function collapseWhitespaceSmart(str, prevTag, nextTag, prevAttrs, nextAttrs, options, inlineElements, inlineTextSet) {
|
|
172
|
+
if (!str) return str;
|
|
173
|
+
|
|
174
|
+
// Fast path: No whitespace at all—every decision below would leave `str`
|
|
175
|
+
// untouched (`collapseWhitespace` returns such strings unchanged)
|
|
176
|
+
if (!/[ \n\r\t\f\xA0]/.test(str)) {
|
|
177
|
+
return str;
|
|
178
|
+
}
|
|
179
|
+
|
|
172
180
|
const prevTagName = prevTag && (prevTag.charAt(0) === '/' ? prevTag.slice(1) : prevTag);
|
|
173
181
|
const nextTagName = nextTag && (nextTag.charAt(0) === '/' ? nextTag.slice(1) : nextTag);
|
|
174
182
|
|
|
@@ -178,14 +186,17 @@ function collapseWhitespaceSmart(str, prevTag, nextTag, prevAttrs, nextAttrs, op
|
|
|
178
186
|
|
|
179
187
|
let trimLeft = prevTag && !inlineElementsToKeepWhitespace.has(prevTag);
|
|
180
188
|
|
|
189
|
+
// Whether the text is whitespace only—one scan, shared by the checks below
|
|
190
|
+
// (`str` is known non-empty here)
|
|
191
|
+
const isPureWhitespace = !/\S/.test(str);
|
|
192
|
+
|
|
181
193
|
// Smart default behavior: Collapse space after non-rendering elements (`type="hidden"`)
|
|
182
194
|
// This happens even in basic `collapseWhitespace` mode (safe optimization)
|
|
183
|
-
if (!trimLeft && prevIsHidden &&
|
|
195
|
+
if (!trimLeft && prevIsHidden && isPureWhitespace) {
|
|
184
196
|
trimLeft = true;
|
|
185
197
|
}
|
|
186
198
|
|
|
187
199
|
// Aggressive mode: Collapse between all form controls (pure whitespace only)
|
|
188
|
-
const isPureWhitespace = str && !/\S/.test(str);
|
|
189
200
|
if (!trimLeft && prevTagName && nextTagName &&
|
|
190
201
|
options.collapseInlineTagWhitespace &&
|
|
191
202
|
isPureWhitespace &&
|
|
@@ -208,7 +219,7 @@ function collapseWhitespaceSmart(str, prevTag, nextTag, prevAttrs, nextAttrs, op
|
|
|
208
219
|
let trimRight = nextTag && !inlineElementsToKeepWhitespace.has(nextTag);
|
|
209
220
|
|
|
210
221
|
// Smart default behavior: Collapse space before non-rendering elements (`type="hidden"`)
|
|
211
|
-
if (!trimRight && nextIsHidden &&
|
|
222
|
+
if (!trimRight && nextIsHidden && isPureWhitespace) {
|
|
212
223
|
trimRight = true;
|
|
213
224
|
}
|
|
214
225
|
|