html-minifier-next 3.2.1 → 4.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -26
- package/cli.js +1 -1
- package/dist/htmlminifier.cjs +38 -37
- package/dist/htmlminifier.esm.bundle.js +3875 -25023
- package/package.json +5 -5
- package/src/htmlminifier.js +37 -36
- package/src/htmlparser.js +1 -1
- package/dist/htmlminifier.umd.bundle.js +0 -62195
- package/dist/htmlminifier.umd.bundle.min.js +0 -9
package/package.json
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
"bugs": "https://github.com/j9t/html-minifier-next/issues",
|
|
7
7
|
"dependencies": {
|
|
8
8
|
"change-case": "^4.1.2",
|
|
9
|
-
"
|
|
10
|
-
"commander": "^14.0.1",
|
|
9
|
+
"commander": "^14.0.2",
|
|
11
10
|
"entities": "^7.0.0",
|
|
11
|
+
"lightningcss": "^1.28.2",
|
|
12
12
|
"relateurl": "^0.2.7",
|
|
13
13
|
"terser": "^5.44.0"
|
|
14
14
|
},
|
|
@@ -16,12 +16,12 @@
|
|
|
16
16
|
"devDependencies": {
|
|
17
17
|
"@commitlint/cli": "^20.1.0",
|
|
18
18
|
"@eslint/js": "^9.37.0",
|
|
19
|
-
"@rollup/plugin-commonjs": "^28.0.
|
|
19
|
+
"@rollup/plugin-commonjs": "^28.0.9",
|
|
20
20
|
"@rollup/plugin-json": "^6.1.0",
|
|
21
21
|
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
22
22
|
"@rollup/plugin-terser": "^0.4.4",
|
|
23
23
|
"eslint": "^9.37.0",
|
|
24
|
-
"rollup": "^4.52.
|
|
24
|
+
"rollup": "^4.52.5",
|
|
25
25
|
"rollup-plugin-polyfill-node": "^0.13.0",
|
|
26
26
|
"vite": "^7.1.12"
|
|
27
27
|
},
|
|
@@ -78,5 +78,5 @@
|
|
|
78
78
|
"test:watch": "node --test --watch tests/*.spec.js"
|
|
79
79
|
},
|
|
80
80
|
"type": "module",
|
|
81
|
-
"version": "
|
|
81
|
+
"version": "4.0.2"
|
|
82
82
|
}
|
package/src/htmlminifier.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { transform as transformCSS } from 'lightningcss';
|
|
2
2
|
import { decodeHTMLStrict, decodeHTML } from 'entities';
|
|
3
3
|
import RelateURL from 'relateurl';
|
|
4
4
|
import { minify as terser } from 'terser';
|
|
@@ -390,12 +390,8 @@ function isContentSecurityPolicy(tag, attrs) {
|
|
|
390
390
|
}
|
|
391
391
|
}
|
|
392
392
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
// Wrap CSS declarations for CleanCSS > 3.x
|
|
398
|
-
// See https://github.com/jakubpawlowicz/clean-css/issues/418
|
|
393
|
+
// Wrap CSS declarations for inline styles and media queries
|
|
394
|
+
// This ensures proper context for CSS minification
|
|
399
395
|
function wrapCSS(text, type) {
|
|
400
396
|
switch (type) {
|
|
401
397
|
case 'inline':
|
|
@@ -458,7 +454,7 @@ const topLevelTags = new Set(['html', 'head', 'body']);
|
|
|
458
454
|
const compactTags = new Set(['html', 'body']);
|
|
459
455
|
const looseTags = new Set(['head', 'colgroup', 'caption']);
|
|
460
456
|
const trailingTags = new Set(['dt', 'thead']);
|
|
461
|
-
const htmlTags = new Set(['a', 'abbr', 'acronym', 'address', 'applet', 'area', 'article', 'aside', 'audio', 'b', 'base', 'basefont', 'bdi', 'bdo', 'bgsound', 'big', 'blink', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'command', 'content', 'data', 'datalist', 'dd', 'del', 'details', 'dfn', 'dialog', 'dir', 'div', 'dl', 'dt', 'element', 'em', 'embed', 'fieldset', 'figcaption', 'figure', 'font', 'footer', 'form', 'frame', 'frameset', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'iframe', 'image', 'img', 'input', 'ins', 'isindex', 'kbd', 'keygen', 'label', 'legend', 'li', 'link', 'listing', 'main', 'map', 'mark', 'marquee', 'menu', 'menuitem', 'meta', 'meter', 'multicol', 'nav', 'nobr', 'noembed', 'noframes', 'noscript', 'object', 'ol', 'optgroup', 'option', 'output', 'p', 'param', 'picture', 'plaintext', 'pre', 'progress', 'q', 'rb', 'rp', 'rt', 'rtc', 'ruby', 's', 'samp', 'script', 'section', 'select', 'shadow', 'small', 'source', 'spacer', 'span', 'strike', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'title', 'tr', 'track', 'tt', 'u', 'ul', 'var', 'video', 'wbr', 'xmp']);
|
|
457
|
+
const htmlTags = new Set(['a', 'abbr', 'acronym', 'address', 'applet', 'area', 'article', 'aside', 'audio', 'b', 'base', 'basefont', 'bdi', 'bdo', 'bgsound', 'big', 'blink', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'command', 'content', 'data', 'datalist', 'dd', 'del', 'details', 'dfn', 'dialog', 'dir', 'div', 'dl', 'dt', 'element', 'em', 'embed', 'fieldset', 'figcaption', 'figure', 'font', 'footer', 'form', 'frame', 'frameset', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'iframe', 'image', 'img', 'input', 'ins', 'isindex', 'kbd', 'keygen', 'label', 'legend', 'li', 'link', 'listing', 'main', 'map', 'mark', 'marquee', 'menu', 'menuitem', 'meta', 'meter', 'multicol', 'nav', 'nobr', 'noembed', 'noframes', 'noscript', 'object', 'ol', 'optgroup', 'option', 'output', 'p', 'param', 'picture', 'plaintext', 'pre', 'progress', 'q', 'rb', 'rp', 'rt', 'rtc', 'ruby', 's', 'samp', 'script', 'search', 'section', 'select', 'selectedcontent', 'shadow', 'small', 'source', 'spacer', 'span', 'strike', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'title', 'tr', 'track', 'tt', 'u', 'ul', 'var', 'video', 'wbr', 'xmp']);
|
|
462
458
|
|
|
463
459
|
function canRemoveParentTag(optionalStartTag, tag) {
|
|
464
460
|
switch (optionalStartTag) {
|
|
@@ -727,7 +723,7 @@ const processOptions = (inputOptions) => {
|
|
|
727
723
|
return;
|
|
728
724
|
}
|
|
729
725
|
|
|
730
|
-
const
|
|
726
|
+
const lightningCssOptions = typeof option === 'object' ? option : {};
|
|
731
727
|
|
|
732
728
|
options.minifyCSS = async function (text, type) {
|
|
733
729
|
text = await replaceAsync(
|
|
@@ -748,17 +744,38 @@ const processOptions = (inputOptions) => {
|
|
|
748
744
|
|
|
749
745
|
const inputCSS = wrapCSS(text, type);
|
|
750
746
|
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
const outputCSS = unwrapCSS(output.styles, type);
|
|
759
|
-
resolve(outputCSS);
|
|
747
|
+
try {
|
|
748
|
+
const result = transformCSS({
|
|
749
|
+
filename: 'input.css',
|
|
750
|
+
code: Buffer.from(inputCSS),
|
|
751
|
+
minify: true,
|
|
752
|
+
errorRecovery: true,
|
|
753
|
+
...lightningCssOptions
|
|
760
754
|
});
|
|
761
|
-
|
|
755
|
+
|
|
756
|
+
const outputCSS = unwrapCSS(result.code.toString(), type);
|
|
757
|
+
|
|
758
|
+
// If Lightning CSS removed significant content that looks like template syntax or UIDs, return original
|
|
759
|
+
// This preserves:
|
|
760
|
+
// 1. Template code like `<?php ?>`, `<%= %>`, `{{ }}`, etc. (contain `<` or `>` but not `CDATA`)
|
|
761
|
+
// 2. UIDs representing custom fragments (only lowercase letters and digits, no spaces)
|
|
762
|
+
// CDATA sections, HTML entities, and other invalid CSS are allowed to be removed
|
|
763
|
+
const isCDATA = text.includes('<![CDATA[');
|
|
764
|
+
const uidPattern = /[a-z0-9]{10,}/; // UIDs are long alphanumeric strings
|
|
765
|
+
const hasUID = uidPattern.test(text) && !isCDATA; // Exclude CDATA from UID detection
|
|
766
|
+
const looksLikeTemplate = (text.includes('<') || text.includes('>')) && !isCDATA;
|
|
767
|
+
|
|
768
|
+
// Preserve if output is empty and input had template syntax or UIDs
|
|
769
|
+
// This catches cases where Lightning CSS removed content that should be preserved
|
|
770
|
+
if (text.trim() && !outputCSS.trim() && (looksLikeTemplate || hasUID)) {
|
|
771
|
+
return text;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
return outputCSS;
|
|
775
|
+
} catch (err) {
|
|
776
|
+
options.log && options.log(err);
|
|
777
|
+
return text;
|
|
778
|
+
}
|
|
762
779
|
};
|
|
763
780
|
} else if (key === 'minifyJS' && typeof option !== 'function') {
|
|
764
781
|
if (!option) {
|
|
@@ -996,23 +1013,7 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
996
1013
|
return chunks[1] + uidAttr + index + uidAttr + chunks[2];
|
|
997
1014
|
});
|
|
998
1015
|
|
|
999
|
-
|
|
1000
|
-
new CleanCSS().minify(wrapCSS(text, type)).warnings.forEach(function (warning) {
|
|
1001
|
-
const match = uidPattern.exec(warning);
|
|
1002
|
-
if (match) {
|
|
1003
|
-
const id = uidAttr + match[2] + uidAttr;
|
|
1004
|
-
text = text.replace(id, ignoreCSS(id));
|
|
1005
|
-
ids.push(id);
|
|
1006
|
-
}
|
|
1007
|
-
});
|
|
1008
|
-
|
|
1009
|
-
return fn(text, type).then(chunk => {
|
|
1010
|
-
ids.forEach(function (id) {
|
|
1011
|
-
chunk = chunk.replace(ignoreCSS(id), id);
|
|
1012
|
-
});
|
|
1013
|
-
|
|
1014
|
-
return chunk;
|
|
1015
|
-
});
|
|
1016
|
+
return fn(text, type);
|
|
1016
1017
|
};
|
|
1017
1018
|
})(options.minifyCSS);
|
|
1018
1019
|
}
|
package/src/htmlparser.js
CHANGED
|
@@ -58,7 +58,7 @@ let IS_REGEX_CAPTURING_BROKEN = false;
|
|
|
58
58
|
const empty = new CaseInsensitiveSet(['area', 'base', 'basefont', 'br', 'col', 'embed', 'frame', 'hr', 'img', 'input', 'isindex', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr']);
|
|
59
59
|
|
|
60
60
|
// Inline elements
|
|
61
|
-
const inline = new CaseInsensitiveSet(['a', 'abbr', 'acronym', 'applet', 'b', 'basefont', 'bdo', 'big', 'br', 'button', 'cite', 'code', 'del', 'dfn', 'em', 'font', 'i', 'iframe', 'img', 'input', 'ins', 'kbd', 'label', 'map', 'noscript', 'object', 'q', 's', 'samp', 'script', 'select', 'small', 'span', 'strike', 'strong', 'sub', 'sup', 'svg', 'textarea', 'tt', 'u', 'var']);
|
|
61
|
+
const inline = new CaseInsensitiveSet(['a', 'abbr', 'acronym', 'applet', 'b', 'basefont', 'bdo', 'big', 'br', 'button', 'cite', 'code', 'del', 'dfn', 'em', 'font', 'i', 'iframe', 'img', 'input', 'ins', 'kbd', 'label', 'map', 'noscript', 'object', 'q', 's', 'samp', 'script', 'select', 'selectedcontent', 'small', 'span', 'strike', 'strong', 'sub', 'sup', 'svg', 'textarea', 'tt', 'u', 'var']);
|
|
62
62
|
|
|
63
63
|
// Elements that you can, intentionally, leave open
|
|
64
64
|
// (and which close themselves)
|