html-minifier-next 3.2.1 → 4.0.2

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
@@ -97,7 +97,7 @@ const { minify } = require('html-minifier-next');
97
97
  })();
98
98
  ```
99
99
 
100
- See [the original blog post](http://perfectionkills.com/experimenting-with-html-minifier) for details of [how it works](http://perfectionkills.com/experimenting-with-html-minifier#how_it_works), [description of each option](http://perfectionkills.com/experimenting-with-html-minifier#options), [testing results](http://perfectionkills.com/experimenting-with-html-minifier#field_testing), and [conclusions](http://perfectionkills.com/experimenting-with-html-minifier#cost_and_benefits).
100
+ 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), [description of each option](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).
101
101
 
102
102
  For lint-like capabilities, take a look at [HTMLLint](https://github.com/kangax/html-lint).
103
103
 
@@ -110,9 +110,9 @@ Options can be used in config files (camelCase) or via CLI flags (kebab-case wit
110
110
  | Option (config/CLI) | Description | Default |
111
111
  | --- | --- | --- |
112
112
  | `caseSensitive`<br>`--case-sensitive` | Treat attributes in case-sensitive manner (useful for custom HTML elements) | `false` |
113
- | `collapseBooleanAttributes`<br>`--collapse-boolean-attributes` | [Omit attribute values from boolean attributes](http://perfectionkills.com/experimenting-with-html-minifier#collapse_boolean_attributes) | `false` |
113
+ | `collapseBooleanAttributes`<br>`--collapse-boolean-attributes` | [Omit attribute values from boolean attributes](https://perfectionkills.com/experimenting-with-html-minifier#collapse_boolean_attributes) | `false` |
114
114
  | `collapseInlineTagWhitespace`<br>`--collapse-inline-tag-whitespace` | Don’t leave any spaces between `display: inline;` elements when collapsing—use with `collapseWhitespace: true` | `false` |
115
- | `collapseWhitespace`<br>`--collapse-whitespace` | [Collapse whitespace that contributes to text nodes in a document tree](http://perfectionkills.com/experimenting-with-html-minifier#collapse_whitespace) | `false` |
115
+ | `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` |
116
116
  | `conservativeCollapse`<br>`--conservative-collapse` | Always collapse to 1 space (never remove it entirely)—use with `collapseWhitespace: true` | `false` |
117
117
  | `continueOnParseError`<br>`--continue-on-parse-error` | [Handle parse errors](https://html.spec.whatwg.org/multipage/parsing.html#parse-errors) instead of aborting | `false` |
118
118
  | `customAttrAssign`<br>`--custom-attr-assign` | Arrays of regexes that allow to support custom attribute assign expressions (e.g., `<div flex?="{{mode != cover}}"></div>`) | `[]` |
@@ -129,7 +129,7 @@ Options can be used in config files (camelCase) or via CLI flags (kebab-case wit
129
129
  | `keepClosingSlash`<br>`--keep-closing-slash` | Keep the trailing slash on void elements | `false` |
130
130
  | `maxInputLength`<br>`--max-input-length` | Maximum input length to prevent ReDoS attacks (disabled by default) | `undefined` |
131
131
  | `maxLineLength`<br>`--max-line-length` | Specify a maximum line length; compressed output will be split by newlines at valid HTML split-points | |
132
- | `minifyCSS`<br>`--minify-css` | Minify CSS in `style` elements and `style` attributes (uses [clean-css](https://github.com/jakubpawlowicz/clean-css)) | `false` (could be `true`, `Object`, `Function(text, type)`) |
132
+ | `minifyCSS`<br>`--minify-css` | Minify CSS in `style` elements and `style` attributes (uses [Lightning CSS](https://lightningcss.dev/)) | `false` (could be `true`, `Object`, `Function(text, type)`) |
133
133
  | `minifyJS`<br>`--minify-js` | Minify JavaScript in `script` elements and event attributes (uses [Terser](https://github.com/terser/terser)) | `false` (could be `true`, `Object`, `Function(text, inline)`) |
134
134
  | `minifyURLs`<br>`--minify-urls` | Minify URLs in various attributes (uses [relateurl](https://github.com/stevenvachon/relateurl)) | `false` (could be `String`, `Object`, `Function(text)`, `async Function(text)`) |
135
135
  | `noNewlinesBeforeTagClose`<br>`--no-newlines-before-tag-close` | Never add a newline before a tag that closes an element | `false` |
@@ -138,11 +138,11 @@ Options can be used in config files (camelCase) or via CLI flags (kebab-case wit
138
138
  | `processConditionalComments`<br>`--process-conditional-comments` | Process contents of conditional comments through minifier | `false` |
139
139
  | `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.) | `[]` |
