html-minifier-next 5.1.1 → 5.1.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.
@@ -1385,12 +1385,15 @@ function collapseWhitespaceSmart(str, prevTag, nextTag, prevAttrs, nextAttrs, op
1385
1385
 
1386
1386
  // Collapse/trim whitespace for given tag
1387
1387
 
1388
+ const noCollapseWsTags = new Set(['script', 'style', 'pre', 'textarea']);
1389
+ const noTrimWsTags = new Set(['pre', 'textarea']);
1390
+
1388
1391
  function canCollapseWhitespace(tag) {
1389
- return !/^(?:script|style|pre|textarea)$/.test(tag);
1392
+ return !noCollapseWsTags.has(tag);
1390
1393
  }
1391
1394
 
1392
1395
  function canTrimWhitespace(tag) {
1393
- return !/^(?:pre|textarea)$/.test(tag);
1396
+ return !noTrimWsTags.has(tag);
1394
1397
  }
1395
1398
 
1396
1399
  /**
@@ -2220,31 +2223,45 @@ function isBooleanAttribute(attrName, attrValue) {
2220
2223
  (attrValue === '' && emptyCollapsible.has(attrName));
2221
2224
  }
2222
2225
 
2226
+ const uriTypeAttributes = new Map([
2227
+ ['a', new Set(['href'])],
2228
+ ['area', new Set(['href'])],
2229
+ ['link', new Set(['href'])],
2230
+ ['base', new Set(['href'])],
2231
+ ['img', new Set(['src', 'longdesc', 'usemap'])],
2232
+ ['object', new Set(['classid', 'codebase', 'data', 'usemap'])],
2233
+ ['q', new Set(['cite'])],
2234
+ ['blockquote', new Set(['cite'])],
2235
+ ['ins', new Set(['cite'])],
2236
+ ['del', new Set(['cite'])],
2237
+ ['form', new Set(['action'])],
2238
+ ['input', new Set(['src', 'usemap'])],
2239
+ ['head', new Set(['profile'])],
2240
+ ['script', new Set(['src', 'for'])]
2241
+ ]);
2242
+
2223
2243
  function isUriTypeAttribute(attrName, tag) {
2224
- return (
2225
- (/^(?:a|area|link|base)$/.test(tag) && attrName === 'href') ||
2226
- (tag === 'img' && /^(?:src|longdesc|usemap)$/.test(attrName)) ||
2227
- (tag === 'object' && /^(?:classid|codebase|data|usemap)$/.test(attrName)) ||
2228
- (tag === 'q' && attrName === 'cite') ||
2229
- (tag === 'blockquote' && attrName === 'cite') ||
2230
- ((tag === 'ins' || tag === 'del') && attrName === 'cite') ||
2231
- (tag === 'form' && attrName === 'action') ||
2232
- (tag === 'input' && (attrName === 'src' || attrName === 'usemap')) ||
2233
- (tag === 'head' && attrName === 'profile') ||
2234
- (tag === 'script' && (attrName === 'src' || attrName === 'for'))
2235
- );
2244
+ const set = uriTypeAttributes.get(tag);
2245
+ return set ? set.has(attrName) : false;
2236
2246
  }
2237
2247
 
2248
+ const numberTypeAttributes = new Map([
2249
+ ['a', new Set(['tabindex'])],
2250
+ ['area', new Set(['tabindex'])],
2251
+ ['object', new Set(['tabindex'])],
2252
+ ['button', new Set(['tabindex'])],
2253
+ ['input', new Set(['maxlength', 'tabindex'])],
2254
+ ['select', new Set(['size', 'tabindex'])],
2255
+ ['textarea', new Set(['rows', 'cols', 'tabindex'])],
2256
+ ['colgroup', new Set(['span'])],
2257
+ ['col', new Set(['span'])],
2258
+ ['th', new Set(['rowspan', 'colspan'])],
2259
+ ['td', new Set(['rowspan', 'colspan'])]
2260
+ ]);
2261
+
2238
2262
  function isNumberTypeAttribute(attrName, tag) {
2239
- return (
2240
- (/^(?:a|area|object|button)$/.test(tag) && attrName === 'tabindex') ||
2241
- (tag === 'input' && (attrName === 'maxlength' || attrName === 'tabindex')) ||
2242
- (tag === 'select' && (attrName === 'size' || attrName === 'tabindex')) ||
2243
- (tag === 'textarea' && /^(?:rows|cols|tabindex)$/.test(attrName)) ||
2244
- (tag === 'colgroup' && attrName === 'span') ||
2245
- (tag === 'col' && attrName === 'span') ||
2246
- ((tag === 'th' || tag === 'td') && (attrName === 'rowspan' || attrName === 'colspan'))
2247
- );
2263
+ const set = numberTypeAttributes.get(tag);
2264
+ return set ? set.has(attrName) : false;
2248
2265
  }
2249
2266
 
2250
2267
  function isLinkType(tag, attrs, value) {
@@ -3691,6 +3708,7 @@ async function minifyHTML(value, options, partialMarkup) {
3691
3708
  let currentAttrs = [];
3692
3709
  const stackNoTrimWhitespace = [];
3693
3710
  const stackNoCollapseWhitespace = [];
3711
+ let preTextareaDepth = 0; // Count of `pre`/`textarea` entries in `stackNoTrimWhitespace`
3694
3712
  let optionalStartTag = '';
3695
3713
  let optionalEndTag = '';
3696
3714
  let optionalEndTagEmitted = false;
@@ -3851,7 +3869,7 @@ async function minifyHTML(value, options, partialMarkup) {
3851
3869
  let charsIndex = buffer.length - 1;
3852
3870
  if (buffer.length > 1) {
3853
3871
  const item = buffer[buffer.length - 1];
3854
- if (/^(?:<!|$)/.test(item) && item.indexOf(uidIgnore) === -1) {
3872
+ if (/^(?:<!|$)/.test(item) && (!uidIgnore || item.indexOf(uidIgnore) === -1)) {
3855
3873
  charsIndex--;
3856
3874
  }
3857
3875
  }
@@ -3945,6 +3963,7 @@ async function minifyHTML(value, options, partialMarkup) {
3945
3963
  if (!unary) {
3946
3964
  if (!canTrimWhitespace$1(tag, attrs) || stackNoTrimWhitespace.length) {
3947
3965
  stackNoTrimWhitespace.push(tag);
3966
+ if (tag === 'pre' || tag === 'textarea') preTextareaDepth++;
3948
3967
  }
3949
3968
  if (!canCollapseWhitespace$1(tag, attrs) || stackNoCollapseWhitespace.length) {
3950
3969
  stackNoCollapseWhitespace.push(tag);
@@ -3974,11 +3993,14 @@ async function minifyHTML(value, options, partialMarkup) {
3974
3993
  options.sortAttributes(tag, attrs);
3975
3994
  }
3976
3995
 
3996
+ const normalizedAttrs = await Promise.all(
3997
+ attrs.map(attr => normalizeAttr(attr, attrs, tag, options, minifyHTML))
3998
+ );
3977
3999
  const parts = [];
3978
- for (let i = attrs.length, isLast = true; --i >= 0;) {
3979
- const normalized = await normalizeAttr(attrs[i], attrs, tag, options, minifyHTML);
3980
- if (normalized) {
3981
- parts.push(buildAttr(normalized, hasUnarySlash, options, isLast, uidAttr));
4000
+ let isLast = true;
4001
+ for (let i = normalizedAttrs.length - 1; i >= 0; i--) {
4002
+ if (normalizedAttrs[i]) {
4003
+ parts.push(buildAttr(normalizedAttrs[i], hasUnarySlash, options, isLast, uidAttr));
3982
4004
  isLast = false;
3983
4005
  }
3984
4006
  }
@@ -4014,6 +4036,7 @@ async function minifyHTML(value, options, partialMarkup) {
4014
4036
  if (options.collapseWhitespace) {
4015
4037
  if (stackNoTrimWhitespace.length) {
4016
4038
  if (tag === stackNoTrimWhitespace[stackNoTrimWhitespace.length - 1]) {
4039
+ if (tag === 'pre' || tag === 'textarea') preTextareaDepth--;
4017
4040
  stackNoTrimWhitespace.pop();
4018
4041
  }
4019
4042
  } else {
@@ -4115,7 +4138,7 @@ async function minifyHTML(value, options, partialMarkup) {
4115
4138
  // Only trims single trailing newlines (multiple newlines are likely intentional formatting)
4116
4139
  if (options.collapseWhitespace && stackNoTrimWhitespace.length) {
4117
4140
  const topTag = stackNoTrimWhitespace[stackNoTrimWhitespace.length - 1];
4118
- if (stackNoTrimWhitespace.includes('pre') || stackNoTrimWhitespace.includes('textarea')) {
4141
+ if (preTextareaDepth > 0) {
4119
4142
  // Trim trailing whitespace only if it ends with a single newline (not multiple)
4120
4143
  // Multiple newlines are likely intentional formatting, single newline is often a template artifact
4121
4144
  // Treat CRLF (`\r\n`), CR (`\r`), and LF (`\n`) as single line-ending units
@@ -4128,7 +4151,7 @@ async function minifyHTML(value, options, partialMarkup) {
4128
4151
  if (!stackNoTrimWhitespace.length) {
4129
4152
  if (prevTag === 'comment') {
4130
4153
  const prevComment = buffer[buffer.length - 1];
4131
- if (prevComment.indexOf(uidIgnore) === -1) {
4154
+ if (!uidIgnore || prevComment.indexOf(uidIgnore) === -1) {
4132
4155
  if (!prevComment) {
4133
4156
  prevTag = charsPrevTag;
4134
4157
  }
@@ -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":"attributes.d.ts","sourceRoot":"","sources":["../../../src/lib/attributes.js"],"names":[],"mappings":"AAmCA,yDAEC;AAED,mEAOC;AAED,uEAWC;AAED,8DAGC;AAED,4EAOC;AAgCD,mGAuCC;AAED,mEAGC;AAED,qEAGC;AAED,kEAWC;AAED,sEAGC;AAED,8DAWC;AAED,2EAIC;AAED,qEAaC;AAED,wEAUC;AAED,sEAUC;AAED,2EAEC;AAED,2DAEC;AAED,8DAUC;AAED,uEAUC;AAED,oGASC;AAED,4DAOC;AAID,0IAqIC;AAsBD;;;;GAsCC;AAED,6GAuHC;AA7gBD;;;;;;;GAOG;AACH,mEAHW,OAAO,SAuBjB"}
1
+ {"version":3,"file":"attributes.d.ts","sourceRoot":"","sources":["../../../src/lib/attributes.js"],"names":[],"mappings":"AAmCA,yDAEC;AAED,mEAOC;AAED,uEAWC;AAED,8DAGC;AAED,4EAOC;AAgCD,mGAuCC;AAED,mEAGC;AAED,qEAGC;AAED,kEAWC;AAED,sEAGC;AAED,8DAWC;AAED,2EAIC;AAmBD,qEAGC;AAgBD,wEAGC;AAED,sEAUC;AAED,2EAEC;AAED,2DAEC;AAED,8DAUC;AAED,uEAUC;AAED,oGASC;AAED,4DAOC;AAID,0IAqIC;AAsBD;;;;GAsCC;AAED,6GAuHC;AA3hBD;;;;;;;GAOG;AACH,mEAHW,OAAO,SAuBjB"}
@@ -1 +1 @@
1
- {"version":3,"file":"whitespace.d.ts","sourceRoot":"","sources":["../../../src/lib/whitespace.js"],"names":[],"mappings":"AAiBA,8CAOC;AAID,qDAgBC;AAID,iHAqFC;AAID,0KAwEC;AAID,yDAEC;AAED,qDAEC"}
1
+ {"version":3,"file":"whitespace.d.ts","sourceRoot":"","sources":["../../../src/lib/whitespace.js"],"names":[],"mappings":"AAiBA,8CAOC;AAID,qDAgBC;AAID,iHAqFC;AAID,0KAwEC;AAOD,yDAEC;AAED,qDAEC"}
package/package.json CHANGED
@@ -95,5 +95,5 @@
95
95
  },
96
96
  "type": "module",
97
97
  "types": "./dist/types/htmlminifier.d.ts",
98
- "version": "5.1.1"
99
- }
98
+ "version": "5.1.2"
99
+ }
@@ -895,6 +895,7 @@ async function minifyHTML(value, options, partialMarkup) {
895
895
  let currentAttrs = [];
896
896
  const stackNoTrimWhitespace = [];
897
897
  const stackNoCollapseWhitespace = [];
898
+ let preTextareaDepth = 0; // Count of `pre`/`textarea` entries in `stackNoTrimWhitespace`
898
899
  let optionalStartTag = '';
899
900
  let optionalEndTag = '';
900
901
  let optionalEndTagEmitted = false;
@@ -1055,7 +1056,7 @@ async function minifyHTML(value, options, partialMarkup) {
1055
1056
  let charsIndex = buffer.length - 1;
1056
1057
  if (buffer.length > 1) {
1057
1058
  const item = buffer[buffer.length - 1];
1058
- if (/^(?:<!|$)/.test(item) && item.indexOf(uidIgnore) === -1) {
1059
+ if (/^(?:<!|$)/.test(item) && (!uidIgnore || item.indexOf(uidIgnore) === -1)) {
1059
1060
  charsIndex--;
1060
1061
  }
1061
1062
  }
@@ -1149,6 +1150,7 @@ async function minifyHTML(value, options, partialMarkup) {
1149
1150
  if (!unary) {
1150
1151
  if (!canTrimWhitespace(tag, attrs) || stackNoTrimWhitespace.length) {
1151
1152
  stackNoTrimWhitespace.push(tag);
1153
+ if (tag === 'pre' || tag === 'textarea') preTextareaDepth++;
1152
1154
  }
1153
1155
  if (!canCollapseWhitespace(tag, attrs) || stackNoCollapseWhitespace.length) {
1154
1156
  stackNoCollapseWhitespace.push(tag);
@@ -1178,11 +1180,14 @@ async function minifyHTML(value, options, partialMarkup) {
1178
1180
  options.sortAttributes(tag, attrs);
1179
1181
  }
1180
1182
 
1183
+ const normalizedAttrs = await Promise.all(
1184
+ attrs.map(attr => normalizeAttr(attr, attrs, tag, options, minifyHTML))
1185
+ );
1181
1186
  const parts = [];
1182
- for (let i = attrs.length, isLast = true; --i >= 0;) {
1183
- const normalized = await normalizeAttr(attrs[i], attrs, tag, options, minifyHTML);
1184
- if (normalized) {
1185
- parts.push(buildAttr(normalized, hasUnarySlash, options, isLast, uidAttr));
1187
+ let isLast = true;
1188
+ for (let i = normalizedAttrs.length - 1; i >= 0; i--) {
1189
+ if (normalizedAttrs[i]) {
1190
+ parts.push(buildAttr(normalizedAttrs[i], hasUnarySlash, options, isLast, uidAttr));
1186
1191
  isLast = false;
1187
1192
  }
1188
1193
  }
@@ -1218,6 +1223,7 @@ async function minifyHTML(value, options, partialMarkup) {
1218
1223
  if (options.collapseWhitespace) {
1219
1224
  if (stackNoTrimWhitespace.length) {
1220
1225
  if (tag === stackNoTrimWhitespace[stackNoTrimWhitespace.length - 1]) {
1226
+ if (tag === 'pre' || tag === 'textarea') preTextareaDepth--;
1221
1227
  stackNoTrimWhitespace.pop();
1222
1228
  }
1223
1229
  } else {
@@ -1319,7 +1325,7 @@ async function minifyHTML(value, options, partialMarkup) {
1319
1325
  // Only trims single trailing newlines (multiple newlines are likely intentional formatting)
1320
1326
  if (options.collapseWhitespace && stackNoTrimWhitespace.length) {
1321
1327
  const topTag = stackNoTrimWhitespace[stackNoTrimWhitespace.length - 1];
1322
- if (stackNoTrimWhitespace.includes('pre') || stackNoTrimWhitespace.includes('textarea')) {
1328
+ if (preTextareaDepth > 0) {
1323
1329
  // Trim trailing whitespace only if it ends with a single newline (not multiple)
1324
1330
  // Multiple newlines are likely intentional formatting, single newline is often a template artifact
1325
1331
  // Treat CRLF (`\r\n`), CR (`\r`), and LF (`\n`) as single line-ending units
@@ -1332,7 +1338,7 @@ async function minifyHTML(value, options, partialMarkup) {
1332
1338
  if (!stackNoTrimWhitespace.length) {
1333
1339
  if (prevTag === 'comment') {
1334
1340
  const prevComment = buffer[buffer.length - 1];
1335
- if (prevComment.indexOf(uidIgnore) === -1) {
1341
+ if (!uidIgnore || prevComment.indexOf(uidIgnore) === -1) {
1336
1342
  if (!prevComment) {
1337
1343
  prevTag = charsPrevTag;
1338
1344
  }
@@ -191,31 +191,45 @@ function isBooleanAttribute(attrName, attrValue) {
191
191
  (attrValue === '' && emptyCollapsible.has(attrName));
192
192
  }
193
193
 
194
+ const uriTypeAttributes = new Map([
195
+ ['a', new Set(['href'])],
196
+ ['area', new Set(['href'])],
197
+ ['link', new Set(['href'])],
198
+ ['base', new Set(['href'])],
199
+ ['img', new Set(['src', 'longdesc', 'usemap'])],
200
+ ['object', new Set(['classid', 'codebase', 'data', 'usemap'])],
201
+ ['q', new Set(['cite'])],
202
+ ['blockquote', new Set(['cite'])],
203
+ ['ins', new Set(['cite'])],
204
+ ['del', new Set(['cite'])],
205
+ ['form', new Set(['action'])],
206
+ ['input', new Set(['src', 'usemap'])],
207
+ ['head', new Set(['profile'])],
208
+ ['script', new Set(['src', 'for'])]
209
+ ]);
210
+
194
211
  function isUriTypeAttribute(attrName, tag) {
195
- return (
196
- (/^(?:a|area|link|base)$/.test(tag) && attrName === 'href') ||
197
- (tag === 'img' && /^(?:src|longdesc|usemap)$/.test(attrName)) ||
198
- (tag === 'object' && /^(?:classid|codebase|data|usemap)$/.test(attrName)) ||
199
- (tag === 'q' && attrName === 'cite') ||
200
- (tag === 'blockquote' && attrName === 'cite') ||
201
- ((tag === 'ins' || tag === 'del') && attrName === 'cite') ||
202
- (tag === 'form' && attrName === 'action') ||
203
- (tag === 'input' && (attrName === 'src' || attrName === 'usemap')) ||
204
- (tag === 'head' && attrName === 'profile') ||
205
- (tag === 'script' && (attrName === 'src' || attrName === 'for'))
206
- );
212
+ const set = uriTypeAttributes.get(tag);
213
+ return set ? set.has(attrName) : false;
207
214
  }
208
215
 
216
+ const numberTypeAttributes = new Map([
217
+ ['a', new Set(['tabindex'])],
218
+ ['area', new Set(['tabindex'])],
219
+ ['object', new Set(['tabindex'])],
220
+ ['button', new Set(['tabindex'])],
221
+ ['input', new Set(['maxlength', 'tabindex'])],
222
+ ['select', new Set(['size', 'tabindex'])],
223
+ ['textarea', new Set(['rows', 'cols', 'tabindex'])],
224
+ ['colgroup', new Set(['span'])],
225
+ ['col', new Set(['span'])],
226
+ ['th', new Set(['rowspan', 'colspan'])],
227
+ ['td', new Set(['rowspan', 'colspan'])]
228
+ ]);
229
+
209
230
  function isNumberTypeAttribute(attrName, tag) {
210
- return (
211
- (/^(?:a|area|object|button)$/.test(tag) && attrName === 'tabindex') ||
212
- (tag === 'input' && (attrName === 'maxlength' || attrName === 'tabindex')) ||
213
- (tag === 'select' && (attrName === 'size' || attrName === 'tabindex')) ||
214
- (tag === 'textarea' && /^(?:rows|cols|tabindex)$/.test(attrName)) ||
215
- (tag === 'colgroup' && attrName === 'span') ||
216
- (tag === 'col' && attrName === 'span') ||
217
- ((tag === 'th' || tag === 'td') && (attrName === 'rowspan' || attrName === 'colspan'))
218
- );
231
+ const set = numberTypeAttributes.get(tag);
232
+ return set ? set.has(attrName) : false;
219
233
  }
220
234
 
221
235
  function isLinkType(tag, attrs, value) {
@@ -211,12 +211,15 @@ function collapseWhitespaceSmart(str, prevTag, nextTag, prevAttrs, nextAttrs, op
211
211
 
212
212
  // Collapse/trim whitespace for given tag
213
213
 
214
+ const noCollapseWsTags = new Set(['script', 'style', 'pre', 'textarea']);
215
+ const noTrimWsTags = new Set(['pre', 'textarea']);
216
+
214
217
  function canCollapseWhitespace(tag) {
215
- return !/^(?:script|style|pre|textarea)$/.test(tag);
218
+ return !noCollapseWsTags.has(tag);
216
219
  }
217
220
 
218
221
  function canTrimWhitespace(tag) {
219
- return !/^(?:pre|textarea)$/.test(tag);
222
+ return !noTrimWsTags.has(tag);
220
223
  }
221
224
 
222
225
  // Exports