html-minifier-next 1.1.5 → 1.2.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/package.json CHANGED
@@ -15,22 +15,21 @@
15
15
  "description": "Highly configurable, well-tested, JavaScript-based HTML minifier.",
16
16
  "devDependencies": {
17
17
  "@commitlint/cli": "^19.8.1",
18
- "@jest/globals": "^30.0.4",
18
+ "@jest/globals": "^30.0.5",
19
19
  "@rollup/plugin-commonjs": "^28.0.6",
20
20
  "@rollup/plugin-json": "^6.1.0",
21
21
  "@rollup/plugin-node-resolve": "^16.0.1",
22
22
  "@rollup/plugin-terser": "^0.4.4",
23
23
  "alpinejs": "^3.14.9",
24
24
  "commitlint-config-non-conventional": "^1.0.1",
25
- "eslint": "^9.30.1",
25
+ "eslint": "^9.32.0",
26
26
  "husky": "^9.1.7",
27
27
  "is-ci": "^4.1.0",
28
- "jest": "^30.0.4",
29
- "jest-environment-jsdom": "^30.0.4",
30
- "lint-staged": "^16.1.2",
31
- "rollup": "^4.44.1",
28
+ "jest": "^30.0.5",
29
+ "lint-staged": "^16.1.5",
30
+ "rollup": "^4.45.1",
32
31
  "rollup-plugin-polyfill-node": "^0.13.0",
33
- "vite": "^7.0.0"
32
+ "vite": "^7.0.5"
34
33
  },
35
34
  "exports": {
36
35
  ".": {
@@ -72,6 +71,11 @@
72
71
  "main": "./dist/htmlminifier.cjs",
73
72
  "module": "./src/htmlminifier.js",
74
73
  "name": "html-minifier-next",
74
+ "overrides": {
75
+ "glob": "^10.0.0",
76
+ "inflight": "npm:@nodelib/fs.stat@^3.0.0"
77
+ },
78
+ "overrides_comment": "@@ Remove when Jest fixes deprecated glob@7.2.3 and inflight dependencies",
75
79
  "repository": "https://github.com/j9t/html-minifier-next.git",
76
80
  "scripts": {
77
81
  "build": "rollup -c",
@@ -80,11 +84,9 @@
80
84
  "lint": "eslint .",
81
85
  "prepare": "husky",
82
86
  "serve": "npm run build && vite",
83
- "test": "npm run test:node",
84
- "test:node": "NODE_OPTIONS='--experimental-vm-modules --no-warnings' jest --verbose",
85
- "test:watch": "NODE_OPTIONS='--experimental-vm-modules --no-warnings' jest verbose --watch",
86
- "test:web": "NODE_OPTIONS='--experimental-vm-modules --no-warnings' jest --verbose --environment=jsdom"
87
+ "test": "NODE_OPTIONS='--experimental-vm-modules --no-warnings' jest",
88
+ "test:watch": "NODE_OPTIONS='--experimental-vm-modules --no-warnings' jest --watch"
87
89
  },
88
90
  "type": "module",
89
- "version": "1.1.5"
91
+ "version": "1.2.1"
90
92
  }
@@ -52,28 +52,28 @@ function collapseWhitespace(str, options, trimLeft, trimRight, collapseAll) {
52
52
  }
53
53
 
54
54
  if (collapseAll) {
55
- // strip non space whitespace then compress spaces to one
55
+ // Strip non-space whitespace then compress spaces to one
56
56
  str = collapseWhitespaceAll(str);
57
57
  }
58
58
 
59
59
  return lineBreakBefore + str + lineBreakAfter;
60
60
  }
61
61
 
