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 +38 -22
- package/dist/htmlminifier.cjs +27 -0
- package/dist/htmlminifier.esm.bundle.js +27 -0
- package/dist/types/lib/attributes.d.ts.map +1 -1
- package/dist/types/lib/constants.d.ts +13 -1
- package/dist/types/lib/constants.d.ts.map +1 -1
- package/dist/types/lib/whitespace.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/lib/attributes.js +10 -0
- package/src/lib/constants.js +3 -0
- package/src/lib/whitespace.js +16 -1
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
|
-
```
|
|
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
|
-
```
|
|
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
|
|
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
|
|
257
|
+
**To use SWC**, install it as a dependency:
|
|
252
258
|
|
|
253
|
-
```
|
|
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/) |
|
|
301
|
-
| [CERN](https://home.cern/) | 152 |
|
|
302
|
-
| [CSS-Tricks](https://css-tricks.com/) | 162 |
|
|
303
|
-
| [ECMAScript](https://tc39.es/ecma262/) | 7250 | **
|
|
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** |
|
|
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/) |
|
|
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 |
|
|
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/) |
|
|
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 | **
|
|
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/) |
|
|
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** | |
|
|
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
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
package/dist/htmlminifier.cjs
CHANGED
|
@@ -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,
|
|
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
|
|
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
|
|
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":"
|
|
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
package/src/lib/attributes.js
CHANGED
|
@@ -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) {
|
package/src/lib/constants.js
CHANGED
|
@@ -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' }
|
package/src/lib/whitespace.js
CHANGED
|
@@ -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
|
|