html-minifier-next 4.6.0 → 4.7.0
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 +34 -31
- package/cli.js +1 -1
- package/dist/htmlminifier.cjs +253 -30
- package/dist/htmlminifier.esm.bundle.js +253 -30
- package/dist/types/htmlminifier.d.ts.map +1 -1
- package/dist/types/htmlparser.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/htmlminifier.js +152 -25
- package/src/htmlparser.js +101 -5
- package/src/utils.js +1 -1
|
@@ -39166,6 +39166,9 @@ function joinSingleAttrAssigns(handler) {
|
|
|
39166
39166
|
}).join('|');
|
|
39167
39167
|
}
|
|
39168
39168
|
|
|
39169
|
+
// Number of captured parts per `customAttrSurround` pattern
|
|
39170
|
+
const NCP = 7;
|
|
39171
|
+
|
|
39169
39172
|
class HTMLParser {
|
|
39170
39173
|
constructor(html, handler) {
|
|
39171
39174
|
this.html = html;
|
|
@@ -39178,7 +39181,15 @@ class HTMLParser {
|
|
|
39178
39181
|
|
|
39179
39182
|
const stack = []; let lastTag;
|
|
39180
39183
|
const attribute = attrForHandler(handler);
|
|
39181
|
-
let last, prevTag, nextTag;
|
|
39184
|
+
let last, prevTag = undefined, nextTag = undefined;
|
|
39185
|
+
|
|
39186
|
+
// Track position for better error messages
|
|
39187
|
+
let position = 0;
|
|
39188
|
+
const getLineColumn = (pos) => {
|
|
39189
|
+
const lines = this.html.slice(0, pos).split('\n');
|
|
39190
|
+
return { line: lines.length, column: lines[lines.length - 1].length + 1 };
|
|
39191
|
+
};
|
|
39192
|
+
|
|
39182
39193
|
while (html) {
|
|
39183
39194
|
last = html;
|
|
39184
39195
|
// Make sure we’re not in a `script` or `style` element
|
|
@@ -39296,8 +39307,27 @@ class HTMLParser {
|
|
|
39296
39307
|
}
|
|
39297
39308
|
|
|
39298
39309
|
if (html === last) {
|
|
39299
|
-
|
|
39310
|
+
if (handler.continueOnParseError) {
|
|
39311
|
+
// Skip the problematic character and continue
|
|
39312
|
+
if (handler.chars) {
|
|
39313
|
+
await handler.chars(html[0], prevTag, '');
|
|
39314
|
+
}
|
|
39315
|
+
html = html.substring(1);
|
|
39316
|
+
position++;
|
|
39317
|
+
prevTag = '';
|
|
39318
|
+
continue;
|
|
39319
|
+
}
|
|
39320
|
+
const loc = getLineColumn(position);
|
|
39321
|
+
// Include some context before the error position so the snippet contains
|
|
39322
|
+
// the offending markup plus preceding characters (e.g. "invalid<tag").
|
|
39323
|
+
const CONTEXT_BEFORE = 50;
|
|
39324
|
+
const startPos = Math.max(0, position - CONTEXT_BEFORE);
|
|
39325
|
+
const snippet = this.html.slice(startPos, startPos + 200).replace(/\n/g, ' ');
|
|
39326
|
+
throw new Error(
|
|
39327
|
+
`Parse error at line ${loc.line}, column ${loc.column}:\n${snippet}${this.html.length > startPos + 200 ? '…' : ''}`
|
|
39328
|
+
);
|
|
39300
39329
|
}
|
|
39330
|
+
position = this.html.length - html.length;
|
|
39301
39331
|
}
|
|
39302
39332
|
|
|
39303
39333
|
if (!handler.partialMarkup) {
|
|
@@ -39314,10 +39344,77 @@ class HTMLParser {
|
|
|
39314
39344
|
};
|
|
39315
39345
|
input = input.slice(start[0].length);
|
|
39316
39346
|
let end, attr;
|
|
39317
|
-
|
|
39347
|
+
|
|
39348
|
+
// Safety limit: max length of input to check for attributes
|
|
39349
|
+
// Protects against catastrophic backtracking on massive attribute values
|
|
39350
|
+
const MAX_ATTR_PARSE_LENGTH = 20000; // 20 KB should be enough for any reasonable tag
|
|
39351
|
+
|
|
39352
|
+
while (true) {
|
|
39353
|
+
// Check for closing tag first
|
|
39354
|
+
end = input.match(startTagClose);
|
|
39355
|
+
if (end) {
|
|
39356
|
+
break;
|
|
39357
|
+
}
|
|
39358
|
+
|
|
39359
|
+
// Limit the input length we pass to the regex to prevent catastrophic backtracking
|
|
39360
|
+
const isLimited = input.length > MAX_ATTR_PARSE_LENGTH;
|
|
39361
|
+
const searchInput = isLimited ? input.slice(0, MAX_ATTR_PARSE_LENGTH) : input;
|
|
39362
|
+
|
|
39363
|
+
attr = searchInput.match(attribute);
|
|
39364
|
+
|
|
39365
|
+
// If we limited the input and got a match, check if the value might be truncated
|
|
39366
|
+
if (attr && isLimited) {
|
|
39367
|
+
// Check if the attribute value extends beyond our search window
|
|
39368
|
+
const attrEnd = attr[0].length;
|
|
39369
|
+
// If the match ends near the limit, the value might be truncated
|
|
39370
|
+
if (attrEnd > MAX_ATTR_PARSE_LENGTH - 100) {
|
|
39371
|
+
// Manually extract this attribute to handle potentially huge value
|
|
39372
|
+
const manualMatch = input.match(/^\s*([^\s"'<>/=]+)\s*=\s*/);
|
|
39373
|
+
if (manualMatch) {
|
|
39374
|
+
const quoteChar = input[manualMatch[0].length];
|
|
39375
|
+
if (quoteChar === '"' || quoteChar === "'") {
|
|
39376
|
+
const closeQuote = input.indexOf(quoteChar, manualMatch[0].length + 1);
|
|
39377
|
+
if (closeQuote !== -1) {
|
|
39378
|
+
const fullAttr = input.slice(0, closeQuote + 1);
|
|
39379
|
+
const numCustomParts = handler.customAttrSurround
|
|
39380
|
+
? handler.customAttrSurround.length * NCP
|
|
39381
|
+
: 0;
|
|
39382
|
+
const baseIndex = 1 + numCustomParts;
|
|
39383
|
+
|
|
39384
|
+
attr = [];
|
|
39385
|
+
attr[0] = fullAttr;
|
|
39386
|
+
attr[baseIndex] = manualMatch[1]; // Attribute name
|
|
39387
|
+
attr[baseIndex + 1] = '='; // customAssign (falls back to “=” for huge attributes)
|
|
39388
|
+
const value = input.slice(manualMatch[0].length + 1, closeQuote);
|
|
39389
|
+
// Place value at correct index based on quote type
|
|
39390
|
+
if (quoteChar === '"') {
|
|
39391
|
+
attr[baseIndex + 2] = value; // Double-quoted value
|
|
39392
|
+
} else {
|
|
39393
|
+
attr[baseIndex + 3] = value; // Single-quoted value
|
|
39394
|
+
}
|
|
39395
|
+
input = input.slice(fullAttr.length);
|
|
39396
|
+
match.attrs.push(attr);
|
|
39397
|
+
continue;
|
|
39398
|
+
}
|
|
39399
|
+
}
|
|
39400
|
+
// Note: Unquoted attribute values are intentionally not handled here.
|
|
39401
|
+
// Per HTML spec, unquoted values cannot contain spaces or special chars,
|
|
39402
|
+
// making a 20 KB+ unquoted value practically impossible. If encountered,
|
|
39403
|
+
// it’s malformed HTML and using the truncated regex match is acceptable.
|
|
39404
|
+
}
|
|
39405
|
+
}
|
|
39406
|
+
}
|
|
39407
|
+
|
|
39408
|
+
if (!attr) {
|
|
39409
|
+
break;
|
|
39410
|
+
}
|
|
39411
|
+
|
|
39318
39412
|
input = input.slice(attr[0].length);
|
|
39319
39413
|
match.attrs.push(attr);
|
|
39320
39414
|
}
|
|
39415
|
+
|
|
39416
|
+
// Check for closing tag
|
|
39417
|
+
end = input.match(startTagClose);
|
|
39321
39418
|
if (end) {
|
|
39322
39419
|
match.unarySlash = end[1];
|
|
39323
39420
|
match.rest = input.slice(end[0].length);
|
|
@@ -39410,7 +39507,6 @@ class HTMLParser {
|
|
|
39410
39507
|
|
|
39411
39508
|
const attrs = match.attrs.map(function (args) {
|
|
39412
39509
|
let name, value, customOpen, customClose, customAssign, quote;
|
|
39413
|
-
const ncp = 7; // Number of captured parts, scalar
|
|
39414
39510
|
|
|
39415
39511
|
// Hackish workaround for FF bug https://bugzilla.mozilla.org/show_bug.cgi?id=369778
|
|
39416
39512
|
if (IS_REGEX_CAPTURING_BROKEN && args[0].indexOf('""') === -1) {
|
|
@@ -39438,7 +39534,7 @@ class HTMLParser {
|
|
|
39438
39534
|
|
|
39439
39535
|
let j = 1;
|
|
39440
39536
|
if (handler.customAttrSurround) {
|
|
39441
|
-
for (let i = 0, l = handler.customAttrSurround.length; i < l; i++, j +=
|
|
39537
|
+
for (let i = 0, l = handler.customAttrSurround.length; i < l; i++, j += NCP) {
|
|
39442
39538
|
name = args[j + 1];
|
|
39443
39539
|
if (name) {
|
|
39444
39540
|
quote = populate(j + 2);
|
|
@@ -39661,18 +39757,88 @@ function getPresetNames() {
|
|
|
39661
39757
|
return Object.keys(presets);
|
|
39662
39758
|
}
|
|
39663
39759
|
|
|
39664
|
-
|
|
39760
|
+
// Hoisted, reusable RegExp patterns and tiny helpers to avoid repeated allocations in hot paths
|
|
39761
|
+
const RE_WS_START = /^[ \n\r\t\f]+/;
|
|
39762
|
+
const RE_WS_END = /[ \n\r\t\f]+$/;
|
|
39763
|
+
const RE_ALL_WS_NBSP = /[ \n\r\t\f\xA0]+/g;
|
|
39764
|
+
const RE_NBSP_LEADING_GROUP = /(^|\xA0+)[^\xA0]+/g;
|
|
39765
|
+
const RE_NBSP_LEAD_GROUP = /(\xA0+)[^\xA0]+/g;
|
|
39766
|
+
const RE_NBSP_TRAILING_GROUP = /[^\xA0]+(\xA0+)/g;
|
|
39767
|
+
const RE_NBSP_TRAILING_STRIP = /[^\xA0]+$/;
|
|
39768
|
+
const RE_CONDITIONAL_COMMENT = /^\[if\s[^\]]+]|\[endif]$/;
|
|
39769
|
+
const RE_EVENT_ATTR_DEFAULT = /^on[a-z]{3,}$/;
|
|
39770
|
+
const RE_CAN_REMOVE_ATTR_QUOTES = /^[^ \t\n\f\r"'`=<>]+$/;
|
|
39771
|
+
const RE_TRAILING_SEMICOLON = /;$/;
|
|
39772
|
+
const RE_AMP_ENTITY = /&(#?[0-9a-zA-Z]+;)/g;
|
|
39773
|
+
|
|
39774
|
+
// Tiny stable stringify for options signatures (sorted keys, shallow, nested objects)
|
|
39775
|
+
function stableStringify(obj) {
|
|
39776
|
+
if (obj == null || typeof obj !== 'object') return JSON.stringify(obj);
|
|
39777
|
+
if (Array.isArray(obj)) return '[' + obj.map(stableStringify).join(',') + ']';
|
|
39778
|
+
const keys = Object.keys(obj).sort();
|
|
39779
|
+
let out = '{';
|
|
39780
|
+
for (let i = 0; i < keys.length; i++) {
|
|
39781
|
+
const k = keys[i];
|
|
39782
|
+
out += JSON.stringify(k) + ':' + stableStringify(obj[k]) + (i < keys.length - 1 ? ',' : '');
|
|
39783
|
+
}
|
|
39784
|
+
return out + '}';
|
|
39785
|
+
}
|
|
39786
|
+
|
|
39787
|
+
// Minimal LRU cache for strings and promises
|
|
39788
|
+
class LRU {
|
|
39789
|
+
constructor(limit = 200) {
|
|
39790
|
+
this.limit = limit;
|
|
39791
|
+
this.map = new Map();
|
|
39792
|
+
}
|
|
39793
|
+
get(key) {
|
|
39794
|
+
const v = this.map.get(key);
|
|
39795
|
+
if (v !== undefined) {
|
|
39796
|
+
this.map.delete(key);
|
|
39797
|
+
this.map.set(key, v);
|
|
39798
|
+
}
|
|
39799
|
+
return v;
|
|
39800
|
+
}
|
|
39801
|
+
set(key, value) {
|
|
39802
|
+
if (this.map.has(key)) this.map.delete(key);
|
|
39803
|
+
this.map.set(key, value);
|
|
39804
|
+
if (this.map.size > this.limit) {
|
|
39805
|
+
const first = this.map.keys().next().value;
|
|
39806
|
+
this.map.delete(first);
|
|
39807
|
+
}
|
|
39808
|
+
}
|
|
39809
|
+
delete(key) { this.map.delete(key); }
|
|
39810
|
+
}
|
|
39811
|
+
|
|
39812
|
+
// Per-process caches
|
|
39813
|
+
const jsMinifyCache = new LRU(200);
|
|
39814
|
+
const cssMinifyCache = new LRU(200);
|
|
39815
|
+
|
|
39816
|
+
const trimWhitespace = str => {
|
|
39817
|
+
if (!str) return str;
|
|
39818
|
+
// Fast path: if no whitespace at start or end, return early
|
|
39819
|
+
if (!/^[ \n\r\t\f]/.test(str) && !/[ \n\r\t\f]$/.test(str)) {
|
|
39820
|
+
return str;
|
|
39821
|
+
}
|
|
39822
|
+
return str.replace(RE_WS_START, '').replace(RE_WS_END, '');
|
|
39823
|
+
};
|
|
39665
39824
|
|
|
39666
39825
|
function collapseWhitespaceAll(str) {
|
|
39826
|
+
if (!str) return str;
|
|
39827
|
+
// Fast path: if there are no common whitespace characters, return early
|
|
39828
|
+
if (!/[ \n\r\t\f\xA0]/.test(str)) {
|
|
39829
|
+
return str;
|
|
39830
|
+
}
|
|
39667
39831
|
// Non-breaking space is specifically handled inside the replacer function here:
|
|
39668
|
-
return str
|
|
39669
|
-
return spaces === '\t' ? '\t' : spaces.replace(
|
|
39832
|
+
return str.replace(RE_ALL_WS_NBSP, function (spaces) {
|
|
39833
|
+
return spaces === '\t' ? '\t' : spaces.replace(RE_NBSP_LEADING_GROUP, '$1 ');
|
|
39670
39834
|
});
|
|
39671
39835
|
}
|
|
39672
39836
|
|
|
39673
39837
|
function collapseWhitespace(str, options, trimLeft, trimRight, collapseAll) {
|
|
39674
39838
|
let lineBreakBefore = ''; let lineBreakAfter = '';
|
|
39675
39839
|
|
|
39840
|
+
if (!str) return str;
|
|
39841
|
+
|
|
39676
39842
|
if (options.preserveLineBreaks) {
|
|
39677
39843
|
str = str.replace(/^[ \n\r\t\f]*?[\n\r][ \n\r\t\f]*/, function () {
|
|
39678
39844
|
lineBreakBefore = '\n';
|
|
@@ -39690,7 +39856,7 @@ function collapseWhitespace(str, options, trimLeft, trimRight, collapseAll) {
|
|
|
39690
39856
|
if (conservative && spaces === '\t') {
|
|
39691
39857
|
return '\t';
|
|
39692
39858
|
}
|
|
39693
|
-
return spaces.replace(/^[^\xA0]+/, '').replace(
|
|
39859
|
+
return spaces.replace(/^[^\xA0]+/, '').replace(RE_NBSP_LEAD_GROUP, '$1 ') || (conservative ? ' ' : '');
|
|
39694
39860
|
});
|
|
39695
39861
|
}
|
|
39696
39862
|
|
|
@@ -39701,7 +39867,7 @@ function collapseWhitespace(str, options, trimLeft, trimRight, collapseAll) {
|
|
|
39701
39867
|
if (conservative && spaces === '\t') {
|
|
39702
39868
|
return '\t';
|
|
39703
39869
|
}
|
|
39704
|
-
return spaces.replace(
|
|
39870
|
+
return spaces.replace(RE_NBSP_TRAILING_GROUP, ' $1').replace(RE_NBSP_TRAILING_STRIP, '') || (conservative ? ' ' : '');
|
|
39705
39871
|
});
|
|
39706
39872
|
}
|
|
39707
39873
|
|
|
@@ -39733,7 +39899,7 @@ function collapseWhitespaceSmart(str, prevTag, nextTag, options, inlineElements,
|
|
|
39733
39899
|
}
|
|
39734
39900
|
|
|
39735
39901
|
function isConditionalComment(text) {
|
|
39736
|
-
return
|
|
39902
|
+
return RE_CONDITIONAL_COMMENT.test(text);
|
|
39737
39903
|
}
|
|
39738
39904
|
|
|
39739
39905
|
function isIgnoredComment(text, options) {
|
|
@@ -39755,12 +39921,12 @@ function isEventAttribute(attrName, options) {
|
|
|
39755
39921
|
}
|
|
39756
39922
|
return false;
|
|
39757
39923
|
}
|
|
39758
|
-
return
|
|
39924
|
+
return RE_EVENT_ATTR_DEFAULT.test(attrName);
|
|
39759
39925
|
}
|
|
39760
39926
|
|
|
39761
39927
|
function canRemoveAttributeQuotes(value) {
|
|
39762
39928
|
// https://mathiasbynens.be/notes/unquoted-attribute-values
|
|
39763
|
-
return
|
|
39929
|
+
return RE_CAN_REMOVE_ATTR_QUOTES.test(value);
|
|
39764
39930
|
}
|
|
39765
39931
|
|
|
39766
39932
|
function attributesInclude(attributes, attribute) {
|
|
@@ -39971,7 +40137,7 @@ async function cleanAttributeValue(tag, attrName, attrValue, options, attrs, min
|
|
|
39971
40137
|
} else if (attrName === 'style') {
|
|
39972
40138
|
attrValue = trimWhitespace(attrValue);
|
|
39973
40139
|
if (attrValue) {
|
|
39974
|
-
if (
|
|
40140
|
+
if (attrValue.endsWith(';') && !/&#?[0-9a-zA-Z]+;$/.test(attrValue)) {
|
|
39975
40141
|
attrValue = attrValue.replace(/\s*;$/, ';');
|
|
39976
40142
|
}
|
|
39977
40143
|
attrValue = await options.minifyCSS(attrValue, 'inline');
|
|
@@ -40290,7 +40456,10 @@ async function normalizeAttr(attr, attrs, tag, options) {
|
|
|
40290
40456
|
let attrValue = attr.value;
|
|
40291
40457
|
|
|
40292
40458
|
if (options.decodeEntities && attrValue) {
|
|
40293
|
-
|
|
40459
|
+
// Fast path: only decode when entities are present
|
|
40460
|
+
if (attrValue.indexOf('&') !== -1) {
|
|
40461
|
+
attrValue = decodeHTMLStrict(attrValue);
|
|
40462
|
+
}
|
|
40294
40463
|
}
|
|
40295
40464
|
|
|
40296
40465
|
if ((options.removeRedundantAttributes &&
|
|
@@ -40311,8 +40480,8 @@ async function normalizeAttr(attr, attrs, tag, options) {
|
|
|
40311
40480
|
return;
|
|
40312
40481
|
}
|
|
40313
40482
|
|
|
40314
|
-
if (options.decodeEntities && attrValue) {
|
|
40315
|
-
attrValue = attrValue.replace(
|
|
40483
|
+
if (options.decodeEntities && attrValue && attrValue.indexOf('&') !== -1) {
|
|
40484
|
+
attrValue = attrValue.replace(RE_AMP_ENTITY, '&$1');
|
|
40316
40485
|
}
|
|
40317
40486
|
|
|
40318
40487
|
return {
|
|
@@ -40432,6 +40601,10 @@ const processOptions = (inputOptions) => {
|
|
|
40432
40601
|
const lightningCssOptions = typeof option === 'object' ? option : {};
|
|
40433
40602
|
|
|
40434
40603
|
options.minifyCSS = async function (text, type) {
|
|
40604
|
+
// Fast path: nothing to minify
|
|
40605
|
+
if (!text || !text.trim()) {
|
|
40606
|
+
return text;
|
|
40607
|
+
}
|
|
40435
40608
|
text = await replaceAsync(
|
|
40436
40609
|
text,
|
|
40437
40610
|
/(url\s*\(\s*)(?:"([^"]*)"|'([^']*)'|([^\s)]+))(\s*\))/ig,
|
|
@@ -40450,10 +40623,20 @@ const processOptions = (inputOptions) => {
|
|
|
40450
40623
|
}
|
|
40451
40624
|
}
|
|
40452
40625
|
);
|
|
40453
|
-
|
|
40626
|
+
// Cache key: wrapped content, type, options signature
|
|
40454
40627
|
const inputCSS = wrapCSS(text, type);
|
|
40628
|
+
const cssSig = stableStringify({ type, opts: lightningCssOptions, cont: !!options.continueOnMinifyError });
|
|
40629
|
+
// For large inputs, use length and content fingerprint (first/last 50 chars) to prevent collisions
|
|
40630
|
+
const cssKey = inputCSS.length > 2048
|
|
40631
|
+
? (inputCSS.length + '|' + inputCSS.slice(0, 50) + inputCSS.slice(-50) + '|' + type + '|' + cssSig)
|
|
40632
|
+
: (inputCSS + '|' + type + '|' + cssSig);
|
|
40455
40633
|
|
|
40456
40634
|
try {
|
|
40635
|
+
const cached = cssMinifyCache.get(cssKey);
|
|
40636
|
+
if (cached) {
|
|
40637
|
+
return cached;
|
|
40638
|
+
}
|
|
40639
|
+
|
|
40457
40640
|
const result = transform({
|
|
40458
40641
|
filename: 'input.css',
|
|
40459
40642
|
code: Buffer.from(inputCSS),
|
|
@@ -40476,12 +40659,12 @@ const processOptions = (inputOptions) => {
|
|
|
40476
40659
|
|
|
40477
40660
|
// Preserve if output is empty and input had template syntax or UIDs
|
|
40478
40661
|
// This catches cases where Lightning CSS removed content that should be preserved
|
|
40479
|
-
|
|
40480
|
-
return text;
|
|
40481
|
-
}
|
|
40662
|
+
const finalOutput = (text.trim() && !outputCSS.trim() && (looksLikeTemplate || hasUID)) ? text : outputCSS;
|
|
40482
40663
|
|
|
40483
|
-
|
|
40664
|
+
cssMinifyCache.set(cssKey, finalOutput);
|
|
40665
|
+
return finalOutput;
|
|
40484
40666
|
} catch (err) {
|
|
40667
|
+
cssMinifyCache.delete(cssKey);
|
|
40485
40668
|
if (!options.continueOnMinifyError) {
|
|
40486
40669
|
throw err;
|
|
40487
40670
|
}
|
|
@@ -40507,10 +40690,39 @@ const processOptions = (inputOptions) => {
|
|
|
40507
40690
|
|
|
40508
40691
|
terserOptions.parse.bare_returns = inline;
|
|
40509
40692
|
|
|
40693
|
+
let jsKey;
|
|
40510
40694
|
try {
|
|
40511
|
-
|
|
40512
|
-
|
|
40695
|
+
// Fast path: avoid invoking Terser for empty/whitespace-only content
|
|
40696
|
+
if (!code || !code.trim()) {
|
|
40697
|
+
return '';
|
|
40698
|
+
}
|
|
40699
|
+
// Cache key: content, inline, options signature (subset)
|
|
40700
|
+
const terserSig = stableStringify({
|
|
40701
|
+
compress: terserOptions.compress,
|
|
40702
|
+
mangle: terserOptions.mangle,
|
|
40703
|
+
ecma: terserOptions.ecma,
|
|
40704
|
+
toplevel: terserOptions.toplevel,
|
|
40705
|
+
module: terserOptions.module,
|
|
40706
|
+
keep_fnames: terserOptions.keep_fnames,
|
|
40707
|
+
format: terserOptions.format,
|
|
40708
|
+
cont: !!options.continueOnMinifyError,
|
|
40709
|
+
});
|
|
40710
|
+
// For large inputs, use length and content fingerprint (first/last 50 chars) to prevent collisions
|
|
40711
|
+
jsKey = (code.length > 2048 ? (code.length + '|' + code.slice(0, 50) + code.slice(-50) + '|') : (code + '|')) + (inline ? '1' : '0') + '|' + terserSig;
|
|
40712
|
+
const cached = jsMinifyCache.get(jsKey);
|
|
40713
|
+
if (cached) {
|
|
40714
|
+
return await cached;
|
|
40715
|
+
}
|
|
40716
|
+
const inFlight = (async () => {
|
|
40717
|
+
const result = await minify$1(code, terserOptions);
|
|
40718
|
+
return result.code.replace(RE_TRAILING_SEMICOLON, '');
|
|
40719
|
+
})();
|
|
40720
|
+
jsMinifyCache.set(jsKey, inFlight);
|
|
40721
|
+
const resolved = await inFlight;
|
|
40722
|
+
jsMinifyCache.set(jsKey, resolved);
|
|
40723
|
+
return resolved;
|
|
40513
40724
|
} catch (err) {
|
|
40725
|
+
if (jsKey) jsMinifyCache.delete(jsKey);
|
|
40514
40726
|
if (!options.continueOnMinifyError) {
|
|
40515
40727
|
throw err;
|
|
40516
40728
|
}
|
|
@@ -40601,8 +40813,11 @@ async function createSortFns(value, options, uidIgnore, uidAttr) {
|
|
|
40601
40813
|
currentTag = '';
|
|
40602
40814
|
},
|
|
40603
40815
|
chars: async function (text) {
|
|
40816
|
+
// Only recursively scan HTML content, not JSON-LD or other non-HTML script types
|
|
40817
|
+
// `scan()` is for analyzing HTML attribute order, not for parsing JSON
|
|
40604
40818
|
if (options.processScripts && specialContentTags.has(currentTag) &&
|
|
40605
|
-
options.processScripts.indexOf(currentType) > -1
|
|
40819
|
+
options.processScripts.indexOf(currentType) > -1 &&
|
|
40820
|
+
currentType === 'text/html') {
|
|
40606
40821
|
await scan(text);
|
|
40607
40822
|
}
|
|
40608
40823
|
}
|
|
@@ -40615,7 +40830,8 @@ async function createSortFns(value, options, uidIgnore, uidAttr) {
|
|
|
40615
40830
|
options.log = identity;
|
|
40616
40831
|
options.sortAttributes = false;
|
|
40617
40832
|
options.sortClassName = false;
|
|
40618
|
-
|
|
40833
|
+
const firstPassOutput = await minifyHTML(value, options);
|
|
40834
|
+
await scan(firstPassOutput);
|
|
40619
40835
|
options.log = log;
|
|
40620
40836
|
if (attrChains) {
|
|
40621
40837
|
const attrSorters = Object.create(null);
|
|
@@ -40968,7 +41184,9 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
40968
41184
|
prevTag = prevTag === '' ? 'comment' : prevTag;
|
|
40969
41185
|
nextTag = nextTag === '' ? 'comment' : nextTag;
|
|
40970
41186
|
if (options.decodeEntities && text && !specialContentTags.has(currentTag)) {
|
|
40971
|
-
|
|
41187
|
+
if (text.indexOf('&') !== -1) {
|
|
41188
|
+
text = decodeHTML(text);
|
|
41189
|
+
}
|
|
40972
41190
|
}
|
|
40973
41191
|
if (options.collapseWhitespace) {
|
|
40974
41192
|
if (!stackNoTrimWhitespace.length) {
|
|
@@ -41042,11 +41260,16 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
41042
41260
|
charsPrevTag = /^\s*$/.test(text) ? prevTag : 'comment';
|
|
41043
41261
|
if (options.decodeEntities && text && !specialContentTags.has(currentTag)) {
|
|
41044
41262
|
// Escape any `&` symbols that start either:
|
|
41045
|
-
// 1) a legacy named character reference (i.e
|
|
41046
|
-
// 2) or any other character reference (i.e
|
|
41263
|
+
// 1) a legacy named character reference (i.e., one that doesn’t end with `;`)
|
|
41264
|
+
// 2) or any other character reference (i.e., one that does end with `;`)
|
|
41047
41265
|
// Note that `&` can be escaped as `&`, without the semi-colon.
|
|
41048
41266
|
// https://mathiasbynens.be/notes/ambiguous-ampersands
|
|
41049
|
-
|
|
41267
|
+
if (text.indexOf('&') !== -1) {
|
|
41268
|
+
text = text.replace(/&((?: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, '&$1');
|
|
41269
|
+
}
|
|
41270
|
+
if (text.indexOf('<') !== -1) {
|
|
41271
|
+
text = text.replace(/</g, '<');
|
|
41272
|
+
}
|
|
41050
41273
|
}
|
|
41051
41274
|
if (uidPattern && options.collapseWhitespace && stackNoTrimWhitespace.length) {
|
|
41052
41275
|
text = text.replace(uidPattern, function (match, prefix, index) {
|
|
@@ -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":"AAknDO,8BAJI,MAAM,YACN,eAAe,GACb,OAAO,CAAC,MAAM,CAAC,CAQ3B;;;;;;;;;;;;UAUS,MAAM;YACN,MAAM;YACN,MAAM;mBACN,MAAM;iBACN,MAAM;kBACN,MAAM;;;;;;;;;;;;;4BAQN,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,EAAE,qBAAqB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,KAAK,OAAO;;;;;;;wBAMjG,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,EAAE,KAAK,EAAE,aAAa,EAAE,GAAG,SAAS,EAAE,iBAAiB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,KAAK,OAAO;;;;;;;;oBAMhH,OAAO;;;;;;;;gCAOP,OAAO;;;;;;;;kCAOP,OAAO;;;;;;;;yBAOP,OAAO;;;;;;;;2BAOP,OAAO;;;;;;;;4BAOP,OAAO;;;;;;;2BAOP,OAAO;;;;;;;;uBAMP,MAAM,EAAE;;;;;;yBAOR,MAAM;;;;;;yBAKN,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE;;;;;;;4BAKlB,MAAM,EAAE;;;;;;;oCAMR,MAAM;;;;;;;qBAMN,OAAO;;;;;;;YAMP,OAAO;;;;;;;;2BAMP,MAAM,EAAE;;;;;;;;;4BAOR,MAAM,EAAE;;;;;;;+BAQR,OAAO;;;;;;;2BAMP,SAAS,CAAC,MAAM,CAAC;;;;;;uBAMjB,OAAO;;;;;;;;UAKP,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI;;;;;;;;qBAO1B,MAAM;;;;;;;oBAON,MAAM;;;;;;;;;;gBAMN,OAAO,GAAG,OAAO,CAAC,OAAO,cAAc,EAAE,gBAAgB,CAAC,OAAO,cAAc,EAAE,aAAa,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC;;;;;;;;;;eAS9J,OAAO,GAAG,OAAO,QAAQ,EAAE,aAAa,GAAG,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC;;;;;;;;;;iBASzG,OAAO,GAAG,MAAM,GAAG,OAAO,WAAW,EAAE,OAAO,GAAG,CAAC,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC;;;;;;;;WAS7F,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM;;;;;;;+BAOxB,OAAO;;;;;;;;;;oBAMP,OAAO;;;;;;;;yBASP,OAAO;;;;;;;gCAOP,OAAO;;;;;;;;iCAMP,OAAO;;;;;;;;;;qBAOP,MAAM,EAAE;;;;;;;qBASR,IAAI,GAAG,GAAG;;;;;;;4BAMV,OAAO;;;;;;;;qBAMP,OAAO;;;;;;;;;4BAOP,OAAO,GAAG,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;;;;;;;;0BAQtD,OAAO;;;;;;;;yBAOP,OAAO;;;;;;;;gCAOP,OAAO;;;;;;;iCAOP,OAAO;;;;;;;oCAMP,OAAO;;;;;;;;;;0BAMP,OAAO;;;;;;;;;qBASP,OAAO,GAAG,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,KAAK,IAAI,CAAC;;;;;;;;;oBAQzD,OAAO,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;;;;;;;;0BAQrC,OAAO;;;;;;;sBAOP,OAAO;;wBA38DkC,cAAc;0BAAd,cAAc;+BAAd,cAAc"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"htmlparser.d.ts","sourceRoot":"","sources":["../../src/htmlparser.js"],"names":[],"mappings":"AAgDA,4BAAoE;
|
|
1
|
+
{"version":3,"file":"htmlparser.d.ts","sourceRoot":"","sources":["../../src/htmlparser.js"],"names":[],"mappings":"AAgDA,4BAAoE;AA4DpE;IACE,qCAGC;IAFC,UAAgB;IAChB,aAAsB;IAGxB,uBA6bC;CACF"}
|
package/package.json
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"relateurl": "^0.2.7",
|
|
14
14
|
"terser": "^5.44.1"
|
|
15
15
|
},
|
|
16
|
-
"description": "Highly configurable, well-tested, JavaScript-based HTML minifier",
|
|
16
|
+
"description": "Highly configurable, well-tested, JavaScript-based HTML minifier (enhanced successor of HTML Minifier)",
|
|
17
17
|
"devDependencies": {
|
|
18
18
|
"@commitlint/cli": "^20.1.0",
|
|
19
19
|
"@eslint/js": "^9.39.1",
|
|
@@ -84,5 +84,5 @@
|
|
|
84
84
|
"test:watch": "node --test --watch tests/*.spec.js"
|
|
85
85
|
},
|
|
86
86
|
"type": "module",
|
|
87
|
-
"version": "4.
|
|
87
|
+
"version": "4.7.0"
|
|
88
88
|
}
|