html-minifier-next 5.1.0 → 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.
@@ -2,8 +2,6 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- var entities = require('entities');
6
-
7
5
  /*
8
6
  * HTML Parser By John Resig (ejohn.org)
9
7
  * Modified by Juriy “kangax” Zaytsev
@@ -1387,12 +1385,15 @@ function collapseWhitespaceSmart(str, prevTag, nextTag, prevAttrs, nextAttrs, op
1387
1385
 
1388
1386
  // Collapse/trim whitespace for given tag
1389
1387
 
1388
+ const noCollapseWsTags = new Set(['script', 'style', 'pre', 'textarea']);
1389
+ const noTrimWsTags = new Set(['pre', 'textarea']);
1390
+
1390
1391
  function canCollapseWhitespace(tag) {
1391
- return !/^(?:script|style|pre|textarea)$/.test(tag);
1392
+ return !noCollapseWsTags.has(tag);
1392
1393
  }
1393
1394
 
1394
1395
  function canTrimWhitespace(tag) {
1395
- return !/^(?:pre|textarea)$/.test(tag);
1396
+ return !noTrimWsTags.has(tag);
1396
1397
  }
1397
1398
 
1398
1399
  /**
@@ -2052,6 +2053,16 @@ const processOptions = (inputOptions, { getLightningCSS, getTerser, getSwc, getS
2052
2053
  // Imports
2053
2054
 
2054
2055
 
2056
+ // Lazy-load entities only when `decodeEntities` is enabled
2057
+
2058
+ let decodeHTMLStrictPromise;
2059
+ async function getDecodeHTMLStrict() {
2060
+ if (!decodeHTMLStrictPromise) {
2061
+ decodeHTMLStrictPromise = import('entities').then(m => m.decodeHTMLStrict);
2062
+ }
2063
+ return decodeHTMLStrictPromise;
2064
+ }
2065
+
2055
2066
  // Validators
2056
2067
 
2057
2068
  function isConditionalComment(text) {
@@ -2212,31 +2223,45 @@ function isBooleanAttribute(attrName, attrValue) {
2212
2223
  (attrValue === '' && emptyCollapsible.has(attrName));
2213
2224
  }
2214
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
+
2215
2243
  function isUriTypeAttribute(attrName, tag) {
2216
- return (
2217
- (/^(?:a|area|link|base)$/.test(tag) && attrName === 'href') ||
2218
- (tag === 'img' && /^(?:src|longdesc|usemap)$/.test(attrName)) ||
2219
- (tag === 'object' && /^(?:classid|codebase|data|usemap)$/.test(attrName)) ||
2220
- (tag === 'q' && attrName === 'cite') ||
2221
- (tag === 'blockquote' && attrName === 'cite') ||
2222
- ((tag === 'ins' || tag === 'del') && attrName === 'cite') ||
2223
- (tag === 'form' && attrName === 'action') ||
2224
- (tag === 'input' && (attrName === 'src' || attrName === 'usemap')) ||
2225
- (tag === 'head' && attrName === 'profile') ||
2226
- (tag === 'script' && (attrName === 'src' || attrName === 'for'))
2227
- );
2244
+ const set = uriTypeAttributes.get(tag);
2245
+ return set ? set.has(attrName) : false;
2228
2246
  }
2229
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
+
2230
2262
  function isNumberTypeAttribute(attrName, tag) {
2231
- return (
2232
- (/^(?:a|area|object|button)$/.test(tag) && attrName === 'tabindex') ||
2233
- (tag === 'input' && (attrName === 'maxlength' || attrName === 'tabindex')) ||
2234
- (tag === 'select' && (attrName === 'size' || attrName === 'tabindex')) ||
2235
- (tag === 'textarea' && /^(?:rows|cols|tabindex)$/.test(attrName)) ||
2236
- (tag === 'colgroup' && attrName === 'span') ||
2237
- (tag === 'col' && attrName === 'span') ||
2238
- ((tag === 'th' || tag === 'td') && (attrName === 'rowspan' || attrName === 'colspan'))
2239
- );
2263
+ const set = numberTypeAttributes.get(tag);
2264
+ return set ? set.has(attrName) : false;
2240
2265
  }
2241
2266
 
2242
2267
  function isLinkType(tag, attrs, value) {
@@ -2467,7 +2492,7 @@ async function normalizeAttr(attr, attrs, tag, options, minifyHTML) {
2467
2492
  if (options.decodeEntities && attrValue) {
2468
2493
  // Fast path: Only decode when entities are present
2469
2494
  if (attrValue.indexOf('&') !== -1) {
2470
- attrValue = entities.decodeHTMLStrict(attrValue);
2495
+ attrValue = (await getDecodeHTMLStrict())(attrValue);
2471
2496
  }
2472
2497
  }
2473
2498
 
@@ -2891,6 +2916,14 @@ async function getSvgo() {
2891
2916
  return svgoPromise;
2892
2917
  }
2893
2918
 
2919
+ let decodeHTMLPromise;
2920
+ async function getDecodeHTML() {
2921
+ if (!decodeHTMLPromise) {
2922
+ decodeHTMLPromise = import('entities').then(m => m.decodeHTML);
2923
+ }
2924
+ return decodeHTMLPromise;
2925
+ }
2926
+
2894
2927
  // Minification caches (initialized on first use with configurable sizes)
2895
2928
  let cssMinifyCache = null;
2896
2929
  let jsMinifyCache = null;
@@ -3675,6 +3708,7 @@ async function minifyHTML(value, options, partialMarkup) {
3675
3708
  let currentAttrs = [];
3676
3709
  const stackNoTrimWhitespace = [];
3677
3710
  const stackNoCollapseWhitespace = [];
3711
+ let preTextareaDepth = 0; // Count of `pre`/`textarea` entries in `stackNoTrimWhitespace`
3678
3712
  let optionalStartTag = '';
3679
3713
  let optionalEndTag = '';
3680
3714
  let optionalEndTagEmitted = false;
@@ -3835,7 +3869,7 @@ async function minifyHTML(value, options, partialMarkup) {
3835
3869
  let charsIndex = buffer.length - 1;
3836
3870
  if (buffer.length > 1) {
3837
3871
  const item = buffer[buffer.length - 1];
3838
- if (/^(?:<!|$)/.test(item) && item.indexOf(uidIgnore) === -1) {
3872
+ if (/^(?:<!|$)/.test(item) && (!uidIgnore || item.indexOf(uidIgnore) === -1)) {
3839
3873
  charsIndex--;
3840
3874
  }
3841
3875
  }
@@ -3929,6 +3963,7 @@ async function minifyHTML(value, options, partialMarkup) {
3929
3963
  if (!unary) {
3930
3964
  if (!canTrimWhitespace$1(tag, attrs) || stackNoTrimWhitespace.length) {
3931
3965
  stackNoTrimWhitespace.push(tag);
3966
+ if (tag === 'pre' || tag === 'textarea') preTextareaDepth++;
3932
3967
  }
3933
3968
  if (!canCollapseWhitespace$1(tag, attrs) || stackNoCollapseWhitespace.length) {
3934
3969
  stackNoCollapseWhitespace.push(tag);
@@ -3958,11 +3993,14 @@ async function minifyHTML(value, options, partialMarkup) {
3958
3993
  options.sortAttributes(tag, attrs);
3959
3994
  }
3960
3995
 
3996
+ const normalizedAttrs = await Promise.all(
3997
+ attrs.map(attr => normalizeAttr(attr, attrs, tag, options, minifyHTML))
3998
+ );
3961
3999
  const parts = [];
3962
- for (let i = attrs.length, isLast = true; --i >= 0;) {
3963
- const normalized = await normalizeAttr(attrs[i], attrs, tag, options, minifyHTML);
3964
- if (normalized) {
3965
- 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));
3966
4004
  isLast = false;
3967
4005
  }
3968
4006
  }
@@ -3998,6 +4036,7 @@ async function minifyHTML(value, options, partialMarkup) {
3998
4036
  if (options.collapseWhitespace) {
3999
4037
  if (stackNoTrimWhitespace.length) {
4000
4038
  if (tag === stackNoTrimWhitespace[stackNoTrimWhitespace.length - 1]) {
4039
+ if (tag === 'pre' || tag === 'textarea') preTextareaDepth--;
4001
4040
  stackNoTrimWhitespace.pop();
4002
4041
  }
4003
4042
  } else {
@@ -4091,7 +4130,7 @@ async function minifyHTML(value, options, partialMarkup) {
4091
4130
  nextAttrs = nextAttrs || [];
4092
4131
  if (options.decodeEntities && text && !specialContentElements.has(currentTag)) {
4093
4132
  if (text.indexOf('&') !== -1) {
4094
- text = entities.decodeHTML(text);
4133
+ text = (await getDecodeHTML())(text);
4095
4134
  }
4096
4135
  }
4097
4136
  // Trim outermost newline-based whitespace inside `pre`/`textarea` elements
@@ -4099,7 +4138,7 @@ async function minifyHTML(value, options, partialMarkup) {
4099
4138
  // Only trims single trailing newlines (multiple newlines are likely intentional formatting)
4100
4139
  if (options.collapseWhitespace && stackNoTrimWhitespace.length) {
4101
4140
  const topTag = stackNoTrimWhitespace[stackNoTrimWhitespace.length - 1];
4102
- if (stackNoTrimWhitespace.includes('pre') || stackNoTrimWhitespace.includes('textarea')) {
4141
+ if (preTextareaDepth > 0) {
4103
4142
  // Trim trailing whitespace only if it ends with a single newline (not multiple)
4104
4143
  // Multiple newlines are likely intentional formatting, single newline is often a template artifact
4105
4144
  // Treat CRLF (`\r\n`), CR (`\r`), and LF (`\n`) as single line-ending units
@@ -4112,7 +4151,7 @@ async function minifyHTML(value, options, partialMarkup) {
4112
4151
  if (!stackNoTrimWhitespace.length) {
4113
4152
  if (prevTag === 'comment') {
4114
4153
  const prevComment = buffer[buffer.length - 1];
4115
- if (prevComment.indexOf(uidIgnore) === -1) {
4154
+ if (!uidIgnore || prevComment.indexOf(uidIgnore) === -1) {
4116
4155
  if (!prevComment) {
4117
4156
  prevTag = charsPrevTag;
4118
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":"AA0BA,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
@@ -14,12 +14,12 @@
14
14
  "description": "Super-configurable and well-tested web page minifier (enhanced successor of HTML Minifier)",
15
15
  "devDependencies": {
16
16
  "@commitlint/cli": "^20.3.1",
17
- "@eslint/js": "^9.39.2",
17
+ "@eslint/js": "^10.0.1",
18
18
  "@rollup/plugin-commonjs": "^29.0.0",
19
19
  "@rollup/plugin-json": "^6.1.0",
20
20
  "@rollup/plugin-node-resolve": "^16.0.3",
21
21
  "@swc/core": "^1.15.11",
22
- "eslint": "^9.39.2",
22
+ "eslint": "^10.0.0",
23
23
  "rollup": "^4.57.1",
24
24
  "rollup-plugin-polyfill-node": "^0.13.0",
25
25
  "typescript": "^5.9.3",
@@ -31,12 +31,12 @@
31
31
  "import": "./src/htmlminifier.js",
32
32
  "require": "./dist/htmlminifier.cjs"
33
33
  },
34
- "./dist/*": "./dist/*.js",
35
34
  "./package.json": "./package.json"
36
35
  },
37
36
  "files": [
38
37
  "cli.js",
39
- "dist/",
38
+ "dist/htmlminifier.cjs",
39
+ "dist/types/",
40
40
  "src/"
41
41
  ],
42
42
  "funding": "https://github.com/j9t/html-minifier-next?sponsor=1",
@@ -95,5 +95,5 @@
95
95
  },
96
96
  "type": "module",
97
97
  "types": "./dist/types/htmlminifier.d.ts",
98
- "version": "5.1.0"
99
- }
98
+ "version": "5.1.2"
99
+ }
@@ -1,6 +1,5 @@
1
1
  // Imports
2
2
 
3
- import { decodeHTML } from 'entities';
4
3
  import { HTMLParser, endTag } from './htmlparser.js';
5
4
  import TokenChain from './tokenchain.js';
6
5
  import { presets, getPreset, getPresetNames } from './presets.js';
@@ -100,6 +99,14 @@ async function getSvgo() {
100
99
  return svgoPromise;
101
100
  }
102
101
 
102
+ let decodeHTMLPromise;
103
+ async function getDecodeHTML() {
104
+ if (!decodeHTMLPromise) {
105
+ decodeHTMLPromise = import('entities').then(m => m.decodeHTML);
106
+ }
107
+ return decodeHTMLPromise;
108
+ }
109
+
103
110
  // Minification caches (initialized on first use with configurable sizes)
104
111
  let cssMinifyCache = null;
105
112
  let jsMinifyCache = null;
@@ -888,6 +895,7 @@ async function minifyHTML(value, options, partialMarkup) {
888
895
  let currentAttrs = [];
889
896
  const stackNoTrimWhitespace = [];
890
897
  const stackNoCollapseWhitespace = [];
898
+ let preTextareaDepth = 0; // Count of `pre`/`textarea` entries in `stackNoTrimWhitespace`
891
899
  let optionalStartTag = '';
892
900
  let optionalEndTag = '';
893
901
  let optionalEndTagEmitted = false;
@@ -1048,7 +1056,7 @@ async function minifyHTML(value, options, partialMarkup) {
1048
1056
  let charsIndex = buffer.length - 1;
1049
1057
  if (buffer.length > 1) {
1050
1058
  const item = buffer[buffer.length - 1];
1051
- if (/^(?:<!|$)/.test(item) && item.indexOf(uidIgnore) === -1) {
1059
+ if (/^(?:<!|$)/.test(item) && (!uidIgnore || item.indexOf(uidIgnore) === -1)) {
1052
1060
  charsIndex--;
1053
1061
  }
1054
1062
  }
@@ -1142,6 +1150,7 @@ async function minifyHTML(value, options, partialMarkup) {
1142
1150
  if (!unary) {
1143
1151
  if (!canTrimWhitespace(tag, attrs) || stackNoTrimWhitespace.length) {
1144
1152
  stackNoTrimWhitespace.push(tag);
1153
+ if (tag === 'pre' || tag === 'textarea') preTextareaDepth++;
1145
1154
  }
1146
1155
  if (!canCollapseWhitespace(tag, attrs) || stackNoCollapseWhitespace.length) {
1147
1156
  stackNoCollapseWhitespace.push(tag);
@@ -1171,11 +1180,14 @@ async function minifyHTML(value, options, partialMarkup) {
1171
1180
  options.sortAttributes(tag, attrs);
1172
1181
  }
1173
1182
 
1183
+ const normalizedAttrs = await Promise.all(
1184
+ attrs.map(attr => normalizeAttr(attr, attrs, tag, options, minifyHTML))
1185
+ );
1174
1186
  const parts = [];
1175
- for (let i = attrs.length, isLast = true; --i >= 0;) {
1176
- const normalized = await normalizeAttr(attrs[i], attrs, tag, options, minifyHTML);
1177
- if (normalized) {
1178
- 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));
1179
1191
  isLast = false;
1180
1192
  }
1181
1193
  }
@@ -1211,6 +1223,7 @@ async function minifyHTML(value, options, partialMarkup) {
1211
1223
  if (options.collapseWhitespace) {
1212
1224
  if (stackNoTrimWhitespace.length) {
1213
1225
  if (tag === stackNoTrimWhitespace[stackNoTrimWhitespace.length - 1]) {
1226
+ if (tag === 'pre' || tag === 'textarea') preTextareaDepth--;
1214
1227
  stackNoTrimWhitespace.pop();
1215
1228
  }
1216
1229
  } else {
@@ -1304,7 +1317,7 @@ async function minifyHTML(value, options, partialMarkup) {
1304
1317
  nextAttrs = nextAttrs || [];
1305
1318
  if (options.decodeEntities && text && !specialContentElements.has(currentTag)) {
1306
1319
  if (text.indexOf('&') !== -1) {
1307
- text = decodeHTML(text);
1320
+ text = (await getDecodeHTML())(text);
1308
1321
  }
1309
1322
  }
1310
1323
  // Trim outermost newline-based whitespace inside `pre`/`textarea` elements
@@ -1312,7 +1325,7 @@ async function minifyHTML(value, options, partialMarkup) {
1312
1325
  // Only trims single trailing newlines (multiple newlines are likely intentional formatting)
1313
1326
  if (options.collapseWhitespace && stackNoTrimWhitespace.length) {
1314
1327
  const topTag = stackNoTrimWhitespace[stackNoTrimWhitespace.length - 1];
1315
- if (stackNoTrimWhitespace.includes('pre') || stackNoTrimWhitespace.includes('textarea')) {
1328
+ if (preTextareaDepth > 0) {
1316
1329
  // Trim trailing whitespace only if it ends with a single newline (not multiple)
1317
1330
  // Multiple newlines are likely intentional formatting, single newline is often a template artifact
1318
1331
  // Treat CRLF (`\r\n`), CR (`\r`), and LF (`\n`) as single line-ending units
@@ -1325,7 +1338,7 @@ async function minifyHTML(value, options, partialMarkup) {
1325
1338
  if (!stackNoTrimWhitespace.length) {
1326
1339
  if (prevTag === 'comment') {
1327
1340
  const prevComment = buffer[buffer.length - 1];
1328
- if (prevComment.indexOf(uidIgnore) === -1) {
1341
+ if (!uidIgnore || prevComment.indexOf(uidIgnore) === -1) {
1329
1342
  if (!prevComment) {
1330
1343
  prevTag = charsPrevTag;
1331
1344
  }
@@ -1,6 +1,5 @@
1
1
  // Imports
2
2
 
3
- import { decodeHTMLStrict } from 'entities';
4
3
  import {
5
4
  RE_CONDITIONAL_COMMENT,
6
5
  RE_EVENT_ATTR_DEFAULT,
@@ -22,6 +21,16 @@ import {
22
21
  import { trimWhitespace, collapseWhitespaceAll } from './whitespace.js';
23
22
  import { shouldMinifyInnerHTML } from './options.js';
24
23
 
24
+ // Lazy-load entities only when `decodeEntities` is enabled
25
+
26
+ let decodeHTMLStrictPromise;
27
+ async function getDecodeHTMLStrict() {
28
+ if (!decodeHTMLStrictPromise) {
29
+ decodeHTMLStrictPromise = import('entities').then(m => m.decodeHTMLStrict);
30
+ }
31
+ return decodeHTMLStrictPromise;
32
+ }
33
+
25
34
  // Validators
26
35
 
27
36
  function isConditionalComment(text) {
@@ -182,31 +191,45 @@ function isBooleanAttribute(attrName, attrValue) {
182
191
  (attrValue === '' && emptyCollapsible.has(attrName));
183
192
  }
184
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
+
185
211
  function isUriTypeAttribute(attrName, tag) {
186
- return (
187
- (/^(?:a|area|link|base)$/.test(tag) && attrName === 'href') ||
188
- (tag === 'img' && /^(?:src|longdesc|usemap)$/.test(attrName)) ||
189
- (tag === 'object' && /^(?:classid|codebase|data|usemap)$/.test(attrName)) ||
190
- (tag === 'q' && attrName === 'cite') ||
191
- (tag === 'blockquote' && attrName === 'cite') ||
192
- ((tag === 'ins' || tag === 'del') && attrName === 'cite') ||
193
- (tag === 'form' && attrName === 'action') ||
194
- (tag === 'input' && (attrName === 'src' || attrName === 'usemap')) ||
195
- (tag === 'head' && attrName === 'profile') ||
196
- (tag === 'script' && (attrName === 'src' || attrName === 'for'))
197
- );
212
+ const set = uriTypeAttributes.get(tag);
213
+ return set ? set.has(attrName) : false;
198
214
  }
199
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
+
200
230
  function isNumberTypeAttribute(attrName, tag) {
201
- return (
202
- (/^(?:a|area|object|button)$/.test(tag) && attrName === 'tabindex') ||
203
- (tag === 'input' && (attrName === 'maxlength' || attrName === 'tabindex')) ||
204
- (tag === 'select' && (attrName === 'size' || attrName === 'tabindex')) ||
205
- (tag === 'textarea' && /^(?:rows|cols|tabindex)$/.test(attrName)) ||
206
- (tag === 'colgroup' && attrName === 'span') ||
207
- (tag === 'col' && attrName === 'span') ||
208
- ((tag === 'th' || tag === 'td') && (attrName === 'rowspan' || attrName === 'colspan'))
209
- );
231
+ const set = numberTypeAttributes.get(tag);
232
+ return set ? set.has(attrName) : false;
210
233
  }
211
234
 
212
235
  function isLinkType(tag, attrs, value) {
@@ -437,7 +460,7 @@ async function normalizeAttr(attr, attrs, tag, options, minifyHTML) {
437
460
  if (options.decodeEntities && attrValue) {
438
461
  // Fast path: Only decode when entities are present
439
462
  if (attrValue.indexOf('&') !== -1) {
440
- attrValue = decodeHTMLStrict(attrValue);
463
+ attrValue = (await getDecodeHTMLStrict())(attrValue);
441
464
  }
442
465
  }
443
466
 
@@ -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