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 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 `./node_modules/html-minifier-next/html-minifier-next.schema.json` instead of the URL.)
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 `bundle.js` looks exactly like a class nobody uses. List those under `safelist`, as strings or regular expressions:
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, HTML Minifier Next 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.
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
- This minifier includes protection against regular expression denial of service (ReDoS) attacks:
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
- * Custom fragment quantifier limits: The `customFragmentQuantifierLimit` option (default: 200) prevents exponential backtracking by replacing unlimited quantifiers (`*`, `+`) with bounded ones in regular expressions.
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
- * 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.
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
- * Enhanced pattern detection: The minifier detects and warns about various ReDoS-prone patterns including nested quantifiers, alternation with quantifiers, and multiple unlimited quantifiers.
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:** When using custom `ignoreCustomFragments`, ensure your regular expressions don’t contain unlimited quantifiers (`*`, `+`) without bounds, as these can lead to ReDoS vulnerabilities.
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** (recommended):
566
+ **Safe patterns:**
567
567
 
568
568
  ```js
569
569
  ignoreCustomFragments: [
570
- /<%[\s\S]{0,1000}?%>/, // JSP/ASP with explicit bounds
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
- **Potentially unsafe patterns** (will trigger warnings):
576
+ **Unsafe patterns** (these trigger warnings):
577
577
 
578
578
  ```js
579
579
  ignoreCustomFragments: [
580
- /<%[\s\S]*?%>/, // Unlimited quantifiers
581
- /<!--[\s\S]*?-->/, // Could cause issues with very long comments
582
- /\{\{.*?\}\}/, // Nested unlimited quantifiers
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 own processing time
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’s own code rather than the bundled minifiers.
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