html-minifier-next 4.16.2 → 4.16.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 +7 -7
- package/dist/htmlminifier.cjs +63 -20
- package/dist/htmlminifier.esm.bundle.js +63 -20
- package/dist/types/htmlminifier.d.ts.map +1 -1
- package/dist/types/lib/attributes.d.ts.map +1 -1
- package/dist/types/lib/constants.d.ts +6 -0
- package/dist/types/lib/constants.d.ts.map +1 -1
- package/dist/types/lib/options.d.ts +2 -1
- package/dist/types/lib/options.d.ts.map +1 -1
- package/dist/types/lib/svg.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/htmlminifier.js +7 -3
- package/src/lib/attributes.js +32 -12
- package/src/lib/constants.js +12 -0
- package/src/lib/options.js +21 -3
- package/src/lib/svg.js +3 -2
package/README.md
CHANGED
|
@@ -359,24 +359,24 @@ How does HTML Minifier Next compare to other minifiers? (All minification with t
|
|
|
359
359
|
| --- | --- | --- | --- | --- | --- | --- | --- |
|
|
360
360
|
| [A List Apart](https://alistapart.com/) | 59 | **50** | 51 | 52 | 51 | 54 | 52 |
|
|
361
361
|
| [Apple](https://www.apple.com/) | 211 | **176** | 187 | 189 | 190 | 191 | 192 |
|
|
362
|
-
| [BBC](https://www.bbc.co.uk/) |
|
|
362
|
+
| [BBC](https://www.bbc.co.uk/) | 686 | **624** | 643 | 644 | 645 | 680 | 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/) | 1587 | 1476 | **1421** | 1511 | 1522 | 1533 | 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 | 223 | 225 | 244 | 225 |
|
|
372
372
|
| [Google](https://www.google.com/) | 18 | **16** | 17 | 17 | 17 | 18 | 18 |
|
|
373
|
-
| [Ground News](https://ground.news/) |
|
|
373
|
+
| [Ground News](https://ground.news/) | 2291 | **2005** | 2106 | 2136 | 2139 | 2278 | n/a |
|
|
374
374
|
| [HTML Living Standard](https://html.spec.whatwg.org/multipage/) | 149 | 148 | 153 | **147** | 149 | 155 | 149 |
|
|
375
375
|
| [Igalia](https://www.igalia.com/) | 50 | **33** | 36 | 36 | 36 | 37 | 37 |
|
|
376
376
|
| [Leanpub](https://leanpub.com/) | 235 | **205** | 220 | 219 | 220 | 230 | 232 |
|
|
377
377
|
| [Mastodon](https://mastodon.social/explore) | 37 | **28** | 32 | 35 | 35 | 36 | 36 |
|
|
378
378
|
| [MDN](https://developer.mozilla.org/en-US/) | 109 | **62** | 64 | 65 | 65 | 68 | 68 |
|
|
379
|
-
| [Middle East Eye](https://www.middleeasteye.net/) | 223 | **
|
|
379
|
+
| [Middle East Eye](https://www.middleeasteye.net/) | 223 | **197** | 203 | 201 | 201 | 202 | 203 |
|
|
380
380
|
| [Mistral AI](https://mistral.ai/) | 361 | **319** | 324 | 326 | 327 | 357 | n/a |
|
|
381
381
|
| [Mozilla](https://www.mozilla.org/) | 45 | **31** | 34 | 34 | 34 | 35 | 35 |
|
|
382
382
|
| [Nielsen Norman Group](https://www.nngroup.com/) | 86 | 68 | **55** | 74 | 75 | 77 | 76 |
|
|
@@ -386,8 +386,8 @@ How does HTML Minifier Next compare to other minifiers? (All minification with t
|
|
|
386
386
|
| [TPGi](https://www.tpgi.com/) | 174 | **158** | 159 | 163 | 165 | 171 | 171 |
|
|
387
387
|
| [United Nations](https://www.un.org/en/) | 152 | **112** | 121 | 125 | 125 | 130 | 123 |
|
|
388
388
|
| [Vivaldi](https://vivaldi.com/) | 92 | **74** | n/a | 79 | 81 | 83 | 81 |
|
|
389
|
-
| [W3C](https://www.w3.org/) |
|
|
390
|
-
| **Average processing time** | |
|
|
389
|
+
| [W3C](https://www.w3.org/) | 51 | **36** | 39 | 38 | 38 | 41 | 39 |
|
|
390
|
+
| **Average processing time** | | 100 ms (30/30) | 157 ms (29/30) | 52 ms (30/30) | **14 ms (30/30)** | 278 ms (30/30) | 1385 ms (24/30) |
|
|
391
391
|
|
|
392
392
|
(Last updated: Dec 27, 2025)
|
|
393
393
|
<!-- End auto-generated -->
|
package/dist/htmlminifier.cjs
CHANGED
|
@@ -885,6 +885,12 @@ const RE_EVENT_ATTR_DEFAULT = /^on[a-z]{3,}$/;
|
|
|
885
885
|
const RE_CAN_REMOVE_ATTR_QUOTES = /^[^ \t\n\f\r"'`=<>]+$/;
|
|
886
886
|
const RE_TRAILING_SEMICOLON = /;$/;
|
|
887
887
|
const RE_AMP_ENTITY = /&(#?[0-9a-zA-Z]+;)/g;
|
|
888
|
+
const RE_LEGACY_ENTITIES = /&((?:Iacute|aacute|uacute|plusmn|Otilde|otilde|agrave|Agrave|Yacute|yacute|Oslash|oslash|atilde|Atilde|brvbar|ccedil|Ccedil|Ograve|curren|divide|eacute|Eacute|ograve|Oacute|egrave|Egrave|Ugrave|frac12|frac14|frac34|ugrave|oacute|iacute|Ntilde|ntilde|Uacute|middot|igrave|Igrave|iquest|Aacute|cedil|laquo|micro|iexcl|Icirc|icirc|acirc|Ucirc|Ecirc|ocirc|Ocirc|ecirc|ucirc|Aring|aring|AElig|aelig|acute|pound|raquo|Acirc|times|THORN|szlig|thorn|COPY|auml|ordf|ordm|Uuml|macr|uuml|Auml|ouml|Ouml|para|nbsp|euml|quot|QUOT|Euml|yuml|cent|sect|copy|sup1|sup2|sup3|iuml|Iuml|ETH|shy|reg|not|yen|amp|AMP|REG|uml|eth|deg|gt|GT|LT|lt)(?!;)|(?:#?[0-9a-zA-Z]+;))/g;
|
|
889
|
+
const RE_ESCAPE_LT = /</g;
|
|
890
|
+
const RE_ATTR_WS_CHECK = /[ \n\r\t\f]/;
|
|
891
|
+
const RE_ATTR_WS_COLLAPSE = /[ \n\r\t\f]+/g;
|
|
892
|
+
const RE_ATTR_WS_TRIM = /^[ \n\r\t\f]+|[ \n\r\t\f]+$/g;
|
|
893
|
+
const RE_NUMERIC_VALUE = /-?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?/g;
|
|
888
894
|
|
|
889
895
|
// Inline element Sets for whitespace handling
|
|
890
896
|
|
|
@@ -1375,7 +1381,7 @@ function minifyPathData(pathData, precision = 3) {
|
|
|
1375
1381
|
if (!pathData || typeof pathData !== 'string') return pathData;
|
|
1376
1382
|
|
|
1377
1383
|
// First, minify all numbers
|
|
1378
|
-
let result = pathData.replace(
|
|
1384
|
+
let result = pathData.replace(RE_NUMERIC_VALUE, (match) => {
|
|
1379
1385
|
return minifyNumber(match, precision);
|
|
1380
1386
|
});
|
|
1381
1387
|
|
|
@@ -1602,7 +1608,7 @@ function minifySVGAttributeValue(name, value, options = {}) {
|
|
|
1602
1608
|
|
|
1603
1609
|
// Numeric attributes get precision reduction and whitespace minification
|
|
1604
1610
|
if (NUMERIC_ATTRS.has(name)) {
|
|
1605
|
-
const minified = value.replace(
|
|
1611
|
+
const minified = value.replace(RE_NUMERIC_VALUE, (match) => {
|
|
1606
1612
|
return minifyNumber(match, precision);
|
|
1607
1613
|
});
|
|
1608
1614
|
return minifyAttributeWhitespace(minified);
|
|
@@ -1689,9 +1695,10 @@ function shouldMinifyInnerHTML(options) {
|
|
|
1689
1695
|
* @param {Function} deps.getSwc - Function to lazily load @swc/core
|
|
1690
1696
|
* @param {LRU} deps.cssMinifyCache - CSS minification cache
|
|
1691
1697
|
* @param {LRU} deps.jsMinifyCache - JS minification cache
|
|
1698
|
+
* @param {LRU} deps.urlMinifyCache - URL minification cache
|
|
1692
1699
|
* @returns {MinifierOptions} Normalized options with defaults applied
|
|
1693
1700
|
*/
|
|
1694
|
-
const processOptions = (inputOptions, { getLightningCSS, getTerser, getSwc, cssMinifyCache, jsMinifyCache } = {}) => {
|
|
1701
|
+
const processOptions = (inputOptions, { getLightningCSS, getTerser, getSwc, cssMinifyCache, jsMinifyCache, urlMinifyCache } = {}) => {
|
|
1695
1702
|
const options = {
|
|
1696
1703
|
name: function (name) {
|
|
1697
1704
|
return name.toLowerCase();
|
|
@@ -1968,16 +1975,33 @@ const processOptions = (inputOptions, { getLightningCSS, getTerser, getSwc, cssM
|
|
|
1968
1975
|
// Cache RelateURL instance for reuse (expensive to create)
|
|
1969
1976
|
const relateUrlInstance = new RelateURL(relateUrlOptions.site || '', relateUrlOptions);
|
|
1970
1977
|
|
|
1978
|
+
// Create instance-specific cache (results depend on site configuration)
|
|
1979
|
+
const instanceCache = urlMinifyCache ? new (urlMinifyCache.constructor)(500) : null;
|
|
1980
|
+
|
|
1971
1981
|
options.minifyURLs = function (text) {
|
|
1972
|
-
// Fast-path: Skip if text doesn
|
|
1982
|
+
// Fast-path: Skip if text doesn't look like a URL that needs processing
|
|
1973
1983
|
// Only process if contains URL-like characters (`/`, `:`, `#`, `?`) or spaces that need encoding
|
|
1974
1984
|
if (!/[/:?#\s]/.test(text)) {
|
|
1975
1985
|
return text;
|
|
1976
1986
|
}
|
|
1977
1987
|
|
|
1988
|
+
// Check instance-specific cache
|
|
1989
|
+
if (instanceCache) {
|
|
1990
|
+
const cached = instanceCache.get(text);
|
|
1991
|
+
if (cached !== undefined) {
|
|
1992
|
+
return cached;
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
|
|
1978
1996
|
try {
|
|
1979
|
-
|
|
1997
|
+
const result = relateUrlInstance.relate(text);
|
|
1998
|
+
// Cache successful results
|
|
1999
|
+
if (instanceCache) {
|
|
2000
|
+
instanceCache.set(text, result);
|
|
2001
|
+
}
|
|
2002
|
+
return result;
|
|
1980
2003
|
} catch (err) {
|
|
2004
|
+
// Don’t cache errors
|
|
1981
2005
|
if (!options.continueOnMinifyError) {
|
|
1982
2006
|
throw err;
|
|
1983
2007
|
}
|
|
@@ -2052,9 +2076,28 @@ function attributesInclude(attributes, attribute) {
|
|
|
2052
2076
|
}
|
|
2053
2077
|
|
|
2054
2078
|
function isAttributeRedundant(tag, attrName, attrValue, attrs) {
|
|
2079
|
+
// Fast-path: Check if this element–attribute combination can possibly be redundant
|
|
2080
|
+
// before doing expensive string operations
|
|
2081
|
+
|
|
2082
|
+
// Check if attribute name is in general defaults
|
|
2083
|
+
const hasGeneralDefault = attrName in generalDefaults;
|
|
2084
|
+
|
|
2085
|
+
// Check if element has any default attributes
|
|
2086
|
+
const tagHasDefaults = tag in tagDefaults;
|
|
2087
|
+
|
|
2088
|
+
// Check for legacy attribute rules (element- and attribute-specific)
|
|
2089
|
+
const isLegacyAttr = (tag === 'script' && (attrName === 'language' || attrName === 'charset')) ||
|
|
2090
|
+
(tag === 'a' && attrName === 'name');
|
|
2091
|
+
|
|
2092
|
+
// If none of these conditions apply, attribute cannot be redundant
|
|
2093
|
+
if (!hasGeneralDefault && !tagHasDefaults && !isLegacyAttr) {
|
|
2094
|
+
return false;
|
|
2095
|
+
}
|
|
2096
|
+
|
|
2097
|
+
// Now we know we need to check the value, so normalize it
|
|
2055
2098
|
attrValue = attrValue ? trimWhitespace(attrValue.toLowerCase()) : '';
|
|
2056
2099
|
|
|
2057
|
-
// Legacy
|
|
2100
|
+
// Legacy attribute checks
|
|
2058
2101
|
if (tag === 'script' && attrName === 'language' && attrValue === 'javascript') {
|
|
2059
2102
|
return true;
|
|
2060
2103
|
}
|
|
@@ -2066,12 +2109,12 @@ function isAttributeRedundant(tag, attrName, attrValue, attrs) {
|
|
|
2066
2109
|
}
|
|
2067
2110
|
|
|
2068
2111
|
// Check general defaults
|
|
2069
|
-
if (generalDefaults[attrName] === attrValue) {
|
|
2112
|
+
if (hasGeneralDefault && generalDefaults[attrName] === attrValue) {
|
|
2070
2113
|
return true;
|
|
2071
2114
|
}
|
|
2072
2115
|
|
|
2073
2116
|
// Check tag-specific defaults
|
|
2074
|
-
return tagDefaults[tag]
|
|
2117
|
+
return tagHasDefaults && tagDefaults[tag][attrName] === attrValue;
|
|
2075
2118
|
}
|
|
2076
2119
|
|
|
2077
2120
|
function isScriptTypeAttribute(attrValue = '') {
|
|
@@ -2216,15 +2259,13 @@ async function cleanAttributeValue(tag, attrName, attrValue, options, attrs, min
|
|
|
2216
2259
|
// Apply early whitespace normalization if enabled
|
|
2217
2260
|
// Preserves special spaces (non-breaking space, hair space, etc.) for consistency with `collapseWhitespace`
|
|
2218
2261
|
if (options.collapseAttributeWhitespace) {
|
|
2219
|
-
//
|
|
2220
|
-
|
|
2221
|
-
//
|
|
2222
|
-
|
|
2223
|
-
//
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
return ' ';
|
|
2227
|
-
});
|
|
2262
|
+
// Fast path: Only process if whitespace exists (avoids regex overhead on clean values)
|
|
2263
|
+
if (RE_ATTR_WS_CHECK.test(attrValue)) {
|
|
2264
|
+
// Two-pass approach (faster than single-pass with callback)
|
|
2265
|
+
// First: Collapse internal whitespace sequences to single space
|
|
2266
|
+
// Second: Trim leading/trailing whitespace
|
|
2267
|
+
attrValue = attrValue.replace(RE_ATTR_WS_COLLAPSE, ' ').replace(RE_ATTR_WS_TRIM, '');
|
|
2268
|
+
}
|
|
2228
2269
|
}
|
|
2229
2270
|
|
|
2230
2271
|
if (isEventAttribute(attrName, options)) {
|
|
@@ -2756,6 +2797,7 @@ async function getSwc() {
|
|
|
2756
2797
|
|
|
2757
2798
|
const cssMinifyCache = new LRU(500);
|
|
2758
2799
|
const jsMinifyCache = new LRU(500);
|
|
2800
|
+
const urlMinifyCache = new LRU(500);
|
|
2759
2801
|
|
|
2760
2802
|
// Type definitions
|
|
2761
2803
|
|
|
@@ -3823,10 +3865,10 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
3823
3865
|
// Note that `&` can be escaped as `&`, without the semi-colon.
|
|
3824
3866
|
// https://mathiasbynens.be/notes/ambiguous-ampersands
|
|
3825
3867
|
if (text.indexOf('&') !== -1) {
|
|
3826
|
-
text = text.replace(
|
|
3868
|
+
text = text.replace(RE_LEGACY_ENTITIES, '&$1');
|
|
3827
3869
|
}
|
|
3828
3870
|
if (text.indexOf('<') !== -1) {
|
|
3829
|
-
text = text.replace(
|
|
3871
|
+
text = text.replace(RE_ESCAPE_LT, '<');
|
|
3830
3872
|
}
|
|
3831
3873
|
}
|
|
3832
3874
|
if (uidPattern && options.collapseWhitespace && stackNoTrimWhitespace.length) {
|
|
@@ -4035,7 +4077,8 @@ const minify = async function (value, options) {
|
|
|
4035
4077
|
getTerser,
|
|
4036
4078
|
getSwc,
|
|
4037
4079
|
cssMinifyCache,
|
|
4038
|
-
jsMinifyCache
|
|
4080
|
+
jsMinifyCache,
|
|
4081
|
+
urlMinifyCache
|
|
4039
4082
|
});
|
|
4040
4083
|
const result = await minifyHTML(value, options);
|
|
4041
4084
|
options.log('minified in: ' + (Date.now() - start) + 'ms');
|
|
@@ -3497,6 +3497,12 @@ const RE_EVENT_ATTR_DEFAULT = /^on[a-z]{3,}$/;
|
|
|
3497
3497
|
const RE_CAN_REMOVE_ATTR_QUOTES = /^[^ \t\n\f\r"'`=<>]+$/;
|
|
3498
3498
|
const RE_TRAILING_SEMICOLON = /;$/;
|
|
3499
3499
|
const RE_AMP_ENTITY = /&(#?[0-9a-zA-Z]+;)/g;
|
|
3500
|
+
const RE_LEGACY_ENTITIES = /&((?:Iacute|aacute|uacute|plusmn|Otilde|otilde|agrave|Agrave|Yacute|yacute|Oslash|oslash|atilde|Atilde|brvbar|ccedil|Ccedil|Ograve|curren|divide|eacute|Eacute|ograve|Oacute|egrave|Egrave|Ugrave|frac12|frac14|frac34|ugrave|oacute|iacute|Ntilde|ntilde|Uacute|middot|igrave|Igrave|iquest|Aacute|cedil|laquo|micro|iexcl|Icirc|icirc|acirc|Ucirc|Ecirc|ocirc|Ocirc|ecirc|ucirc|Aring|aring|AElig|aelig|acute|pound|raquo|Acirc|times|THORN|szlig|thorn|COPY|auml|ordf|ordm|Uuml|macr|uuml|Auml|ouml|Ouml|para|nbsp|euml|quot|QUOT|Euml|yuml|cent|sect|copy|sup1|sup2|sup3|iuml|Iuml|ETH|shy|reg|not|yen|amp|AMP|REG|uml|eth|deg|gt|GT|LT|lt)(?!;)|(?:#?[0-9a-zA-Z]+;))/g;
|
|
3501
|
+
const RE_ESCAPE_LT = /</g;
|
|
3502
|
+
const RE_ATTR_WS_CHECK = /[ \n\r\t\f]/;
|
|
3503
|
+
const RE_ATTR_WS_COLLAPSE = /[ \n\r\t\f]+/g;
|
|
3504
|
+
const RE_ATTR_WS_TRIM = /^[ \n\r\t\f]+|[ \n\r\t\f]+$/g;
|
|
3505
|
+
const RE_NUMERIC_VALUE = /-?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?/g;
|
|
3500
3506
|
|
|
3501
3507
|
// Inline element Sets for whitespace handling
|
|
3502
3508
|
|
|
@@ -6517,7 +6523,7 @@ function minifyPathData(pathData, precision = 3) {
|
|
|
6517
6523
|
if (!pathData || typeof pathData !== 'string') return pathData;
|
|
6518
6524
|
|
|
6519
6525
|
// First, minify all numbers
|
|
6520
|
-
let result = pathData.replace(
|
|
6526
|
+
let result = pathData.replace(RE_NUMERIC_VALUE, (match) => {
|
|
6521
6527
|
return minifyNumber(match, precision);
|
|
6522
6528
|
});
|
|
6523
6529
|
|
|
@@ -6744,7 +6750,7 @@ function minifySVGAttributeValue(name, value, options = {}) {
|
|
|
6744
6750
|
|
|
6745
6751
|
// Numeric attributes get precision reduction and whitespace minification
|
|
6746
6752
|
if (NUMERIC_ATTRS.has(name)) {
|
|
6747
|
-
const minified = value.replace(
|
|
6753
|
+
const minified = value.replace(RE_NUMERIC_VALUE, (match) => {
|
|
6748
6754
|
return minifyNumber(match, precision);
|
|
6749
6755
|
});
|
|
6750
6756
|
return minifyAttributeWhitespace(minified);
|
|
@@ -6831,9 +6837,10 @@ function shouldMinifyInnerHTML(options) {
|
|
|
6831
6837
|
* @param {Function} deps.getSwc - Function to lazily load @swc/core
|
|
6832
6838
|
* @param {LRU} deps.cssMinifyCache - CSS minification cache
|
|
6833
6839
|
* @param {LRU} deps.jsMinifyCache - JS minification cache
|
|
6840
|
+
* @param {LRU} deps.urlMinifyCache - URL minification cache
|
|
6834
6841
|
* @returns {MinifierOptions} Normalized options with defaults applied
|
|
6835
6842
|
*/
|
|
6836
|
-
const processOptions = (inputOptions, { getLightningCSS, getTerser, getSwc, cssMinifyCache, jsMinifyCache } = {}) => {
|
|
6843
|
+
const processOptions = (inputOptions, { getLightningCSS, getTerser, getSwc, cssMinifyCache, jsMinifyCache, urlMinifyCache } = {}) => {
|
|
6837
6844
|
const options = {
|
|
6838
6845
|
name: function (name) {
|
|
6839
6846
|
return name.toLowerCase();
|
|
@@ -7110,16 +7117,33 @@ const processOptions = (inputOptions, { getLightningCSS, getTerser, getSwc, cssM
|
|
|
7110
7117
|
// Cache RelateURL instance for reuse (expensive to create)
|
|
7111
7118
|
const relateUrlInstance = new RelateURL(relateUrlOptions.site || '', relateUrlOptions);
|
|
7112
7119
|
|
|
7120
|
+
// Create instance-specific cache (results depend on site configuration)
|
|
7121
|
+
const instanceCache = urlMinifyCache ? new (urlMinifyCache.constructor)(500) : null;
|
|
7122
|
+
|
|
7113
7123
|
options.minifyURLs = function (text) {
|
|
7114
|
-
// Fast-path: Skip if text doesn
|
|
7124
|
+
// Fast-path: Skip if text doesn't look like a URL that needs processing
|
|
7115
7125
|
// Only process if contains URL-like characters (`/`, `:`, `#`, `?`) or spaces that need encoding
|
|
7116
7126
|
if (!/[/:?#\s]/.test(text)) {
|
|
7117
7127
|
return text;
|
|
7118
7128
|
}
|
|
7119
7129
|
|
|
7130
|
+
// Check instance-specific cache
|
|
7131
|
+
if (instanceCache) {
|
|
7132
|
+
const cached = instanceCache.get(text);
|
|
7133
|
+
if (cached !== undefined) {
|
|
7134
|
+
return cached;
|
|
7135
|
+
}
|
|
7136
|
+
}
|
|
7137
|
+
|
|
7120
7138
|
try {
|
|
7121
|
-
|
|
7139
|
+
const result = relateUrlInstance.relate(text);
|
|
7140
|
+
// Cache successful results
|
|
7141
|
+
if (instanceCache) {
|
|
7142
|
+
instanceCache.set(text, result);
|
|
7143
|
+
}
|
|
7144
|
+
return result;
|
|
7122
7145
|
} catch (err) {
|
|
7146
|
+
// Don’t cache errors
|
|
7123
7147
|
if (!options.continueOnMinifyError) {
|
|
7124
7148
|
throw err;
|
|
7125
7149
|
}
|
|
@@ -7194,9 +7218,28 @@ function attributesInclude(attributes, attribute) {
|
|
|
7194
7218
|
}
|
|
7195
7219
|
|
|
7196
7220
|
function isAttributeRedundant(tag, attrName, attrValue, attrs) {
|
|
7221
|
+
// Fast-path: Check if this element–attribute combination can possibly be redundant
|
|
7222
|
+
// before doing expensive string operations
|
|
7223
|
+
|
|
7224
|
+
// Check if attribute name is in general defaults
|
|
7225
|
+
const hasGeneralDefault = attrName in generalDefaults;
|
|
7226
|
+
|
|
7227
|
+
// Check if element has any default attributes
|
|
7228
|
+
const tagHasDefaults = tag in tagDefaults;
|
|
7229
|
+
|
|
7230
|
+
// Check for legacy attribute rules (element- and attribute-specific)
|
|
7231
|
+
const isLegacyAttr = (tag === 'script' && (attrName === 'language' || attrName === 'charset')) ||
|
|
7232
|
+
(tag === 'a' && attrName === 'name');
|
|
7233
|
+
|
|
7234
|
+
// If none of these conditions apply, attribute cannot be redundant
|
|
7235
|
+
if (!hasGeneralDefault && !tagHasDefaults && !isLegacyAttr) {
|
|
7236
|
+
return false;
|
|
7237
|
+
}
|
|
7238
|
+
|
|
7239
|
+
// Now we know we need to check the value, so normalize it
|
|
7197
7240
|
attrValue = attrValue ? trimWhitespace(attrValue.toLowerCase()) : '';
|
|
7198
7241
|
|
|
7199
|
-
// Legacy
|
|
7242
|
+
// Legacy attribute checks
|
|
7200
7243
|
if (tag === 'script' && attrName === 'language' && attrValue === 'javascript') {
|
|
7201
7244
|
return true;
|
|
7202
7245
|
}
|
|
@@ -7208,12 +7251,12 @@ function isAttributeRedundant(tag, attrName, attrValue, attrs) {
|
|
|
7208
7251
|
}
|
|
7209
7252
|
|
|
7210
7253
|
// Check general defaults
|
|
7211
|
-
if (generalDefaults[attrName] === attrValue) {
|
|
7254
|
+
if (hasGeneralDefault && generalDefaults[attrName] === attrValue) {
|
|
7212
7255
|
return true;
|
|
7213
7256
|
}
|
|
7214
7257
|
|
|
7215
7258
|
// Check tag-specific defaults
|
|
7216
|
-
return tagDefaults[tag]
|
|
7259
|
+
return tagHasDefaults && tagDefaults[tag][attrName] === attrValue;
|
|
7217
7260
|
}
|
|
7218
7261
|
|
|
7219
7262
|
function isScriptTypeAttribute(attrValue = '') {
|
|
@@ -7358,15 +7401,13 @@ async function cleanAttributeValue(tag, attrName, attrValue, options, attrs, min
|
|
|
7358
7401
|
// Apply early whitespace normalization if enabled
|
|
7359
7402
|
// Preserves special spaces (non-breaking space, hair space, etc.) for consistency with `collapseWhitespace`
|
|
7360
7403
|
if (options.collapseAttributeWhitespace) {
|
|
7361
|
-
//
|
|
7362
|
-
|
|
7363
|
-
//
|
|
7364
|
-
|
|
7365
|
-
//
|
|
7366
|
-
|
|
7367
|
-
|
|
7368
|
-
return ' ';
|
|
7369
|
-
});
|
|
7404
|
+
// Fast path: Only process if whitespace exists (avoids regex overhead on clean values)
|
|
7405
|
+
if (RE_ATTR_WS_CHECK.test(attrValue)) {
|
|
7406
|
+
// Two-pass approach (faster than single-pass with callback)
|
|
7407
|
+
// First: Collapse internal whitespace sequences to single space
|
|
7408
|
+
// Second: Trim leading/trailing whitespace
|
|
7409
|
+
attrValue = attrValue.replace(RE_ATTR_WS_COLLAPSE, ' ').replace(RE_ATTR_WS_TRIM, '');
|
|
7410
|
+
}
|
|
7370
7411
|
}
|
|
7371
7412
|
|
|
7372
7413
|
if (isEventAttribute(attrName, options)) {
|
|
@@ -7898,6 +7939,7 @@ async function getSwc() {
|
|
|
7898
7939
|
|
|
7899
7940
|
const cssMinifyCache = new LRU(500);
|
|
7900
7941
|
const jsMinifyCache = new LRU(500);
|
|
7942
|
+
const urlMinifyCache = new LRU(500);
|
|
7901
7943
|
|
|
7902
7944
|
// Type definitions
|
|
7903
7945
|
|
|
@@ -8965,10 +9007,10 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
8965
9007
|
// Note that `&` can be escaped as `&`, without the semi-colon.
|
|
8966
9008
|
// https://mathiasbynens.be/notes/ambiguous-ampersands
|
|
8967
9009
|
if (text.indexOf('&') !== -1) {
|
|
8968
|
-
text = text.replace(
|
|
9010
|
+
text = text.replace(RE_LEGACY_ENTITIES, '&$1');
|
|
8969
9011
|
}
|
|
8970
9012
|
if (text.indexOf('<') !== -1) {
|
|
8971
|
-
text = text.replace(
|
|
9013
|
+
text = text.replace(RE_ESCAPE_LT, '<');
|
|
8972
9014
|
}
|
|
8973
9015
|
}
|
|
8974
9016
|
if (uidPattern && options.collapseWhitespace && stackNoTrimWhitespace.length) {
|
|
@@ -9177,7 +9219,8 @@ const minify$1 = async function (value, options) {
|
|
|
9177
9219
|
getTerser,
|
|
9178
9220
|
getSwc,
|
|
9179
9221
|
cssMinifyCache,
|
|
9180
|
-
jsMinifyCache
|
|
9222
|
+
jsMinifyCache,
|
|
9223
|
+
urlMinifyCache
|
|
9181
9224
|
});
|
|
9182
9225
|
const result = await minifyHTML(value, options);
|
|
9183
9226
|
options.log('minified in: ' + (Date.now() - start) + 'ms');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"htmlminifier.d.ts","sourceRoot":"","sources":["../../src/htmlminifier.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"htmlminifier.d.ts","sourceRoot":"","sources":["../../src/htmlminifier.js"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attributes.d.ts","sourceRoot":"","sources":["../../../src/lib/attributes.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"attributes.d.ts","sourceRoot":"","sources":["../../../src/lib/attributes.js"],"names":[],"mappings":"AA0BA,yDAEC;AAED,mEAOC;AAED,uEAWC;AAED,8DAGC;AAED,4EAOC;AAED,mGAwCC;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,0IAgJC;AAsBD;;;;GAwCC;AAED,6GA4EC"}
|
|
@@ -10,6 +10,12 @@ export const RE_EVENT_ATTR_DEFAULT: RegExp;
|
|
|
10
10
|
export const RE_CAN_REMOVE_ATTR_QUOTES: RegExp;
|
|
11
11
|
export const RE_TRAILING_SEMICOLON: RegExp;
|
|
12
12
|
export const RE_AMP_ENTITY: RegExp;
|
|
13
|
+
export const RE_LEGACY_ENTITIES: RegExp;
|
|
14
|
+
export const RE_ESCAPE_LT: RegExp;
|
|
15
|
+
export const RE_ATTR_WS_CHECK: RegExp;
|
|
16
|
+
export const RE_ATTR_WS_COLLAPSE: RegExp;
|
|
17
|
+
export const RE_ATTR_WS_TRIM: RegExp;
|
|
18
|
+
export const RE_NUMERIC_VALUE: RegExp;
|
|
13
19
|
export const inlineElementsToKeepWhitespaceAround: Set<string>;
|
|
14
20
|
export const inlineElementsToKeepWhitespaceWithin: Set<string>;
|
|
15
21
|
export const inlineElementsToKeepWhitespace: Set<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;
|
|
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;AAC5C,wCAAwqB;AACxqB,kCAA0B;AAC1B,sCAAuC;AACvC,yCAA4C;AAC5C,qCAAuD;AACvD,sCAAmE;AAKnE,+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"}
|
|
@@ -7,9 +7,10 @@ export function shouldMinifyInnerHTML(options: any): boolean;
|
|
|
7
7
|
* @param {Function} deps.getSwc - Function to lazily load @swc/core
|
|
8
8
|
* @param {LRU} deps.cssMinifyCache - CSS minification cache
|
|
9
9
|
* @param {LRU} deps.jsMinifyCache - JS minification cache
|
|
10
|
+
* @param {LRU} deps.urlMinifyCache - URL minification cache
|
|
10
11
|
* @returns {MinifierOptions} Normalized options with defaults applied
|
|
11
12
|
*/
|
|
12
|
-
export function processOptions(inputOptions: Partial<MinifierOptions>, { getLightningCSS, getTerser, getSwc, cssMinifyCache, jsMinifyCache }?: {
|
|
13
|
+
export function processOptions(inputOptions: Partial<MinifierOptions>, { getLightningCSS, getTerser, getSwc, cssMinifyCache, jsMinifyCache, urlMinifyCache }?: {
|
|
13
14
|
getLightningCSS: Function;
|
|
14
15
|
getTerser: Function;
|
|
15
16
|
getSwc: Function;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../../../src/lib/options.js"],"names":[],"mappings":"AAWA,6DAUC;AAID
|
|
1
|
+
{"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../../../src/lib/options.js"],"names":[],"mappings":"AAWA,6DAUC;AAID;;;;;;;;;;GAUG;AACH,6CAVW,OAAO,CAAC,eAAe,CAAC,0FAEhC;IAAuB,eAAe;IACf,SAAS;IACT,MAAM;CAA2B,GAI9C,eAAe,CA4U3B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"svg.d.ts","sourceRoot":"","sources":["../../../src/lib/svg.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"svg.d.ts","sourceRoot":"","sources":["../../../src/lib/svg.js"],"names":[],"mappings":"AAmVA;;;;;;GAMG;AACH,8CALW,MAAM,SACN,MAAM,kBAEJ,MAAM,CA0BlB;AAED;;;;;;;GAOG;AACH,8CANW,MAAM,QACN,MAAM,SACN,MAAM,kBAEJ,OAAO,CAanB;AAED;;;;GAIG;AACH,6DAkBC"}
|
package/package.json
CHANGED
package/src/htmlminifier.js
CHANGED
|
@@ -8,6 +8,8 @@ import { presets, getPreset, getPresetNames } from './presets.js';
|
|
|
8
8
|
import { LRU, identity, uniqueId } from './lib/utils.js';
|
|
9
9
|
|
|
10
10
|
import {
|
|
11
|
+
RE_LEGACY_ENTITIES,
|
|
12
|
+
RE_ESCAPE_LT,
|
|
11
13
|
inlineElementsToKeepWhitespaceAround,
|
|
12
14
|
inlineElementsToKeepWhitespaceWithin,
|
|
13
15
|
specialContentTags,
|
|
@@ -93,6 +95,7 @@ async function getSwc() {
|
|
|
93
95
|
|
|
94
96
|
const cssMinifyCache = new LRU(500);
|
|
95
97
|
const jsMinifyCache = new LRU(500);
|
|
98
|
+
const urlMinifyCache = new LRU(500);
|
|
96
99
|
|
|
97
100
|
// Type definitions
|
|
98
101
|
|
|
@@ -1160,10 +1163,10 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
1160
1163
|
// Note that `&` can be escaped as `&`, without the semi-colon.
|
|
1161
1164
|
// https://mathiasbynens.be/notes/ambiguous-ampersands
|
|
1162
1165
|
if (text.indexOf('&') !== -1) {
|
|
1163
|
-
text = text.replace(
|
|
1166
|
+
text = text.replace(RE_LEGACY_ENTITIES, '&$1');
|
|
1164
1167
|
}
|
|
1165
1168
|
if (text.indexOf('<') !== -1) {
|
|
1166
|
-
text = text.replace(
|
|
1169
|
+
text = text.replace(RE_ESCAPE_LT, '<');
|
|
1167
1170
|
}
|
|
1168
1171
|
}
|
|
1169
1172
|
if (uidPattern && options.collapseWhitespace && stackNoTrimWhitespace.length) {
|
|
@@ -1372,7 +1375,8 @@ export const minify = async function (value, options) {
|
|
|
1372
1375
|
getTerser,
|
|
1373
1376
|
getSwc,
|
|
1374
1377
|
cssMinifyCache,
|
|
1375
|
-
jsMinifyCache
|
|
1378
|
+
jsMinifyCache,
|
|
1379
|
+
urlMinifyCache
|
|
1376
1380
|
});
|
|
1377
1381
|
const result = await minifyHTML(value, options);
|
|
1378
1382
|
options.log('minified in: ' + (Date.now() - start) + 'ms');
|
package/src/lib/attributes.js
CHANGED
|
@@ -6,6 +6,9 @@ import {
|
|
|
6
6
|
RE_EVENT_ATTR_DEFAULT,
|
|
7
7
|
RE_CAN_REMOVE_ATTR_QUOTES,
|
|
8
8
|
RE_AMP_ENTITY,
|
|
9
|
+
RE_ATTR_WS_CHECK,
|
|
10
|
+
RE_ATTR_WS_COLLAPSE,
|
|
11
|
+
RE_ATTR_WS_TRIM,
|
|
9
12
|
generalDefaults,
|
|
10
13
|
tagDefaults,
|
|
11
14
|
executableScriptsMimetypes,
|
|
@@ -62,9 +65,28 @@ function attributesInclude(attributes, attribute) {
|
|
|
62
65
|
}
|
|
63
66
|
|
|
64
67
|
function isAttributeRedundant(tag, attrName, attrValue, attrs) {
|
|
68
|
+
// Fast-path: Check if this element–attribute combination can possibly be redundant
|
|
69
|
+
// before doing expensive string operations
|
|
70
|
+
|
|
71
|
+
// Check if attribute name is in general defaults
|
|
72
|
+
const hasGeneralDefault = attrName in generalDefaults;
|
|
73
|
+
|
|
74
|
+
// Check if element has any default attributes
|
|
75
|
+
const tagHasDefaults = tag in tagDefaults;
|
|
76
|
+
|
|
77
|
+
// Check for legacy attribute rules (element- and attribute-specific)
|
|
78
|
+
const isLegacyAttr = (tag === 'script' && (attrName === 'language' || attrName === 'charset')) ||
|
|
79
|
+
(tag === 'a' && attrName === 'name');
|
|
80
|
+
|
|
81
|
+
// If none of these conditions apply, attribute cannot be redundant
|
|
82
|
+
if (!hasGeneralDefault && !tagHasDefaults && !isLegacyAttr) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Now we know we need to check the value, so normalize it
|
|
65
87
|
attrValue = attrValue ? trimWhitespace(attrValue.toLowerCase()) : '';
|
|
66
88
|
|
|
67
|
-
// Legacy
|
|
89
|
+
// Legacy attribute checks
|
|
68
90
|
if (tag === 'script' && attrName === 'language' && attrValue === 'javascript') {
|
|
69
91
|
return true;
|
|
70
92
|
}
|
|
@@ -76,12 +98,12 @@ function isAttributeRedundant(tag, attrName, attrValue, attrs) {
|
|
|
76
98
|
}
|
|
77
99
|
|
|
78
100
|
// Check general defaults
|
|
79
|
-
if (generalDefaults[attrName] === attrValue) {
|
|
101
|
+
if (hasGeneralDefault && generalDefaults[attrName] === attrValue) {
|
|
80
102
|
return true;
|
|
81
103
|
}
|
|
82
104
|
|
|
83
105
|
// Check tag-specific defaults
|
|
84
|
-
return tagDefaults[tag]
|
|
106
|
+
return tagHasDefaults && tagDefaults[tag][attrName] === attrValue;
|
|
85
107
|
}
|
|
86
108
|
|
|
87
109
|
function isScriptTypeAttribute(attrValue = '') {
|
|
@@ -226,15 +248,13 @@ async function cleanAttributeValue(tag, attrName, attrValue, options, attrs, min
|
|
|
226
248
|
// Apply early whitespace normalization if enabled
|
|
227
249
|
// Preserves special spaces (non-breaking space, hair space, etc.) for consistency with `collapseWhitespace`
|
|
228
250
|
if (options.collapseAttributeWhitespace) {
|
|
229
|
-
//
|
|
230
|
-
|
|
231
|
-
//
|
|
232
|
-
|
|
233
|
-
//
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
return ' ';
|
|
237
|
-
});
|
|
251
|
+
// Fast path: Only process if whitespace exists (avoids regex overhead on clean values)
|
|
252
|
+
if (RE_ATTR_WS_CHECK.test(attrValue)) {
|
|
253
|
+
// Two-pass approach (faster than single-pass with callback)
|
|
254
|
+
// First: Collapse internal whitespace sequences to single space
|
|
255
|
+
// Second: Trim leading/trailing whitespace
|
|
256
|
+
attrValue = attrValue.replace(RE_ATTR_WS_COLLAPSE, ' ').replace(RE_ATTR_WS_TRIM, '');
|
|
257
|
+
}
|
|
238
258
|
}
|
|
239
259
|
|
|
240
260
|
if (isEventAttribute(attrName, options)) {
|
package/src/lib/constants.js
CHANGED
|
@@ -12,6 +12,12 @@ const RE_EVENT_ATTR_DEFAULT = /^on[a-z]{3,}$/;
|
|
|
12
12
|
const RE_CAN_REMOVE_ATTR_QUOTES = /^[^ \t\n\f\r"'`=<>]+$/;
|
|
13
13
|
const RE_TRAILING_SEMICOLON = /;$/;
|
|
14
14
|
const RE_AMP_ENTITY = /&(#?[0-9a-zA-Z]+;)/g;
|
|
15
|
+
const RE_LEGACY_ENTITIES = /&((?:Iacute|aacute|uacute|plusmn|Otilde|otilde|agrave|Agrave|Yacute|yacute|Oslash|oslash|atilde|Atilde|brvbar|ccedil|Ccedil|Ograve|curren|divide|eacute|Eacute|ograve|Oacute|egrave|Egrave|Ugrave|frac12|frac14|frac34|ugrave|oacute|iacute|Ntilde|ntilde|Uacute|middot|igrave|Igrave|iquest|Aacute|cedil|laquo|micro|iexcl|Icirc|icirc|acirc|Ucirc|Ecirc|ocirc|Ocirc|ecirc|ucirc|Aring|aring|AElig|aelig|acute|pound|raquo|Acirc|times|THORN|szlig|thorn|COPY|auml|ordf|ordm|Uuml|macr|uuml|Auml|ouml|Ouml|para|nbsp|euml|quot|QUOT|Euml|yuml|cent|sect|copy|sup1|sup2|sup3|iuml|Iuml|ETH|shy|reg|not|yen|amp|AMP|REG|uml|eth|deg|gt|GT|LT|lt)(?!;)|(?:#?[0-9a-zA-Z]+;))/g;
|
|
16
|
+
const RE_ESCAPE_LT = /</g;
|
|
17
|
+
const RE_ATTR_WS_CHECK = /[ \n\r\t\f]/;
|
|
18
|
+
const RE_ATTR_WS_COLLAPSE = /[ \n\r\t\f]+/g;
|
|
19
|
+
const RE_ATTR_WS_TRIM = /^[ \n\r\t\f]+|[ \n\r\t\f]+$/g;
|
|
20
|
+
const RE_NUMERIC_VALUE = /-?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?/g;
|
|
15
21
|
|
|
16
22
|
// Inline element Sets for whitespace handling
|
|
17
23
|
|
|
@@ -169,6 +175,12 @@ export {
|
|
|
169
175
|
RE_CAN_REMOVE_ATTR_QUOTES,
|
|
170
176
|
RE_TRAILING_SEMICOLON,
|
|
171
177
|
RE_AMP_ENTITY,
|
|
178
|
+
RE_LEGACY_ENTITIES,
|
|
179
|
+
RE_ESCAPE_LT,
|
|
180
|
+
RE_ATTR_WS_CHECK,
|
|
181
|
+
RE_ATTR_WS_COLLAPSE,
|
|
182
|
+
RE_ATTR_WS_TRIM,
|
|
183
|
+
RE_NUMERIC_VALUE,
|
|
172
184
|
|
|
173
185
|
// Inline element Sets
|
|
174
186
|
inlineElementsToKeepWhitespaceAround,
|
package/src/lib/options.js
CHANGED
|
@@ -31,9 +31,10 @@ function shouldMinifyInnerHTML(options) {
|
|
|
31
31
|
* @param {Function} deps.getSwc - Function to lazily load @swc/core
|
|
32
32
|
* @param {LRU} deps.cssMinifyCache - CSS minification cache
|
|
33
33
|
* @param {LRU} deps.jsMinifyCache - JS minification cache
|
|
34
|
+
* @param {LRU} deps.urlMinifyCache - URL minification cache
|
|
34
35
|
* @returns {MinifierOptions} Normalized options with defaults applied
|
|
35
36
|
*/
|
|
36
|
-
const processOptions = (inputOptions, { getLightningCSS, getTerser, getSwc, cssMinifyCache, jsMinifyCache } = {}) => {
|
|
37
|
+
const processOptions = (inputOptions, { getLightningCSS, getTerser, getSwc, cssMinifyCache, jsMinifyCache, urlMinifyCache } = {}) => {
|
|
37
38
|
const options = {
|
|
38
39
|
name: function (name) {
|
|
39
40
|
return name.toLowerCase();
|
|
@@ -310,16 +311,33 @@ const processOptions = (inputOptions, { getLightningCSS, getTerser, getSwc, cssM
|
|
|
310
311
|
// Cache RelateURL instance for reuse (expensive to create)
|
|
311
312
|
const relateUrlInstance = new RelateURL(relateUrlOptions.site || '', relateUrlOptions);
|
|
312
313
|
|
|
314
|
+
// Create instance-specific cache (results depend on site configuration)
|
|
315
|
+
const instanceCache = urlMinifyCache ? new (urlMinifyCache.constructor)(500) : null;
|
|
316
|
+
|
|
313
317
|
options.minifyURLs = function (text) {
|
|
314
|
-
// Fast-path: Skip if text doesn
|
|
318
|
+
// Fast-path: Skip if text doesn't look like a URL that needs processing
|
|
315
319
|
// Only process if contains URL-like characters (`/`, `:`, `#`, `?`) or spaces that need encoding
|
|
316
320
|
if (!/[/:?#\s]/.test(text)) {
|
|
317
321
|
return text;
|
|
318
322
|
}
|
|
319
323
|
|
|
324
|
+
// Check instance-specific cache
|
|
325
|
+
if (instanceCache) {
|
|
326
|
+
const cached = instanceCache.get(text);
|
|
327
|
+
if (cached !== undefined) {
|
|
328
|
+
return cached;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
320
332
|
try {
|
|
321
|
-
|
|
333
|
+
const result = relateUrlInstance.relate(text);
|
|
334
|
+
// Cache successful results
|
|
335
|
+
if (instanceCache) {
|
|
336
|
+
instanceCache.set(text, result);
|
|
337
|
+
}
|
|
338
|
+
return result;
|
|
322
339
|
} catch (err) {
|
|
340
|
+
// Don’t cache errors
|
|
323
341
|
if (!options.continueOnMinifyError) {
|
|
324
342
|
throw err;
|
|
325
343
|
}
|
package/src/lib/svg.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { LRU } from './utils.js';
|
|
13
|
+
import { RE_NUMERIC_VALUE } from './constants.js';
|
|
13
14
|
|
|
14
15
|
// Cache for minified numbers
|
|
15
16
|
const numberCache = new LRU(100);
|
|
@@ -128,7 +129,7 @@ function minifyPathData(pathData, precision = 3) {
|
|
|
128
129
|
if (!pathData || typeof pathData !== 'string') return pathData;
|
|
129
130
|
|
|
130
131
|
// First, minify all numbers
|
|
131
|
-
let result = pathData.replace(
|
|
132
|
+
let result = pathData.replace(RE_NUMERIC_VALUE, (match) => {
|
|
132
133
|
return minifyNumber(match, precision);
|
|
133
134
|
});
|
|
134
135
|
|
|
@@ -355,7 +356,7 @@ export function minifySVGAttributeValue(name, value, options = {}) {
|
|
|
355
356
|
|
|
356
357
|
// Numeric attributes get precision reduction and whitespace minification
|
|
357
358
|
if (NUMERIC_ATTRS.has(name)) {
|
|
358
|
-
const minified = value.replace(
|
|
359
|
+
const minified = value.replace(RE_NUMERIC_VALUE, (match) => {
|
|
359
360
|
return minifyNumber(match, precision);
|
|
360
361
|
});
|
|
361
362
|
return minifyAttributeWhitespace(minified);
|