62
- // non-empty tags that will maintain whitespace around them
63
- const inlineTags = new Set(['a', 'abbr', 'acronym', 'b', 'bdi', 'bdo', 'big', 'button', 'cite', 'code', 'del', 'dfn', 'em', 'font', 'i', 'ins', 'kbd', 'label', 'mark', 'math', 'nobr', 'object', 'q', 'rp', 'rt', 'rtc', 'ruby', 's', 'samp', 'select', 'small', 'span', 'strike', 'strong', 'sub', 'sup', 'svg', 'textarea', 'time', 'tt', 'u', 'var']);
64
- // non-empty tags that will maintain whitespace within them
65
- const inlineTextTags = new Set(['a', 'abbr', 'acronym', 'b', 'big', 'del', 'em', 'font', 'i', 'ins', 'kbd', 'mark', 'nobr', 'rp', 's', 'samp', 'small', 'span', 'strike', 'strong', 'sub', 'sup', 'time', 'tt', 'u', 'var']);
66
- // self-closing tags that will maintain whitespace around them
67
- const selfClosingInlineTags = new Set(['comment', 'img', 'input', 'wbr']);
62
+ // Non-empty elements that will maintain whitespace around them
63
+ const inlineElementsToKeepWhitespaceAround = ['a', 'abbr', 'acronym', 'b', 'bdi', 'bdo', 'big', 'button', 'cite', 'code', 'del', 'dfn', 'em', 'font', 'i', 'img', 'input', 'ins', 'kbd', 'label', 'mark', 'math', 'meter', 'nobr', 'object', 'output', 'progress', 'q', 'rp', 'rt', 'rtc', 'ruby', 's', 'samp', 'select', 'small', 'span', 'strike', 'strong', 'sub', 'sup', 'svg', 'textarea', 'time', 'tt', 'u', 'var', 'wbr'];
64
+ // Non-empty elements that will maintain whitespace within them
65
+ const inlineElementsToKeepWhitespaceWithin = new Set(['a', 'abbr', 'acronym', 'b', 'big', 'del', 'em', 'font', 'i', 'ins', 'kbd', 'mark', 'nobr', 'rp', 's', 'samp', 'small', 'span', 'strike', 'strong', 'sub', 'sup', 'time', 'tt', 'u', 'var']);
66
+ // Elements that will always maintain whitespace around them
67
+ const inlineElementsToKeepWhitespace = new Set(['comment', 'img', 'input', 'wbr']);
68
68
 