140
140
  | `quoteCharacter`<br>`--quote-character` | Type of quote to use for attribute values (`'` or `"`) | |
141
- | `removeAttributeQuotes`<br>`--remove-attribute-quotes` | [Remove quotes around attributes when possible](http://perfectionkills.com/experimenting-with-html-minifier#remove_attribute_quotes) | `false` |
142
- | `removeComments`<br>`--remove-comments` | [Strip HTML comments](http://perfectionkills.com/experimenting-with-html-minifier#remove_comments) | `false` |
143
- | `removeEmptyAttributes`<br>`--remove-empty-attributes` | [Remove all attributes with whitespace-only values](http://perfectionkills.com/experimenting-with-html-minifier#remove_empty_or_blank_attributes) | `false` (could be `true`, `Function(attrName, tag)`) |
144
- | `removeEmptyElements`<br>`--remove-empty-elements` | [Remove all elements with empty contents](http://perfectionkills.com/experimenting-with-html-minifier#remove_empty_elements) | `false` |
145
- | `removeOptionalTags`<br>`--remove-optional-tags` | [Remove optional tags](http://perfectionkills.com/experimenting-with-html-minifier#remove_optional_tags) | `false` |
141
+ | `removeAttributeQuotes`<br>`--remove-attribute-quotes` | [Remove quotes around attributes when possible](https://perfectionkills.com/experimenting-with-html-minifier#remove_attribute_quotes) | `false` |
142
+ | `removeComments`<br>`--remove-comments` | [Strip HTML comments](https://perfectionkills.com/experimenting-with-html-minifier#remove_comments) | `false` |
143
+ | `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)`) |
144
+ | `removeEmptyElements`<br>`--remove-empty-elements` | [Remove all elements with empty contents](https://perfectionkills.com/experimenting-with-html-minifier#remove_empty_elements) | `false` |
145
+ | `removeOptionalTags`<br>`--remove-optional-tags` | [Remove optional tags](https://perfectionkills.com/experimenting-with-html-minifier#remove_optional_tags) | `false` |
146
146
  | `removeRedundantAttributes`<br>`--remove-redundant-attributes` | [Remove attributes when value matches default](https://meiert.com/blog/optional-html/#toc-attribute-values) | `false` |
147
147
  | `removeScriptTypeAttributes`<br>`--remove-script-type-attributes` | Remove `type="text/javascript"` from `script` elements; other `type` attribute values are left intact | `false` |
148
148
  | `removeStyleLinkTypeAttributes`<br>`--remove-style-link-type-attributes` | Remove `type="text/css"` from `style` and `link` elements; other `type` attribute values are left intact | `false` |
@@ -150,11 +150,50 @@ Options can be used in config files (camelCase) or via CLI flags (kebab-case wit
150
150
  | `sortAttributes`<br>`--sort-attributes` | [Sort attributes by frequency](#sorting-attributes-and-style-classes) | `false` |
151
151
  | `sortClassName`<br>`--sort-class-name` | [Sort style classes by frequency](#sorting-attributes-and-style-classes) | `false` |
152
152
  | `trimCustomFragments`<br>`--trim-custom-fragments` | Trim whitespace around `ignoreCustomFragments` | `false` |
153
- | `useShortDoctype`<br>`--use-short-doctype` | [Replaces the doctype with the short (HTML) doctype](http://perfectionkills.com/experimenting-with-html-minifier#use_short_doctype) | `false` |
153
+ | `useShortDoctype`<br>`--use-short-doctype` | [Replaces the doctype with the short (HTML) doctype](https://perfectionkills.com/experimenting-with-html-minifier#use_short_doctype) | `false` |
154
154
 
155
155
  ### Sorting attributes and style classes
156
156
 
157
- Minifier options like `sortAttributes` and `sortClassName` wont 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.
157
+ Minifier options like `sortAttributes` and `sortClassName` 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.
158
+
159
+ ### CSS minification with Lightning CSS
160
+
161
+ When `minifyCSS` is set to `true`, HTML Minifier Next uses [Lightning CSS](https://lightningcss.dev/) to minify CSS in `<style>` elements and `style` attributes. Lightning CSS provides excellent minification by default.
162
+
163
+ You can pass Lightning CSS configuration options by providing an object:
164
+
165
+ ```js
166
+ const result = await minify(html, {
167
+ minifyCSS: {
168
+ targets: {
169
+ // Browser targets for vendor prefix handling
170
+ chrome: 95,
171
+ firefox: 90,
172
+ safari: 14
173
+ },
174
+ unusedSymbols: ['unused-class', 'old-animation']
175
+ }
176
+ });
177
+ ```
178
+
179
+ Available Lightning CSS options when passed as an object:
180
+
181
+ * `targets`: Browser targets for vendor prefix optimization (e.g., `{ chrome: 95, firefox: 90 }`)
182
+ * `unusedSymbols`: Array of class names, IDs, keyframe names, and CSS variables to remove
183
+ * `errorRecovery`: Boolean to skip invalid rules instead of throwing errors (disabled in Lightning CSS, but enabled by default in HMN)
184
+ * `sourceMap`: Boolean to generate source maps
185
+
186
+ For advanced usage, you can also pass a function:
187
+
188
+ ```js
189
+ const result = await minify(html, {
190
+ minifyCSS: function(text, type) {
191
+ // `text`: CSS string to minify
192
+ // `type`: `inline` for style attributes, `media` for media queries, `undefined` for `<style>` elements
193
+ return yourCustomMinifier(text);
194
+ }
195
+ });
196
+ ```
158
197
 
159
198
  ## Minification comparison
160
199
 
@@ -162,25 +201,25 @@ How does HTML Minifier Next compare to other solutions, like [minimize](https://
162
201
 
163
202
  | Site | Original Size (KB) | HTML Minifier Next | minimize | html­compressor.com | htmlnano | minify-html |
164
203
  | --- | --- | --- | --- | --- | --- | --- |
165
- | [A List Apart](https://alistapart.com/) | 62 | **53** | 58 | 56 | 54 | 55 |
204
+ | [A List Apart](https://alistapart.com/) | 62 | **52** | 58 | 56 | 54 | 55 |
166
205
  | [Amazon](https://www.amazon.com/) | 206 | **195** | 203 | 200 | 196 | n/a |
167
- | [Apple](https://www.apple.com/) | 183 | **143** | 169 | 167 | 160 | 166 |
168
- | [BBC](https://www.bbc.co.uk/) | 689 | **633** | 683 | n/a | 646 | 648 |
169
- | [CSS-Tricks](https://css-tricks.com/) | 163 | **121** | 149 | 146 | 127 | 145 |
170
- | [ECMAScript](https://tc39.es/ecma262/) | 7233 | **6338** | 6610 | n/a | 6557 | 6563 |
171
- | [EFF](https://www.eff.org/) | 57 | **48** | 52 | 52 | 51 | 50 |
172
- | [FAZ](https://www.faz.net/aktuell/) | 1871 | 1747 | 1785 | n/a | **1647** | n/a |
173
- | [Frontend Dogma](https://frontenddogma.com/) | 119 | **114** | 128 | 119 | 126 | 119 |
206
+ | [Apple](https://www.apple.com/) | 153 | **116** | 140 | 140 | 133 | 138 |
207
+ | [BBC](https://www.bbc.co.uk/) | 590 | **534** | 585 | n/a | 553 | 555 |
208
+ | [CSS-Tricks](https://css-tricks.com/) | 165 | **124** | 151 | 147 | 129 | 146 |
209
+ | [ECMAScript](https://tc39.es/ecma262/) | 7238 | **6342** | 6614 | n/a | 6561 | 6567 |
210
+ | [EFF](https://www.eff.org/) | 57 | **48** | 52 | 51 | 51 | 49 |
211
+ | [FAZ](https://www.faz.net/aktuell/) | 1847 | 1722 | 1762 | n/a | **1628** | n/a |
212
+ | [Frontend Dogma](https://frontenddogma.com/) | 219 | **211** | 237 | 218 | 232 | 218 |
174
213
  | [Google](https://www.google.com/) | 18 | **16** | 18 | 18 | 17 | n/a |
175
- | [Ground News](https://ground.news/) | 3308 | **2946** | 3295 | n/a | 3042 | n/a |
214
+ | [Ground News](https://ground.news/) | 1709 | **1463** | 1696 | n/a | 1567 | n/a |
176
215
  | [HTML](https://html.spec.whatwg.org/multipage/) | 149 | **147** | 155 | 148 | 153 | 149 |
177
- | [Leanpub](https://leanpub.com/) | 1279 | **1063** | 1274 | n/a | 1070 | n/a |
216
+ | [Leanpub](https://leanpub.com/) | 1424 | **1190** | 1418 | n/a | 1197 | n/a |
178
217
  | [Mastodon](https://mastodon.social/explore) | 35 | **26** | 34 | 34 | 30 | 33 |
179
- | [MDN](https://developer.mozilla.org/en-US/) | 107 | **61** | 67 | 67 | 63 | n/a |
180
- | [Middle East Eye](https://www.middleeasteye.net/) | 224 | **197** | 204 | 205 | 204 | 202 |
181
- | [SitePoint](https://www.sitepoint.com/) | 485 | **354** | 481 | n/a | 423 | 465 |
182
- | [United Nations](https://www.un.org/en/) | 151 | **114** | 130 | 123 | 121 | 124 |
183
- | [W3C](https://www.w3.org/) | 50 | **36** | 41 | 39 | 38 | 39 |
218
+ | [MDN](https://developer.mozilla.org/en-US/) | 107 | **62** | 67 | 68 | 64 | n/a |
219
+ | [Middle East Eye](https://www.middleeasteye.net/) | 224 | **197** | 203 | 204 | 204 | 201 |
220
+ | [SitePoint](https://www.sitepoint.com/) | 536 | **405** | 532 | n/a | 474 | 516 |
221
+ | [United Nations](https://www.un.org/en/) | 151 | **113** | 130 | 123 | 121 | 125 |
222
+ | [W3C](https://www.w3.org/) | 50 | **36** | 41 | 39 | 39 | 39 |
184
223
 
185
224
  ## Examples
186
225
 
package/cli.js CHANGED
@@ -135,7 +135,7 @@ const mainOptions = {
135
135
  keepClosingSlash: 'Keep the trailing slash on void elements',
136
136
  maxInputLength: ['Maximum input length to prevent ReDoS attacks', parseValidInt('maxInputLength')],
137
137
  maxLineLength: ['Specify a maximum line length; compressed output will be split by newlines at valid HTML split-points', parseValidInt('maxLineLength')],
138
- minifyCSS: ['Minify CSS in “style” elements and “style” attributes (uses clean-css)', parseJSON],
138
+ minifyCSS: ['Minify CSS in “style” elements and “style” attributes (uses Lightning CSS)', parseJSON],
139
139
  minifyJS: ['Minify JavaScript in “script” elements and event attributes (uses Terser)', parseJSON],
140
140
  minifyURLs: ['Minify URLs in various attributes (uses relateurl)', parseJSON],
141
141
  noNewlinesBeforeTagClose: 'Never add a newline before a tag that closes an element',
@@ -2,7 +2,7 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- var CleanCSS = require('clean-css');
5
+ var lightningcss = require('lightningcss');
6
6
  var entities = require('entities');
7
7
  var RelateURL = require('relateurl');
8
8
  var terser = require('terser');
@@ -68,7 +68,7 @@ let IS_REGEX_CAPTURING_BROKEN = false;
68
68
  const empty = new CaseInsensitiveSet(['area', 'base', 'basefont', 'br', 'col', 'embed', 'frame', 'hr', 'img', 'input', 'isindex', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr']);
69
69
 
70
70
  // Inline elements
71
- const inline = new CaseInsensitiveSet(['a', 'abbr', 'acronym', 'applet', 'b', 'basefont', 'bdo', 'big', 'br', 'button', 'cite', 'code', 'del', 'dfn', 'em', 'font', 'i', 'iframe', 'img', 'input', 'ins', 'kbd', 'label', 'map', 'noscript', 'object', 'q', 's', 'samp', 'script', 'select', 'small', 'span', 'strike', 'strong', 'sub', 'sup', 'svg', 'textarea', 'tt', 'u', 'var']);
71
+ const inline = new CaseInsensitiveSet(['a', 'abbr', 'acronym', 'applet', 'b', 'basefont', 'bdo', 'big', 'br', 'button', 'cite', 'code', 'del', 'dfn', 'em', 'font', 'i', 'iframe', 'img', 'input', 'ins', 'kbd', 'label', 'map', 'noscript', 'object', 'q', 's', 'samp', 'script', 'select', 'selectedcontent', 'small', 'span', 'strike', 'strong', 'sub', 'sup', 'svg', 'textarea', 'tt', 'u', 'var']);
72
72
 
73
73
  // Elements that you can, intentionally, leave open
74
74
  // (and which close themselves)
@@ -920,12 +920,8 @@ function isContentSecurityPolicy(tag, attrs) {
920
920
  }
921
921
  }
922
922
 
923
- function ignoreCSS(id) {
924
- return '/* clean-css ignore:start */' + id + '/* clean-css ignore:end */';
925
- }
926
-
927
- // Wrap CSS declarations for CleanCSS > 3.x
928
- // See https://github.com/jakubpawlowicz/clean-css/issues/418
923
+ // Wrap CSS declarations for inline styles and media queries
924
+ // This ensures proper context for CSS minification
929
925
  function wrapCSS(text, type) {
930
926
  switch (type) {
931
927
  case 'inline':
@@ -988,7 +984,7 @@ const topLevelTags = new Set(['html', 'head', 'body']);
988
984
  const compactTags = new Set(['html', 'body']);
989
985
  const looseTags = new Set(['head', 'colgroup', 'caption']);
990
986
  const trailingTags = new Set(['dt', 'thead']);
991
- const htmlTags = new Set(['a', 'abbr', 'acronym', 'address', 'applet', 'area', 'article', 'aside', 'audio', 'b', 'base', 'basefont', 'bdi', 'bdo', 'bgsound', 'big', 'blink', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'command', 'content', 'data', 'datalist', 'dd', 'del', 'details', 'dfn', 'dialog', 'dir', 'div', 'dl', 'dt', 'element', 'em', 'embed', 'fieldset', 'figcaption', 'figure', 'font', 'footer', 'form', 'frame', 'frameset', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'iframe', 'image', 'img', 'input', 'ins', 'isindex', 'kbd', 'keygen', 'label', 'legend', 'li', 'link', 'listing', 'main', 'map', 'mark', 'marquee', 'menu', 'menuitem', 'meta', 'meter', 'multicol', 'nav', 'nobr', 'noembed', 'noframes', 'noscript', 'object', 'ol', 'optgroup', 'option', 'output', 'p', 'param', 'picture', 'plaintext', 'pre', 'progress', 'q', 'rb', 'rp', 'rt', 'rtc', 'ruby', 's', 'samp', 'script', 'section', 'select', 'shadow', 'small', 'source', 'spacer', 'span', 'strike', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'title', 'tr', 'track', 'tt', 'u', 'ul', 'var', 'video', 'wbr', 'xmp']);
987
+ const htmlTags = new Set(['a', 'abbr', 'acronym', 'address', 'applet', 'area', 'article', 'aside', 'audio', 'b', 'base', 'basefont', 'bdi', 'bdo', 'bgsound', 'big', 'blink', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'command', 'content', 'data', 'datalist', 'dd', 'del', 'details', 'dfn', 'dialog', 'dir', 'div', 'dl', 'dt', 'element', 'em', 'embed', 'fieldset', 'figcaption', 'figure', 'font', 'footer', 'form', 'frame', 'frameset', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'iframe', 'image', 'img', 'input', 'ins', 'isindex', 'kbd', 'keygen', 'label', 'legend', 'li', 'link', 'listing', 'main', 'map', 'mark', 'marquee', 'menu', 'menuitem', 'meta', 'meter', 'multicol', 'nav', 'nobr', 'noembed', 'noframes', 'noscript', 'object', 'ol', 'optgroup', 'option', 'output', 'p', 'param', 'picture', 'plaintext', 'pre', 'progress', 'q', 'rb', 'rp', 'rt', 'rtc', 'ruby', 's', 'samp', 'script', 'search', 'section', 'select', 'selectedcontent', 'shadow', 'small', 'source', 'spacer', 'span', 'strike', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'title', 'tr', 'track', 'tt', 'u', 'ul', 'var', 'video', 'wbr', 'xmp']);
992
988
 
993
989
  function canRemoveParentTag(optionalStartTag, tag) {
994
990
  switch (optionalStartTag) {
@@ -1257,7 +1253,7 @@ const processOptions = (inputOptions) => {
1257
1253
  return;
1258
1254
  }
1259
1255
 
1260
- const cleanCssOptions = typeof option === 'object' ? option : {};
1256
+ const lightningCssOptions = typeof option === 'object' ? option : {};
1261
1257
 
1262
1258
  options.minifyCSS = async function (text, type) {
1263
1259
  text = await replaceAsync(
@@ -1278,17 +1274,38 @@ const processOptions = (inputOptions) => {
1278
1274
 
1279
1275
  const inputCSS = wrapCSS(text, type);
1280
1276
 
1281
- return new Promise((resolve) => {
1282
- new CleanCSS(cleanCssOptions).minify(inputCSS, (_err, output) => {
1283
- if (output.errors.length > 0) {
1284
- output.errors.forEach(options.log);
1285
- resolve(text);
1286
- }
1287
-
1288
- const outputCSS = unwrapCSS(output.styles, type);
1289
- resolve(outputCSS);
1277
+ try {
1278
+ const result = lightningcss.transform({
1279
+ filename: 'input.css',
1280
+ code: Buffer.from(inputCSS),
1281
+ minify: true,
1282
+ errorRecovery: true,
1283
+ ...lightningCssOptions
1290
1284
  });
1291
- });
1285
+
1286
+ const outputCSS = unwrapCSS(result.code.toString(), type);
1287
+
1288
+ // If Lightning CSS removed significant content that looks like template syntax or UIDs, return original
1289
+ // This preserves:
1290
+ // 1. Template code like `<?php ?>`, `<%= %>`, `{{ }}`, etc. (contain `<` or `>` but not `CDATA`)
1291
+ // 2. UIDs representing custom fragments (only lowercase letters and digits, no spaces)
1292
+ // CDATA sections, HTML entities, and other invalid CSS are allowed to be removed
1293
+ const isCDATA = text.includes('<![CDATA[');
1294
+ const uidPattern = /[a-z0-9]{10,}/; // UIDs are long alphanumeric strings
1295
+ const hasUID = uidPattern.test(text) && !isCDATA; // Exclude CDATA from UID detection
1296
+ const looksLikeTemplate = (text.includes('<') || text.includes('>')) && !isCDATA;
1297
+
1298
+ // Preserve if output is empty and input had template syntax or UIDs
1299
+ // This catches cases where Lightning CSS removed content that should be preserved
1300
+ if (text.trim() && !outputCSS.trim() && (looksLikeTemplate || hasUID)) {
1301
+ return text;
1302
+ }
1303
+
1304
+ return outputCSS;
1305
+ } catch (err) {
1306
+ options.log && options.log(err);
1307
+ return text;
1308
+ }
1292
1309
  };
1293
1310
  } else if (key === 'minifyJS' && typeof option !== 'function') {
1294
1311
  if (!option) {
@@ -1526,23 +1543,7 @@ async function minifyHTML(value, options, partialMarkup) {
1526
1543
  return chunks[1] + uidAttr + index + uidAttr + chunks[2];
1527
1544
  });
1528
1545
 
1529
- const ids = [];
1530
- new CleanCSS().minify(wrapCSS(text, type)).warnings.forEach(function (warning) {
1531
- const match = uidPattern.exec(warning);
1532
- if (match) {
1533
- const id = uidAttr + match[2] + uidAttr;
1534
- text = text.replace(id, ignoreCSS(id));
1535
- ids.push(id);
1536
- }
1537
- });
1538
-
1539
- return fn(text, type).then(chunk => {
1540
- ids.forEach(function (id) {
1541
- chunk = chunk.replace(ignoreCSS(id), id);
1542
- });
1543
-
1544
- return chunk;
1545
- });
1546
+ return fn(text, type);
1546
1547
  };
1547
1548
  })(options.minifyCSS);
1548
1549
  }