html-minifier 5.0.2 → 5.0.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 -4
- package/cli.js +89 -32
- package/dist/types/htmlminifier.d.ts.map +1 -1
- package/dist/types/htmlparser.d.ts.map +1 -1
- package/dist/types/lib/attributes.d.ts.map +1 -1
- package/dist/types/lib/constants.d.ts +5 -1
- package/dist/types/lib/constants.d.ts.map +1 -1
- package/dist/types/lib/elements.d.ts.map +1 -1
- package/dist/types/lib/options.d.ts +1 -0
- package/dist/types/lib/options.d.ts.map +1 -1
- package/package.json +3 -1
- package/src/htmlminifier.js +131 -30
- package/src/htmlparser.js +309 -24
- package/src/lib/attributes.js +77 -12
- package/src/lib/constants.js +14 -3
- package/src/lib/elements.js +7 -0
- package/src/lib/options.js +5 -4
package/src/htmlminifier.js
CHANGED
|
@@ -17,7 +17,10 @@ import {
|
|
|
17
17
|
RE_WS_ONLY,
|
|
18
18
|
RE_WS_NBSP_END,
|
|
19
19
|
RE_STYLE_ELEMENT,
|
|
20
|
+
RE_ENDS_COLLAPSIBLE,
|
|
21
|
+
RE_DOCTYPE_SEGMENT,
|
|
20
22
|
formattingElements,
|
|
23
|
+
inlineElementsToKeepWhitespace,
|
|
21
24
|
inlineElementsToKeepWhitespaceAround,
|
|
22
25
|
inlineElementsToKeepWhitespaceWithin,
|
|
23
26
|
inlineElementsTransparent,
|
|
@@ -826,13 +829,13 @@ function mergeConsecutiveScripts(html, hasMathML = false) {
|
|
|
826
829
|
}
|
|
827
830
|
if (boolConflict || a1.nonce !== a2.nonce || (!mergedAny && insideMath(m1.index))) break;
|
|
828
831
|
|
|
829
|
-
// Join with a semicolon,
|
|
832
|
+
// Join with a semicolon, after a newline where the first may end in a `//` comment
|
|
830
833
|
const content2 = html.slice(tagEnd2 + 1, close2.index);
|
|
831
834
|
const c1 = mergedContent.trim();
|
|
832
835
|
const c2 = content2.trim();
|
|
833
836
|
if (c1 && c2) {
|
|
834
837
|
const lastLine = c1.slice(c1.lastIndexOf('\n') + 1);
|
|
835
|
-
const separator = lastLine.includes('//') ? '\n' : (c1.endsWith(';') ? '' : ';');
|
|
838
|
+
const separator = lastLine.includes('//') ? '\n;' : (c1.endsWith(';') ? '' : ';');
|
|
836
839
|
mergedContent = c1 + separator + c2;
|
|
837
840
|
} else {
|
|
838
841
|
mergedContent = c1 || c2;
|
|
@@ -1124,8 +1127,12 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
1124
1127
|
// Open elements under `removeEmptyElements`, so an element whose content
|
|
1125
1128
|
// went is removed, too: where its start tag sits in `buffer`, and whether
|
|
1126
1129
|
// anything kept went in since
|
|
1127
|
-
/** @type {Array<{tag: string, index: number, empty: boolean}>} */
|
|
1130
|
+
/** @type {Array<{tag: string, index: number, empty: boolean, omittedStartTag?: {tag: string, text: string} | undefined, omittedEndTag?: {tag: string, text: string} | undefined, impliedEnds?: Array<{tag: string, text: string}> | undefined}>} */
|
|
1128
1131
|
const openElements = [];
|
|
1132
|
+
// End tags not written, for the start tag that comes next (`text` is empty where one can’t be)
|
|
1133
|
+
/** @type {Array<{tag: string, text: string}>} */
|
|
1134
|
+
let impliedEnds = [];
|
|
1135
|
+
let impliedEndsIndex = -1;
|
|
1129
1136
|
// Under `selfCloseForeignElements`, the open `svg` and `math` elements and the integration points
|
|
1130
1137
|
// in them, whose children are HTML, which a slash does not close
|
|
1131
1138
|
/** @type {string[]} */
|
|
@@ -1149,7 +1156,7 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
1149
1156
|
const stackNoTrimWhitespace = [];
|
|
1150
1157
|
/** @type {string[]} */
|
|
1151
1158
|
const stackNoCollapseWhitespace = [];
|
|
1152
|
-
let
|
|
1159
|
+
let preDepth = 0; // Count of `pre` entries in `stackNoTrimWhitespace`
|
|
1153
1160
|
// Where the last element whose whitespace may not be trimmed ended in `buffer`;
|
|
1154
1161
|
// what lies up to there stays as written, also where `canTrimWhitespace` decides
|
|
1155
1162
|
// by attributes the trim walk lacks
|
|
@@ -1503,8 +1510,11 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
1503
1510
|
return index;
|
|
1504
1511
|
}
|
|
1505
1512
|
|
|
1506
|
-
function removeStartTag() {
|
|
1507
|
-
|
|
1513
|
+
function removeStartTag(/** @type {boolean} */ capture = false) {
|
|
1514
|
+
const index = lastStartTagIndex();
|
|
1515
|
+
const text = capture ? buffer.slice(index).join('') : undefined;
|
|
1516
|
+
bufferTruncate(index);
|
|
1517
|
+
return text;
|
|
1508
1518
|
}
|
|
1509
1519
|
|
|
1510
1520
|
// Write the last start tag as self-closing, where it is the last thing written and holds no
|
|
@@ -1549,6 +1559,26 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
1549
1559
|
return lowerTag === 'svg' || lowerTag === 'math' || scope === undefined || scope === 'svg' || scope === 'math';
|
|
1550
1560
|
}
|
|
1551
1561
|
|
|
1562
|
+
// Browsers can close a formatting element with others left open inside it;
|
|
1563
|
+
// any other end tag that doesn’t match the top entry of a stack is stray
|
|
1564
|
+
function canCloseBelowTop(/** @type {string} */ tag, /** @type {string} */ lowerTag) {
|
|
1565
|
+
return formattingElements.has((lowerTag || tag).toLowerCase());
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
// Returns whether `tag` was open
|
|
1569
|
+
function removeClosedTag(/** @type {string[]} */ tags, /** @type {string} */ tag, /** @type {string} */ lowerTag) {
|
|
1570
|
+
if (tags[tags.length - 1] === tag) {
|
|
1571
|
+
tags.pop();
|
|
1572
|
+
return true;
|
|
1573
|
+
}
|
|
1574
|
+
const index = canCloseBelowTop(tag, lowerTag) ? tags.lastIndexOf(tag) : -1;
|
|
1575
|
+
if (index === -1) {
|
|
1576
|
+
return false;
|
|
1577
|
+
}
|
|
1578
|
+
tags.splice(index, 1);
|
|
1579
|
+
return true;
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1552
1582
|
function markOpenElementFilled() {
|
|
1553
1583
|
// Guarded, as reading index -1 is a slow property lookup, and this runs for every text node
|
|
1554
1584
|
if (openElements.length) {
|
|
@@ -1596,11 +1626,12 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
1596
1626
|
while (index > 0 && !RE_END_TAG.test(buffer[index] ?? '')) {
|
|
1597
1627
|
index--;
|
|
1598
1628
|
}
|
|
1629
|
+
const text = buffer[index] ?? '';
|
|
1599
1630
|
// Text that follows the end tag is kept as is, so drop the tag alone. Whitespace kept
|
|
1600
1631
|
// verbatim—in `pre`, in `textarea`, or wherever `canTrimWhitespace` says so—counts as
|
|
1601
1632
|
// kept text too, no matter how whitespace is collapsed elsewhere.
|
|
1602
1633
|
const noTrim = insideNoTrim || stackNoTrimWhitespace.length > 0;
|
|
1603
|
-
if (index < buffer.length - 1 && (keepsWhitespace() || noTrim) && RE_END_TAG.test(
|
|
1634
|
+
if (index < buffer.length - 1 && (keepsWhitespace() || noTrim) && RE_END_TAG.test(text)) {
|
|
1604
1635
|
buffer.splice(index, 1);
|
|
1605
1636
|
bufferTags?.splice(index, 1);
|
|
1606
1637
|
// Collapsed whitespace can merge, kept whitespace can leave a line of only whitespace
|
|
@@ -1611,9 +1642,10 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
1611
1642
|
removeWhitespaceLine(index);
|
|
1612
1643
|
}
|
|
1613
1644
|
}
|
|
1614
|
-
return;
|
|
1645
|
+
return text;
|
|
1615
1646
|
}
|
|
1616
1647
|
bufferTruncate(index);
|
|
1648
|
+
return text;
|
|
1617
1649
|
}
|
|
1618
1650
|
|
|
1619
1651
|
// Look for trailing whitespace, bypassing inline tags
|
|
@@ -1775,11 +1807,11 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
1775
1807
|
|
|
1776
1808
|
// Whitespace collapsing phase (sync)
|
|
1777
1809
|
function charsCollapse(/** @type {string} */ text) {
|
|
1778
|
-
// Trim a single trailing newline inside `pre
|
|
1779
|
-
//
|
|
1810
|
+
// Trim a single trailing newline inside `pre`, often a template engine artifact (several are
|
|
1811
|
+
// likely intentional formatting); in `textarea`, it is part of the value
|
|
1780
1812
|
if (options.collapseWhitespace && stackNoTrimWhitespace.length) {
|
|
1781
1813
|
const topTag = stackNoTrimWhitespace[stackNoTrimWhitespace.length - 1];
|
|
1782
|
-
if (
|
|
1814
|
+
if (preDepth > 0 && topTag?.toLowerCase() !== 'textarea') {
|
|
1783
1815
|
// Treat CRLF (`\r\n`), CR (`\r`), and LF (`\n`) as single line-ending units
|
|
1784
1816
|
if (textNextTag && textNextTag === '/' + topTag && /[^\r\n](?:\r\n|\r|\n)[ \t]*$/.test(text)) {
|
|
1785
1817
|
text = text.replace(RE_LINE_BREAK_BLANKS_END, '');
|
|
@@ -1803,6 +1835,10 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
1803
1835
|
const charsIndex = buffer.length - 2;
|
|
1804
1836
|
buffer[charsIndex] = (buffer[charsIndex] ?? '').replace(/\s+$/, function (trailingSpaces) {
|
|
1805
1837
|
text = trailingSpaces + text;
|
|
1838
|
+
// It moved, and is judged again as part of this text
|
|
1839
|
+
if (currentChars.endsWith(trailingSpaces)) {
|
|
1840
|
+
currentChars = currentChars.slice(0, -trailingSpaces.length);
|
|
1841
|
+
}
|
|
1806
1842
|
return '';
|
|
1807
1843
|
});
|
|
1808
1844
|
}
|
|
@@ -1835,7 +1871,7 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
1835
1871
|
trimTrailingWhitespace(tagIndex - 1, 'br');
|
|
1836
1872
|
}
|
|
1837
1873
|
} else if (inlineSets.withinEither.has(textPrevTag)) {
|
|
1838
|
-
text = collapseWhitespace(text, options,
|
|
1874
|
+
text = collapseWhitespace(text, options, RE_ENDS_COLLAPSIBLE.test(currentChars), false, false, stackNoCollapseWhitespace.length > 0);
|
|
1839
1875
|
}
|
|
1840
1876
|
}
|
|
1841
1877
|
if (textPrevTag || textNextTag) {
|
|
@@ -2344,11 +2380,20 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
2344
2380
|
currentTag = tag;
|
|
2345
2381
|
charsPrevTag = tag;
|
|
2346
2382
|
if (!inlineTextSet.has(lowerTag || tag)) {
|
|
2347
|
-
|
|
2383
|
+
// Replaced content counts as content for the whitespace after what holds it
|
|
2384
|
+
currentChars = inlineElementsToKeepWhitespace.has(lowerTag || tag) ? '|' : '';
|
|
2348
2385
|
}
|
|
2349
2386
|
hasChars = false;
|
|
2350
2387
|
currentAttrs = attrs;
|
|
2351
2388
|
|
|
2389
|
+
// What this start tag closed, which is left to the end tags if its element is removed
|
|
2390
|
+
const impliedEndsClosed = impliedEndsIndex === buffer.length ? impliedEnds : undefined;
|
|
2391
|
+
|
|
2392
|
+
// What this start tag made optional, for if the element then turns out to be removed
|
|
2393
|
+
/** @type {{tag: string, text: string} | undefined} */
|
|
2394
|
+
let omittedStartTag;
|
|
2395
|
+
/** @type {{tag: string, text: string} | undefined} */
|
|
2396
|
+
let omittedEndTag;
|
|
2352
2397
|
let optional = options.removeOptionalTags;
|
|
2353
2398
|
if (optional) {
|
|
2354
2399
|
const htmlTag = htmlElements.has(tag);
|
|
@@ -2359,7 +2404,10 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
2359
2404
|
// `<colgroup>` may be omitted if first thing inside is `<col>`
|
|
2360
2405
|
// `<tbody>` may be omitted if first thing inside is `<tr>`
|
|
2361
2406
|
if (htmlTag && canRemoveParentTag(optionalStartTag, tag)) {
|
|
2362
|
-
removeStartTag();
|
|
2407
|
+
const text = removeStartTag(options.removeEmptyElements);
|
|
2408
|
+
if (text !== undefined) {
|
|
2409
|
+
omittedStartTag = { tag: optionalStartTag, text };
|
|
2410
|
+
}
|
|
2363
2411
|
}
|
|
2364
2412
|
optionalStartTag = '';
|
|
2365
2413
|
// End-tag-followed-by-start-tag omission rules
|
|
@@ -2367,7 +2415,10 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
2367
2415
|
if (htmlTag && canRemovePrecedingTag(optionalEndTag, tag) && (tableClosesP || optionalEndTag !== 'p' || tag !== 'table')) {
|
|
2368
2416
|
// Only a start tag that gets written can stand in for the end tag
|
|
2369
2417
|
if (optionalEndTagEmitted && (!autoGenerated || options.includeAutoGeneratedTags)) {
|
|
2370
|
-
removeEndTag();
|
|
2418
|
+
const text = removeEndTag();
|
|
2419
|
+
if (options.removeEmptyElements) {
|
|
2420
|
+
omittedEndTag = { tag: optionalEndTag, text };
|
|
2421
|
+
}
|
|
2371
2422
|
}
|
|
2372
2423
|
// `<colgroup>` cannot be omitted if preceding `</colgroup>` is omitted
|
|
2373
2424
|
// `<tbody>` cannot be omitted if preceding `</tbody>`, `</thead>`, or `</tfoot>` is
|
|
@@ -2390,7 +2441,7 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
2390
2441
|
const tagLower = lowerTag || tag;
|
|
2391
2442
|
if (!canTrimWhitespace(tagLower, tagOut, attrs) || stackNoTrimWhitespace.length) {
|
|
2392
2443
|
stackNoTrimWhitespace.push(tag);
|
|
2393
|
-
if (tagLower === 'pre'
|
|
2444
|
+
if (tagLower === 'pre') preDepth++;
|
|
2394
2445
|
}
|
|
2395
2446
|
if (options.collapseWhitespace && (!canCollapseWhitespace(tagLower, tagOut, attrs) || stackNoCollapseWhitespace.length)) {
|
|
2396
2447
|
stackNoCollapseWhitespace.push(tag);
|
|
@@ -2420,7 +2471,7 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
2420
2471
|
markOpenElementFilled();
|
|
2421
2472
|
} else {
|
|
2422
2473
|
// A start tag not written can’t be removed with its element
|
|
2423
|
-
openElements.push({ tag, index: buffer.length - 1, empty: !autoGenerated || Boolean(options.includeAutoGeneratedTags) });
|
|
2474
|
+
openElements.push({ tag, index: buffer.length - 1, empty: !autoGenerated || Boolean(options.includeAutoGeneratedTags), omittedStartTag, omittedEndTag, impliedEnds: impliedEndsClosed });
|
|
2424
2475
|
}
|
|
2425
2476
|
}
|
|
2426
2477
|
|
|
@@ -2513,10 +2564,9 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
2513
2564
|
const insideNoTrim = stackNoTrimWhitespace.length > 0;
|
|
2514
2565
|
let closesNoTrim = false;
|
|
2515
2566
|
if (stackNoTrimWhitespace.length) {
|
|
2516
|
-
if (
|
|
2567
|
+
if (removeClosedTag(stackNoTrimWhitespace, tag, lowerTag)) {
|
|
2517
2568
|
const tagLower = lowerTag || tag;
|
|
2518
|
-
if (tagLower === 'pre'
|
|
2519
|
-
stackNoTrimWhitespace.pop();
|
|
2569
|
+
if (tagLower === 'pre') preDepth--;
|
|
2520
2570
|
closesNoTrim = !stackNoTrimWhitespace.length;
|
|
2521
2571
|
}
|
|
2522
2572
|
} else if (options.collapseWhitespace) {
|
|
@@ -2526,9 +2576,7 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
2526
2576
|
transparentDepth--;
|
|
2527
2577
|
}
|
|
2528
2578
|
let closesNoCollapse = false;
|
|
2529
|
-
if (stackNoCollapseWhitespace.length &&
|
|
2530
|
-
tag === stackNoCollapseWhitespace[stackNoCollapseWhitespace.length - 1]) {
|
|
2531
|
-
stackNoCollapseWhitespace.pop();
|
|
2579
|
+
if (stackNoCollapseWhitespace.length && removeClosedTag(stackNoCollapseWhitespace, tag, lowerTag)) {
|
|
2532
2580
|
closesNoCollapse = !stackNoCollapseWhitespace.length;
|
|
2533
2581
|
}
|
|
2534
2582
|
|
|
@@ -2539,6 +2587,7 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
2539
2587
|
const skipsEndTag = autoGenerated && (!options.includeAutoGeneratedTags || Boolean(holdsTable));
|
|
2540
2588
|
impliedStartTag = '';
|
|
2541
2589
|
|
|
2590
|
+
let startTagRemoved = false;
|
|
2542
2591
|
let isElementEmpty = false;
|
|
2543
2592
|
if (tag === currentTag) {
|
|
2544
2593
|
currentTag = '';
|
|
@@ -2546,7 +2595,19 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
2546
2595
|
}
|
|
2547
2596
|
selfCloses &&= isElementEmpty;
|
|
2548
2597
|
// A stray end tag has no open element of its own
|
|
2549
|
-
|
|
2598
|
+
let openElement;
|
|
2599
|
+
if (options.removeEmptyElements && openElements.length) {
|
|
2600
|
+
if (openElements[openElements.length - 1]?.tag === tag) {
|
|
2601
|
+
openElement = openElements.pop();
|
|
2602
|
+
} else if (canCloseBelowTop(tag, lowerTag)) {
|
|
2603
|
+
const index = openElements.findLastIndex(entry => entry.tag === tag);
|
|
2604
|
+
if (index !== -1) {
|
|
2605
|
+
openElement = openElements.splice(index, 1)[0];
|
|
2606
|
+
// Closed around what is open inside it, so that holds this end tag now
|
|
2607
|
+
currentTag = '';
|
|
2608
|
+
}
|
|
2609
|
+
}
|
|
2610
|
+
}
|
|
2550
2611
|
// Emptied by removing what it held, as long as its start tag is still the last one written
|
|
2551
2612
|
const isElementEmptied = !isElementEmpty && Boolean(openElement?.empty) && lastStartTagIndex() === openElement?.index;
|
|
2552
2613
|
|
|
@@ -2554,6 +2615,7 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
2554
2615
|
// `<html>`, `<head>`, or `<body>` may be omitted if the element is empty
|
|
2555
2616
|
if (isElementEmpty && topLevelElements.has(optionalStartTag)) {
|
|
2556
2617
|
removeStartTag();
|
|
2618
|
+
startTagRemoved = true;
|
|
2557
2619
|
}
|
|
2558
2620
|
optionalStartTag = '';
|
|
2559
2621
|
// `</html>` or `</body>` may be omitted if not followed by comment
|
|
@@ -2572,7 +2634,7 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
2572
2634
|
let isElementRemoved = false;
|
|
2573
2635
|
// A formatting element closed by a block is reopened after it by browsers, so it has to stay
|
|
2574
2636
|
if (options.removeEmptyElements && (isElementEmpty || isElementEmptied) && !options.insideForeignContent && canRemoveElement(tag, attrs) &&
|
|
2575
|
-
!(autoGenerated && formattingElements.has(tag))) {
|
|
2637
|
+
!(autoGenerated && formattingElements.has(tag)) && !openElement?.impliedEnds?.some(implied => !implied.text)) {
|
|
2576
2638
|
let preserve = false;
|
|
2577
2639
|
if (removeEmptyElementsExcept.length) {
|
|
2578
2640
|
const normalizedAttrs = attrs.map((/** @type {HTMLAttribute} */ attr) => ({ ...attr, name: resolveName(attr.name) }));
|
|
@@ -2580,11 +2642,37 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
2580
2642
|
}
|
|
2581
2643
|
|
|
2582
2644
|
if (!preserve) {
|
|
2583
|
-
|
|
2645
|
+
// Once it is gone, the last start tag is another element’s
|
|
2646
|
+
if (!startTagRemoved) {
|
|
2647
|
+
removeStartTag();
|
|
2648
|
+
}
|
|
2584
2649
|
isElementRemoved = true;
|
|
2585
2650
|
optionalStartTag = '';
|
|
2586
2651
|
optionalEndTag = '';
|
|
2587
2652
|
optionalEndTagEmitted = false;
|
|
2653
|
+
// What it made optional is up to what comes next now
|
|
2654
|
+
if (openElement?.omittedEndTag) {
|
|
2655
|
+
bufferPush(openElement.omittedEndTag.text, true);
|
|
2656
|
+
optionalEndTag = openElement.omittedEndTag.tag;
|
|
2657
|
+
optionalEndTagEmitted = true;
|
|
2658
|
+
}
|
|
2659
|
+
if (openElement?.omittedStartTag) {
|
|
2660
|
+
bufferPush(openElement.omittedStartTag.text, true);
|
|
2661
|
+
optionalStartTag = openElement.omittedStartTag.tag;
|
|
2662
|
+
}
|
|
2663
|
+
if (openElement?.impliedEnds) {
|
|
2664
|
+
// Browsers reopen formatting elements after what closed them, which their end tags
|
|
2665
|
+
// would prevent and whitespace following an omitted end tag would change
|
|
2666
|
+
const written = openElement.impliedEnds.filter(implied => !formattingElements.has(implied.tag));
|
|
2667
|
+
for (const implied of written) {
|
|
2668
|
+
bufferPush(implied.text, true);
|
|
2669
|
+
}
|
|
2670
|
+
const last = written[written.length - 1];
|
|
2671
|
+
if (last && written.length === openElement.impliedEnds.length && optionalEndTags.has(last.tag)) {
|
|
2672
|
+
optionalEndTag = last.tag;
|
|
2673
|
+
optionalEndTagEmitted = true;
|
|
2674
|
+
}
|
|
2675
|
+
}
|
|
2588
2676
|
} else {
|
|
2589
2677
|
// Preserve the element—add closing tag
|
|
2590
2678
|
if (skipsEndTag) {
|
|
@@ -2595,7 +2683,7 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
2595
2683
|
charsPrevTag = '/' + tag;
|
|
2596
2684
|
if (!inlineElements.has(lowerTag || tag)) {
|
|
2597
2685
|
currentChars = '';
|
|
2598
|
-
} else if (isElementEmpty) {
|
|
2686
|
+
} else if (isElementEmpty || !inlineTextSet.has(lowerTag || tag)) {
|
|
2599
2687
|
currentChars += '|';
|
|
2600
2688
|
}
|
|
2601
2689
|
}
|
|
@@ -2608,11 +2696,20 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
2608
2696
|
charsPrevTag = '/' + tag;
|
|
2609
2697
|
if (!inlineElements.has(lowerTag || tag)) {
|
|
2610
2698
|
currentChars = '';
|
|
2611
|
-
} else if (isElementEmpty) {
|
|
2699
|
+
} else if (isElementEmpty || !inlineTextSet.has(lowerTag || tag)) {
|
|
2700
|
+
// What is not text counts as content for the whitespace after it
|
|
2612
2701
|
currentChars += '|';
|
|
2613
2702
|
}
|
|
2614
2703
|
}
|
|
2615
2704
|
|
|
2705
|
+
if (skipsEndTag && !isElementRemoved && options.removeEmptyElements) {
|
|
2706
|
+
if (impliedEndsIndex !== buffer.length) {
|
|
2707
|
+
impliedEnds = [];
|
|
2708
|
+
impliedEndsIndex = buffer.length;
|
|
2709
|
+
}
|
|
2710
|
+
impliedEnds.push({ tag, text: holdsTable ? '' : endTagText });
|
|
2711
|
+
}
|
|
2712
|
+
|
|
2616
2713
|
if (closesNoTrim && !isElementRemoved) {
|
|
2617
2714
|
noTrimEndIndex = buffer.length - 1;
|
|
2618
2715
|
}
|
|
@@ -2710,12 +2807,16 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
2710
2807
|
strayEnd: function () {
|
|
2711
2808
|
removed = true;
|
|
2712
2809
|
},
|
|
2713
|
-
doctype: function (/** @type {string} */ doctype, /** @type {boolean} */ noQuirksMode) {
|
|
2810
|
+
doctype: function (/** @type {string} */ doctype, /** @type {boolean} */ noQuirksMode, /** @type {boolean} */ isIgnored) {
|
|
2714
2811
|
tableClosesP = noQuirksMode;
|
|
2812
|
+
if (isIgnored) {
|
|
2813
|
+
removed = true;
|
|
2814
|
+
return;
|
|
2815
|
+
}
|
|
2715
2816
|
bufferPush(options.useShortDoctype
|
|
2716
2817
|
? '<!doctype' +
|
|
2717
2818
|
(options.removeTagWhitespace ? '' : ' ') + 'html>'
|
|
2718
|
-
: collapseWhitespaceAll(
|
|
2819
|
+
: doctype.replace(RE_DOCTYPE_SEGMENT, (segment, quoted) => quoted ?? collapseWhitespaceAll(segment)), false);
|
|
2719
2820
|
}
|
|
2720
2821
|
});
|
|
2721
2822
|
|