html-minifier-next 4.9.0 → 4.9.1
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 +2 -2
- package/dist/htmlminifier.cjs +75 -0
- package/dist/htmlminifier.esm.bundle.js +75 -0
- package/dist/types/htmlminifier.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/htmlminifier.js +75 -0
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
HTML Minifier Next (HMN) is a **super-configurable, well-tested, JavaScript-based HTML minifier**.
|
|
6
6
|
|
|
7
|
-
The project was based on [HTML Minifier Terser](https://github.com/terser/html-minifier-terser), which in turn had been based on [Juriy Zaytsev’s HTML Minifier](https://github.com/kangax/html-minifier). HMN offers additional features, but is backwards-compatible with both. The project was set up because as of 2025, both HTML Minifier Terser and HTML Minifier had been unmaintained for a few years. As the project seems maintainable [to me, [Jens](https://meiert.com/)]—even more so with community support—, it’s being [updated, extended, and documented](https://github.com/j9t/html-minifier-next/blob/main/CHANGELOG.md) further in this place.
|
|
7
|
+
The project was based on [HTML Minifier Terser](https://github.com/terser/html-minifier-terser), which in turn had been based on [Juriy “kangax” Zaytsev’s HTML Minifier](https://github.com/kangax/html-minifier). HMN offers additional features, but is backwards-compatible with both. The project was set up because as of 2025, both HTML Minifier Terser and HTML Minifier had been unmaintained for a few years. As the project seems maintainable [to me, [Jens](https://meiert.com/), an HTML optimizer]—even more so with community support—, it’s being [updated, extended, and documented](https://github.com/j9t/html-minifier-next/blob/main/CHANGELOG.md) further in this place.
|
|
8
8
|
|
|
9
9
|
## Installation
|
|
10
10
|
|
|
@@ -431,4 +431,4 @@ npm run benchmarks
|
|
|
431
431
|
|
|
432
432
|
## Acknowledgements
|
|
433
433
|
|
|
434
|
-
With many thanks to all the previous authors of HTML Minifier, especially [Juriy Zaytsev](https://github.com/kangax), and to everyone who helped make this new edition better, particularly [Daniel Ruf](https://github.com/DanielRuf) and [Jonas Geiler](https://github.com/jonasgeiler).
|
|
434
|
+
With many thanks to all the previous authors of HTML Minifier, especially [Juriy “kangax” Zaytsev](https://github.com/kangax), and to everyone who helped make this new edition better, particularly [Daniel Ruf](https://github.com/DanielRuf) and [Jonas Geiler](https://github.com/jonasgeiler).
|
package/dist/htmlminifier.cjs
CHANGED
|
@@ -2363,6 +2363,7 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
2363
2363
|
const ignoredMarkupChunks = [];
|
|
2364
2364
|
const ignoredCustomMarkupChunks = [];
|
|
2365
2365
|
let uidIgnore;
|
|
2366
|
+
let uidIgnorePlaceholderPattern;
|
|
2366
2367
|
let uidAttr;
|
|
2367
2368
|
let uidPattern;
|
|
2368
2369
|
// Create inline tags/text sets with custom elements
|
|
@@ -2396,6 +2397,7 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
2396
2397
|
if (!uidIgnore) {
|
|
2397
2398
|
uidIgnore = uniqueId(value);
|
|
2398
2399
|
const pattern = new RegExp('^' + uidIgnore + '([0-9]+)$');
|
|
2400
|
+
uidIgnorePlaceholderPattern = new RegExp('^<!--' + uidIgnore + '(\\d+)-->$');
|
|
2399
2401
|
if (options.ignoreCustomComments) {
|
|
2400
2402
|
options.ignoreCustomComments = options.ignoreCustomComments.slice();
|
|
2401
2403
|
} else {
|
|
@@ -2820,6 +2822,79 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
2820
2822
|
optionalStartTag = '';
|
|
2821
2823
|
optionalEndTag = '';
|
|
2822
2824
|
}
|
|
2825
|
+
|
|
2826
|
+
// Optimize whitespace collapsing between consecutive `htmlmin:ignore` placeholder comments
|
|
2827
|
+
if (options.collapseWhitespace && text && uidIgnorePlaceholderPattern) {
|
|
2828
|
+
if (uidIgnorePlaceholderPattern.test(text)) {
|
|
2829
|
+
// Check if previous buffer items are: [ignore-placeholder, whitespace-only text]
|
|
2830
|
+
if (buffer.length >= 2) {
|
|
2831
|
+
const prevText = buffer[buffer.length - 1];
|
|
2832
|
+
const prevComment = buffer[buffer.length - 2];
|
|
2833
|
+
|
|
2834
|
+
// Check if previous item is whitespace-only and item before that is ignore-placeholder
|
|
2835
|
+
if (prevText && /^\s+$/.test(prevText) &&
|
|
2836
|
+
prevComment && uidIgnorePlaceholderPattern.test(prevComment)) {
|
|
2837
|
+
// Extract the index from both placeholders to check their content
|
|
2838
|
+
const currentMatch = text.match(uidIgnorePlaceholderPattern);
|
|
2839
|
+
const prevMatch = prevComment.match(uidIgnorePlaceholderPattern);
|
|
2840
|
+
|
|
2841
|
+
if (currentMatch && prevMatch) {
|
|
2842
|
+
const currentIndex = +currentMatch[1];
|
|
2843
|
+
const prevIndex = +prevMatch[1];
|
|
2844
|
+
|
|
2845
|
+
// Defensive bounds check to ensure indices are valid
|
|
2846
|
+
if (currentIndex < ignoredMarkupChunks.length && prevIndex < ignoredMarkupChunks.length) {
|
|
2847
|
+
const currentContent = ignoredMarkupChunks[currentIndex];
|
|
2848
|
+
const prevContent = ignoredMarkupChunks[prevIndex];
|
|
2849
|
+
|
|
2850
|
+
// Only collapse whitespace if both blocks contain HTML (start with `<`)
|
|
2851
|
+
// Don’t collapse if either contains plain text, as that would change meaning
|
|
2852
|
+
// Note: This check will match HTML comments (`<!-- … -->`), but the tag-name
|
|
2853
|
+
// regex below requires starting with a letter, so comments are intentionally
|
|
2854
|
+
// excluded by the `currentTagMatch && prevTagMatch` guard
|
|
2855
|
+
if (currentContent && prevContent && /^\s*</.test(currentContent) && /^\s*</.test(prevContent)) {
|
|
2856
|
+
// Extract tag names from the HTML content (excludes comments, processing instructions, etc.)
|
|
2857
|
+
const currentTagMatch = currentContent.match(/^\s*<([a-zA-Z][\w:-]*)/);
|
|
2858
|
+
const prevTagMatch = prevContent.match(/^\s*<([a-zA-Z][\w:-]*)/);
|
|
2859
|
+
|
|
2860
|
+
// Only collapse if both matched valid element tags (not comments/text)
|
|
2861
|
+
// and both tags are block-level (inline elements need whitespace preserved)
|
|
2862
|
+
if (currentTagMatch && prevTagMatch) {
|
|
2863
|
+
const currentTag = options.name(currentTagMatch[1]);
|
|
2864
|
+
const prevTag = options.name(prevTagMatch[1]);
|
|
2865
|
+
|
|
2866
|
+
// Don’t collapse between inline elements
|
|
2867
|
+
if (!inlineElements.has(currentTag) && !inlineElements.has(prevTag)) {
|
|
2868
|
+
// Collapse whitespace respecting context rules
|
|
2869
|
+
let collapsedText = prevText;
|
|
2870
|
+
|
|
2871
|
+
// Apply `collapseWhitespace` with appropriate context
|
|
2872
|
+
if (!stackNoTrimWhitespace.length && !stackNoCollapseWhitespace.length) {
|
|
2873
|
+
// Not in pre or other no-collapse context
|
|
2874
|
+
if (options.preserveLineBreaks && /[\n\r]/.test(prevText)) {
|
|
2875
|
+
// Preserve line break as single newline
|
|
2876
|
+
collapsedText = '\n';
|
|
2877
|
+
} else if (options.conservativeCollapse) {
|
|
2878
|
+
// Conservative mode: keep single space
|
|
2879
|
+
collapsedText = ' ';
|
|
2880
|
+
} else {
|
|
2881
|
+
// Aggressive mode: remove all whitespace
|
|
2882
|
+
collapsedText = '';
|
|
2883
|
+
}
|
|
2884
|
+
}
|
|
2885
|
+
|
|
2886
|
+
// Replace the whitespace in buffer
|
|
2887
|
+
buffer[buffer.length - 1] = collapsedText;
|
|
2888
|
+
}
|
|
2889
|
+
}
|
|
2890
|
+
}
|
|
2891
|
+
}
|
|
2892
|
+
}
|
|
2893
|
+
}
|
|
2894
|
+
}
|
|
2895
|
+
}
|
|
2896
|
+
}
|
|
2897
|
+
|
|
2823
2898
|
buffer.push(text);
|
|
2824
2899
|
},
|
|
2825
2900
|
doctype: function (doctype) {
|
|
@@ -7505,6 +7505,7 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
7505
7505
|
const ignoredMarkupChunks = [];
|
|
7506
7506
|
const ignoredCustomMarkupChunks = [];
|
|
7507
7507
|
let uidIgnore;
|
|
7508
|
+
let uidIgnorePlaceholderPattern;
|
|
7508
7509
|
let uidAttr;
|
|
7509
7510
|
let uidPattern;
|
|
7510
7511
|
// Create inline tags/text sets with custom elements
|
|
@@ -7538,6 +7539,7 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
7538
7539
|
if (!uidIgnore) {
|
|
7539
7540
|
uidIgnore = uniqueId(value);
|
|
7540
7541
|
const pattern = new RegExp('^' + uidIgnore + '([0-9]+)$');
|
|
7542
|
+
uidIgnorePlaceholderPattern = new RegExp('^<!--' + uidIgnore + '(\\d+)-->$');
|
|
7541
7543
|
if (options.ignoreCustomComments) {
|
|
7542
7544
|
options.ignoreCustomComments = options.ignoreCustomComments.slice();
|
|
7543
7545
|
} else {
|
|
@@ -7962,6 +7964,79 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
7962
7964
|
optionalStartTag = '';
|
|
7963
7965
|
optionalEndTag = '';
|
|
7964
7966
|
}
|
|
7967
|
+
|
|
7968
|
+
// Optimize whitespace collapsing between consecutive `htmlmin:ignore` placeholder comments
|
|
7969
|
+
if (options.collapseWhitespace && text && uidIgnorePlaceholderPattern) {
|
|
7970
|
+
if (uidIgnorePlaceholderPattern.test(text)) {
|
|
7971
|
+
// Check if previous buffer items are: [ignore-placeholder, whitespace-only text]
|
|
7972
|
+
if (buffer.length >= 2) {
|
|
7973
|
+
const prevText = buffer[buffer.length - 1];
|
|
7974
|
+
const prevComment = buffer[buffer.length - 2];
|
|
7975
|
+
|
|
7976
|
+
// Check if previous item is whitespace-only and item before that is ignore-placeholder
|
|
7977
|
+
if (prevText && /^\s+$/.test(prevText) &&
|
|
7978
|
+
prevComment && uidIgnorePlaceholderPattern.test(prevComment)) {
|
|
7979
|
+
// Extract the index from both placeholders to check their content
|
|
7980
|
+
const currentMatch = text.match(uidIgnorePlaceholderPattern);
|
|
7981
|
+
const prevMatch = prevComment.match(uidIgnorePlaceholderPattern);
|
|
7982
|
+
|
|
7983
|
+
if (currentMatch && prevMatch) {
|
|
7984
|
+
const currentIndex = +currentMatch[1];
|
|
7985
|
+
const prevIndex = +prevMatch[1];
|
|
7986
|
+
|
|
7987
|
+
// Defensive bounds check to ensure indices are valid
|
|
7988
|
+
if (currentIndex < ignoredMarkupChunks.length && prevIndex < ignoredMarkupChunks.length) {
|
|
7989
|
+
const currentContent = ignoredMarkupChunks[currentIndex];
|
|
7990
|
+
const prevContent = ignoredMarkupChunks[prevIndex];
|
|
7991
|
+
|
|
7992
|
+
// Only collapse whitespace if both blocks contain HTML (start with `<`)
|
|
7993
|
+
// Don’t collapse if either contains plain text, as that would change meaning
|
|
7994
|
+
// Note: This check will match HTML comments (`<!-- … -->`), but the tag-name
|
|
7995
|
+
// regex below requires starting with a letter, so comments are intentionally
|
|
7996
|
+
// excluded by the `currentTagMatch && prevTagMatch` guard
|
|
7997
|
+
if (currentContent && prevContent && /^\s*</.test(currentContent) && /^\s*</.test(prevContent)) {
|
|
7998
|
+
// Extract tag names from the HTML content (excludes comments, processing instructions, etc.)
|
|
7999
|
+
const currentTagMatch = currentContent.match(/^\s*<([a-zA-Z][\w:-]*)/);
|
|
8000
|
+
const prevTagMatch = prevContent.match(/^\s*<([a-zA-Z][\w:-]*)/);
|
|
8001
|
+
|
|
8002
|
+
// Only collapse if both matched valid element tags (not comments/text)
|
|
8003
|
+
// and both tags are block-level (inline elements need whitespace preserved)
|
|
8004
|
+
if (currentTagMatch && prevTagMatch) {
|
|
8005
|
+
const currentTag = options.name(currentTagMatch[1]);
|
|
8006
|
+
const prevTag = options.name(prevTagMatch[1]);
|
|
8007
|
+
|
|
8008
|
+
// Don’t collapse between inline elements
|
|
8009
|
+
if (!inlineElements.has(currentTag) && !inlineElements.has(prevTag)) {
|
|
8010
|
+
// Collapse whitespace respecting context rules
|
|
8011
|
+
let collapsedText = prevText;
|
|
8012
|
+
|
|
8013
|
+
// Apply `collapseWhitespace` with appropriate context
|
|
8014
|
+
if (!stackNoTrimWhitespace.length && !stackNoCollapseWhitespace.length) {
|
|
8015
|
+
// Not in pre or other no-collapse context
|
|
8016
|
+
if (options.preserveLineBreaks && /[\n\r]/.test(prevText)) {
|
|
8017
|
+
// Preserve line break as single newline
|
|
8018
|
+
collapsedText = '\n';
|
|
8019
|
+
} else if (options.conservativeCollapse) {
|
|
8020
|
+
// Conservative mode: keep single space
|
|
8021
|
+
collapsedText = ' ';
|
|
8022
|
+
} else {
|
|
8023
|
+
// Aggressive mode: remove all whitespace
|
|
8024
|
+
collapsedText = '';
|
|
8025
|
+
}
|
|
8026
|
+
}
|
|
8027
|
+
|
|
8028
|
+
// Replace the whitespace in buffer
|
|
8029
|
+
buffer[buffer.length - 1] = collapsedText;
|
|
8030
|
+
}
|
|
8031
|
+
}
|
|
8032
|
+
}
|
|
8033
|
+
}
|
|
8034
|
+
}
|
|
8035
|
+
}
|
|
8036
|
+
}
|
|
8037
|
+
}
|
|
8038
|
+
}
|
|
8039
|
+
|
|
7965
8040
|
buffer.push(text);
|
|
7966
8041
|
},
|
|
7967
8042
|
doctype: function (doctype) {
|
|
@@ -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":"AAgvEO,8BAJI,MAAM,YACN,eAAe,GACb,OAAO,CAAC,MAAM,CAAC,CAQ3B;;;;;;;;;;;;UAvtES,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;;;;;;;;;;;;;;;;;;;;;;;;;;gCAOP,MAAM,EAAE;;;;;;;;yBAyBR,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;;wBAnYkC,cAAc;0BAAd,cAAc;+BAAd,cAAc"}
|
package/package.json
CHANGED
package/src/htmlminifier.js
CHANGED
|
@@ -1657,6 +1657,7 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
1657
1657
|
const ignoredMarkupChunks = [];
|
|
1658
1658
|
const ignoredCustomMarkupChunks = [];
|
|
1659
1659
|
let uidIgnore;
|
|
1660
|
+
let uidIgnorePlaceholderPattern;
|
|
1660
1661
|
let uidAttr;
|
|
1661
1662
|
let uidPattern;
|
|
1662
1663
|
// Create inline tags/text sets with custom elements
|
|
@@ -1690,6 +1691,7 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
1690
1691
|
if (!uidIgnore) {
|
|
1691
1692
|
uidIgnore = uniqueId(value);
|
|
1692
1693
|
const pattern = new RegExp('^' + uidIgnore + '([0-9]+)$');
|
|
1694
|
+
uidIgnorePlaceholderPattern = new RegExp('^<!--' + uidIgnore + '(\\d+)-->$');
|
|
1693
1695
|
if (options.ignoreCustomComments) {
|
|
1694
1696
|
options.ignoreCustomComments = options.ignoreCustomComments.slice();
|
|
1695
1697
|
} else {
|
|
@@ -2114,6 +2116,79 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
2114
2116
|
optionalStartTag = '';
|
|
2115
2117
|
optionalEndTag = '';
|
|
2116
2118
|
}
|
|
2119
|
+
|
|
2120
|
+
// Optimize whitespace collapsing between consecutive `htmlmin:ignore` placeholder comments
|
|
2121
|
+
if (options.collapseWhitespace && text && uidIgnorePlaceholderPattern) {
|
|
2122
|
+
if (uidIgnorePlaceholderPattern.test(text)) {
|
|
2123
|
+
// Check if previous buffer items are: [ignore-placeholder, whitespace-only text]
|
|
2124
|
+
if (buffer.length >= 2) {
|
|
2125
|
+
const prevText = buffer[buffer.length - 1];
|
|
2126
|
+
const prevComment = buffer[buffer.length - 2];
|
|
2127
|
+
|
|
2128
|
+
// Check if previous item is whitespace-only and item before that is ignore-placeholder
|
|
2129
|
+
if (prevText && /^\s+$/.test(prevText) &&
|
|
2130
|
+
prevComment && uidIgnorePlaceholderPattern.test(prevComment)) {
|
|
2131
|
+
// Extract the index from both placeholders to check their content
|
|
2132
|
+
const currentMatch = text.match(uidIgnorePlaceholderPattern);
|
|
2133
|
+
const prevMatch = prevComment.match(uidIgnorePlaceholderPattern);
|
|
2134
|
+
|
|
2135
|
+
if (currentMatch && prevMatch) {
|
|
2136
|
+
const currentIndex = +currentMatch[1];
|
|
2137
|
+
const prevIndex = +prevMatch[1];
|
|
2138
|
+
|
|
2139
|
+
// Defensive bounds check to ensure indices are valid
|
|
2140
|
+
if (currentIndex < ignoredMarkupChunks.length && prevIndex < ignoredMarkupChunks.length) {
|
|
2141
|
+
const currentContent = ignoredMarkupChunks[currentIndex];
|
|
2142
|
+
const prevContent = ignoredMarkupChunks[prevIndex];
|
|
2143
|
+
|
|
2144
|
+
// Only collapse whitespace if both blocks contain HTML (start with `<`)
|
|
2145
|
+
// Don’t collapse if either contains plain text, as that would change meaning
|
|
2146
|
+
// Note: This check will match HTML comments (`<!-- … -->`), but the tag-name
|
|
2147
|
+
// regex below requires starting with a letter, so comments are intentionally
|
|
2148
|
+
// excluded by the `currentTagMatch && prevTagMatch` guard
|
|
2149
|
+
if (currentContent && prevContent && /^\s*</.test(currentContent) && /^\s*</.test(prevContent)) {
|
|
2150
|
+
// Extract tag names from the HTML content (excludes comments, processing instructions, etc.)
|
|
2151
|
+
const currentTagMatch = currentContent.match(/^\s*<([a-zA-Z][\w:-]*)/);
|
|
2152
|
+
const prevTagMatch = prevContent.match(/^\s*<([a-zA-Z][\w:-]*)/);
|
|
2153
|
+
|
|
2154
|
+
// Only collapse if both matched valid element tags (not comments/text)
|
|
2155
|
+
// and both tags are block-level (inline elements need whitespace preserved)
|
|
2156
|
+
if (currentTagMatch && prevTagMatch) {
|
|
2157
|
+
const currentTag = options.name(currentTagMatch[1]);
|
|
2158
|
+
const prevTag = options.name(prevTagMatch[1]);
|
|
2159
|
+
|
|
2160
|
+
// Don’t collapse between inline elements
|
|
2161
|
+
if (!inlineElements.has(currentTag) && !inlineElements.has(prevTag)) {
|
|
2162
|
+
// Collapse whitespace respecting context rules
|
|
2163
|
+
let collapsedText = prevText;
|
|
2164
|
+
|
|
2165
|
+
// Apply `collapseWhitespace` with appropriate context
|
|
2166
|
+
if (!stackNoTrimWhitespace.length && !stackNoCollapseWhitespace.length) {
|
|
2167
|
+
// Not in pre or other no-collapse context
|
|
2168
|
+
if (options.preserveLineBreaks && /[\n\r]/.test(prevText)) {
|
|
2169
|
+
// Preserve line break as single newline
|
|
2170
|
+
collapsedText = '\n';
|
|
2171
|
+
} else if (options.conservativeCollapse) {
|
|
2172
|
+
// Conservative mode: keep single space
|
|
2173
|
+
collapsedText = ' ';
|
|
2174
|
+
} else {
|
|
2175
|
+
// Aggressive mode: remove all whitespace
|
|
2176
|
+
collapsedText = '';
|
|
2177
|
+
}
|
|
2178
|
+
}
|
|
2179
|
+
|
|
2180
|
+
// Replace the whitespace in buffer
|
|
2181
|
+
buffer[buffer.length - 1] = collapsedText;
|
|
2182
|
+
}
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
|
|
2117
2192
|
buffer.push(text);
|
|
2118
2193
|
},
|
|
2119
2194
|
doctype: function (doctype) {
|