html-minifier-next 8.5.0 → 8.5.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 CHANGED
@@ -151,7 +151,7 @@ Options can be used in config files (camelCase) or via CLI flags (kebab-case wit
151
151
  | `customEventAttributes`<br>`--custom-event-attributes` | Array of regexes that allow to support custom event attributes (e.g., `ng-click`)—use with `minifyJS` | `[ /^on[a-z]{3,}$/ ]` |
152
152
  | `decodeEntities`<br>`--decode-entities` | Use direct Unicode characters whenever possible | `false` |
153
153
  | `ignoreCustomComments`<br>`--ignore-custom-comments` | Array of regexes that allow to ignore matching comments | `[ /^!/, /^\s*#/ ]` |
154
- | `ignoreCustomFragments`<br>`--ignore-custom-fragments` | Array of regexes that allow to ignore certain fragments, when matched (e.g., `<?php … ?>`, `{{ … }}`, etc.) | `[ /<%[\s\S]*?%>/, /<\?[\s\S]*?\?>/ ]` |
154
+ | `ignoreCustomFragments`<br>`--ignore-custom-fragments` | Array of regexes that allow to ignore certain fragments, when matched (e.g., `<?php … ?>`, `{{ … }}`, etc.; see [“Minifying server-side templates”](#minifying-server-side-templates)) | `[ /<%[\s\S]*?%>/, /<\?[\s\S]*?\?>/ ]` |
155
155
  | `includeAutoGeneratedTags`<br>`--include-auto-generated-tags` | Insert elements generated by HTML parser | `false` |
156
156
  | `inlineCustomElements`<br>`--inline-custom-elements` | Array of names of custom elements which are inline, for whitespace handling—use with `collapseWhitespace` | `[]` |
157
157
  | `keepClosingSlash`<br>`--keep-closing-slash` | Keep the trailing slash on void elements and other start tags that carry one, and read it as closing the element | `false` |
@@ -181,7 +181,7 @@ Options can be used in config files (camelCase) or via CLI flags (kebab-case wit
181
181
  | `sortAttributes`<br>`--sort-attributes` | [Sort attributes by frequency](#sorting-attributes-and-style-classes) | `false` |
182
182
  | `sortClassNames`<br>`--sort-class-names` | [Sort style classes by frequency](#sorting-attributes-and-style-classes) | `false` |
183
183
  | `strictCustomFragments`<br>`--strict-custom-fragments` | [Reject `ignoreCustomFragments` patterns that risk catastrophic backtracking](#redos-protection) (rather than warning about them) | `false` |
184
- | `trimCustomFragments`<br>`--trim-custom-fragments` | Trim whitespace around custom fragments (`ignoreCustomFragments`)—use with `collapseWhitespace` | `false` |
184
+ | `trimCustomFragments`<br>`--trim-custom-fragments` | Trim whitespace around custom fragments (`ignoreCustomFragments`) where it does not affect the output (see [“Minifying server-side templates”](#minifying-server-side-templates))—use with `collapseWhitespace` | `false` |
185
185
  | `useShortDoctype`<br>`--use-short-doctype` | [Replaces the doctype with the short HTML doctype](https://perfectionkills.com/experimenting-with-html-minifier/#use_short_doctype) | `false` |
186
186
 
187
187
  ### API-only options
@@ -680,6 +680,14 @@ For partial HTML fragments (such as template includes, SSI fragments, or closing
680
680
 
681
681
  To validate complete HTML markup, use [the W3C validator](https://validator.w3.org/) or one of [several validator packages](https://meiert.com/blog/html-validator-packages/).
682
682
 
683
+ ### Minifying server-side templates
684
+
685
+ By default, `ignoreCustomFragments` preserves `<? … ?>` and `<% … %>` blocks, so PHP, ERB, JSP, and ASP templates can be minified as part of a build, with the markup, CSS, and JavaScript around those blocks being minified as usual. For includes and other partials, add `partialMarkup`.
686
+
687
+ Whitespace next to template blocks is collapsed but kept, as it may matter once the template renders. `trimCustomFragments` removes it where HTML Minifier Next would remove whitespace anyway, as between block-level elements: `<ul> <?php foreach ($items as $item): ?> <li>` loses it, `Hello <?= $name ?>!` keeps it. Tags keep the spacing of their source, and whitespace next to template blocks in attribute values, CSS, and JavaScript is always kept.
688
+
689
+ Where pages are rendered ahead of serving, as with static exports or full-page caches, you can also minify the rendered HTML instead, which makes this a non-issue.
690
+
683
691
  ### Regex options and flags
684
692
 
685
693
  `customAttrAssign` and `customAttrSurround` patterns are merged into one attribute pattern which carries no flags of its own. `i` and `s` are written into each pattern’s source instead, so they survive the merge. `u`, `v`, and `m` cannot be, and none of them fails loudly when dropped: `u` and `v` only narrow what syntax is legal, so a source valid under either stays valid without it and quietly matches something else—a dropped `u` leaves `\p{L}` matching the literal text `p{L}`—while a dropped `m` leaves `^` and `$` matching at the ends of the input rather than of each line.
@@ -492,9 +492,10 @@ export type MinifierOptions = {
492
492
  */
493
493
  sortClassNames?: boolean | ((value: string) => string);
494
494
  /**
495
- * When true, whitespace around ignored custom fragments may be trimmed
496
- * more aggressively. This affects how preserved fragments interact with
497
- * surrounding whitespace collapse.
495
+ * When true, whitespace around ignored custom fragments is removed rather
496
+ * than collapsed where the minifier removes whitespace anyway (e.g., between
497
+ * block-level elements). Whitespace in tags, attribute values, CSS, and
498
+ * JavaScript is kept.
498
499
  *
499
500
  * Default: `false`
500
501
  */
@@ -1 +1 @@
1
- {"version":3,"file":"htmlminifier.d.ts","sourceRoot":"","sources":["../../src/htmlminifier.js"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"htmlminifier.d.ts","sourceRoot":"","sources":["../../src/htmlminifier.js"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"htmlparser.d.ts","sourceRoot":"","sources":["../../src/htmlparser.js"],"names":[],"mappings":"AAeG,YAYG,iBAAiB,GAZV;IACR,KAAK,CAAC,WAAW;IACjB,GAAG,CAAC,WAAW;IACf,KAAK,CAAC,WAAW;IACjB,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,WAAW;IACnB,oBAAoB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC3C,aAAa,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACpC,YAAY,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACnC,gBAAgB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACvC,kBAAkB,CAAC,EAAE,MAAM,EAAE,EAAE,GAAG,SAAS,CAAC;IAC5C,gBAAgB,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAA;CACxC,CAAmB;AA0CvB,eAAO,MAAM,MAAM,QAA8C,CAAC;AAoLlE,qBAAa,UAAU;IAMd,IAAI;IACJ,OAAO;IANd;;;OAGG;IACH,YAAY,IAAI,EAHL,MAGK,EAAE,OAAO,EAFd,iBAEc,EAGxB;IAEK,KAAK,kBAwyBV;CACF"}
1
+ {"version":3,"file":"htmlparser.d.ts","sourceRoot":"","sources":["../../src/htmlparser.js"],"names":[],"mappings":"AAeG,YAYG,iBAAiB,GAZV;IACR,KAAK,CAAC,WAAW;IACjB,GAAG,CAAC,WAAW;IACf,KAAK,CAAC,WAAW;IACjB,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,WAAW;IACnB,oBAAoB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC3C,aAAa,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACpC,YAAY,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACnC,gBAAgB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACvC,kBAAkB,CAAC,EAAE,MAAM,EAAE,EAAE,GAAG,SAAS,CAAC;IAC5C,gBAAgB,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAA;CACxC,CAAmB;AA0CvB,eAAO,MAAM,MAAM,QAA8C,CAAC;AAoLlE,qBAAa,UAAU;IAMd,IAAI;IACJ,OAAO;IANd;;;OAGG;IACH,YAAY,IAAI,EAHL,MAGK,EAAE,OAAO,EAFd,iBAEc,EAGxB;IAEK,KAAK,kBA2yBV;CACF"}
@@ -132,7 +132,7 @@
132
132
  }
133
133
  },
134
134
  "ignoreCustomFragments": {
135
- "description": "Array of regexes that allow to ignore certain fragments, when matched (e.g., `<?php … ?>`, `{{ … }}`)",
135
+ "description": "Array of regexes that allow to ignore certain fragments, when matched (e.g., `<?php … ?>`, `{{ … }}`, etc.)",
136
136
  "type": [
137
137
  "string",
138
138
  "array"
@@ -253,7 +253,7 @@
253
253
  "type": "boolean"
254
254
  },
255
255
  "removeEmptyElementsExcept": {
256
- "description": "Array of elements to preserve (e.g., `td`, `<span aria-hidden=\"true\">`)—use with `--remove-empty-elements`",
256
+ "description": "Array of elements to preserve (e.g., `td`, `<span aria-hidden=\"true\">`, etc.)—use with `--remove-empty-elements`",
257
257
  "type": [
258
258
  "string",
259
259
  "array"
@@ -290,7 +290,7 @@
290
290
  "type": "boolean"
291
291
  },
292
292
  "trimCustomFragments": {
293
- "description": "Trim whitespace around custom fragments (`--ignore-custom-fragments`)—use with `--collapse-whitespace`",
293
+ "description": "Trim whitespace around custom fragments (`--ignore-custom-fragments`) where it does not affect the output—use with `--collapse-whitespace`",
294
294
  "type": "boolean"
295
295
  },
296
296
  "strictCustomFragments": {
package/package.json CHANGED
@@ -95,5 +95,5 @@
95
95
  },
96
96
  "type": "module",
97
97
  "types": "dist/types/htmlminifier.d.ts",
98
- "version": "8.5.0"
98
+ "version": "8.5.1"
99
99
  }
@@ -512,9 +512,10 @@ import { toFragment, replaceCustomFragments } from './lib/fragments.js';
512
512
  * Default: `false`
513
513
  *
514
514
  * @prop {boolean} [trimCustomFragments]
515
- * When true, whitespace around ignored custom fragments may be trimmed
516
- * more aggressively. This affects how preserved fragments interact with
517
- * surrounding whitespace collapse.
515
+ * When true, whitespace around ignored custom fragments is removed rather
516
+ * than collapsed where the minifier removes whitespace anyway (e.g., between
517
+ * block-level elements). Whitespace in tags, attribute values, CSS, and
518
+ * JavaScript is kept.
518
519
  *
519
520
  * Default: `false`
520
521
  *
@@ -623,6 +624,7 @@ const DEFAULT_JS_TYPES = new Set(['', 'text/javascript', 'application/javascript
623
624
  const RE_START_TAG = /^<[^/!]/;
624
625
  const RE_END_TAG = /^<\//;
625
626
  const RE_WHITESPACE_ONLY = /^\s+$/;
627
+ const RE_TABS = /\t/g;
626
628
  const RE_TRAILING_WHITESPACE = /\s$/;
627
629
 
628
630
  // Pre-compiled patterns for `htmlmin:ignore` block content analysis
@@ -1065,6 +1067,15 @@ async function minifyHTML(value, options, partialMarkup) {
1065
1067
  const ignoredMarkupChunks = [];
1066
1068
  /** @type {Array<RegExpExecArray | null>} */
1067
1069
  const ignoredCustomMarkupChunks = [];
1070
+ // Custom fragments whose surrounding whitespace is never trimmed: in tags, where it
1071
+ // separates attributes, and in CSS and JavaScript, where minifiers can’t judge it
1072
+ /** @type {Set<number>} */
1073
+ const customMarkupInTags = new Set();
1074
+ /** @type {Set<number>} */
1075
+ const customMarkupInCode = new Set();
1076
+ // Attributes joined with the custom fragments they run into, to be written as they are
1077
+ /** @type {WeakSet<HTMLAttribute>} */
1078
+ const opaqueAttrs = new WeakSet();
1068
1079
  /** @type {string | undefined} */
1069
1080
  let uidIgnore;
1070
1081
  /** @type {RegExp | undefined} */
@@ -1075,6 +1086,8 @@ async function minifyHTML(value, options, partialMarkup) {
1075
1086
  let uidPattern;
1076
1087
  /** @type {RegExp | undefined} */
1077
1088
  let uidAttrLeadingPattern;
1089
+ /** @type {RegExp | undefined} */
1090
+ let uidAttrExactPattern;
1078
1091
  // Create inline tags/text sets with custom elements
1079
1092
  const customElementsInput = options.inlineCustomElements ?? [];
1080
1093
  const customElementsArr = Array.isArray(customElementsInput) ? customElementsInput : Array.from(customElementsInput);
@@ -1149,12 +1162,14 @@ async function minifyHTML(value, options, partialMarkup) {
1149
1162
  uidAttr = uniqueId(value);
1150
1163
  uidPattern = new RegExp('(\\s*)' + uidAttr + '([0-9]+)' + uidAttr + '(\\s*)', 'g');
1151
1164
  uidAttrLeadingPattern = new RegExp('^\\s*' + uidAttr + '(\\d+)' + uidAttr);
1165
+ uidAttrExactPattern = new RegExp('^' + uidAttr + '(\\d+)' + uidAttr + '$');
1152
1166
 
1153
1167
  if (options.minifyCSS !== identity) {
1154
1168
  options.minifyCSS = (function (/** @type {ProcessedOptions['minifyCSS']} */ fn) {
1155
1169
  return function (/** @type {string} */ text, /** @type {string | undefined} */ type, /** @type {ProcessedOptions['cssContext']} */ context) {
1156
1170
  text = text.replace(/** @type {RegExp} */ (uidPattern), function (/** @type {string} */ _match, /** @type {string} */ _prefix, /** @type {string} */ index) {
1157
1171
  const chunks = ignoredCustomMarkupChunks[+index];
1172
+ customMarkupInCode.add(+index);
1158
1173
  return (chunks?.[1] ?? '') + uidAttr + index + uidAttr + (chunks?.[2] ?? '');
1159
1174
  });
1160
1175
 
@@ -1168,6 +1183,7 @@ async function minifyHTML(value, options, partialMarkup) {
1168
1183
  return function (/** @type {string} */ text, /** @type {boolean | undefined} */ inline, /** @type {boolean | undefined} */ isModule) {
1169
1184
  return fn(text.replace(/** @type {RegExp} */ (uidPattern), function (/** @type {string} */ _match, /** @type {string} */ _prefix, /** @type {string} */ index) {
1170
1185
  const chunks = ignoredCustomMarkupChunks[+index];
1186
+ customMarkupInCode.add(+index);
1171
1187
  return (chunks?.[1] ?? '') + uidAttr + index + uidAttr + (chunks?.[2] ?? '');
1172
1188
  }), inline, isModule);
1173
1189
  };
@@ -1183,6 +1199,77 @@ async function minifyHTML(value, options, partialMarkup) {
1183
1199
  value = replaceCustomFragments(value, customFragments, replaceFragment);
1184
1200
  }
1185
1201
 
1202
+ // Record the custom fragments in (part of) a tag
1203
+ function markTagFragments(/** @type {string} */ str) {
1204
+ for (const uidMatch of str.matchAll(/** @type {RegExp} */ (uidPattern))) {
1205
+ customMarkupInTags.add(+(uidMatch[2] ?? 0));
1206
+ }
1207
+ }
1208
+
1209
+ // Source text of an attribute, as far as it matters
1210
+ function rawAttr(/** @type {HTMLAttribute} */ attr) {
1211
+ if (opaqueAttrs.has(attr)) {
1212
+ return attr.name;
1213
+ }
1214
+ let raw = attr.name;
1215
+ // Skip values the parser filled in for bare boolean attributes
1216
+ if (attr.value !== undefined && !(attr.quote === '' && attr.value === attr.name)) {
1217
+ raw += (attr.customAssign ?? '=') + (attr.quote ?? '') + attr.value + (attr.quote ?? '');
1218
+ }
1219
+ return (attr.customOpen ?? '') + raw + (attr.customClose ?? '');
1220
+ }
1221
+
1222
+ /**
1223
+ * Join each attribute a custom fragment runs into without whitespace with that fragment,
1224
+ * so that no later step separates, drops, changes, or reorders them
1225
+ * @param {HTMLAttribute[]} attrs - Joined in place
1226
+ * @returns {boolean} Whether the first attribute runs into the tag name
1227
+ */
1228
+ function joinFragmentAttrs(attrs) {
1229
+ const pattern = /** @type {RegExp} */ (uidAttrExactPattern);
1230
+ let joinsTag = false;
1231
+ let writeIndex = 0;
1232
+ // Padding of the previous attribute if that is a fragment without value
1233
+ /** @type {RegExpExecArray | null | undefined} */
1234
+ let prevChunks = null;
1235
+ for (const attr of attrs) {
1236
+ const uidMatch = pattern.exec(attr.name);
1237
+ const chunks = uidMatch ? ignoredCustomMarkupChunks[+(uidMatch[1] ?? 0)] : null;
1238
+ const joins = prevChunks ? prevChunks[2] === '' : chunks?.[1] === '';
1239
+ prevChunks = attr.value === undefined ? chunks : null;
1240
+ if (!joins) {
1241
+ attrs[writeIndex++] = attr;
1242
+ continue;
1243
+ }
1244
+ const prev = writeIndex ? attrs[writeIndex - 1] : undefined;
1245
+ if (!prev) {
1246
+ joinsTag = true;
1247
+ }
1248
+ /** @type {HTMLAttribute} */
1249
+ const joined = { name: (prev ? rawAttr(prev) : '') + rawAttr(attr), customAssign: '=', customOpen: '', customClose: '', quote: '' };
1250
+ opaqueAttrs.add(joined);
1251
+ attrs[prev ? writeIndex - 1 : writeIndex++] = joined;
1252
+ }
1253
+ attrs.length = writeIndex;
1254
+ return joinsTag;
1255
+ }
1256
+
1257
+ /**
1258
+ * Restore the whitespace on one side of a custom fragment
1259
+ * @param {string} str - Whitespace the minifier left there and the fragment’s own padding
1260
+ * @param {boolean} trim
1261
+ * @param {boolean} leading
1262
+ */
1263
+ function restoreFragmentSide(str, trim, leading) {
1264
+ if (!str) {
1265
+ return str;
1266
+ }
1267
+ return collapseWhitespace(str, {
1268
+ preserveLineBreaks: options.preserveLineBreaks,
1269
+ conservativeCollapse: !trim
1270
+ }, leading, !leading);
1271
+ }
1272
+
1186
1273
  function canCollapseWhitespace(/** @type {string} */ tag, /** @type {HTMLAttribute[]} */ attrs) {
1187
1274
  return options.canCollapseWhitespace(tag, attrs, defaultCanCollapseWhitespace);
1188
1275
  }
@@ -1754,6 +1841,10 @@ async function minifyHTML(value, options, partialMarkup) {
1754
1841
 
1755
1842
  buffer.push(openTag);
1756
1843
 
1844
+ const fragmentJoinsTag = uidAttrExactPattern ? joinFragmentAttrs(attrs) : false;
1845
+ // Whatever runs into the tag name has to stay first
1846
+ const tagFragment = fragmentJoinsTag ? attrs.shift() : undefined;
1847
+
1757
1848
  // Remove duplicate attributes (per HTML spec, first occurrence wins)
1758
1849
  // Duplicate attributes result in invalid HTML
1759
1850
  // https://html.spec.whatwg.org/multipage/parsing.html#attribute-name-state
@@ -1763,11 +1854,17 @@ async function minifyHTML(value, options, partialMarkup) {
1763
1854
  // By the time tags are processed, `createSortFns` has replaced any truthy non-function value
1764
1855
  /** @type {(tag: string, attrs: HTMLAttribute[]) => void} */ (options.sortAttributes)(tag, attrs);
1765
1856
  }
1857
+ if (tagFragment) {
1858
+ attrs.unshift(tagFragment);
1859
+ }
1766
1860
 
1767
1861
  const attrResults = new Array(attrs.length);
1768
1862
  let anyThenable = false;
1769
1863
  for (let i = 0; i < attrs.length; i++) {
1770
- const result = normalizeAttr(/** @type {HTMLAttribute} */ (attrs[i]), attrs, tag, options, minifyHTML);
1864
+ const attr = /** @type {HTMLAttribute} */ (attrs[i]);
1865
+ const result = opaqueAttrs.has(attr)
1866
+ ? { name: attr.name, value: undefined, attr }
1867
+ : normalizeAttr(attr, attrs, tag, options, minifyHTML);
1771
1868
  if (!anyThenable && isThenable(result)) {
1772
1869
  anyThenable = true;
1773
1870
  }
@@ -1785,11 +1882,18 @@ async function minifyHTML(value, options, partialMarkup) {
1785
1882
  }
1786
1883
  }
1787
1884
  if (lastKeptIndex !== -1) {
1788
- buffer.push(' ');
1885
+ if (!fragmentJoinsTag) {
1886
+ buffer.push(' ');
1887
+ }
1789
1888
  for (let i = 0; i <= lastKeptIndex; i++) {
1790
1889
  const normalizedAttr = normalizedAttrs[i];
1791
1890
  if (normalizedAttr) {
1792
- buffer.push(buildAttr(normalizedAttr, hasUnarySlash, options, i === lastKeptIndex, uidAttr));
1891
+ if (uidAttr && normalizedAttr.name.includes(uidAttr)) {
1892
+ markTagFragments(normalizedAttr.name);
1893
+ }
1894
+ const builtAttr = buildAttr(normalizedAttr, hasUnarySlash, options, i === lastKeptIndex, uidAttr);
1895
+ // One entry with the tag name, so that no line break can come between them
1896
+ buffer.push(i === 0 && fragmentJoinsTag ? buffer.pop() + builtAttr : builtAttr);
1793
1897
  }
1794
1898
  }
1795
1899
  } else if (optional && optionalStartTags.has(tag)) {
@@ -1815,7 +1919,7 @@ async function minifyHTML(value, options, partialMarkup) {
1815
1919
  currentTag = '';
1816
1920
  }
1817
1921
  },
1818
- end: function (/** @type {string} */ tag, /** @type {HTMLAttribute[]} */ attrs, /** @type {boolean} */ autoGenerated) {
1922
+ end: function (/** @type {string} */ tag, /** @type {HTMLAttribute[]} */ attrs, /** @type {boolean} */ autoGenerated, /** @type {string | undefined} */ rest) {
1819
1923
  // As in `start`: `lowerTag` stays '' when no foreign content is around
1820
1924
  let lowerTag = '';
1821
1925
  // Named as the start tag was: an `svg` or `math` element in its own context,
@@ -1833,6 +1937,18 @@ async function minifyHTML(value, options, partialMarkup) {
1833
1937
  }
1834
1938
  }
1835
1939
  tag = name(tag);
1940
+ let endTagText = '</' + tag + '>';
1941
+ // Custom fragments after the name are kept, next to it if they were in the source
1942
+ if (rest && uidAttr && rest.includes(uidAttr)) {
1943
+ markTagFragments(rest);
1944
+ const uidMatch = /** @type {RegExp} */ (uidAttrLeadingPattern).exec(rest);
1945
+ const joinsTag = uidMatch !== null && rest.startsWith('\t' + uidAttr) &&
1946
+ ignoredCustomMarkupChunks[+(uidMatch[1] ?? 0)]?.[1] === '';
1947
+ // Anything else an end tag holds is dropped, as without fragments
1948
+ const marker = uidAttr;
1949
+ const fragments = Array.from(rest.matchAll(/** @type {RegExp} */ (uidPattern)), uid => marker + (uid[2] ?? '') + marker);
1950
+ endTagText = '</' + tag + (joinsTag ? '' : ' ') + fragments.join(' ') + '>';
1951
+ }
1836
1952
 
1837
1953
  // Check if current tag is in a whitespace stack
1838
1954
  if (options.collapseWhitespace) {
@@ -1894,7 +2010,7 @@ async function minifyHTML(value, options, partialMarkup) {
1894
2010
  if (autoGenerated && !options.includeAutoGeneratedTags) {
1895
2011
  optionalEndTagEmitted = false;
1896
2012
  } else {
1897
- buffer.push('</' + tag + '>');
2013
+ buffer.push(endTagText);
1898
2014
  }
1899
2015
  charsPrevTag = '/' + tag;
1900
2016
  if (!inlineElements.has(tag)) {
@@ -1907,7 +2023,7 @@ async function minifyHTML(value, options, partialMarkup) {
1907
2023
  if (autoGenerated && !options.includeAutoGeneratedTags) {
1908
2024
  optionalEndTagEmitted = false;
1909
2025
  } else {
1910
- buffer.push('</' + tag + '>');
2026
+ buffer.push(endTagText);
1911
2027
  }
1912
2028
  charsPrevTag = '/' + tag;
1913
2029
  if (!inlineElements.has(tag)) {
@@ -2024,21 +2140,26 @@ async function minifyHTML(value, options, partialMarkup) {
2024
2140
 
2025
2141
  return joinResultSegments(buffer, options, uidPattern
2026
2142
  ? function (/** @type {string} */ str) {
2027
- return str.replace(/** @type {RegExp} */ (uidPattern), function (/** @type {string} */ match, /** @type {string} */ prefix, /** @type {string} */ index, /** @type {string} */ suffix) {
2028
- let chunk = ignoredCustomMarkupChunks[+index]?.[0] ?? match;
2029
- if (options.collapseWhitespace) {
2030
- if (prefix !== '\t') {
2031
- chunk = prefix + chunk;
2032
- }
2033
- if (suffix !== '\t') {
2034
- chunk += suffix;
2035
- }
2036
- return collapseWhitespace(chunk, {
2037
- preserveLineBreaks: options.preserveLineBreaks,
2038
- conservativeCollapse: !options.trimCustomFragments
2039
- }, /^[ \n\r\t\f]/.test(chunk), /[ \n\r\t\f]$/.test(chunk));
2143
+ return str.replace(/** @type {RegExp} */ (uidPattern), function (/** @type {string} */ match, /** @type {string} */ prefix, /** @type {string} */ index, /** @type {string} */ suffix, /** @type {number} */ offset, /** @type {string} */ string) {
2144
+ const chunks = ignoredCustomMarkupChunks[+index];
2145
+ if (!chunks) {
2146
+ return match;
2147
+ }
2148
+ if (!options.collapseWhitespace) {
2149
+ return chunks[0];
2040
2150
  }
2041
- return chunk;
2151
+ const leading = chunks[1] ?? '';
2152
+ const trailing = chunks[2] ?? '';
2153
+ const fragment = chunks[0].slice(leading.length, chunks[0].length - trailing.length);
2154
+ const inTag = customMarkupInTags.has(+index);
2155
+ const keep = inTag || customMarkupInCode.has(+index) || !options.trimCustomFragments;
2156
+ // The padding tabs are whitespace to the minifier, so where one is gone, the
2157
+ // minifier removed the whitespace, which makes it safe to trim
2158
+ const trimLeading = !keep && prefix === '';
2159
+ const trimTrailing = inTag ? string.charCodeAt(offset + match.length) === 62 /* > */ : !keep && suffix === '';
2160
+ return restoreFragmentSide(prefix.replace(RE_TABS, '') + leading, trimLeading, true) +
2161
+ fragment +
2162
+ restoreFragmentSide(suffix.replace(RE_TABS, '') + trailing, trimTrailing, false);
2042
2163
  });
2043
2164
  }
2044
2165
  : identity, uidIgnore
package/src/htmlparser.js CHANGED
@@ -1033,11 +1033,14 @@ export class HTMLParser {
1033
1033
  stackIndex = 0;
1034
1034
  }
1035
1035
 
1036
+ // Whatever follows the name, which browsers drop but a custom fragment may stand in for
1037
+ const rest = tag.length > tagName.length + 3 ? tag.slice(tagName.length + 2, -1) : '';
1038
+
1036
1039
  if (stackIndex >= 0) {
1037
1040
  // Close all the open elements, up the stack
1038
1041
  for (let i = stack.length - 1; i >= stackIndex; i--) {
1039
1042
  if (handler.end) {
1040
- handler.end(stack[i]?.tag, stack[i]?.attrs, i > stackIndex || !tag);
1043
+ handler.end(stack[i]?.tag, stack[i]?.attrs, i > stackIndex || !tag, i === stackIndex ? rest : '');
1041
1044
  }
1042
1045
  }
1043
1046
 
@@ -1048,7 +1051,7 @@ export class HTMLParser {
1048
1051
  } else if (handler.partialMarkup && tagName) {
1049
1052
  // In partial markup mode, preserve stray end tags
1050
1053
  if (handler.end) {
1051
- handler.end(tagName, [], false);
1054
+ handler.end(tagName, [], false, rest);
1052
1055
  }
1053
1056
  } else if (lowerTagName === 'br') {
1054
1057
  if (handler.start) {
@@ -81,7 +81,7 @@ const optionDefinitions = {
81
81
  type: 'regexpArray'
82
82
  },
83
83
  ignoreCustomFragments: {
84
- description: 'Array of regexes that allow to ignore certain fragments, when matched (e.g., `<?php \u2026 ?>`, `{{ \u2026 }}`)',
84
+ description: 'Array of regexes that allow to ignore certain fragments, when matched (e.g., `<?php \u2026 ?>`, `{{ \u2026 }}`, etc.)',
85
85
  type: 'regexpArray'
86
86
  },
87
87
  includeAutoGeneratedTags: {
@@ -169,7 +169,7 @@ const optionDefinitions = {
169
169
  type: 'boolean'
170
170
  },
171
171
  removeEmptyElementsExcept: {
172
- description: 'Array of elements to preserve (e.g., `td`, `<span aria-hidden="true">`)—use with `--remove-empty-elements`',
172
+ description: 'Array of elements to preserve (e.g., `td`, `<span aria-hidden="true">`, etc.)—use with `--remove-empty-elements`',
173
173
  type: 'jsonArray'
174
174
  },
175
175
  removeOptionalTags: {
@@ -197,7 +197,7 @@ const optionDefinitions = {
197
197
  type: 'boolean'
198
198
  },
199
199
  trimCustomFragments: {
200
- description: 'Trim whitespace around custom fragments (`--ignore-custom-fragments`)—use with `--collapse-whitespace`',
200
+ description: 'Trim whitespace around custom fragments (`--ignore-custom-fragments`) where it does not affect the output—use with `--collapse-whitespace`',
201
201
  type: 'boolean'
202
202
  },
203
203
  strictCustomFragments: {