html-minifier-next 7.6.0 → 8.0.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 +18 -21
- package/cli.js +152 -50
- package/dist/types/htmlminifier.d.ts +6 -4
- package/dist/types/htmlminifier.d.ts.map +1 -1
- package/dist/types/lib/attributes.d.ts +2 -2
- package/dist/types/lib/attributes.d.ts.map +1 -1
- package/dist/types/lib/constants.d.ts +2 -2
- package/dist/types/lib/constants.d.ts.map +1 -1
- package/dist/types/lib/fragments.d.ts +46 -0
- package/dist/types/lib/fragments.d.ts.map +1 -0
- package/dist/types/lib/option-definitions.d.ts +21 -198
- package/dist/types/lib/option-definitions.d.ts.map +1 -1
- package/dist/types/lib/options.d.ts +1 -1
- package/dist/types/lib/options.d.ts.map +1 -1
- package/dist/types/lib/unused-css.d.ts +5 -4
- package/dist/types/lib/unused-css.d.ts.map +1 -1
- package/dist/types/lib/utils.d.ts +18 -1
- package/dist/types/lib/utils.d.ts.map +1 -1
- package/dist/types/tokenchain.d.ts +2 -2
- package/dist/types/tokenchain.d.ts.map +1 -1
- package/html-minifier-next.schema.json +4 -5
- package/package.json +1 -1
- package/src/htmlminifier.js +49 -64
- package/src/htmlparser.js +4 -4
- package/src/lib/attributes.js +63 -6
- package/src/lib/constants.js +6 -8
- package/src/lib/fragments.js +274 -0
- package/src/lib/option-definitions.js +12 -4
- package/src/lib/options.js +25 -17
- package/src/lib/unused-css.js +5 -22
- package/src/lib/utils.js +308 -3
- package/src/tokenchain.js +37 -30
package/README.md
CHANGED
|
@@ -69,7 +69,7 @@ For editor support (validation, autocomplete, and inline documentation) in JSON
|
|
|
69
69
|
}
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
-
(If HMN is installed locally, you can also use the path
|
|
72
|
+
(If HMN is installed locally, you can also use the path ./node_modules/html-minifier-next/html-minifier-next.schema.json instead of the URL.)
|
|
73
73
|
|
|
74
74
|
**JavaScript module configuration example** (requires `"type": "module"` in the project’s package.json, or use a .mjs extension):
|
|
75
75
|
|
|
@@ -144,7 +144,6 @@ Options can be used in config files (camelCase) or via CLI flags (kebab-case wit
|
|
|
144
144
|
| `customAttrCollapse`<br>`--custom-attr-collapse` | Regex that specifies custom attribute to strip newlines from (e.g., `/ng-class/`) | `undefined` |
|
|
145
145
|
| `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
146
|
| `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,}$/ ]` |
|
|
147
|
-
| `customFragmentQuantifierLimit`<br>`--custom-fragment-quantifier-limit` | Set maximum quantifier limit for custom fragments to prevent ReDoS attacks | `200` |
|
|
148
147
|
| `decodeEntities`<br>`--decode-entities` | Use direct Unicode characters whenever possible | `false` |
|
|
149
148
|
| `ignoreCustomComments`<br>`--ignore-custom-comments` | Array of regexes that allow to ignore matching comments | `[ /^!/, /^\s*#/ ]` |
|
|
150
149
|
| `ignoreCustomFragments`<br>`--ignore-custom-fragments` | Array of regexes that allow to ignore certain fragments, when matched (e.g., `<?php … ?>`, `{{ … }}`, etc.) | `[ /<%[\s\S]*?%>/, /<\?[\s\S]*?\?>/ ]` |
|
|
@@ -176,6 +175,7 @@ Options can be used in config files (camelCase) or via CLI flags (kebab-case wit
|
|
|
176
175
|
| `removeUnusedCSS`<br>`--remove-unused-css` | [Remove unused CSS rules](#unused-css-removal) from `style` elements; requires `minifyCSS`; **note that this can change how a document renders** | `false` (could be `true`, `{ safelist, scripts }`) |
|
|
177
176
|
| `sortAttributes`<br>`--sort-attributes` | [Sort attributes by frequency](#sorting-attributes-and-style-classes) | `false` |
|
|
178
177
|
| `sortClassNames`<br>`--sort-class-names` | [Sort style classes by frequency](#sorting-attributes-and-style-classes) | `false` |
|
|
178
|
+
| `strictCustomFragments`<br>`--strict-custom-fragments` | [Reject `ignoreCustomFragments` patterns that risk catastrophic backtracking](#redos-protection) (rather than warning about them) | `false` |
|
|
179
179
|
| `trimCustomFragments`<br>`--trim-custom-fragments` | Trim whitespace around custom fragments (`ignoreCustomFragments`) | `false` |
|
|
180
180
|
| `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
181
|
|
|
@@ -253,7 +253,7 @@ Symbols are considered used when they appear
|
|
|
253
253
|
|
|
254
254
|
Names carrying characters that end a CSS identifier—`md:flex`, `w-1/2`, `p-[3px]`—are matched as whole tokens, so utility-CSS class names survive whether they come from markup, a `data-*` value, or a string in an inline script.
|
|
255
255
|
|
|
256
|
-
**Class names that only appear in external scripts cannot be detected.** A minifier sees one document, not the DOM that scripts later build from it, so a class added by
|
|
256
|
+
**Class names that only appear in external scripts cannot be detected.** A minifier sees one document, not the DOM that scripts later build from it, so a class added by bundle.js looks exactly like a class nobody uses. List those under `safelist`, as strings or regular expressions:
|
|
257
257
|
|
|
258
258
|
```js
|
|
259
259
|
const result = await minify(html, {
|
|
@@ -539,7 +539,7 @@ SVG and MathML elements are automatically recognized as foreign elements, and wh
|
|
|
539
539
|
|
|
540
540
|
### Working with invalid or partial markup
|
|
541
541
|
|
|
542
|
-
By default,
|
|
542
|
+
By default, HMN parses markup into a complete tree structure, then modifies it (removing anything that was specified for removal, ignoring anything that was specified to be ignored, etc.), then creates markup from that tree and returns it.
|
|
543
543
|
|
|
544
544
|
_Input markup (e.g., `<p id="">foo`) → Internal representation of markup in a form of tree (e.g., `{ tag: "p", attr: "id", children: ["foo"] }`) → Transformation of internal representation (e.g., removal of `id` attribute) → Output of resulting markup (e.g., `<p>foo</p>`)_
|
|
545
545
|
|
|
@@ -551,36 +551,35 @@ To validate complete HTML markup, use [the W3C validator](https://validator.w3.o
|
|
|
551
551
|
|
|
552
552
|
### ReDoS protection
|
|
553
553
|
|
|
554
|
-
|
|
554
|
+
You can use `ignoreCustomFragments` to hand HTML Minifier Next a regular expression to run against your documents. This is also where a regular expression denial of service (ReDoS) could originate:
|
|
555
555
|
|
|
556
|
-
*
|
|
556
|
+
* Matching without backtracking: A pattern that wraps an any-character or negated-class body in literal delimiters—`<%[\s\S]*?%>` or `\{\{[^}]*?\}\}`, and every other shape below—is matched by scanning for those delimiters in linear time, with no regular expression involved. Patterns of other shapes run as regular expressions, one per pattern, so each keeps its own flags.
|
|
557
557
|
|
|
558
|
-
*
|
|
558
|
+
* Pattern detection: HMN warns about the shapes that backtrack catastrophically—an unlimited quantifier over a group that itself contains a quantifier that can vary (`(a+)+`, `(a?)+`) or alternation (`(a|b)*`), and the same atom repeated unboundedly twice in a row (`.*.*`). A fixed count does not vary, so `(?:a{4})+` passes. These are also shapes a linear scan cannot stand in for. `strictCustomFragments` refuses them with an error instead, which is worth enabling where the patterns or the input are not entirely under your control. A pattern longer than 10,000 characters or nested more than 50 groups deep is judged risky without being analyzed further, so that reading the pattern cannot itself become the expensive step.
|
|
559
559
|
|
|
560
|
-
*
|
|
560
|
+
* Input length limits: The `maxInputLength` option allows you to set a maximum input size to prevent processing of excessively large inputs that could cause performance issues.
|
|
561
561
|
|
|
562
|
-
**Important:**
|
|
562
|
+
**Important:** A single unlimited quantifier is not one of those shapes: `[\s\S]*?` running up to a literal terminator matches in linear time, and it is how HMN’s defaults are written. Bounds are still worth adding where you know the maximum length of a fragment, since they cap how far a failing match can scan.
|
|
563
563
|
|
|
564
564
|
#### Custom fragment examples
|
|
565
565
|
|
|
566
|
-
**Safe patterns
|
|
566
|
+
**Safe patterns:**
|
|
567
567
|
|
|
568
568
|
```js
|
|
569
569
|
ignoreCustomFragments: [
|
|
570
|
-
/<%[\s\S]
|
|
571
|
-
/<\?php[\s\S]{0,5000}?\?>/, // PHP with bounds
|
|
570
|
+
/<%[\s\S]*?%>/, // Lazy scan up to a literal terminator
|
|
571
|
+
/<\?php[\s\S]{0,5000}?\?>/, // PHP with explicit bounds
|
|
572
572
|
/\{\{[^}]{0,500}\}\}/ // Handlebars without nested braces
|
|
573
573
|
]
|
|
574
574
|
```
|
|
575
575
|
|
|
576
|
-
**
|
|
576
|
+
**Unsafe patterns** (these trigger warnings):
|
|
577
577
|
|
|
578
578
|
```js
|
|
579
579
|
ignoreCustomFragments: [
|
|
580
|
-
/<%
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
/(script|style)[\s\S]*?/ // Multiple unlimited quantifiers
|
|
580
|
+
/<%(\s|\S)*?%>/, // Unlimited quantifier over an alternating group
|
|
581
|
+
/\{\{([^}]+)+\}\}/, // Nested unlimited quantifiers
|
|
582
|
+
/<!--[\s\S]*[\s\S]*-->/ // The same atom repeated unboundedly twice
|
|
584
583
|
]
|
|
585
584
|
```
|
|
586
585
|
|
|
@@ -600,8 +599,6 @@ ignoreCustomFragments: [/\{\{[\s\S]{0,500}?\}\}/]
|
|
|
600
599
|
ignoreCustomFragments: [/\{\{[\s\S]{0,500}?\}\}/]
|
|
601
600
|
```
|
|
602
601
|
|
|
603
|
-
**Important:** When using custom `ignoreCustomFragments`, the minifier automatically applies bounded quantifiers to prevent ReDoS attacks, but you can also write safer patterns yourself using explicit bounds.
|
|
604
|
-
|
|
605
602
|
##### Escaping patterns in different contexts
|
|
606
603
|
|
|
607
604
|
The escaping requirements for `ignoreCustomFragments` patterns differ depending on how you’re using HMN:
|
|
@@ -682,11 +679,11 @@ Parameters:
|
|
|
682
679
|
|
|
683
680
|
* No argument: Runs and, if a baseline exists, shows size and time deltas
|
|
684
681
|
* `--save`: Saves the run as the baseline (e.g., on `main` before switching to a branch)
|
|
685
|
-
* `--core`: Disables the external minifiers (CSS, JS, SVG, URLs) to isolate HMN’s
|
|
682
|
+
* `--core`: Disables the external minifiers (CSS, JS, SVG, URLs) to isolate HMN’s processing time
|
|
686
683
|
* `--iterations=N`: Sets the number of timed iterations (default 5; the median is reported)
|
|
687
684
|
* `--config=PATH`: Uses an alternative options file (default html-minifier-next.config.json)
|
|
688
685
|
|
|
689
|
-
To compare branches (A/B run), execute `npm run benchmark -- --save` on `main`, then `npm run benchmark` on the branch to see the deltas. Add `--core` on both ends when measuring changes to HMN
|
|
686
|
+
To compare branches (A/B run), execute `npm run benchmark -- --save` on `main`, then `npm run benchmark` on the branch to see the deltas. Add `--core` on both ends when measuring changes to HMN rather than bundled minifiers.
|
|
690
687
|
|
|
691
688
|
#### Profiling
|
|
692
689
|
|