html-minifier-next 4.14.0 → 4.14.3

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
@@ -14,6 +14,12 @@ From npm for use as a command line app:
14
14
  npm i -g html-minifier-next
15
15
  ```
16
16
 
17
+ Or use directly with npx (no installation required):
18
+
19
+ ```shell
20
+ npx html-minifier-next --help
21
+ ```
22
+
17
23
  From npm for programmatic use:
18
24
 
19
25
  ```shell
@@ -67,7 +73,7 @@ module.exports = {
67
73
 
68
74
  **Using a configuration file:**
69
75
 
70
- ```bash
76
+ ```shell
71
77
  # Specify config file
72
78
  html-minifier-next --config-file=html-minifier.json --input-dir=src --output-dir=dist
73
79
 
@@ -116,7 +122,7 @@ To review the specific options set, [presets.js](https://github.com/j9t/html-min
116
122
 
117
123
  **Using presets:**
118
124
 
119
- ```bash
125
+ ```shell
120
126
  # Via CLI flag
121
127
  html-minifier-next --preset conservative input.html
122
128
 
@@ -237,7 +243,7 @@ You can choose between different JS minifiers using the `engine` field:
237
243
  ```js
238
244
  const result = await minify(html, {
239
245
  minifyJS: {
240
- engine: 'swc', // Use swc for faster minification
246
+ engine: 'swc', // Use SWC for faster minification
241
247
  // SWC-specific options here
242
248
  }
243
249
  });
@@ -248,12 +254,14 @@ const result = await minify(html, {
248
254
  * `terser` (default): The standard JavaScript minifier with excellent compression
249
255
  * [`swc`](https://swc.rs/): Rust-based minifier that’s significantly faster than Terser (requires separate installation)
250
256
 
251
- **To use swc**, install it as a dependency:
257
+ **To use SWC**, install it as a dependency:
252
258
 
253
- ```bash
259
+ ```shell
254
260
  npm i @swc/core
255
261
  ```
256
262
 
263
+ (Build-only users may want to install it as a dev dependency: `npm i -D @swc/core`.)
264
+
257
265
  **Important:** Inline event handlers (e.g., `onclick="return false"`) always use Terser regardless of the `engine` setting, as SWC doesn’t support bare return statements. This is handled automatically—you don’t need to do anything special.
258
266
 
259
267
  You can pass engine-specific configuration options:
@@ -297,31 +305,33 @@ How does HTML Minifier Next compare to other minifiers? (All with the most aggre
297
305
  | --- | --- | --- | --- | --- | --- | --- | --- | --- |
298
306
  | [A List Apart](https://alistapart.com/) | 59 | **50** | **50** | 51 | 52 | 51 | 54 | 52 |
299
307
  | [Apple](https://www.apple.com/) | 266 | **207** | **207** | 236 | 239 | 240 | 242 | 243 |
300
- | [BBC](https://www.bbc.co.uk/) | 701 | **636** | 646 | 658 | 659 | 660 | 695 | n/a |
301
- | [CERN](https://home.cern/) | 152 | 85 | **84** | 91 | 91 | 91 | 93 | 96 |
302
- | [CSS-Tricks](https://css-tricks.com/) | 162 | 121 | **120** | 127 | 143 | 143 | 148 | 145 |
303
- | [ECMAScript](https://tc39.es/ecma262/) | 7250 | **6352** | **6352** | 6573 | 6455 | 6578 | 6626 | n/a |
308
+ | [BBC](https://www.bbc.co.uk/) | 727 | **661** | 671 | 683 | 683 | 685 | 721 | n/a |
309
+ | [CERN](https://home.cern/) | 152 | **83** | 84 | 91 | 91 | 91 | 93 | 96 |
310
+ | [CSS-Tricks](https://css-tricks.com/) | 162 | 122 | **120** | 127 | 143 | 143 | 148 | 145 |
311
+ | [ECMAScript](https://tc39.es/ecma262/) | 7250 | **6354** | **6354** | 6573 | 6455 | 6578 | 6626 | n/a |
304
312
  | [EDRi](https://edri.org/) | 80 | **59** | 60 | 70 | 70 | 71 | 75 | 73 |
305
- | [EFF](https://www.eff.org/) | 55 | **45** | 46 | 49 | 48 | 48 | 50 | 50 |
313
+ | [EFF](https://www.eff.org/) | 55 | **45** | 47 | 49 | 48 | 48 | 50 | 50 |
306
314
  | [European Alternatives](https://european-alternatives.eu/) | 48 | **30** | **30** | 32 | 32 | 32 | 32 | 32 |
307
- | [FAZ](https://www.faz.net/aktuell/) | 1603 | 1494 | 1499 | **1437** | 1526 | 1538 | 1549 | n/a |
315
+ | [FAZ](https://www.faz.net/aktuell/) | 1591 | 1486 | 1491 | **1427** | 1515 | 1526 | 1538 | n/a |
308
316
  | [French Tech](https://lafrenchtech.gouv.fr/) | 152 | **122** | **122** | 126 | 125 | 125 | 132 | 127 |
309
- | [Frontend Dogma](https://frontenddogma.com/) | 224 | **214** | 216 | 237 | 222 | 224 | 243 | 223 |
317
+ | [Frontend Dogma](https://frontenddogma.com/) | 224 | **214** | 216 | 237 | 222 | 224 | 243 | 224 |
310
318
  | [Google](https://www.google.com/) | 18 | **16** | 17 | 17 | 17 | 17 | 18 | 18 |
311
- | [Ground News](https://ground.news/) | 1713 | **1476** | 1477 | 1577 | 1602 | 1607 | 1699 | n/a |
319
+ | [Ground News](https://ground.news/) | 2358 | **2076** | 2077 | 2173 | 2198 | 2202 | 2345 | n/a |
312
320
  | [HTML Living Standard](https://html.spec.whatwg.org/multipage/) | 149 | **147** | **147** | 153 | **147** | 149 | 155 | 149 |
313
321
  | [Igalia](https://www.igalia.com/) | 50 | **34** | **34** | 36 | 36 | 36 | 37 | 37 |
322
+ | [Leanpub](https://leanpub.com/) | 222 | 192 | **191** | 207 | 207 | 207 | 217 | 219 |
314
323
  | [Mastodon](https://mastodon.social/explore) | 37 | **28** | **28** | 32 | 35 | 35 | 36 | 36 |
315
324
  | [MDN](https://developer.mozilla.org/en-US/) | 109 | **62** | **62** | 64 | 65 | 65 | 68 | 68 |
316
- | [Middle East Eye](https://www.middleeasteye.net/) | 222 | **195** | **195** | 202 | 200 | 200 | 202 | 203 |
325
+ | [Middle East Eye](https://www.middleeasteye.net/) | 222 | **196** | **196** | 202 | 200 | 200 | 202 | 203 |
317
326
  | [Nielsen Norman Group](https://www.nngroup.com/) | 86 | 74 | 74 | **55** | 74 | 75 | 77 | 76 |
318
- | [SitePoint](https://www.sitepoint.com/) | 501 | **370** | **370** | 442 | 475 | 480 | 498 | n/a |
327
+ | [SitePoint](https://www.sitepoint.com/) | 491 | **360** | **360** | 431 | 465 | 470 | 488 | n/a |
319
328
  | [TetraLogical](https://tetralogical.com/) | 44 | 38 | 38 | **35** | 38 | 39 | 39 | 39 |
320
329
  | [TPGi](https://www.tpgi.com/) | 175 | **159** | 161 | 160 | 164 | 166 | 172 | 172 |
330
+ | [United Nations](https://www.un.org/en/) | 152 | **113** | 114 | 121 | 125 | 125 | 131 | 124 |
321
331
  | [W3C](https://www.w3.org/) | 50 | **36** | **36** | 39 | 38 | 38 | 41 | 39 |
322
- | **Average processing time** | | 256 ms (24/24) | 352 ms (24/24) | 167 ms (24/24) | 54 ms (24/24) | **15 ms (24/24)** | 339 ms (24/24) | 3288 ms (19/24) |
332
+ | **Average processing time** | | 259 ms (26/26) | 375 ms (26/26) | 164 ms (26/26) | 54 ms (26/26) | **16 ms (26/26)** | 327 ms (26/26) | 1497 ms (21/26) |
323
333
 
324
- (Last updated: Dec 20, 2025)
334
+ (Last updated: Dec 21, 2025)
325
335
  <!-- End auto-generated -->
326
336
 
327
337
  ## Examples
@@ -330,13 +340,19 @@ How does HTML Minifier Next compare to other minifiers? (All with the most aggre
330
340
 
331
341
  **Sample command line:**
332
342
 
333
- ```bash
343
+ ```shell
334
344
  html-minifier-next --collapse-whitespace --remove-comments --minify-js --input-dir=. --output-dir=example
335
345
  ```
336
346
 
347
+ Another example, using npx:
348
+
349
+ ```shell
350
+ npx html-minifier-next --input-dir=test --file-ext html --preset comprehensive --output-dir example
351
+ ```
352
+
337
353
  **Process specific files and directories:**
338
354
 
339
- ```bash
355
+ ```shell
340
356
  # Process only HTML files
341
357
  html-minifier-next --collapse-whitespace --input-dir=src --output-dir=dist --file-ext=html
342
358
 
@@ -354,7 +370,7 @@ html-minifier-next --collapse-whitespace --input-dir=src --output-dir=dist
354
370
 
355
371
  **Exclude directories from processing:**
356
372
 
357
- ```bash
373
+ ```shell
358
374
  # Ignore a single directory
359
375
  html-minifier-next --collapse-whitespace --input-dir=src --output-dir=dist --ignore-dir=libs
360
376
 
@@ -367,7 +383,7 @@ html-minifier-next --collapse-whitespace --input-dir=src --output-dir=dist --ign
367
383
 
368
384
  **Dry run mode (preview outcome without writing files):**
369
385
 
370
- ```bash
386
+ ```shell
371
387
  # Preview with output file
372
388
  html-minifier-next input.html -o output.html --dry --collapse-whitespace
373
389
 
@@ -382,7 +398,7 @@ html-minifier-next --input-dir=src --output-dir=dist --dry --collapse-whitespace
382
398
 
383
399
  **Verbose mode (show detailed processing information):**
384
400
 
385
- ```bash
401
+ ```shell
386
402
  # Show processing details while minifying
387
403
  html-minifier-next --input-dir=src --output-dir=dist --verbose --collapse-whitespace
388
404
  # Output: Options: collapseWhitespace, html5, includeAutoGeneratedTags
@@ -928,10 +928,13 @@ const tagDefaults = {
928
928
  colorspace: 'limited-srgb',
929
929
  type: 'text'
930
930
  },
931
+ link: { media: 'all' },
931
932
  marquee: {
932
933
  behavior: 'scroll',
933
934
  direction: 'left'
934
935
  },
936
+ meta: { media: 'all' },
937
+ source: { media: 'all' },
935
938
  style: { media: 'all' },
936
939
  textarea: { wrap: 'soft' },
937
940
  track: { kind: 'subtitles' }
@@ -1132,10 +1135,24 @@ function collapseWhitespaceSmart(str, prevTag, nextTag, options, inlineElements,
1132
1135
  if (trimLeft && !options.collapseInlineTagWhitespace) {
1133
1136
  trimLeft = prevTag.charAt(0) === '/' ? !inlineElements.has(prevTag.slice(1)) : !inlineTextSet.has(prevTag);
1134
1137
  }
1138
+ // When `collapseInlineTagWhitespace` is enabled, still preserve whitespace around inline text elements
1139
+ if (trimLeft && options.collapseInlineTagWhitespace) {
1140
+ const tagName = prevTag.charAt(0) === '/' ? prevTag.slice(1) : prevTag;
1141
+ if (inlineElementsToKeepWhitespaceWithin.has(tagName)) {
1142
+ trimLeft = false;
1143
+ }
1144
+ }
1135
1145
  let trimRight = nextTag && !inlineElementsToKeepWhitespace.has(nextTag);
1136
1146
  if (trimRight && !options.collapseInlineTagWhitespace) {
1137
1147
  trimRight = nextTag.charAt(0) === '/' ? !inlineTextSet.has(nextTag.slice(1)) : !inlineElements.has(nextTag);
1138
1148
  }
1149
+ // When `collapseInlineTagWhitespace` is enabled, still preserve whitespace around inline text elements
1150
+ if (trimRight && options.collapseInlineTagWhitespace) {
1151
+ const tagName = nextTag.charAt(0) === '/' ? nextTag.slice(1) : nextTag;
1152
+ if (inlineElementsToKeepWhitespaceWithin.has(tagName)) {
1153
+ trimRight = false;
1154
+ }
1155
+ }
1139
1156
  return collapseWhitespace(str, options, trimLeft, trimRight, prevTag && nextTag);
1140
1157
  }
1141
1158
 
@@ -1782,6 +1799,11 @@ async function cleanAttributeValue(tag, attrName, attrValue, options, attrs, min
1782
1799
  }
1783
1800
  try {
1784
1801
  attrValue = await options.minifyCSS(attrValue, 'inline');
1802
+ // After minification, check if CSS consists entirely of invalid properties (no values)
1803
+ // E.g., `color:` or `margin:;padding:` should be treated as empty
1804
+ if (attrValue && /^(?:[a-z-]+:\s*;?\s*)+$/i.test(attrValue)) {
1805
+ attrValue = '';
1806
+ }
1785
1807
  } catch (err) {
1786
1808
  if (!options.continueOnMinifyError) {
1787
1809
  throw err;
@@ -1830,6 +1852,11 @@ async function cleanAttributeValue(tag, attrName, attrValue, options, attrs, min
1830
1852
  attrValue = trimWhitespace(attrValue.replace(/\s*;\s*/g, ';'));
1831
1853
  } else if (isMediaQuery(tag, attrs, attrName)) {
1832
1854
  attrValue = trimWhitespace(attrValue);
1855
+ // Only minify actual media queries (those with features in parentheses)
1856
+ // Skip simple media types like `all`, `screen`, `print` which are already minimal
1857
+ if (!/[()]/.test(attrValue)) {
1858
+ return attrValue;
1859
+ }
1833
1860
  try {
1834
1861
  return await options.minifyCSS(attrValue, 'media');
1835
1862
  } catch (err) {
@@ -3540,10 +3540,13 @@ const tagDefaults = {
3540
3540
  colorspace: 'limited-srgb',
3541
3541
  type: 'text'
3542
3542
  },
3543
+ link: { media: 'all' },
3543
3544
  marquee: {
3544
3545
  behavior: 'scroll',
3545
3546
  direction: 'left'
3546
3547
  },
3548
+ meta: { media: 'all' },
3549
+ source: { media: 'all' },
3547
3550
  style: { media: 'all' },
3548
3551
  textarea: { wrap: 'soft' },
3549
3552
  track: { kind: 'subtitles' }
@@ -3744,10 +3747,24 @@ function collapseWhitespaceSmart(str, prevTag, nextTag, options, inlineElements,
3744
3747
  if (trimLeft && !options.collapseInlineTagWhitespace) {
3745
3748
  trimLeft = prevTag.charAt(0) === '/' ? !inlineElements.has(prevTag.slice(1)) : !inlineTextSet.has(prevTag);
3746
3749
  }
3750
+ // When `collapseInlineTagWhitespace` is enabled, still preserve whitespace around inline text elements
3751
+ if (trimLeft && options.collapseInlineTagWhitespace) {
3752
+ const tagName = prevTag.charAt(0) === '/' ? prevTag.slice(1) : prevTag;
3753
+ if (inlineElementsToKeepWhitespaceWithin.has(tagName)) {
3754
+ trimLeft = false;
3755
+ }
3756
+ }
3747
3757
  let trimRight = nextTag && !inlineElementsToKeepWhitespace.has(nextTag);
3748
3758
  if (trimRight && !options.collapseInlineTagWhitespace) {
3749
3759
  trimRight = nextTag.charAt(0) === '/' ? !inlineTextSet.has(nextTag.slice(1)) : !inlineElements.has(nextTag);
3750
3760
  }
3761
+ // When `collapseInlineTagWhitespace` is enabled, still preserve whitespace around inline text elements
3762
+ if (trimRight && options.collapseInlineTagWhitespace) {
3763
+ const tagName = nextTag.charAt(0) === '/' ? nextTag.slice(1) : nextTag;
3764
+ if (inlineElementsToKeepWhitespaceWithin.has(tagName)) {
3765
+ trimRight = false;
3766
+ }
3767
+ }
3751
3768
  return collapseWhitespace(str, options, trimLeft, trimRight, prevTag && nextTag);
3752
3769
  }
3753
3770
 
@@ -6924,6 +6941,11 @@ async function cleanAttributeValue(tag, attrName, attrValue, options, attrs, min
6924
6941
  }
6925
6942
  try {
6926
6943
  attrValue = await options.minifyCSS(attrValue, 'inline');
6944
+ // After minification, check if CSS consists entirely of invalid properties (no values)
6945
+ // E.g., `color:` or `margin:;padding:` should be treated as empty
6946
+ if (attrValue && /^(?:[a-z-]+:\s*;?\s*)+$/i.test(attrValue)) {
6947
+ attrValue = '';
6948
+ }
6927
6949
  } catch (err) {
6928
6950
  if (!options.continueOnMinifyError) {
6929
6951
  throw err;
@@ -6972,6 +6994,11 @@ async function cleanAttributeValue(tag, attrName, attrValue, options, attrs, min
6972
6994
  attrValue = trimWhitespace(attrValue.replace(/\s*;\s*/g, ';'));
6973
6995
  } else if (isMediaQuery(tag, attrs, attrName)) {
6974
6996
  attrValue = trimWhitespace(attrValue);
6997
+ // Only minify actual media queries (those with features in parentheses)
6998
+ // Skip simple media types like `all`, `screen`, `print` which are already minimal
6999
+ if (!/[()]/.test(attrValue)) {
7000
+ return attrValue;
7001
+ }
6975
7002
  try {
6976
7003
  return await options.minifyCSS(attrValue, 'media');
6977
7004
  } catch (err) {
@@ -1 +1 @@
1
- {"version":3,"file":"attributes.d.ts","sourceRoot":"","sources":["../../../src/lib/attributes.js"],"names":[],"mappings":"AAsBA,yDAEC;AAED,mEAOC;AAED,uEAWC;AAED,8DAGC;AAED,4EAOC;AAED,mGAqBC;AAED,mEAGC;AAED,qEAGC;AAED,kEAWC;AAED,sEAGC;AAED,4DAWC;AAED,2EAEC;AAED,qEAaC;AAED,wEAUC;AAED,sEAUC;AAED,2EAEC;AAED,2DAEC;AAED,8DAUC;AAED,uEAUC;AAED,oGASC;AAED,4DAOC;AAID,0IAqHC;AAsBD;;;;GAsCC;AAED,6GA4EC"}
1
+ {"version":3,"file":"attributes.d.ts","sourceRoot":"","sources":["../../../src/lib/attributes.js"],"names":[],"mappings":"AAsBA,yDAEC;AAED,mEAOC;AAED,uEAWC;AAED,8DAGC;AAED,4EAOC;AAED,mGAqBC;AAED,mEAGC;AAED,qEAGC;AAED,kEAWC;AAED,sEAGC;AAED,4DAWC;AAED,2EAEC;AAED,qEAaC;AAED,wEAUC;AAED,sEAUC;AAED,2EAEC;AAED,2DAEC;AAED,8DAUC;AAED,uEAUC;AAED,oGASC;AAED,4DAOC;AAID,0IA+HC;AAsBD;;;;GAsCC;AAED,6GA4EC"}
@@ -41,12 +41,24 @@ export namespace tagDefaults {
41
41
  let type_1: string;
42
42
  export { type_1 as type };
43
43
  }
44
+ namespace link {
45
+ let media: string;
46
+ }
44
47
  namespace marquee {
45
48
  let behavior: string;
46
49
  let direction: string;
47
50
  }
51
+ namespace meta {
52
+ let media_1: string;
53
+ export { media_1 as media };
54
+ }
55
+ namespace source {
56
+ let media_2: string;
57
+ export { media_2 as media };
58
+ }
48
59
  namespace style {
49
- let media: string;
60
+ let media_3: string;
61
+ export { media_3 as media };
50
62
  }
51
63
  namespace textarea {
52
64
  let wrap: string;
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/lib/constants.js"],"names":[],"mappings":"AAEA,iCAAoC;AACpC,+BAAkC;AAClC,oCAA2C;AAC3C,2CAAmD;AACnD,wCAA8C;AAC9C,4CAAkD;AAClD,4CAA2C;AAC3C,4CAA0D;AAC1D,2CAA8C;AAC9C,+CAA0D;AAC1D,2CAAmC;AACnC,mCAA4C;AAK5C,+DAAgb;AAGhb,+DAA6O;AAG7O,yDAAmF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuCnF,qDAQG;AAEH,+CAEG;AAcH,0CAUG;AApBH,0CAAwhB;AAExhB,yCAAkD;AAIlD,qCAA8C;AAuB9C,4CAAiF;AAEjF,0CAAoM;AAEpM,qCAAwF;AAExF,0CAA8C;AAE9C,qCAA6S;AAE7S,sCAAsF;AAEtF,6CAA8D;AAE9D,gDAAqD;AAErD,oCAAkD;AAElD,2CAAqD;AAErD,2CAA8D;AAE9D,mCAAuC;AAEvC,uCAAuD;AAEvD,sCAA8C;AAE9C,oCAA2D;AAE3D,uCAA8C;AAE9C,mCAA+wC;AAI/wC,sCAEsD;AAItD,6CAAwD"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/lib/constants.js"],"names":[],"mappings":"AAEA,iCAAoC;AACpC,+BAAkC;AAClC,oCAA2C;AAC3C,2CAAmD;AACnD,wCAA8C;AAC9C,4CAAkD;AAClD,4CAA2C;AAC3C,4CAA0D;AAC1D,2CAA8C;AAC9C,+CAA0D;AAC1D,2CAAmC;AACnC,mCAA4C;AAK5C,+DAAgb;AAGhb,+DAA6O;AAG7O,yDAAmF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0CnF,qDAQG;AAEH,+CAEG;AAcH,0CAUG;AApBH,0CAAwhB;AAExhB,yCAAkD;AAIlD,qCAA8C;AAuB9C,4CAAiF;AAEjF,0CAAoM;AAEpM,qCAAwF;AAExF,0CAA8C;AAE9C,qCAA6S;AAE7S,sCAAsF;AAEtF,6CAA8D;AAE9D,gDAAqD;AAErD,oCAAkD;AAElD,2CAAqD;AAErD,2CAA8D;AAE9D,mCAAuC;AAEvC,uCAAuD;AAEvD,sCAA8C;AAE9C,oCAA2D;AAE3D,uCAA8C;AAE9C,mCAA+wC;AAI/wC,sCAEsD;AAItD,6CAAwD"}
@@ -1 +1 @@
1
- {"version":3,"file":"whitespace.d.ts","sourceRoot":"","sources":["../../../src/lib/whitespace.js"],"names":[],"mappings":"AAeA,8CAOC;AAID,qDAgBC;AAID,iHAyDC;AAID,0IAUC;AAID,yDAEC;AAED,qDAEC"}
1
+ {"version":3,"file":"whitespace.d.ts","sourceRoot":"","sources":["../../../src/lib/whitespace.js"],"names":[],"mappings":"AAgBA,8CAOC;AAID,qDAgBC;AAID,iHAyDC;AAID,0IAwBC;AAID,yDAEC;AAED,qDAEC"}
package/package.json CHANGED
@@ -93,5 +93,5 @@
93
93
  "test:watch": "node --test --watch tests/*.spec.js"
94
94
  },
95
95
  "type": "module",
96
- "version": "4.14.0"
96
+ "version": "4.14.3"
97
97
  }
@@ -272,6 +272,11 @@ async function cleanAttributeValue(tag, attrName, attrValue, options, attrs, min
272
272
  }
273
273
  try {
274
274
  attrValue = await options.minifyCSS(attrValue, 'inline');
275
+ // After minification, check if CSS consists entirely of invalid properties (no values)
276
+ // E.g., `color:` or `margin:;padding:` should be treated as empty
277
+ if (attrValue && /^(?:[a-z-]+:\s*;?\s*)+$/i.test(attrValue)) {
278
+ attrValue = '';
279
+ }
275
280
  } catch (err) {
276
281
  if (!options.continueOnMinifyError) {
277
282
  throw err;
@@ -320,6 +325,11 @@ async function cleanAttributeValue(tag, attrName, attrValue, options, attrs, min
320
325
  attrValue = trimWhitespace(attrValue.replace(/\s*;\s*/g, ';'));
321
326
  } else if (isMediaQuery(tag, attrs, attrName)) {
322
327
  attrValue = trimWhitespace(attrValue);
328
+ // Only minify actual media queries (those with features in parentheses)
329
+ // Skip simple media types like `all`, `screen`, `print` which are already minimal
330
+ if (!/[()]/.test(attrValue)) {
331
+ return attrValue;
332
+ }
323
333
  try {
324
334
  return await options.minifyCSS(attrValue, 'media');
325
335
  } catch (err) {
@@ -48,10 +48,13 @@ const tagDefaults = {
48
48
  colorspace: 'limited-srgb',
49
49
  type: 'text'
50
50
  },
51
+ link: { media: 'all' },
51
52
  marquee: {
52
53
  behavior: 'scroll',
53
54
  direction: 'left'
54
55
  },
56
+ meta: { media: 'all' },
57
+ source: { media: 'all' },
55
58
  style: { media: 'all' },
56
59
  textarea: { wrap: 'soft' },
57
60
  track: { kind: 'subtitles' }
@@ -8,7 +8,8 @@ import {
8
8
  RE_NBSP_LEAD_GROUP,
9
9
  RE_NBSP_TRAILING_GROUP,
10
10
  RE_NBSP_TRAILING_STRIP,
11
- inlineElementsToKeepWhitespace
11
+ inlineElementsToKeepWhitespace,
12
+ inlineElementsToKeepWhitespaceWithin
12
13
  } from './constants.js';
13
14
 
14
15
  // Trim whitespace
@@ -110,10 +111,24 @@ function collapseWhitespaceSmart(str, prevTag, nextTag, options, inlineElements,
110
111
  if (trimLeft && !options.collapseInlineTagWhitespace) {
111
112
  trimLeft = prevTag.charAt(0) === '/' ? !inlineElements.has(prevTag.slice(1)) : !inlineTextSet.has(prevTag);
112
113
  }
114
+ // When `collapseInlineTagWhitespace` is enabled, still preserve whitespace around inline text elements
115
+ if (trimLeft && options.collapseInlineTagWhitespace) {
116
+ const tagName = prevTag.charAt(0) === '/' ? prevTag.slice(1) : prevTag;
117
+ if (inlineElementsToKeepWhitespaceWithin.has(tagName)) {
118
+ trimLeft = false;
119
+ }
120
+ }
113
121
  let trimRight = nextTag && !inlineElementsToKeepWhitespace.has(nextTag);
114
122
  if (trimRight && !options.collapseInlineTagWhitespace) {
115
123
  trimRight = nextTag.charAt(0) === '/' ? !inlineTextSet.has(nextTag.slice(1)) : !inlineElements.has(nextTag);
116
124
  }
125
+ // When `collapseInlineTagWhitespace` is enabled, still preserve whitespace around inline text elements
126
+ if (trimRight && options.collapseInlineTagWhitespace) {
127
+ const tagName = nextTag.charAt(0) === '/' ? nextTag.slice(1) : nextTag;
128
+ if (inlineElementsToKeepWhitespaceWithin.has(tagName)) {
129
+ trimRight = false;
130
+ }
131
+ }
117
132
  return collapseWhitespace(str, options, trimLeft, trimRight, prevTag && nextTag);
118
133
  }
119
134