html-minifier-next 4.16.1 → 4.16.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 +13 -11
- package/dist/htmlminifier.cjs +61 -47
- package/dist/htmlminifier.esm.bundle.js +60 -46
- package/dist/types/lib/options.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/htmlminifier.js +4 -4
- package/src/lib/options.js +58 -44
package/README.md
CHANGED
|
@@ -358,36 +358,38 @@ How does HTML Minifier Next compare to other minifiers? (All minification with t
|
|
|
358
358
|
| Site | Original Size (KB) | [HTML Minifier Next](https://github.com/j9t/html-minifier-next) ([config](https://github.com/j9t/html-minifier-next/blob/main/benchmarks/html-minifier.json))<br>[](https://socket.dev/npm/package/html-minifier-next) | [htmlnano](https://github.com/posthtml/htmlnano)<br>[](https://socket.dev/npm/package/htmlnano) | [@swc/html](https://github.com/swc-project/swc)<br>[](https://socket.dev/npm/package/@swc/html) | [minify-html](https://github.com/wilsonzlin/minify-html)<br>[](https://socket.dev/npm/package/@minify-html/node) | [minimize](https://github.com/Swaagie/minimize)<br>[](https://socket.dev/npm/package/minimize) | [htmlcompressor.com](https://htmlcompressor.com/) |
|
|
359
359
|
| --- | --- | --- | --- | --- | --- | --- | --- |
|
|
360
360
|
| [A List Apart](https://alistapart.com/) | 59 | **50** | 51 | 52 | 51 | 54 | 52 |
|
|
361
|
-
| [Apple](https://www.apple.com/) | 211 | **
|
|
362
|
-
| [BBC](https://www.bbc.co.uk/) |
|
|
361
|
+
| [Apple](https://www.apple.com/) | 211 | **176** | 187 | 189 | 190 | 191 | 192 |
|
|
362
|
+
| [BBC](https://www.bbc.co.uk/) | 674 | **613** | 633 | 633 | 634 | 669 | n/a |
|
|
363
363
|
| [CERN](https://home.cern/) | 152 | **83** | 91 | 91 | 91 | 93 | 96 |
|
|
364
364
|
| [CSS-Tricks](https://css-tricks.com/) | 162 | **119** | 127 | 143 | 143 | 148 | 144 |
|
|
365
365
|
| [ECMAScript](https://tc39.es/ecma262/) | 7250 | **6401** | 6573 | 6455 | 6578 | 6626 | n/a |
|
|
366
366
|
| [EDRi](https://edri.org/) | 80 | **59** | 70 | 70 | 71 | 75 | 73 |
|
|
367
367
|
| [EFF](https://www.eff.org/) | 54 | **45** | 49 | 47 | 48 | 49 | 49 |
|
|
368
368
|
| [European Alternatives](https://european-alternatives.eu/) | 48 | **30** | 32 | 32 | 32 | 32 | 32 |
|
|
369
|
-
| [FAZ](https://www.faz.net/aktuell/) |
|
|
369
|
+
| [FAZ](https://www.faz.net/aktuell/) | 1570 | 1458 | **1405** | 1494 | 1505 | 1516 | n/a |
|
|
370
370
|
| [French Tech](https://lafrenchtech.gouv.fr/) | 153 | **122** | 126 | 126 | 126 | 132 | 127 |
|
|
371
|
-
| [Frontend Dogma](https://frontenddogma.com/) | 225 | **217** | 238 |
|
|
371
|
+
| [Frontend Dogma](https://frontenddogma.com/) | 225 | **217** | 238 | 224 | 225 | 244 | 225 |
|
|
372
372
|
| [Google](https://www.google.com/) | 18 | **16** | 17 | 17 | 17 | 18 | 18 |
|
|
373
|
+
| [Ground News](https://ground.news/) | 2259 | **1978** | 2077 | 2107 | 2109 | 2246 | n/a |
|
|
373
374
|
| [HTML Living Standard](https://html.spec.whatwg.org/multipage/) | 149 | 148 | 153 | **147** | 149 | 155 | 149 |
|
|
374
|
-
| [Igalia](https://www.igalia.com/) | 50 | **33** | 36 | 36 | 36 | 37 |
|
|
375
|
-
| [Leanpub](https://leanpub.com/) |
|
|
375
|
+
| [Igalia](https://www.igalia.com/) | 50 | **33** | 36 | 36 | 36 | 37 | 37 |
|
|
376
|
+
| [Leanpub](https://leanpub.com/) | 235 | **205** | 220 | 219 | 220 | 230 | 232 |
|
|
376
377
|
| [Mastodon](https://mastodon.social/explore) | 37 | **28** | 32 | 35 | 35 | 36 | 36 |
|
|
377
|
-
| [MDN](https://developer.mozilla.org/en-US/) |
|
|
378
|
-
| [Middle East Eye](https://www.middleeasteye.net/) | 223 | **
|
|
378
|
+
| [MDN](https://developer.mozilla.org/en-US/) | 109 | **62** | 64 | 65 | 65 | 68 | 68 |
|
|
379
|
+
| [Middle East Eye](https://www.middleeasteye.net/) | 223 | **196** | 203 | 201 | 201 | 202 | 203 |
|
|
379
380
|
| [Mistral AI](https://mistral.ai/) | 361 | **319** | 324 | 326 | 327 | 357 | n/a |
|
|
380
381
|
| [Mozilla](https://www.mozilla.org/) | 45 | **31** | 34 | 34 | 34 | 35 | 35 |
|
|
381
382
|
| [Nielsen Norman Group](https://www.nngroup.com/) | 86 | 68 | **55** | 74 | 75 | 77 | 76 |
|
|
383
|
+
| [SitePoint](https://www.sitepoint.com/) | 482 | **351** | 422 | 456 | 460 | 478 | n/a |
|
|
382
384
|
| [Startup-Verband](https://startupverband.de/) | 42 | **29** | 30 | 30 | 30 | 31 | 30 |
|
|
383
385
|
| [TetraLogical](https://tetralogical.com/) | 44 | 38 | **35** | 38 | 39 | 39 | 39 |
|
|
384
|
-
| [TPGi](https://www.tpgi.com/) |
|
|
386
|
+
| [TPGi](https://www.tpgi.com/) | 174 | **158** | 159 | 163 | 165 | 171 | 171 |
|
|
385
387
|
| [United Nations](https://www.un.org/en/) | 152 | **112** | 121 | 125 | 125 | 130 | 123 |
|
|
386
388
|
| [Vivaldi](https://vivaldi.com/) | 92 | **74** | n/a | 79 | 81 | 83 | 81 |
|
|
387
389
|
| [W3C](https://www.w3.org/) | 50 | **36** | 39 | 38 | 38 | 41 | 39 |
|
|
388
|
-
| **Average processing time** | | 120 ms (
|
|
390
|
+
| **Average processing time** | | 120 ms (30/30) | 143 ms (29/30) | 49 ms (30/30) | **13 ms (30/30)** | 270 ms (30/30) | 1252 ms (24/30) |
|
|
389
391
|
|
|
390
|
-
(Last updated: Dec
|
|
392
|
+
(Last updated: Dec 27, 2025)
|
|
391
393
|
<!-- End auto-generated -->
|
|
392
394
|
|
|
393
395
|
Notes: Minimize does not minify CSS and JS. [HTML Minifier Terser](https://github.com/terser/html-minifier-terser) is currently not included due to issues around whitespace collapsing and removal of code using modern CSS features, issues which appeared to distort the data.
|
package/dist/htmlminifier.cjs
CHANGED
|
@@ -1764,24 +1764,30 @@ const processOptions = (inputOptions, { getLightningCSS, getTerser, getSwc, cssM
|
|
|
1764
1764
|
if (!text || !text.trim()) {
|
|
1765
1765
|
return text;
|
|
1766
1766
|
}
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1767
|
+
|
|
1768
|
+
// Optimization: Only process URLs if minification is enabled (not identity function)
|
|
1769
|
+
// This avoids expensive `replaceAsync` when URL minification is disabled
|
|
1770
|
+
if (options.minifyURLs !== identity) {
|
|
1771
|
+
text = await replaceAsync(
|
|
1772
|
+
text,
|
|
1773
|
+
/(url\s*\(\s*)(?:"([^"]*)"|'([^']*)'|([^\s)]+))(\s*\))/ig,
|
|
1774
|
+
async function (match, prefix, dq, sq, unq, suffix) {
|
|
1775
|
+
const quote = dq != null ? '"' : (sq != null ? "'" : '');
|
|
1776
|
+
const url = dq ?? sq ?? unq ?? '';
|
|
1777
|
+
try {
|
|
1778
|
+
const out = await options.minifyURLs(url);
|
|
1779
|
+
return prefix + quote + (typeof out === 'string' ? out : url) + quote + suffix;
|
|
1780
|
+
} catch (err) {
|
|
1781
|
+
if (!options.continueOnMinifyError) {
|
|
1782
|
+
throw err;
|
|
1783
|
+
}
|
|
1784
|
+
options.log && options.log(err);
|
|
1785
|
+
return match;
|
|
1779
1786
|
}
|
|
1780
|
-
options.log && options.log(err);
|
|
1781
|
-
return match;
|
|
1782
1787
|
}
|
|
1783
|
-
|
|
1784
|
-
|
|
1788
|
+
);
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1785
1791
|
// Cache key: Wrapped content, type, options signature
|
|
1786
1792
|
const inputCSS = wrapCSS(text, type);
|
|
1787
1793
|
const cssSig = stableStringify({ type, opts: lightningCssOptions, cont: !!options.continueOnMinifyError });
|
|
@@ -1793,36 +1799,44 @@ const processOptions = (inputOptions, { getLightningCSS, getTerser, getSwc, cssM
|
|
|
1793
1799
|
try {
|
|
1794
1800
|
const cached = cssMinifyCache.get(cssKey);
|
|
1795
1801
|
if (cached) {
|
|
1796
|
-
|
|
1802
|
+
// Support both resolved values and in-flight promises
|
|
1803
|
+
return await cached;
|
|
1797
1804
|
}
|
|
1798
1805
|
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1806
|
+
// In-flight promise caching: Prevent duplicate concurrent minifications
|
|
1807
|
+
// of the same CSS content (same pattern as JS minification)
|
|
1808
|
+
const inFlight = (async () => {
|
|
1809
|
+
const transformCSS = await getLightningCSS();
|
|
1810
|
+
// Note: `Buffer.from()` is required—Lightning CSS API expects Uint8Array
|
|
1811
|
+
const result = transformCSS({
|
|
1812
|
+
filename: 'input.css',
|
|
1813
|
+
code: Buffer.from(inputCSS),
|
|
1814
|
+
minify: true,
|
|
1815
|
+
errorRecovery: !!options.continueOnMinifyError,
|
|
1816
|
+
...lightningCssOptions
|
|
1817
|
+
});
|
|
1818
|
+
|
|
1819
|
+
const outputCSS = unwrapCSS(result.code.toString(), type);
|
|
1820
|
+
|
|
1821
|
+
// If Lightning CSS removed significant content that looks like template syntax or UIDs, return original
|
|
1822
|
+
// This preserves:
|
|
1823
|
+
// 1. Template code like `<?php ?>`, `<%= ?>`, `{{ }}`, etc. (contain `<` or `>` but not `CDATA`)
|
|
1824
|
+
// 2. UIDs representing custom fragments (only lowercase letters and digits, no spaces)
|
|
1825
|
+
// CDATA sections, HTML entities, and other invalid CSS are allowed to be removed
|
|
1826
|
+
const isCDATA = text.includes('<![CDATA[');
|
|
1827
|
+
const uidPattern = /[a-z0-9]{10,}/; // UIDs are long alphanumeric strings
|
|
1828
|
+
const hasUID = uidPattern.test(text) && !isCDATA; // Exclude CDATA from UID detection
|
|
1829
|
+
const looksLikeTemplate = (text.includes('<') || text.includes('>')) && !isCDATA;
|
|
1830
|
+
|
|
1831
|
+
// Preserve if output is empty and input had template syntax or UIDs
|
|
1832
|
+
// This catches cases where Lightning CSS removed content that should be preserved
|
|
1833
|
+
return (text.trim() && !outputCSS.trim() && (looksLikeTemplate || hasUID)) ? text : outputCSS;
|
|
1834
|
+
})();
|
|
1823
1835
|
|
|
1824
|
-
cssMinifyCache.set(cssKey,
|
|
1825
|
-
|
|
1836
|
+
cssMinifyCache.set(cssKey, inFlight);
|
|
1837
|
+
const resolved = await inFlight;
|
|
1838
|
+
cssMinifyCache.set(cssKey, resolved);
|
|
1839
|
+
return resolved;
|
|
1826
1840
|
} catch (err) {
|
|
1827
1841
|
cssMinifyCache.delete(cssKey);
|
|
1828
1842
|
if (!options.continueOnMinifyError) {
|
|
@@ -2740,8 +2754,8 @@ async function getSwc() {
|
|
|
2740
2754
|
|
|
2741
2755
|
// Minification caches
|
|
2742
2756
|
|
|
2743
|
-
const cssMinifyCache = new LRU(
|
|
2744
|
-
const jsMinifyCache = new LRU(
|
|
2757
|
+
const cssMinifyCache = new LRU(500);
|
|
2758
|
+
const jsMinifyCache = new LRU(500);
|
|
2745
2759
|
|
|
2746
2760
|
// Type definitions
|
|
2747
2761
|
|
|
@@ -3284,7 +3298,7 @@ async function createSortFns(value, options, uidIgnore, uidAttr, ignoredMarkupCh
|
|
|
3284
3298
|
attrSorters[tag] = attrChains[tag].createSorter();
|
|
3285
3299
|
}
|
|
3286
3300
|
// Memoize sorted attribute orders—attribute sets often repeat in templates
|
|
3287
|
-
const attrOrderCache = new LRU(
|
|
3301
|
+
const attrOrderCache = new LRU(500);
|
|
3288
3302
|
|
|
3289
3303
|
options.sortAttributes = function (tag, attrs) {
|
|
3290
3304
|
const sorter = attrSorters[tag];
|
|
@@ -3315,7 +3329,7 @@ async function createSortFns(value, options, uidIgnore, uidAttr, ignoredMarkupCh
|
|
|
3315
3329
|
if (classChain) {
|
|
3316
3330
|
const sorter = classChain.createSorter();
|
|
3317
3331
|
// Memoize `sortClassName` results—class lists often repeat in templates
|
|
3318
|
-
const classNameCache = new LRU(
|
|
3332
|
+
const classNameCache = new LRU(500);
|
|
3319
3333
|
|
|
3320
3334
|
options.sortClassName = function (value) {
|
|
3321
3335
|
// Fast path: Single class (no spaces) needs no sorting
|
|
@@ -6906,24 +6906,30 @@ const processOptions = (inputOptions, { getLightningCSS, getTerser, getSwc, cssM
|
|
|
6906
6906
|
if (!text || !text.trim()) {
|
|
6907
6907
|
return text;
|
|
6908
6908
|
}
|
|
6909
|
-
|
|
6910
|
-
|
|
6911
|
-
|
|
6912
|
-
|
|
6913
|
-
|
|
6914
|
-
|
|
6915
|
-
|
|
6916
|
-
|
|
6917
|
-
|
|
6918
|
-
|
|
6919
|
-
|
|
6920
|
-
|
|
6909
|
+
|
|
6910
|
+
// Optimization: Only process URLs if minification is enabled (not identity function)
|
|
6911
|
+
// This avoids expensive `replaceAsync` when URL minification is disabled
|
|
6912
|
+
if (options.minifyURLs !== identity) {
|
|
6913
|
+
text = await replaceAsync(
|
|
6914
|
+
text,
|
|
6915
|
+
/(url\s*\(\s*)(?:"([^"]*)"|'([^']*)'|([^\s)]+))(\s*\))/ig,
|
|
6916
|
+
async function (match, prefix, dq, sq, unq, suffix) {
|
|
6917
|
+
const quote = dq != null ? '"' : (sq != null ? "'" : '');
|
|
6918
|
+
const url = dq ?? sq ?? unq ?? '';
|
|
6919
|
+
try {
|
|
6920
|
+
const out = await options.minifyURLs(url);
|
|
6921
|
+
return prefix + quote + (typeof out === 'string' ? out : url) + quote + suffix;
|
|
6922
|
+
} catch (err) {
|
|
6923
|
+
if (!options.continueOnMinifyError) {
|
|
6924
|
+
throw err;
|
|
6925
|
+
}
|
|
6926
|
+
options.log && options.log(err);
|
|
6927
|
+
return match;
|
|
6921
6928
|
}
|
|
6922
|
-
options.log && options.log(err);
|
|
6923
|
-
return match;
|
|
6924
6929
|
}
|
|
6925
|
-
|
|
6926
|
-
|
|
6930
|
+
);
|
|
6931
|
+
}
|
|
6932
|
+
|
|
6927
6933
|
// Cache key: Wrapped content, type, options signature
|
|
6928
6934
|
const inputCSS = wrapCSS(text, type);
|
|
6929
6935
|
const cssSig = stableStringify({ type, opts: lightningCssOptions, cont: !!options.continueOnMinifyError });
|
|
@@ -6935,36 +6941,44 @@ const processOptions = (inputOptions, { getLightningCSS, getTerser, getSwc, cssM
|
|
|
6935
6941
|
try {
|
|
6936
6942
|
const cached = cssMinifyCache.get(cssKey);
|
|
6937
6943
|
if (cached) {
|
|
6938
|
-
|
|
6944
|
+
// Support both resolved values and in-flight promises
|
|
6945
|
+
return await cached;
|
|
6939
6946
|
}
|
|
6940
6947
|
|
|
6941
|
-
|
|
6942
|
-
|
|
6943
|
-
|
|
6944
|
-
|
|
6945
|
-
|
|
6946
|
-
|
|
6947
|
-
|
|
6948
|
-
|
|
6949
|
-
|
|
6950
|
-
|
|
6951
|
-
|
|
6952
|
-
|
|
6953
|
-
// This preserves:
|
|
6954
|
-
// 1. Template code like `<?php ?>`, `<%= %>`, `{{ }}`, etc. (contain `<` or `>` but not `CDATA`)
|
|
6955
|
-
// 2. UIDs representing custom fragments (only lowercase letters and digits, no spaces)
|
|
6956
|
-
// CDATA sections, HTML entities, and other invalid CSS are allowed to be removed
|
|
6957
|
-
const isCDATA = text.includes('<![CDATA[');
|
|
6958
|
-
const uidPattern = /[a-z0-9]{10,}/; // UIDs are long alphanumeric strings
|
|
6959
|
-
const hasUID = uidPattern.test(text) && !isCDATA; // Exclude CDATA from UID detection
|
|
6960
|
-
const looksLikeTemplate = (text.includes('<') || text.includes('>')) && !isCDATA;
|
|
6948
|
+
// In-flight promise caching: Prevent duplicate concurrent minifications
|
|
6949
|
+
// of the same CSS content (same pattern as JS minification)
|
|
6950
|
+
const inFlight = (async () => {
|
|
6951
|
+
const transformCSS = await getLightningCSS();
|
|
6952
|
+
// Note: `Buffer.from()` is required—Lightning CSS API expects Uint8Array
|
|
6953
|
+
const result = transformCSS({
|
|
6954
|
+
filename: 'input.css',
|
|
6955
|
+
code: Buffer.from(inputCSS),
|
|
6956
|
+
minify: true,
|
|
6957
|
+
errorRecovery: !!options.continueOnMinifyError,
|
|
6958
|
+
...lightningCssOptions
|
|
6959
|
+
});
|
|
6961
6960
|
|
|
6962
|
-
|
|
6963
|
-
|
|
6964
|
-
|
|
6961
|
+
const outputCSS = unwrapCSS(result.code.toString(), type);
|
|
6962
|
+
|
|
6963
|
+
// If Lightning CSS removed significant content that looks like template syntax or UIDs, return original
|
|
6964
|
+
// This preserves:
|
|
6965
|
+
// 1. Template code like `<?php ?>`, `<%= ?>`, `{{ }}`, etc. (contain `<` or `>` but not `CDATA`)
|
|
6966
|
+
// 2. UIDs representing custom fragments (only lowercase letters and digits, no spaces)
|
|
6967
|
+
// CDATA sections, HTML entities, and other invalid CSS are allowed to be removed
|
|
6968
|
+
const isCDATA = text.includes('<![CDATA[');
|
|
6969
|
+
const uidPattern = /[a-z0-9]{10,}/; // UIDs are long alphanumeric strings
|
|
6970
|
+
const hasUID = uidPattern.test(text) && !isCDATA; // Exclude CDATA from UID detection
|
|
6971
|
+
const looksLikeTemplate = (text.includes('<') || text.includes('>')) && !isCDATA;
|
|
6972
|
+
|
|
6973
|
+
// Preserve if output is empty and input had template syntax or UIDs
|
|
6974
|
+
// This catches cases where Lightning CSS removed content that should be preserved
|
|
6975
|
+
return (text.trim() && !outputCSS.trim() && (looksLikeTemplate || hasUID)) ? text : outputCSS;
|
|
6976
|
+
})();
|
|
6965
6977
|
|
|
6966
|
-
cssMinifyCache.set(cssKey,
|
|
6967
|
-
|
|
6978
|
+
cssMinifyCache.set(cssKey, inFlight);
|
|
6979
|
+
const resolved = await inFlight;
|
|
6980
|
+
cssMinifyCache.set(cssKey, resolved);
|
|
6981
|
+
return resolved;
|
|
6968
6982
|
} catch (err) {
|
|
6969
6983
|
cssMinifyCache.delete(cssKey);
|
|
6970
6984
|
if (!options.continueOnMinifyError) {
|
|
@@ -7882,8 +7896,8 @@ async function getSwc() {
|
|
|
7882
7896
|
|
|
7883
7897
|
// Minification caches
|
|
7884
7898
|
|
|
7885
|
-
const cssMinifyCache = new LRU(
|
|
7886
|
-
const jsMinifyCache = new LRU(
|
|
7899
|
+
const cssMinifyCache = new LRU(500);
|
|
7900
|
+
const jsMinifyCache = new LRU(500);
|
|
7887
7901
|
|
|
7888
7902
|
// Type definitions
|
|
7889
7903
|
|
|
@@ -8426,7 +8440,7 @@ async function createSortFns(value, options, uidIgnore, uidAttr, ignoredMarkupCh
|
|
|
8426
8440
|
attrSorters[tag] = attrChains[tag].createSorter();
|
|
8427
8441
|
}
|
|
8428
8442
|
// Memoize sorted attribute orders—attribute sets often repeat in templates
|
|
8429
|
-
const attrOrderCache = new LRU(
|
|
8443
|
+
const attrOrderCache = new LRU(500);
|
|
8430
8444
|
|
|
8431
8445
|
options.sortAttributes = function (tag, attrs) {
|
|
8432
8446
|
const sorter = attrSorters[tag];
|
|
@@ -8457,7 +8471,7 @@ async function createSortFns(value, options, uidIgnore, uidAttr, ignoredMarkupCh
|
|
|
8457
8471
|
if (classChain) {
|
|
8458
8472
|
const sorter = classChain.createSorter();
|
|
8459
8473
|
// Memoize `sortClassName` results—class lists often repeat in templates
|
|
8460
|
-
const classNameCache = new LRU(
|
|
8474
|
+
const classNameCache = new LRU(500);
|
|
8461
8475
|
|
|
8462
8476
|
options.sortClassName = function (value) {
|
|
8463
8477
|
// Fast path: Single class (no spaces) needs no sorting
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../../../src/lib/options.js"],"names":[],"mappings":"AAWA,6DAUC;AAID;;;;;;;;;GASG;AACH,6CATW,OAAO,CAAC,eAAe,CAAC,0EAEhC;IAAuB,eAAe;IACf,SAAS;IACT,MAAM;CAA2B,GAG9C,eAAe,
|
|
1
|
+
{"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../../../src/lib/options.js"],"names":[],"mappings":"AAWA,6DAUC;AAID;;;;;;;;;GASG;AACH,6CATW,OAAO,CAAC,eAAe,CAAC,0EAEhC;IAAuB,eAAe;IACf,SAAS;IACT,MAAM;CAA2B,GAG9C,eAAe,CA2T3B"}
|
package/package.json
CHANGED
package/src/htmlminifier.js
CHANGED
|
@@ -91,8 +91,8 @@ async function getSwc() {
|
|
|
91
91
|
|
|
92
92
|
// Minification caches
|
|
93
93
|
|
|
94
|
-
const cssMinifyCache = new LRU(
|
|
95
|
-
const jsMinifyCache = new LRU(
|
|
94
|
+
const cssMinifyCache = new LRU(500);
|
|
95
|
+
const jsMinifyCache = new LRU(500);
|
|
96
96
|
|
|
97
97
|
// Type definitions
|
|
98
98
|
|
|
@@ -635,7 +635,7 @@ async function createSortFns(value, options, uidIgnore, uidAttr, ignoredMarkupCh
|
|
|
635
635
|
attrSorters[tag] = attrChains[tag].createSorter();
|
|
636
636
|
}
|
|
637
637
|
// Memoize sorted attribute orders—attribute sets often repeat in templates
|
|
638
|
-
const attrOrderCache = new LRU(
|
|
638
|
+
const attrOrderCache = new LRU(500);
|
|
639
639
|
|
|
640
640
|
options.sortAttributes = function (tag, attrs) {
|
|
641
641
|
const sorter = attrSorters[tag];
|
|
@@ -666,7 +666,7 @@ async function createSortFns(value, options, uidIgnore, uidAttr, ignoredMarkupCh
|
|
|
666
666
|
if (classChain) {
|
|
667
667
|
const sorter = classChain.createSorter();
|
|
668
668
|
// Memoize `sortClassName` results—class lists often repeat in templates
|
|
669
|
-
const classNameCache = new LRU(
|
|
669
|
+
const classNameCache = new LRU(500);
|
|
670
670
|
|
|
671
671
|
options.sortClassName = function (value) {
|
|
672
672
|
// Fast path: Single class (no spaces) needs no sorting
|
package/src/lib/options.js
CHANGED
|
@@ -106,24 +106,30 @@ const processOptions = (inputOptions, { getLightningCSS, getTerser, getSwc, cssM
|
|
|
106
106
|
if (!text || !text.trim()) {
|
|
107
107
|
return text;
|
|
108
108
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
109
|
+
|
|
110
|
+
// Optimization: Only process URLs if minification is enabled (not identity function)
|
|
111
|
+
// This avoids expensive `replaceAsync` when URL minification is disabled
|
|
112
|
+
if (options.minifyURLs !== identity) {
|
|
113
|
+
text = await replaceAsync(
|
|
114
|
+
text,
|
|
115
|
+
/(url\s*\(\s*)(?:"([^"]*)"|'([^']*)'|([^\s)]+))(\s*\))/ig,
|
|
116
|
+
async function (match, prefix, dq, sq, unq, suffix) {
|
|
117
|
+
const quote = dq != null ? '"' : (sq != null ? "'" : '');
|
|
118
|
+
const url = dq ?? sq ?? unq ?? '';
|
|
119
|
+
try {
|
|
120
|
+
const out = await options.minifyURLs(url);
|
|
121
|
+
return prefix + quote + (typeof out === 'string' ? out : url) + quote + suffix;
|
|
122
|
+
} catch (err) {
|
|
123
|
+
if (!options.continueOnMinifyError) {
|
|
124
|
+
throw err;
|
|
125
|
+
}
|
|
126
|
+
options.log && options.log(err);
|
|
127
|
+
return match;
|
|
121
128
|
}
|
|
122
|
-
options.log && options.log(err);
|
|
123
|
-
return match;
|
|
124
129
|
}
|
|
125
|
-
|
|
126
|
-
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
127
133
|
// Cache key: Wrapped content, type, options signature
|
|
128
134
|
const inputCSS = wrapCSS(text, type);
|
|
129
135
|
const cssSig = stableStringify({ type, opts: lightningCssOptions, cont: !!options.continueOnMinifyError });
|
|
@@ -135,36 +141,44 @@ const processOptions = (inputOptions, { getLightningCSS, getTerser, getSwc, cssM
|
|
|
135
141
|
try {
|
|
136
142
|
const cached = cssMinifyCache.get(cssKey);
|
|
137
143
|
if (cached) {
|
|
138
|
-
|
|
144
|
+
// Support both resolved values and in-flight promises
|
|
145
|
+
return await cached;
|
|
139
146
|
}
|
|
140
147
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
148
|
+
// In-flight promise caching: Prevent duplicate concurrent minifications
|
|
149
|
+
// of the same CSS content (same pattern as JS minification)
|
|
150
|
+
const inFlight = (async () => {
|
|
151
|
+
const transformCSS = await getLightningCSS();
|
|
152
|
+
// Note: `Buffer.from()` is required—Lightning CSS API expects Uint8Array
|
|
153
|
+
const result = transformCSS({
|
|
154
|
+
filename: 'input.css',
|
|
155
|
+
code: Buffer.from(inputCSS),
|
|
156
|
+
minify: true,
|
|
157
|
+
errorRecovery: !!options.continueOnMinifyError,
|
|
158
|
+
...lightningCssOptions
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
const outputCSS = unwrapCSS(result.code.toString(), type);
|
|
162
|
+
|
|
163
|
+
// If Lightning CSS removed significant content that looks like template syntax or UIDs, return original
|
|
164
|
+
// This preserves:
|
|
165
|
+
// 1. Template code like `<?php ?>`, `<%= ?>`, `{{ }}`, etc. (contain `<` or `>` but not `CDATA`)
|
|
166
|
+
// 2. UIDs representing custom fragments (only lowercase letters and digits, no spaces)
|
|
167
|
+
// CDATA sections, HTML entities, and other invalid CSS are allowed to be removed
|
|
168
|
+
const isCDATA = text.includes('<![CDATA[');
|
|
169
|
+
const uidPattern = /[a-z0-9]{10,}/; // UIDs are long alphanumeric strings
|
|
170
|
+
const hasUID = uidPattern.test(text) && !isCDATA; // Exclude CDATA from UID detection
|
|
171
|
+
const looksLikeTemplate = (text.includes('<') || text.includes('>')) && !isCDATA;
|
|
172
|
+
|
|
173
|
+
// Preserve if output is empty and input had template syntax or UIDs
|
|
174
|
+
// This catches cases where Lightning CSS removed content that should be preserved
|
|
175
|
+
return (text.trim() && !outputCSS.trim() && (looksLikeTemplate || hasUID)) ? text : outputCSS;
|
|
176
|
+
})();
|
|
177
|
+
|
|
178
|
+
cssMinifyCache.set(cssKey, inFlight);
|
|
179
|
+
const resolved = await inFlight;
|
|
180
|
+
cssMinifyCache.set(cssKey, resolved);
|
|
181
|
+
return resolved;
|
|
168
182
|
} catch (err) {
|
|
169
183
|
cssMinifyCache.delete(cssKey);
|
|
170
184
|
if (!options.continueOnMinifyError) {
|