html-minifier-next 8.5.3 → 8.7.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
@@ -4,7 +4,7 @@
4
4
 
5
5
  Your web page optimization precision tool: HTML Minifier Next (HMN) is a **highly effective, super-configurable, well-tested HTML minifier**, written in JavaScript, that also handles in-document CSS, JavaScript, and SVG minification.
6
6
 
7
- The project was based on [HTML Minifier Terser (HMT)](https://github.com/terser/html-minifier-terser), which in turn had been based on [Juriy “kangax” Zaytsev’s HTML Minifier (HM)](https://github.com/kangax/html-minifier). HMN is **the official successor to HTML Minifier**: It’s actively maintained, richer in features, easier to use, and significantly faster. Note that HMN is largely compatible with HM and HMT but isn’t fully drop-in—find [migration guidance in the changelog](https://github.com/j9t/html-minifier-next/blob/main/CHANGELOG.md).
7
+ The project was based on [HTML Minifier Terser (HMT)](https://github.com/terser/html-minifier-terser), which in turn had been based on [Juriy “kangax” Zaytsev’s HTML Minifier (HM)](https://github.com/kangax/html-minifier). HMN is **the official successor to HTML Minifier**: It’s actively maintained, richer in features, easier to use, and significantly faster ([HMN engineering philosophy](https://meiert.com/blog/html-minifier-next-engineering-philosophy/)). Note that HMN is largely compatible with HM and HMT but isn’t fully drop-in—find [migration guidance in the changelog](https://github.com/j9t/html-minifier-next/blob/main/CHANGELOG.md).
8
8
 
9
9
  ## Installation
10
10
 
@@ -32,7 +32,7 @@ Use `npx html-minifier-next --help` to check all available options:
32
32
 
33
33
  | Option | Description | Example |
34
34
  | --- | --- | --- |
35
- | `--zero`, `-z` | Minify all HTML files in the current folder and its subfolders in place (except node_modules), using comprehensive settings (standalone—flag is ignored when combined with other options) | `npx html-minifier-next --zero` |
35
+ | `--zero`, `-z` | Minify all HTML files in the current folder and its subfolders in place (except node_modules), using comprehensive settings (standalone—the flag is ignored when combined with other options) | `npx html-minifier-next --zero` |
36
36
  | `--input-dir <dir>`, `-I <dir>` | Specify an input directory | `--input-dir=src` |
37
37
  | `--ignore-dir <patterns>`, `-X <patterns>` | Exclude directories—relative to input directory—from processing (comma-separated, overrides config file setting) | `--ignore-dir=libs`, `--ignore-dir=libs,vendor,node_modules` |
38
38
  | `--output-dir <dir>`, `-O <dir>` | Specify an output directory | `--output-dir=dist` |
@@ -141,6 +141,7 @@ Options can be used in config files (camelCase) or via CLI flags (kebab-case wit
141
141
  | `collapseAttributeWhitespace`<br>`--collapse-attribute-whitespace` | Trim and collapse whitespace characters within attribute values | `false` |
142
142
  | `collapseBooleanAttributes`<br>`--collapse-boolean-attributes` | [Omit attribute values from boolean attributes](https://perfectionkills.com/experimenting-with-html-minifier/#collapse_boolean_attributes) | `false` |
143
143
  | `collapseInlineTagWhitespace`<br>`--collapse-inline-tag-whitespace` | Collapse whitespace more aggressively between inline elements—use with [`collapseWhitespace`](#combining-whitespace-options) | `false` |
144
+ | `collapseNoBreakSpaces`<br>`--collapse-no-break-spaces` | Remove whitespace next to no-break spaces, narrow no-break spaces, and figure spaces in text (e.g., `a &nbsp; b` → `a&nbsp;b`), keeping runs of them—use with [`collapseWhitespace`](#combining-whitespace-options) | `false` |
144
145
  | `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` |
145
146
  | `conservativeCollapse`<br>`--conservative-collapse` | Always collapse to one space (never remove it entirely)—use with [`collapseWhitespace`](#combining-whitespace-options) | `false` |
146
147
  | `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` |
@@ -211,6 +212,7 @@ HTML Minifier Next: Ignoring `conservativeCollapse`—use with `collapseWhitespa
211
212
  | `canMinifyJS` | `minifyJS`, and not [a function of your own](#javascript-minification) |
212
213
  | `canMinifySVG` | `minifySVG`, and not [a function of your own](#svg-minification) |
213
214
  | `collapseInlineTagWhitespace` | `collapseWhitespace` |
215
+ | `collapseNoBreakSpaces` | `collapseWhitespace` |
214
216
  | `conservativeCollapse` | `collapseWhitespace` |
215
217
  | `customEventAttributes` | `minifyJS` |
216
218
  | `inlineCustomElements` | `collapseWhitespace` |
@@ -220,11 +222,11 @@ HTML Minifier Next: Ignoring `conservativeCollapse`—use with `collapseWhitespa
220
222
  | `removeUnusedCSS` | `minifyCSS`, and not [a function of your own](#unused-css-removal) |
221
223
  | `trimCustomFragments` | `collapseWhitespace` |
222
224
 
223
- 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.
225
+ Passing the option `false`, or an empty array, asks for nothing and is not reported. `cacheCSS`, `cacheJS`, and `cacheSVG` are not included because they size a cache rather than transform markup, and therefore don’t change output.
224
226
 
225
227
  ### Combining whitespace options
226
228
 
227
- `collapseInlineTagWhitespace`, `conservativeCollapse`, and `preserveLineBreaks` are modifiers: They do nothing on their own, and only take effect when `collapseWhitespace` is enabled.
229
+ `collapseInlineTagWhitespace`, `collapseNoBreakSpaces`, `conservativeCollapse`, and `preserveLineBreaks` are modifiers: They do nothing on their own, and only take effect when `collapseWhitespace` is enabled.
228
230
 
229
231
  Given input
230
232
 
@@ -247,6 +249,8 @@ you get the following output (condensed, `\n` represents an actual line break):
247
249
 
248
250
  Where the modifiers disagree, the preserving one wins—`conservativeCollapse` and `preserveLineBreaks` do not let `collapseInlineTagWhitespace` remove a space or line break entirely.
249
251
 
252
+ Regardless of these options, whitespace at the start and end of the output goes, and so do lines that a removed comment or tag leaves with nothing but whitespace (blank lines of the source stay). Whitespace stays, however, where it’s kept verbatim (in `pre` or `textarea`, or in `pre`, `textarea`, or `script` content left open at the end), next to custom fragments where their handling keeps it (see `trimCustomFragments`), at either end of partial markup (cf. `partialMarkup`), and where `conservativeCollapse` or `preserveLineBreaks` keep it.
253
+
250
254
  ### Sorting attributes and style classes
251
255
 
252
256
  `sortAttributes` and `sortClassNames` reorder attributes and class names by frequency, so that repeated markup looks more alike. This doesn’t change the plain-text size of the output, only how well it compresses: `sortAttributes` makes Gzip and Brotli output slightly smaller on average, though not on every page (see [“Optimizing for compression”](#optimizing-for-compression)); `sortClassNames` doesn’t help, and alongside `sortAttributes` cancels most of its gain with Brotli.
@@ -503,12 +507,11 @@ HTML Minifier Next uses in-memory caches to improve performance when processing
503
507
  ```js
504
508
  const result = await minify(html, {
505
509
  minifyCSS: true,
510
+ cacheCSS: 750, // CSS cache size, default: 500
506
511
  minifyJS: true,
512
+ cacheJS: 250, // JS cache size, default: 500
507
513
  minifySVG: true,
508
- // Configure cache sizes (in number of entries)
509
- cacheCSS: 750, // CSS cache size, default: 500
510
- cacheJS: 250, // JS cache size, default: 500
511
- cacheSVG: 100 // SVG cache size, default: 500
514
+ cacheSVG: 100 // SVG cache size, default: 500
512
515
  });
513
516
  ```
514
517
 
@@ -676,7 +679,7 @@ By default, HMN parses markup into a complete tree structure, then modifies it (
676
679
 
677
680
  _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>`)_
678
681
 
679
- For partial HTML fragments (such as template includes, SSI fragments, or closing tags without opening tags), use the `partialMarkup: true` option. This preserves stray end tags (closing tags without corresponding opening tags) and prevents auto-closing of unclosed tags at the end of input. Note that normal HTML auto-closing rules still apply during parsing—for example, a closing parent tag will still auto-close its unclosed child elements.
682
+ For partial HTML fragments (such as template includes, SSI fragments, or closing tags without opening tags), use the `partialMarkup: true` option. This preserves stray end tags (closing tags without corresponding opening tags) and prevents auto-closing of unclosed tags at the end of input. Note that normal HTML auto-closing rules still apply during parsing—for example, a closing parent tag will still auto-close its unclosed child elements. Whitespace at the start and end of a fragment stays, too, as it may separate the fragment from what surrounds it (unless `collapseWhitespace` removes it).
680
683
 
681
684
  To validate complete HTML markup, use [the W3C validator](https://validator.w3.org/) or one of [several validator packages](https://meiert.com/blog/html-validator-packages/).
682
685
 
@@ -841,6 +844,8 @@ Parameters:
841
844
 
842
845
  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, or `--cold` when measuring changes to the CSS, JS, or SVG minification paths.
843
846
 
847
+ For changes of around 1% or less, use `npm run benchmark:ab -- main` instead: It times the working tree against any Git ref in the same session, runs both in turn per file, averages both loading orders, and reports `collapseWhitespace` on and off separately. Add `--aa` to compare the ref with a copy of itself—the noise floor any finding must clear. The script’s header lists further parameters.
848
+
844
849
  Reported times are the *fastest* iteration, not the median: Interference can only make a run slower, so the minimum is the most stable estimate. Each run also reports its noise—how far the reported figure moves between the first and second half of the iterations—and any delta smaller than that is marked `within noise` rather than shown as a win or a regression. Raise `--iterations` until the noise sits below the change you are trying to measure.
845
850
 
846
851
  #### Profiling
@@ -855,7 +860,7 @@ This writes a .cpuprofile file to the working directory. Load it with `npx speed
855
860
 
856
861
  ## Acknowledgements
857
862
 
858
- With many thanks to the previous authors of and contributors to HTML Minifier, especially [Juriy “kangax” Zaytsev](https://github.com/kangax), and to everyone who helped make this new edition better, particularly [Daniel Ruf](https://github.com/DanielRuf), [Jonas Geiler](https://github.com/jonasgeiler), and [Chris Morgan](https://github.com/chris-morgan)!
863
+ With many thanks to the previous authors of and contributors to HTML Minifier, especially [Juriy “kangax” Zaytsev](https://github.com/kangax), and to everyone who helped make this new edition better, particularly [Daniel Ruf](https://github.com/DanielRuf), [Jonas Geiler](https://github.com/jonasgeiler), [Chris Morgan](https://github.com/chris-morgan), and [Andreas Borutta](https://github.com/fermiongithub)!
859
864
 
860
865
  ***
861
866
 
package/cli.js CHANGED
@@ -330,7 +330,7 @@ function normalizeConfig(config) {
330
330
 
331
331
  /** @type {Record<string, any>} */
332
332
  let config = {};
333
- program.option('-z, --zero', 'Minify all HTML files in the current folder and its subfolders in place (except node_modules), using comprehensive settings (standalone—flag is ignored when combined with other options)');
333
+ program.option('-z, --zero', 'Minify all HTML files in the current folder and its subfolders in place (except node_modules), using comprehensive settings (standalone—the flag is ignored when combined with other options)');
334
334
  program.option('-I --input-dir <dir>', 'Specify an input directory');
335
335
  program.option('-X --ignore-dir <patterns>', 'Exclude directories—relative to input directory—from processing (comma-separated, overrides config file setting)');
336
336
  program.option('-O --output-dir <dir>', 'Specify an output directory');
@@ -113,6 +113,16 @@ export type MinifierOptions = {
113
113
  * Default: `false`
114
114
  */
115
115
  collapseInlineTagWhitespace?: boolean;
116
+ /**
117
+ * Remove whitespace next to a no-break space (`U+00A0`), narrow no-break
118
+ * space (`U+202F`), or figure space (`U+2007`) in text (for example
119
+ * `a &nbsp; b` → `a&nbsp;b`), which changes how the text renders. Runs of
120
+ * no-break spaces are kept.
121
+ * Must also enable `collapseWhitespace` to have effect.
122
+ *
123
+ * Default: `false`
124
+ */
125
+ collapseNoBreakSpaces?: boolean;
116
126
  /**
117
127
  * Collapse multiple whitespace characters into one where allowed. Also
118
128
  * controls trimming behaviour in several code paths.
@@ -1 +1 @@
1
- {"version":3,"file":"htmlminifier.d.ts","sourceRoot":"","sources":["../../src/htmlminifier.js"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"htmlminifier.d.ts","sourceRoot":"","sources":["../../src/htmlminifier.js"],"names":[],"mappings":"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"}
@@ -4,6 +4,7 @@ export type HTMLParserHandler = {
4
4
  chars?: Function;
5
5
  comment?: Function;
6
6
  doctype?: Function;
7
+ strayEnd?: Function;
7
8
  continueOnParseError?: boolean | undefined;
8
9
  partialMarkup?: boolean | undefined;
9
10
  wantsNextTag?: boolean | undefined;
@@ -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;AA6NvB,qBAAa,UAAU;IAMd,IAAI;IACJ,OAAO;IANd;;;OAGG;IACH,YAAY,IAAI,EAHL,MAGK,EAAE,OAAO,EAFd,iBAEc,EAGxB;IAEK,KAAK,kBA2yBV;CACF"}
1
+ {"version":3,"file":"htmlparser.d.ts","sourceRoot":"","sources":["../../src/htmlparser.js"],"names":[],"mappings":"AAeG,YAaG,iBAAiB,GAbV;IACR,KAAK,CAAC,WAAW;IACjB,GAAG,CAAC,WAAW;IACf,KAAK,CAAC,WAAW;IACjB,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,WAAW;IACnB,QAAQ,CAAC,WAAW;IACpB,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;AA6NvB,qBAAa,UAAU;IAMd,IAAI;IACJ,OAAO;IANd;;;OAGG;IACH,YAAY,IAAI,EAHL,MAGK,EAAE,OAAO,EAFd,iBAEc,EAGxB;IAEK,KAAK,kBA8yBV;CACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"option-definitions.d.ts","sourceRoot":"","sources":["../../../src/lib/option-definitions.js"],"names":[],"mappings":"AAAA;;GAEG;AAGA,YAAkB,gBAAgB,GAClC;;;;IAAmB,WAAW,EAAnB,MAAM,CACjB;;;;IAAoB,sBAAsB,AAA1C,CACA,EADW,MAAM,CACjB;;;;IAAmB,IAAI,EAAZ,MAAM,CACnB;CAAA,CAAA;AALD;;;;;GAKG;AAEH,+CAA+C;AAC/C,QAAA,MAAM,iBAAiB,EADZ,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAuMzC,CAAC;AAEF,QAAA,MAAM,cAAc;IAClB,qBAAqB;IACrB,oBAAoB;IAIpB,qBAAqB;IAIrB,wBAAwB;CACzB,CAAC;AAIF,OAAO,EACL,iBAAiB,EACjB,cAAc,EACf,CAAC"}
1
+ {"version":3,"file":"option-definitions.d.ts","sourceRoot":"","sources":["../../../src/lib/option-definitions.js"],"names":[],"mappings":"AAAA;;GAEG;AAGA,YAAkB,gBAAgB,GAClC;;;;IAAmB,WAAW,EAAnB,MAAM,CACjB;;;;IAAoB,sBAAsB,AAA1C,CACA,EADW,MAAM,CACjB;;;;IAAmB,IAAI,EAAZ,MAAM,CACnB;CAAA,CAAA;AALD;;;;;GAKG;AAEH,+CAA+C;AAC/C,QAAA,MAAM,iBAAiB,EADZ,MAAM,CAAC,MAAM,EAAE,gBAAgB,CA2MzC,CAAC;AAEF,QAAA,MAAM,cAAc;IAClB,qBAAqB;IACrB,oBAAoB;IAIpB,qBAAqB;IAIrB,wBAAwB;CACzB,CAAC;AAIF,OAAO,EACL,iBAAiB,EACjB,cAAc,EACf,CAAC"}
@@ -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":"AAAA;;GAEG;AAGH,OAAO,EAAE,GAAG,EAA+J,MAAM,YAAY,CAAC;AAQ1L,OAAQ,KAAA,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AASrE,YAA4D,UAAU,GAA5D;IAAC,WAAW,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CAAC,CAAY;AAOtE,YAA6C,SAAS,GAA5C;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAC,CAAW;AAUtD,YAqBG,gBAAgB,GArBT,IAAI,CAAC,eAAe,EAAE,QAAQ,GAAG,uBAAuB,GAAG,mBAAmB,GAAG,sBAAsB,GAAG,KAAK,GAAG,WAAW,GAAG,UAAU,GAAG,YAAY,GAAG,WAAW,GAAG,iBAAiB,CAAC,GAAG;IACvM,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IAC/B,GAAG,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,OAAO,CAAC;IAC/B,oBAAoB,EAAE,MAAM,EAAE,CAAC;IAC/B,qBAAqB,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,EAAE,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,KAAK,OAAO,CAAC;IAC7G,iBAAiB,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,EAAE,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,KAAK,OAAO,CAAC;IACzG,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,UAAU,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC3F,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,OAAO,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC3F,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACvD,SAAS,EAAE,CAAC,CAAC,UAAU,EAAE,MAAM,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,IAAI,CAAC;IACrE,YAAY,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,QAAQ,GAAG,OAAO,KAAK,OAAO,CAAC,GAAG,IAAI,CAAC;IAC5E,WAAW,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,GAAG,IAAI,CAAC;IAClE,YAAY,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,GAAG,IAAI,CAAC;IACjD,eAAe,EAAE;QAAC,QAAQ,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAC,GAAG,IAAI,CAAC;IAC7E,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IACtC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IACpC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,oBAAoB,CAAC,EAAE,OAAO,CAAA;CAC/B,CAAkB;AA/CtB,2EAA2E;AAI3E;;;;;;GAMG;AAEH;;;;;GAKG;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAIH,wCAAwC;AACxC,iBAAS,qBAAqB,CAAC,OAAO,EAD1B,gBAC0B,WAUrC;AA0BD,QAAA,MAAM,eAAe,aAA0H,CAAC;AAyB7I,YAAkB,gBAAgB,GAM3B,gBAAgB,EAAE,CAAC;AAZ7B;;;;;;;;;;;;;GAaG;AACH,QAAA,MAAM,kBAAkB;;;sBAhBV,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;;;;;;;IAoChC,CAAC;AAmEF;;;;GAIG;AACH,QAAA,MAAM,cAAc,iBAJT,eAAe,mGACf;IAAC,eAAe,CAAC,EAAE,WAAW,SAAS,CAAC;IAAC,SAAS,CAAC,EAAE,WAAW,SAAS,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,SAAS,CAAC;IAAC,OAAO,CAAC,EAAE,WAAW,SAAS,CAAC;IAAC,cAAc,CAAC,EAAE,GAAG,GAAG,SAAS,CAAC;IAAC,aAAa,CAAC,EAAE,GAAG,GAAG,SAAS,CAAC;IAAC,cAAc,CAAC,EAAE,GAAG,GAAG,SAAS,CAAA;CAAC,KAC5O,gBAukBZ,CAAC;AAIF,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,qBAAqB,EACrB,cAAc,EACf,CAAC"}
@@ -7,6 +7,14 @@ declare const trimWhitespace: (str: string) => string;
7
7
  declare function endsWithWhitespace(str: string): boolean;
8
8
  /** @param {string} str */
9
9
  declare function collapseWhitespaceAll(str: string): string;
10
+ /**
11
+ * @param {string} str
12
+ * @param {{decodeEntities?: boolean | undefined, preserveLineBreaks?: boolean | undefined}} options
13
+ */
14
+ declare function collapseNoBreakSpaces(str: string, options: {
15
+ decodeEntities?: boolean | undefined;
16
+ preserveLineBreaks?: boolean | undefined;
17
+ }): string;
10
18
  /**
11
19
  * @param {string} str
12
20
  * @param {{preserveLineBreaks?: boolean | undefined, conservativeCollapse?: boolean | undefined}} options
@@ -81,5 +89,5 @@ declare function collapseWhitespaceSmart(str: string, prevTag: string, nextTag:
81
89
  declare function canCollapseWhitespace(tag: string): boolean;
82
90
  /** @param {string} tag */
83
91
  declare function canTrimWhitespace(tag: string): boolean;
84
- export { buildInlineSets, defaultInlineSets, endsWithWhitespace, trimWhitespace, collapseWhitespaceAll, collapseWhitespace, collapseWhitespaceSmart, canCollapseWhitespace, canTrimWhitespace };
92
+ export { buildInlineSets, defaultInlineSets, endsWithWhitespace, trimWhitespace, collapseWhitespaceAll, collapseNoBreakSpaces, collapseWhitespace, collapseWhitespaceSmart, canCollapseWhitespace, canTrimWhitespace };
85
93
  //# sourceMappingURL=whitespace.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"whitespace.d.ts","sourceRoot":"","sources":["../../../src/lib/whitespace.js"],"names":[],"mappings":"AAAA;;GAEG;AAyBH,0BAA0B;AAC1B,QAAA,MAAM,cAAc,QADR,MAAM,WAQjB,CAAC;AAaF,0BAA0B;AAC1B,iBAAS,kBAAkB,CAAC,GAAG,EADnB,MACmB,WAE9B;AAaD,0BAA0B;AAC1B,iBAAS,qBAAqB,CAAC,GAAG,EADtB,MACsB,UAOjC;AAoBD;;;;;;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,UAcjF;AAwGE,YAAkB,UAAU,GAC5B;;;;IAAoB,MAAM,EAAnB,GAAG,CAAC,MAAM,CAAC,CAClB;;;;IAAoB,SAAS,EAAtB,GAAG,CAAC,MAAM,CAAC,CAClB;;;;IAAoB,MAAM,EAAnB,GAAG,CAAC,MAAM,CAAC,CAClB;;;;IAAoB,SAAS,EAAtB,GAAG,CAAC,MAAM,CAAC,CAClB;;;;IAAoB,YAAY,EAAzB,GAAG,CAAC,MAAM,CAAC,CACpB;CAAA,CAAA;AAPD;;;;;;;GAOG;AAEH;;;;;;GAMG;AACH,iBAAS,eAAe,CAAC,MAAM,EAJpB,GAAG,CAAC,MAAM,CAIU,EAAE,MAAM,EAH5B,GAAG,CAAC,MAAM,CAGkB,GAF1B,UAAU,CAWtB;AAGD,QAAA,MAAM,iBAAiB,YAA8F,CAAC;AAiBtH;;;;;;;;GAQG;AACH,iBAAS,uBAAuB,CAAC,GAAG,EARzB,MAQyB,EAAE,OAAO,EAPlC,MAOkC,EAAE,OAAO,EAN3C,MAM2C,EAAE,SAAS,EALtD,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAC,CAKkB,EAAE,SAAS,EAJjE,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAC,CAI6B,EAAE,OAAO,EAH1E;IAAC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAAC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAAC,2BAA2B,CAAC,EAAE,OAAO,CAAA;CAG1B,EAAE,UAAU,EAFtF,UAEsF,UA6DhG;AASD,0BAA0B;AAC1B,iBAAS,qBAAqB,CAAC,GAAG,EADtB,MACsB,WAEjC;AAED,0BAA0B;AAC1B,iBAAS,iBAAiB,CAAC,GAAG,EADlB,MACkB,WAE7B;AAID,OAAO,EACL,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,cAAc,EACd,qBAAqB,EACrB,kBAAkB,EAClB,uBAAuB,EACvB,qBAAqB,EACrB,iBAAiB,EAClB,CAAC"}
1
+ {"version":3,"file":"whitespace.d.ts","sourceRoot":"","sources":["../../../src/lib/whitespace.js"],"names":[],"mappings":"AAAA;;GAEG;AAyBH,0BAA0B;AAC1B,QAAA,MAAM,cAAc,QADR,MAAM,WAQjB,CAAC;AAaF,0BAA0B;AAC1B,iBAAS,kBAAkB,CAAC,GAAG,EADnB,MACmB,WAE9B;AAaD,0BAA0B;AAC1B,iBAAS,qBAAqB,CAAC,GAAG,EADtB,MACsB,UAOjC;AAwCD;;;GAGG;AACH,iBAAS,qBAAqB,CAAC,GAAG,EAHvB,MAGuB,EAAE,OAAO,EAFhC;IAAC,cAAc,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAAC,kBAAkB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;CAE/C,UAU1C;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,UAcjF;AAwGE,YAAkB,UAAU,GAC5B;;;;IAAoB,MAAM,EAAnB,GAAG,CAAC,MAAM,CAAC,CAClB;;;;IAAoB,SAAS,EAAtB,GAAG,CAAC,MAAM,CAAC,CAClB;;;;IAAoB,MAAM,EAAnB,GAAG,CAAC,MAAM,CAAC,CAClB;;;;IAAoB,SAAS,EAAtB,GAAG,CAAC,MAAM,CAAC,CAClB;;;;IAAoB,YAAY,EAAzB,GAAG,CAAC,MAAM,CAAC,CACpB;CAAA,CAAA;AAPD;;;;;;;GAOG;AAEH;;;;;;GAMG;AACH,iBAAS,eAAe,CAAC,MAAM,EAJpB,GAAG,CAAC,MAAM,CAIU,EAAE,MAAM,EAH5B,GAAG,CAAC,MAAM,CAGkB,GAF1B,UAAU,CAWtB;AAGD,QAAA,MAAM,iBAAiB,YAA8F,CAAC;AAiBtH;;;;;;;;GAQG;AACH,iBAAS,uBAAuB,CAAC,GAAG,EARzB,MAQyB,EAAE,OAAO,EAPlC,MAOkC,EAAE,OAAO,EAN3C,MAM2C,EAAE,SAAS,EALtD,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAC,CAKkB,EAAE,SAAS,EAJjE,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAC,CAI6B,EAAE,OAAO,EAH1E;IAAC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAAC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAAC,2BAA2B,CAAC,EAAE,OAAO,CAAA;CAG1B,EAAE,UAAU,EAFtF,UAEsF,UA6DhG;AASD,0BAA0B;AAC1B,iBAAS,qBAAqB,CAAC,GAAG,EADtB,MACsB,WAEjC;AAED,0BAA0B;AAC1B,iBAAS,iBAAiB,CAAC,GAAG,EADlB,MACkB,WAE7B;AAID,OAAO,EACL,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,cAAc,EACd,qBAAqB,EACrB,qBAAqB,EACrB,kBAAkB,EAClB,uBAAuB,EACvB,qBAAqB,EACrB,iBAAiB,EAClB,CAAC"}
@@ -67,6 +67,10 @@
67
67
  "description": "Collapse whitespace more aggressively between inline elements—use with `--collapse-whitespace`",
68
68
  "type": "boolean"
69
69
  },
70
+ "collapseNoBreakSpaces": {
71
+ "description": "Remove whitespace next to no-break spaces, narrow no-break spaces, and figure spaces in text (e.g., `a &nbsp; b` → `a&nbsp;b`), keeping runs of them—use with `--collapse-whitespace`",
72
+ "type": "boolean"
73
+ },
70
74
  "collapseWhitespace": {
71
75
  "description": "Collapse whitespace that contributes to text nodes in a document tree",
72
76
  "type": "boolean"
package/package.json CHANGED
@@ -17,11 +17,11 @@
17
17
  "@commitlint/cli": "^21.2.3",
18
18
  "@eslint/js": "^10.0.1",
19
19
  "@swc/core": "^1.16.2",
20
- "@types/node": "^26.6.2",
20
+ "@types/node": "^26.6.3",
21
21
  "eslint": "^10.11.0",
22
22
  "globals": "^17.12.0",
23
23
  "typescript": "^7.0.2",
24
- "vite": "^8.3.0"
24
+ "vite": "^8.3.1"
25
25
  },
26
26
  "engines": {
27
27
  "node": ">=22.13"
@@ -95,5 +95,5 @@
95
95
  },
96
96
  "type": "module",
97
97
  "types": "dist/types/htmlminifier.d.ts",
98
- "version": "8.5.3"
98
+ "version": "8.7.0"
99
99
  }
@@ -37,6 +37,7 @@ import {
37
37
  endsWithWhitespace,
38
38
  trimWhitespace,
39
39
  collapseWhitespaceAll,
40
+ collapseNoBreakSpaces,
40
41
  collapseWhitespace,
41
42
  collapseWhitespaceSmart,
42
43
  canCollapseWhitespace as defaultCanCollapseWhitespace,
@@ -185,6 +186,15 @@ import { toFragment, replaceCustomFragments } from './lib/fragments.js';
185
186
  *
186
187
  * Default: `false`
187
188
  *
189
+ * @prop {boolean} [collapseNoBreakSpaces]
190
+ * Remove whitespace next to a no-break space (`U+00A0`), narrow no-break
191
+ * space (`U+202F`), or figure space (`U+2007`) in text (for example
192
+ * `a &nbsp; b` → `a&nbsp;b`), which changes how the text renders. Runs of
193
+ * no-break spaces are kept.
194
+ * Must also enable `collapseWhitespace` to have effect.
195
+ *
196
+ * Default: `false`
197
+ *
188
198
  * @prop {boolean} [collapseWhitespace]
189
199
  * Collapse multiple whitespace characters into one where allowed. Also
190
200
  * controls trimming behaviour in several code paths.
@@ -630,6 +640,10 @@ const RE_END_TAG = /^<\//;
630
640
  const RE_WHITESPACE_ONLY = /^\s+$/;
631
641
  const RE_TABS = /\t/g;
632
642
  const RE_TRAILING_WHITESPACE = /\s$/;
643
+ const RE_LINE_BREAK_BLANKS_END = /(?:\r\n?|\n)[ \t\f]*$/;
644
+ const RE_BLANKS_LINE_BREAK_START = /^[ \t\f]*(?:\r\n?|\n)/;
645
+ const RE_BLANKS_START = /^[ \t\f]+/;
646
+ const RE_BLANKS_ONLY = /^[ \t\f]+$/;
633
647
 
634
648
  // Pre-compiled patterns for `htmlmin:ignore` block content analysis
635
649
  const RE_HTML_COMMENT_START = /^\s*<!--/;
@@ -1069,6 +1083,10 @@ async function minifyHTML(value, options, partialMarkup) {
1069
1083
  let optionalStartTag = '';
1070
1084
  let optionalEndTag = '';
1071
1085
  let optionalEndTagEmitted = false;
1086
+ // Whether a node was removed since the last output
1087
+ let removed = false;
1088
+ // Whether the latest text was kept verbatim, as in `pre` or `script`
1089
+ let textVerbatim = false;
1072
1090
  /** @type {string[]} */
1073
1091
  const ignoredMarkupChunks = [];
1074
1092
  /** @type {Array<RegExpExecArray | null>} */
@@ -1290,11 +1308,88 @@ async function minifyHTML(value, options, partialMarkup) {
1290
1308
  function bufferPush(/** @type {string} */ entry, /** @type {boolean} */ isTag) {
1291
1309
  buffer.push(entry);
1292
1310
  bufferTags?.push(isTag);
1311
+ if (entry) removed = false;
1293
1312
  }
1294
1313
 
1295
1314
  function bufferTruncate(/** @type {number} */ length) {
1296
1315
  buffer.length = Math.max(0, length);
1297
1316
  if (bufferTags) bufferTags.length = buffer.length;
1317
+ removed = true;
1318
+ }
1319
+
1320
+ // The nearest entries before and from `index` that aren’t empty, as removed nodes leave them
1321
+ /** @returns {[number, number]} */
1322
+ function findNeighbors(/** @type {number} */ index) {
1323
+ let before = index - 1;
1324
+ while (before >= 0 && buffer[before] === '') {
1325
+ before--;
1326
+ }
1327
+ let after = index;
1328
+ while (after < buffer.length && buffer[after] === '') {
1329
+ after++;
1330
+ }
1331
+ return [before, after];
1332
+ }
1333
+
1334
+ // With whitespace kept as is, a removed node must not leave a line of nothing but
1335
+ // whitespace behind: The text around the removal (at `index`) drops that line. The start
1336
+ // of the output counts as a line break, and blank lines of the source stay.
1337
+ function removeWhitespaceLine(/** @type {number} */ index) {
1338
+ const [first, after] = findNeighbors(index);
1339
+ // The end of the output is left to `trimOutputWhitespace`
1340
+ if (after === buffer.length) {
1341
+ return;
1342
+ }
1343
+ const next = buffer[after] ?? '';
1344
+ if (!RE_BLANKS_LINE_BREAK_START.test(next)) {
1345
+ return;
1346
+ }
1347
+ // Blanks between nodes removed from the same line belong to that line
1348
+ let before = first;
1349
+ while (before >= 0 && (buffer[before] === '' || RE_BLANKS_ONLY.test(buffer[before] ?? ''))) {
1350
+ before--;
1351
+ }
1352
+ const prev = buffer[before] ?? '';
1353
+ if (before >= 0 && !RE_LINE_BREAK_BLANKS_END.test(prev)) {
1354
+ return;
1355
+ }
1356
+ for (let i = before + 1; i <= first; i++) {
1357
+ buffer[i] = '';
1358
+ }
1359
+ // The line break goes on the side of the text before the removal, so that the text after
1360
+ // it keeps its own for a removal that may follow
1361
+ if (before < 0) {
1362
+ buffer[after] = next.replace(RE_BLANKS_LINE_BREAK_START, '');
1363
+ } else {
1364
+ buffer[before] = prev.replace(RE_LINE_BREAK_BLANKS_END, '');
1365
+ buffer[after] = next.replace(RE_BLANKS_START, '');
1366
+ }
1367
+ }
1368
+
1369
+ // Whitespace at either end of the output goes, but not verbatim whitespace, nor whitespace
1370
+ // that `conservativeCollapse` or `preserveLineBreaks` promise to keep, nor that of partial
1371
+ // markup, whose ends join other markup
1372
+ function trimOutputWhitespace() {
1373
+ if (options.partialMarkup || (options.collapseWhitespace && (options.conservativeCollapse || options.preserveLineBreaks))) {
1374
+ return;
1375
+ }
1376
+ // Tags start with `<` and end with `>`, so whitespace at either end belongs to text
1377
+ if (!textVerbatim) {
1378
+ let index = buffer.length - 1;
1379
+ while (index >= 0 && (buffer[index] === '' || RE_WS_ONLY.test(buffer[index] ?? ''))) {
1380
+ buffer[index--] = '';
1381
+ }
1382
+ if (index >= 0) {
1383
+ buffer[index] = (buffer[index] ?? '').replace(RE_WS_END, '');
1384
+ }
1385
+ }
1386
+ let index = 0;
1387
+ while (index < buffer.length && (buffer[index] === '' || RE_WS_ONLY.test(buffer[index] ?? ''))) {
1388
+ buffer[index++] = '';
1389
+ }
1390
+ if (index < buffer.length) {
1391
+ buffer[index] = (buffer[index] ?? '').replace(RE_WS_START, '');
1392
+ }
1298
1393
  }
1299
1394
 
1300
1395
  function removeStartTag() {
@@ -1322,14 +1417,7 @@ async function minifyHTML(value, options, partialMarkup) {
1322
1417
  // single character it is due: a line break under `preserveLineBreaks`, a no-break space
1323
1418
  // where the run holds one, a space under `conservativeCollapse`
1324
1419
  function mergeWhitespaceRuns(/** @type {number} */ index) {
1325
- let before = index - 1;
1326
- while (before >= 0 && buffer[before] === '') {
1327
- before--;
1328
- }
1329
- let after = index;
1330
- while (after < buffer.length && buffer[after] === '') {
1331
- after++;
1332
- }
1420
+ const [before, after] = findNeighbors(index);
1333
1421
  if (before < 0 || after >= buffer.length) {
1334
1422
  return;
1335
1423
  }
@@ -1347,7 +1435,8 @@ async function minifyHTML(value, options, partialMarkup) {
1347
1435
  }
1348
1436
  }
1349
1437
 
1350
- function removeEndTag() {
1438
+ // `insideNoTrim` stands in for the stack once the end tag that omits this one has left it
1439
+ function removeEndTag(/** @type {boolean} */ insideNoTrim = false) {
1351
1440
  let index = buffer.length - 1;
1352
1441
  while (index > 0 && !RE_END_TAG.test(buffer[index] ?? '')) {
1353
1442
  index--;
@@ -1355,13 +1444,17 @@ async function minifyHTML(value, options, partialMarkup) {
1355
1444
  // Text that follows the end tag is kept as is, so drop the tag alone. Whitespace kept
1356
1445
  // verbatim—in `pre`, in `textarea`, or wherever `canTrimWhitespace` says so—counts as
1357
1446
  // kept text too, no matter how whitespace is collapsed elsewhere.
1358
- const noTrim = stackNoTrimWhitespace.length > 0;
1447
+ const noTrim = insideNoTrim || stackNoTrimWhitespace.length > 0;
1359
1448
  if (index < buffer.length - 1 && (keepsWhitespace() || noTrim) && RE_END_TAG.test(buffer[index] ?? '')) {
1360
1449
  buffer.splice(index, 1);
1361
1450
  bufferTags?.splice(index, 1);
1362
- // Only collapsed whitespace can merge
1363
- if (options.collapseWhitespace && !noTrim) {
1364
- mergeWhitespaceRuns(index);
1451
+ // Collapsed whitespace can merge, kept whitespace can leave a line of only whitespace
1452
+ if (!noTrim) {
1453
+ if (options.collapseWhitespace) {
1454
+ mergeWhitespaceRuns(index);
1455
+ } else {
1456
+ removeWhitespaceLine(index);
1457
+ }
1365
1458
  }
1366
1459
  return;
1367
1460
  }
@@ -1442,7 +1535,7 @@ async function minifyHTML(value, options, partialMarkup) {
1442
1535
  // Multiple newlines are likely intentional formatting, single newline is often a template artifact
1443
1536
  // Treat CRLF (`\r\n`), CR (`\r`), and LF (`\n`) as single line-ending units
1444
1537
  if (textNextTag && textNextTag === '/' + topTag && /[^\r\n](?:\r\n|\r|\n)[ \t]*$/.test(text)) {
1445
- text = text.replace(/(?:\r\n|\r|\n)[ \t]*$/, '');
1538
+ text = text.replace(RE_LINE_BREAK_BLANKS_END, '');
1446
1539
  }
1447
1540
  }
1448
1541
  }
@@ -1511,6 +1604,9 @@ async function minifyHTML(value, options, partialMarkup) {
1511
1604
  if (!stackNoCollapseWhitespace.length && textNextTag !== 'html' && !(textPrevTag && textNextTag)) {
1512
1605
  text = collapseWhitespace(text, options, false, false, true);
1513
1606
  }
1607
+ if (options.collapseNoBreakSpaces && !stackNoCollapseWhitespace.length && !holdsRawText()) {
1608
+ text = collapseNoBreakSpaces(text, options);
1609
+ }
1514
1610
  }
1515
1611
  return text;
1516
1612
  }
@@ -1556,7 +1652,11 @@ async function minifyHTML(value, options, partialMarkup) {
1556
1652
  }
1557
1653
  }
1558
1654
  charsPrevTag = /^\s*$/.test(text) ? textPrevTag : 'comment';
1559
- if (options.decodeEntities && text && !holdsRawText()) {
1655
+ const rawText = Boolean(text) && holdsRawText();
1656
+ if (text) {
1657
+ textVerbatim = rawText || stackNoTrimWhitespace.length > 0;
1658
+ }
1659
+ if (options.decodeEntities && text && !rawText) {
1560
1660
  // Escape any `&` symbols that start either:
1561
1661
  // 1. a legacy-named character reference (i.e., one that doesn’t end with `;`)
1562
1662
  // 2. or any other character reference (i.e., one that does end with `;`)
@@ -1581,7 +1681,11 @@ async function minifyHTML(value, options, partialMarkup) {
1581
1681
  if (text) {
1582
1682
  hasChars = true;
1583
1683
  }
1684
+ const followsRemoval = removed && Boolean(text) && !options.collapseWhitespace && !stackNoTrimWhitespace.length;
1584
1685
  bufferPush(text, false);
1686
+ if (followsRemoval) {
1687
+ removeWhitespaceLine(buffer.length - 1);
1688
+ }
1585
1689
  }
1586
1690
 
1587
1691
  // Comment finalization (sync): Optional tag handling, `htmlmin:ignore` whitespace collapsing, buffer push
@@ -1845,19 +1949,18 @@ async function minifyHTML(value, options, partialMarkup) {
1845
1949
  optionalEndTagEmitted = false;
1846
1950
  }
1847
1951
 
1848
- // Set whitespace flags for nested tags (e.g., `<code>` within a `<pre>`)
1849
- if (options.collapseWhitespace) {
1850
- if (!stackNoTrimWhitespace.length) {
1851
- squashTrailingWhitespace(tag);
1952
+ // Set whitespace flags for nested tags (e.g., `<code>` within a `<pre>`); verbatim
1953
+ // whitespace is tracked without `collapseWhitespace` too, as `removeWhitespaceLine` spares it
1954
+ if (options.collapseWhitespace && !stackNoTrimWhitespace.length) {
1955
+ squashTrailingWhitespace(tag);
1956
+ }
1957
+ if (!unary) {
1958
+ if (!canTrimWhitespace(tag, attrs) || stackNoTrimWhitespace.length) {
1959
+ stackNoTrimWhitespace.push(tag);
1960
+ if (tag === 'pre' || tag === 'textarea') preTextareaDepth++;
1852
1961
  }
1853
- if (!unary) {
1854
- if (!canTrimWhitespace(tag, attrs) || stackNoTrimWhitespace.length) {
1855
- stackNoTrimWhitespace.push(tag);
1856
- if (tag === 'pre' || tag === 'textarea') preTextareaDepth++;
1857
- }
1858
- if (!canCollapseWhitespace(tag, attrs) || stackNoCollapseWhitespace.length) {
1859
- stackNoCollapseWhitespace.push(tag);
1860
- }
1962
+ if (options.collapseWhitespace && (!canCollapseWhitespace(tag, attrs) || stackNoCollapseWhitespace.length)) {
1963
+ stackNoCollapseWhitespace.push(tag);
1861
1964
  }
1862
1965
  }
1863
1966
 
@@ -1991,19 +2094,18 @@ async function minifyHTML(value, options, partialMarkup) {
1991
2094
  }
1992
2095
 
1993
2096
  // Check if current tag is in a whitespace stack
1994
- if (options.collapseWhitespace) {
1995
- if (stackNoTrimWhitespace.length) {
1996
- if (tag === stackNoTrimWhitespace[stackNoTrimWhitespace.length - 1]) {
1997
- if (tag === 'pre' || tag === 'textarea') preTextareaDepth--;
1998
- stackNoTrimWhitespace.pop();
1999
- }
2000
- } else {
2001
- squashTrailingWhitespace('/' + tag);
2002
- }
2003
- if (stackNoCollapseWhitespace.length &&
2004
- tag === stackNoCollapseWhitespace[stackNoCollapseWhitespace.length - 1]) {
2005
- stackNoCollapseWhitespace.pop();
2097
+ const insideNoTrim = stackNoTrimWhitespace.length > 0;
2098
+ if (stackNoTrimWhitespace.length) {
2099
+ if (tag === stackNoTrimWhitespace[stackNoTrimWhitespace.length - 1]) {
2100
+ if (tag === 'pre' || tag === 'textarea') preTextareaDepth--;
2101
+ stackNoTrimWhitespace.pop();
2006
2102
  }
2103
+ } else if (options.collapseWhitespace) {
2104
+ squashTrailingWhitespace('/' + tag);
2105
+ }
2106
+ if (stackNoCollapseWhitespace.length &&
2107
+ tag === stackNoCollapseWhitespace[stackNoCollapseWhitespace.length - 1]) {
2108
+ stackNoCollapseWhitespace.pop();
2007
2109
  }
2008
2110
 
2009
2111
  let isElementEmpty = false;
@@ -2025,7 +2127,7 @@ async function minifyHTML(value, options, partialMarkup) {
2025
2127
  // (https://html.spec.whatwg.org/multipage/syntax.html#optional-tags);
2026
2128
  // except for `</dt>` or `</thead>`, end tags may be omitted if no more content in parent element
2027
2129
  if (tag && optionalEndTag && optionalEndTagEmitted && !trailingElements.has(optionalEndTag) && (optionalEndTag !== 'p' || (!pInlineElements.has(tag) && !tag.includes('-')))) {
2028
- removeEndTag();
2130
+ removeEndTag(insideNoTrim);
2029
2131
  }
2030
2132
  optionalEndTag = optionalEndTags.has(tag) ? tag : '';
2031
2133
  optionalEndTagEmitted = true;
@@ -2132,12 +2234,16 @@ async function minifyHTML(value, options, partialMarkup) {
2132
2234
  text = prefix + text + suffix;
2133
2235
  } else {
2134
2236
  text = '';
2237
+ removed = true;
2135
2238
  }
2136
2239
  } else {
2137
2240
  text = prefix + text + suffix;
2138
2241
  }
2139
2242
  commentFinalize(text);
2140
2243
  },
2244
+ strayEnd: function () {
2245
+ removed = true;
2246
+ },
2141
2247
  doctype: function (/** @type {string} */ doctype) {
2142
2248
  bufferPush(options.useShortDoctype
2143
2249
  ? '<!doctype' +
@@ -2177,9 +2283,12 @@ async function minifyHTML(value, options, partialMarkup) {
2177
2283
  removeEndTag();
2178
2284
  }
2179
2285
  }
2286
+ // Collapsing also trims whitespace before trailing inline end tags; trimming the output
2287
+ // catches what it leaves at either end, like a space after a removed leading comment
2180
2288
  if (options.collapseWhitespace) {
2181
2289
  squashTrailingWhitespace('br');
2182
2290
  }
2291
+ trimOutputWhitespace();
2183
2292
 
2184
2293
  return joinResultSegments(buffer, bufferTags ?? [], [uidAttr, uidIgnore].filter(marker => marker !== undefined), options, uidPattern
2185
2294
  ? function (/** @type {string} */ str) {
package/src/htmlparser.js CHANGED
@@ -19,6 +19,7 @@ import { endlessRawTextElements, escapableRawTextElements, genericRawTextElement
19
19
  * chars?: Function,
20
20
  * comment?: Function,
21
21
  * doctype?: Function,
22
+ * strayEnd?: Function,
22
23
  * continueOnParseError?: boolean | undefined,
23
24
  * partialMarkup?: boolean | undefined,
24
25
  * wantsNextTag?: boolean | undefined,
@@ -1063,6 +1064,9 @@ export class HTMLParser {
1063
1064
  if (handler.end) {
1064
1065
  handler.end(tagName, []);
1065
1066
  }
1067
+ } else if (handler.strayEnd) {
1068
+ // An end tag with nothing to close is dropped
1069
+ handler.strayEnd();
1066
1070
  }
1067
1071
  // Returned so the parse loop can skip lowercasing the name again
1068
1072
  return lowerTagName;
@@ -39,6 +39,10 @@ const optionDefinitions = {
39
39
  description: 'Collapse whitespace more aggressively between inline elements—use with `--collapse-whitespace`',
40
40
  type: 'boolean'
41
41
  },
42
+ collapseNoBreakSpaces: {
43
+ description: 'Remove whitespace next to no-break spaces, narrow no-break spaces, and figure spaces in text (e.g., `a &nbsp; b` → `a&nbsp;b`), keeping runs of them—use with `--collapse-whitespace`',
44
+ type: 'boolean'
45
+ },
42
46
  collapseWhitespace: {
43
47
  description: 'Collapse whitespace that contributes to text nodes in a document tree',
44
48
  type: 'boolean'
@@ -138,6 +138,7 @@ const optionDependencies = [
138
138
  { option: 'canMinifyJS', requires: 'minifyJS', unusable: replacedByOwnFunction('minifyJS'), clear: true },
139
139
  { option: 'canMinifySVG', requires: 'minifySVG', unusable: replacedByOwnFunction('minifySVG'), clear: true },
140
140
  { option: 'collapseInlineTagWhitespace', requires: 'collapseWhitespace' },
141
+ { option: 'collapseNoBreakSpaces', requires: 'collapseWhitespace' },
141
142
  { option: 'conservativeCollapse', requires: 'collapseWhitespace' },
142
143
  { option: 'customEventAttributes', requires: 'minifyJS' },
143
144
  { option: 'inlineCustomElements', requires: 'collapseWhitespace' },
@@ -70,7 +70,7 @@ function unescapeIdentifier(identifier) {
70
70
  if (!hex) {
71
71
  return literal;
72
72
  }
73
- // Per CSS Syntax spec, a null, surrogate, or out-of-range escape becomes U+FFFD
73
+ // Per CSS Syntax spec, a null, surrogate, or out-of-range escape becomes `U+FFFD`
74
74
  const code = parseInt(hex, 16);
75
75
  return (code === 0 || code > 0x10FFFF || (code >= 0xD800 && code <= 0xDFFF))
76
76
  ? '\uFFFD'
@@ -88,6 +88,44 @@ function collapseWhitespaceAllKnown(str) {
88
88
  return str.replace(RE_ALL_WS_NBSP, collapseRun);
89
89
  }
90
90
 
91
+ // Collapse whitespace into adjacent no-break spaces
92
+
93
+ // A no-break space (`U+00A0`), narrow no-break space (`U+202F`), or figure space (`U+2007`),
94
+ // literal or as a character reference; numeric references and `&nbsp` also work without
95
+ // the semicolon in text
96
+ const RE_NO_BREAK_SPACE_WS = /([ \n\r\t\f]*)(\xA0|\u202F|\u2007|&(?:nbsp;?|NonBreakingSpace;|numsp;|#0*(?:160|8239|8199)(?![0-9]);?|#[xX]0*(?:[aA]0|202[fF]|2007)(?![0-9a-fA-F]);?))([ \n\r\t\f]*)/g;
97
+ // Decoded text holds references only as text, so there the characters alone count
98
+ const RE_NO_BREAK_SPACE_CHAR_WS = /([ \n\r\t\f]*)([\xA0\u202F\u2007])([ \n\r\t\f]*)/g;
99
+ const RE_LINE_BREAK = /[\n\r]/;
100
+
101
+ /**
102
+ * @param {string} _match
103
+ * @param {string} before
104
+ * @param {string} noBreakSpace
105
+ * @param {string} after
106
+ */
107
+ function keepLineBreaks(_match, before, noBreakSpace, after) {
108
+ return (RE_LINE_BREAK.test(before) ? before : '') + noBreakSpace + (RE_LINE_BREAK.test(after) ? after : '');
109
+ }
110
+
111
+ // Callers pass text whose whitespace runs are already collapsed, as each match
112
+ // rescans the run in front of it
113
+ /**
114
+ * @param {string} str
115
+ * @param {{decodeEntities?: boolean | undefined, preserveLineBreaks?: boolean | undefined}} options
116
+ */
117
+ function collapseNoBreakSpaces(str, options) {
118
+ const decoded = Boolean(options.decodeEntities);
119
+ if (!str || (str.indexOf('\xA0') === -1 && str.indexOf('\u202F') === -1 && str.indexOf('\u2007') === -1 && (decoded || str.indexOf('&') === -1))) {
120
+ return str;
121
+ }
122
+ const pattern = decoded ? RE_NO_BREAK_SPACE_CHAR_WS : RE_NO_BREAK_SPACE_WS;
123
+ if (options.preserveLineBreaks) {
124
+ return str.replace(pattern, keepLineBreaks);
125
+ }
126
+ return str.replace(pattern, '$2');
127
+ }
128
+
91
129
  // Collapse whitespace with options
92
130
 
93
131
  /**
@@ -356,6 +394,7 @@ export {
356
394
  endsWithWhitespace,
357
395
  trimWhitespace,
358
396
  collapseWhitespaceAll,
397
+ collapseNoBreakSpaces,
359
398
  collapseWhitespace,
360
399
  collapseWhitespaceSmart,
361
400
  canCollapseWhitespace,