69
- function collapseWhitespaceSmart(str, prevTag, nextTag, options) {
70
- let trimLeft = prevTag && !selfClosingInlineTags.has(prevTag);
69
+ function collapseWhitespaceSmart(str, prevTag, nextTag, options, inlineElements, inlineTextSet) {
70
+ let trimLeft = prevTag && !inlineElementsToKeepWhitespace.has(prevTag);
71
71
  if (trimLeft && !options.collapseInlineTagWhitespace) {
72
- trimLeft = prevTag.charAt(0) === '/' ? !inlineTags.has(prevTag.slice(1)) : !inlineTextTags.has(prevTag);
72
+ trimLeft = prevTag.charAt(0) === '/' ? !inlineElements.has(prevTag.slice(1)) : !inlineTextSet.has(prevTag);
73
73
  }
74
- let trimRight = nextTag && !selfClosingInlineTags.has(nextTag);
74
+ let trimRight = nextTag && !inlineElementsToKeepWhitespace.has(nextTag);
75
75
  if (trimRight && !options.collapseInlineTagWhitespace) {
76
- trimRight = nextTag.charAt(0) === '/' ? !inlineTextTags.has(nextTag.slice(1)) : !inlineTags.has(nextTag);
76
+ trimRight = nextTag.charAt(0) === '/' ? !inlineTextSet.has(nextTag.slice(1)) : !inlineElements.has(nextTag);
77
77
  }
78
78
  return collapseWhitespace(str, options, trimLeft, trimRight, prevTag && nextTag);
79
79
  }
@@ -395,7 +395,7 @@ async function processScript(text, options, currentAttrs) {
395
395
  // Tag omission rules from https://html.spec.whatwg.org/multipage/syntax.html#optional-tags
396
396
  // with the following deviations:
397
397
  // - retain <body> if followed by <noscript>
398
- // - </rb>, </rt>, </rtc>, </rp> & </tfoot> follow https://www.w3.org/TR/html5/syntax.html#optional-tags
398
+ // - </rb>, </rt>, </rtc>, </rp>, and </tfoot> follow https://www.w3.org/TR/html5/syntax.html#optional-tags
399
399
  // - retain all tags which are adjacent to non-standard HTML tags
400
400
  const optionalStartTags = new Set(['html', 'head', 'body', 'colgroup', 'tbody']);
401
401
  const optionalEndTags = new Set(['html', 'head', 'body', 'li', 'dt', 'dd', 'p', 'rb', 'rt', 'rtc', 'rp', 'optgroup', 'option', 'colgroup', 'caption', 'thead', 'tbody', 'tfoot', 'tr', 'td', 'th']);
@@ -605,7 +605,7 @@ function buildAttr(normalized, hasUnarySlash, options, isLast, uidAttr) {
605
605
  emittedAttrValue += ' ';
606
606
  }
607
607
  } else if (isLast && !hasUnarySlash && !/\/$/.test(attrValue)) {
608
- // make sure trailing slash is not interpreted as HTML self-closing tag
608
+ // Make sure trailing slash is not interpreted as HTML self-closing tag
609
609
  emittedAttrValue = attrValue;
610
610
  } else {
611
611
  emittedAttrValue = attrValue + ' ';
@@ -866,10 +866,16 @@ async function minifyHTML(value, options, partialMarkup) {
866
866
  let uidIgnore;
867
867
  let uidAttr;
868
868
  let uidPattern;
869
-
870
- // temporarily replace ignored chunks with comments,
871
- // so that we don't have to worry what's there.
872
- // for all we care there might be
869
+ // Create inline tags/text sets with custom elements
870
+ const customElementsInput = options.inlineCustomElements ?? [];
871
+ const customElementsArr = Array.isArray(customElementsInput) ? customElementsInput : Array.from(customElementsInput);
872
+ const normalizedCustomElements = customElementsArr.map(name => options.name(name));
873
+ const inlineTextSet = new Set([...inlineElementsToKeepWhitespaceWithin, ...normalizedCustomElements]);
874
+ const inlineElements = new Set([...inlineElementsToKeepWhitespaceAround, ...normalizedCustomElements]);
875
+
876
+ // Temporarily replace ignored chunks with comments,
877
+ // so that we don’t have to worry what’s there.
878
+ // For all we care there might be
873
879
  // completely-horribly-broken-alien-non-html-emoj-cthulhu-filled content
874
880
  value = value.replace(/<!-- htmlmin:ignore -->([\s\S]*?)<!-- htmlmin:ignore -->/g, function (match, group1) {
875
881
  if (!uidIgnore) {
@@ -990,20 +996,20 @@ async function minifyHTML(value, options, partialMarkup) {
990
996
  buffer.length = Math.max(0, index);
991
997
  }
992
998
 
993
- // look for trailing whitespaces, bypass any inline tags
999
+ // Look for trailing whitespaces, bypass any inline tags
994
1000
  function trimTrailingWhitespace(index, nextTag) {
995
1001
  for (let endTag = null; index >= 0 && _canTrimWhitespace(endTag); index--) {
996
1002
  const str = buffer[index];
997
1003
  const match = str.match(/^<\/([\w:-]+)>$/);
998
1004
  if (match) {
999
1005
  endTag = match[1];
1000
- } else if (/>$/.test(str) || (buffer[index] = collapseWhitespaceSmart(str, null, nextTag, options))) {
1006
+ } else if (/>$/.test(str) || (buffer[index] = collapseWhitespaceSmart(str, null, nextTag, options, inlineElements, inlineTextSet))) {
1001
1007
  break;
1002
1008
  }
1003
1009
  }
1004
1010
  }
1005
1011
 
1006
- // look for trailing whitespaces from previously processed text
1012
+ // Look for trailing whitespaces from previously processed text
1007
1013
  // which may not be trimmed due to a following comment or an empty
1008
1014
  // element which has now been removed
1009
1015
  function squashTrailingWhitespace(nextTag) {
@@ -1034,7 +1040,7 @@ async function minifyHTML(value, options, partialMarkup) {
1034
1040
  tag = options.name(tag);
1035
1041
  currentTag = tag;
1036
1042
  charsPrevTag = tag;
1037
- if (!inlineTextTags.has(tag)) {
1043
+ if (!inlineTextSet.has(tag)) {
1038
1044
  currentChars = '';
1039
1045
  }
1040
1046
  hasChars = false;
@@ -1052,7 +1058,7 @@ async function minifyHTML(value, options, partialMarkup) {
1052
1058
  removeStartTag();
1053
1059
  }
1054
1060
  optionalStartTag = '';
1055
- // end-tag-followed-by-start-tag omission rules
1061
+ // End-tag-followed-by-start-tag omission rules
1056
1062
  if (htmlTag && canRemovePrecedingTag(optionalEndTag, tag)) {
1057
1063
  removeEndTag();
1058
1064
  // <colgroup> cannot be omitted if preceding </colgroup> is omitted
@@ -1062,7 +1068,7 @@ async function minifyHTML(value, options, partialMarkup) {
1062
1068
  optionalEndTag = '';
1063
1069
  }
1064
1070
 
1065
- // set whitespace flags for nested tags (eg. <code> within a <pre>)
1071
+ // Set whitespace flags for nested tags (eg. <code> within a <pre>)
1066
1072
  if (options.collapseWhitespace) {
1067
1073
  if (!stackNoTrimWhitespace.length) {
1068
1074
  squashTrailingWhitespace(tag);
@@ -1098,7 +1104,7 @@ async function minifyHTML(value, options, partialMarkup) {
1098
1104
  buffer.push(' ');
1099
1105
  buffer.push.apply(buffer, parts);
1100
1106
  } else if (optional && optionalStartTags.has(tag)) {
1101
- // start tag must never be omitted if it has any attributes
1107
+ // Start tag must never be omitted if it has any attributes
1102
1108
  optionalStartTag = tag;
1103
1109
  }
1104
1110
 
@@ -1115,7 +1121,7 @@ async function minifyHTML(value, options, partialMarkup) {
1115
1121
  }
1116
1122
  tag = options.name(tag);
1117
1123
 
1118
- // check if current tag is in a whitespace stack
1124
+ // Check if current tag is in a whitespace stack
1119
1125
  if (options.collapseWhitespace) {
1120
1126
  if (stackNoTrimWhitespace.length) {
1121
1127
  if (tag === stackNoTrimWhitespace[stackNoTrimWhitespace.length - 1]) {
@@ -1153,7 +1159,7 @@ async function minifyHTML(value, options, partialMarkup) {
1153
1159
  }
1154
1160
 
1155
1161
  if (options.removeEmptyElements && isElementEmpty && canRemoveElement(tag, attrs)) {
1156
- // remove last "element" from buffer
1162
+ // Remove last element from buffer
1157
1163
  removeStartTag();
1158
1164
  optionalStartTag = '';
1159
1165
  optionalEndTag = '';
@@ -1164,7 +1170,7 @@ async function minifyHTML(value, options, partialMarkup) {
1164
1170
  buffer.push('</' + tag + '>');
1165
1171
  }
1166
1172
  charsPrevTag = '/' + tag;
1167
- if (!inlineTags.has(tag)) {
1173
+ if (!inlineElements.has(tag)) {
1168
1174
  currentChars = '';
1169
1175
  } else if (isElementEmpty) {
1170
1176
  currentChars += '|';
@@ -1203,12 +1209,12 @@ async function minifyHTML(value, options, partialMarkup) {
1203
1209
  }
1204
1210
  trimTrailingWhitespace(tagIndex - 1, 'br');
1205
1211
  }
1206
- } else if (inlineTextTags.has(prevTag.charAt(0) === '/' ? prevTag.slice(1) : prevTag)) {
1212
+ } else if (inlineTextSet.has(prevTag.charAt(0) === '/' ? prevTag.slice(1) : prevTag)) {
1207
1213
  text = collapseWhitespace(text, options, /(?:^|\s)$/.test(currentChars));
1208
1214
  }
1209
1215
  }
1210
1216
  if (prevTag || nextTag) {
1211
- text = collapseWhitespaceSmart(text, prevTag, nextTag, options);
1217
+ text = collapseWhitespaceSmart(text, prevTag, nextTag, options, inlineElements, inlineTextSet);
1212
1218
  } else {
1213
1219
  text = collapseWhitespace(text, options, true, true);
1214
1220
  }
@@ -1278,7 +1284,7 @@ async function minifyHTML(value, options, partialMarkup) {
1278
1284
  text = prefix + text + suffix;
1279
1285
  }
1280
1286
  if (options.removeOptionalTags && text) {
1281
- // preceding comments suppress tag omissions
1287
+ // Preceding comments suppress tag omissions
1282
1288
  optionalStartTag = '';
1283
1289
  optionalEndTag = '';
1284
1290
  }
@@ -1382,4 +1388,4 @@ export const minify = async function (value, options) {
1382
1388
  return result;
1383
1389
  };
1384
1390
 
1385
- export default { minify };
1391
+ export default { minify };
package/src/htmlparser.js CHANGED
@@ -131,7 +131,7 @@ export class HTMLParser {
131
131
  let last, prevTag, nextTag;
132
132
  while (html) {
133
133
  last = html;
134
- // Make sure we're not in a script or style element
134
+ // Make sure were not in a `script` or `style` element
135
135
  if (!lastTag || !special.has(lastTag)) {
136
136
  let textEnd = html.indexOf('<');
137
137
  if (textEnd === 0) {
@@ -207,7 +207,7 @@ export class HTMLParser {
207
207
  html = '';
208
208
  }
209
209
 
210
- // next tag
210
+ // Next tag
211
211
  let nextTagMatch = parseStartTag(html);
212
212
  if (nextTagMatch) {
213
213
  nextTag = nextTagMatch.tagName;
@@ -320,9 +320,9 @@ export class HTMLParser {
320
320
 
321
321
  const attrs = match.attrs.map(function (args) {
322
322
  let name, value, customOpen, customClose, customAssign, quote;
323
- const ncp = 7; // number of captured parts, scalar
323
+ const ncp = 7; // Number of captured parts, scalar
324
324
 
325
- // hackish work around FF bug https://bugzilla.mozilla.org/show_bug.cgi?id=369778
325
+ // Hackish workaround for FF bug https://bugzilla.mozilla.org/show_bug.cgi?id=369778
326
326
  if (IS_REGEX_CAPTURING_BROKEN && args[0].indexOf('""') === -1) {
327
327
  if (args[3] === '') { delete args[3]; }
328
328
  if (args[4] === '') { delete args[4]; }
@@ -512,13 +512,13 @@ export const HTMLtoDOM = (html, doc) => {
512
512
  }
513
513
  }
514
514
 
515
- // If we're working with a document, inject contents into
516
- // the body element
515
+ // If were working with a document, inject contents into
516
+ // the `body` element
517
517
  let curParentNode = one.body;
518
518
 
519
519
  const parser = new HTMLParser(html, {
520
520
  start: function (tagName, attrs, unary) {
521
- // If it's a pre-built element, then we can ignore
521
+ // If its a pre-built element, then we can ignore
522
522
  // its construction
523
523
  if (one[tagName]) {
524
524
  curParentNode = one[tagName];
@@ -552,7 +552,7 @@ export const HTMLtoDOM = (html, doc) => {
552
552
  curParentNode.appendChild(doc.createTextNode(text));
553
553
  },
554
554
  comment: function (/* text */) {
555
- // create comment node
555
+ // Create comment node
556
556
  },
557
557
  ignore: function (/* text */) {
558
558
  // What to do here?
@@ -562,4 +562,4 @@ export const HTMLtoDOM = (html, doc) => {
562
562
  parser.parse();
563
563
 
564
564
  return doc;
565
- };
565
+ };