Skip to content

Commit e44c426

Browse files
sibiraj-sDanielRuf
andauthored
Replace map utils with Set (#114)
Co-authored-by: Daniel Ruf <827205+DanielRuf@users.noreply.github.com>
1 parent ea7ead7 commit e44c426

File tree

3 files changed

+77
-95
lines changed

3 files changed

+77
-95
lines changed

src/htmlminifier.js

Lines changed: 59 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { minify as terser } from 'terser';
55

66
import { HTMLParser, endTag } from './htmlparser.js';
77
import TokenChain from './tokenchain.js';
8-
import { createMapFromString, createMap, replaceAsync } from './utils.js';
8+
import { replaceAsync } from './utils.js';
99

1010
function trimWhitespace(str) {
1111
return str && str.replace(/^[ \n\r\t\f]+/, '').replace(/[ \n\r\t\f]+$/, '');
@@ -62,20 +62,20 @@ function collapseWhitespace(str, options, trimLeft, trimRight, collapseAll) {
6262
}
6363

6464
// non-empty tags that will maintain whitespace around them
65-
const inlineTags = createMapFromString('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');
65+
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']);
6666
// non-empty tags that will maintain whitespace within them
67-
const inlineTextTags = createMapFromString('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');
67+
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']);
6868
// self-closing tags that will maintain whitespace around them
69-
const selfClosingInlineTags = createMapFromString('comment,img,input,wbr');
69+
const selfClosingInlineTags = new Set(['comment', 'img', 'input', 'wbr']);
7070

7171
function collapseWhitespaceSmart(str, prevTag, nextTag, options) {
72-
let trimLeft = prevTag && !selfClosingInlineTags(prevTag);
72+
let trimLeft = prevTag && !selfClosingInlineTags.has(prevTag);
7373
if (trimLeft && !options.collapseInlineTagWhitespace) {
74-
trimLeft = prevTag.charAt(0) === '/' ? !inlineTags(prevTag.slice(1)) : !inlineTextTags(prevTag);
74+
trimLeft = prevTag.charAt(0) === '/' ? !inlineTags.has(prevTag.slice(1)) : !inlineTextTags.has(prevTag);
7575
}
76-
let trimRight = nextTag && !selfClosingInlineTags(nextTag);
76+
let trimRight = nextTag && !selfClosingInlineTags.has(nextTag);
7777
if (trimRight && !options.collapseInlineTagWhitespace) {
78-
trimRight = nextTag.charAt(0) === '/' ? !inlineTextTags(nextTag.slice(1)) : !inlineTags(nextTag);
78+
trimRight = nextTag.charAt(0) === '/' ? !inlineTextTags.has(nextTag.slice(1)) : !inlineTags.has(nextTag);
7979
}
8080
return collapseWhitespace(str, options, trimLeft, trimRight, prevTag && nextTag);
8181
}
@@ -152,7 +152,7 @@ function isAttributeRedundant(tag, attrName, attrValue, attrs) {
152152

153153
// https://mathiasbynens.be/demo/javascript-mime-type
154154
// https://developer.mozilla.org/en/docs/Web/HTML/Element/script#attr-type
155-
const executableScriptsMimetypes = createMap([
155+
const executableScriptsMimetypes = new Set([
156156
'text/javascript',
157157
'text/ecmascript',
158158
'text/jscript',
@@ -162,18 +162,18 @@ const executableScriptsMimetypes = createMap([
162162
'module'
163163
]);
164164

165-
const keepScriptsMimetypes = createMap([
165+
const keepScriptsMimetypes = new Set([
166166
'module'
167167
]);
168168

169169
function isScriptTypeAttribute(attrValue) {
170170
attrValue = trimWhitespace(attrValue.split(/;/, 2)[0]).toLowerCase();
171-
return attrValue === '' || executableScriptsMimetypes(attrValue);
171+
return attrValue === '' || executableScriptsMimetypes.has(attrValue);
172172
}
173173

174174
function keepScriptTypeAttribute(attrValue) {
175175
attrValue = trimWhitespace(attrValue.split(/;/, 2)[0]).toLowerCase();
176-
return keepScriptsMimetypes(attrValue);
176+
return keepScriptsMimetypes.has(attrValue);
177177
}
178178

179179
function isExecutableScript(tag, attrs) {
@@ -207,11 +207,11 @@ function isStyleSheet(tag, attrs) {
207207
return true;
208208
}
209209

210-
const isSimpleBoolean = createMapFromString('allowfullscreen,async,autofocus,autoplay,checked,compact,controls,declare,default,defaultchecked,defaultmuted,defaultselected,defer,disabled,enabled,formnovalidate,hidden,indeterminate,inert,ismap,itemscope,loop,multiple,muted,nohref,noresize,noshade,novalidate,nowrap,open,pauseonexit,readonly,required,reversed,scoped,seamless,selected,sortable,truespeed,typemustmatch,visible');
211-
const isBooleanValue = createMapFromString('true,false');
210+
const isSimpleBoolean = new Set(['allowfullscreen', 'async', 'autofocus', 'autoplay', 'checked', 'compact', 'controls', 'declare', 'default', 'defaultchecked', 'defaultmuted', 'defaultselected', 'defer', 'disabled', 'enabled', 'formnovalidate', 'hidden', 'indeterminate', 'inert', 'ismap', 'itemscope', 'loop', 'multiple', 'muted', 'nohref', 'noresize', 'noshade', 'novalidate', 'nowrap', 'open', 'pauseonexit', 'readonly', 'required', 'reversed', 'scoped', 'seamless', 'selected', 'sortable', 'truespeed', 'typemustmatch', 'visible']);
211+
const isBooleanValue = new Set(['true', 'false']);
212212

213213
function isBooleanAttribute(attrName, attrValue) {
214-
return isSimpleBoolean(attrName) || (attrName === 'draggable' && !isBooleanValue(attrValue));
214+
return isSimpleBoolean.has(attrName) || (attrName === 'draggable' && !isBooleanValue.has(attrValue));
215215
}
216216

217217
function isUriTypeAttribute(attrName, tag) {
@@ -256,10 +256,10 @@ function isMediaQuery(tag, attrs, attrName) {
256256
return attrName === 'media' && (isLinkType(tag, attrs, 'stylesheet') || isStyleSheet(tag, attrs));
257257
}
258258

259-
const srcsetTags = createMapFromString('img,source');
259+
const srcsetTags = new Set(['img', 'source']);
260260

261261
function isSrcset(attrName, tag) {
262-
return attrName === 'srcset' && srcsetTags(tag);
262+
return attrName === 'srcset' && srcsetTags.has(tag);
263263
}
264264

265265
async function cleanAttributeValue(tag, attrName, attrValue, options, attrs) {
@@ -399,31 +399,31 @@ async function processScript(text, options, currentAttrs) {
399399
// - retain <body> if followed by <noscript>
400400
// - </rb>, </rt>, </rtc>, </rp> & </tfoot> follow https://www.w3.org/TR/html5/syntax.html#optional-tags
401401
// - retain all tags which are adjacent to non-standard HTML tags
402-
const optionalStartTags = createMapFromString('html,head,body,colgroup,tbody');
403-
const optionalEndTags = createMapFromString('html,head,body,li,dt,dd,p,rb,rt,rtc,rp,optgroup,option,colgroup,caption,thead,tbody,tfoot,tr,td,th');
404-
const headerTags = createMapFromString('meta,link,script,style,template,noscript');
405-
const descriptionTags = createMapFromString('dt,dd');
406-
const pBlockTags = createMapFromString('address,article,aside,blockquote,details,div,dl,fieldset,figcaption,figure,footer,form,h1,h2,h3,h4,h5,h6,header,hgroup,hr,main,menu,nav,ol,p,pre,section,table,ul');
407-
const pInlineTags = createMapFromString('a,audio,del,ins,map,noscript,video');
408-
const rubyTags = createMapFromString('rb,rt,rtc,rp');
409-
const rtcTag = createMapFromString('rb,rtc,rp');
410-
const optionTag = createMapFromString('option,optgroup');
411-
const tableContentTags = createMapFromString('tbody,tfoot');
412-
const tableSectionTags = createMapFromString('thead,tbody,tfoot');
413-
const cellTags = createMapFromString('td,th');
414-
const topLevelTags = createMapFromString('html,head,body');
415-
const compactTags = createMapFromString('html,body');
416-
const looseTags = createMapFromString('head,colgroup,caption');
417-
const trailingTags = createMapFromString('dt,thead');
418-
const htmlTags = createMapFromString('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');
402+
const optionalStartTags = new Set(['html', 'head', 'body', 'colgroup', 'tbody']);
403+
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']);
404+
const headerTags = new Set(['meta', 'link', 'script', 'style', 'template', 'noscript']);
405+
const descriptionTags = new Set(['dt', 'dd']);
406+
const pBlockTags = new Set(['address', 'article', 'aside', 'blockquote', 'details', 'div', 'dl', 'fieldset', 'figcaption', 'figure', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'hgroup', 'hr', 'main', 'menu', 'nav', 'ol', 'p', 'pre', 'section', 'table', 'ul']);
407+
const pInlineTags = new Set(['a', 'audio', 'del', 'ins', 'map', 'noscript', 'video']);
408+
const rubyTags = new Set(['rb', 'rt', 'rtc', 'rp']);
409+
const rtcTag = new Set(['rb', 'rtc', 'rp']);
410+
const optionTag = new Set(['option', 'optgroup']);
411+
const tableContentTags = new Set(['tbody', 'tfoot']);
412+
const tableSectionTags = new Set(['thead', 'tbody', 'tfoot']);
413+
const cellTags = new Set(['td', 'th']);
414+
const topLevelTags = new Set(['html', 'head', 'body']);
415+
const compactTags = new Set(['html', 'body']);
416+
const looseTags = new Set(['head', 'colgroup', 'caption']);
417+
const trailingTags = new Set(['dt', 'thead']);
418+
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']);
419419

420420
function canRemoveParentTag(optionalStartTag, tag) {
421421
switch (optionalStartTag) {
422422
case 'html':
423423
case 'head':
424424
return true;
425425
case 'body':
426-
return !headerTags(tag);
426+
return !headerTags.has(tag);
427427
case 'colgroup':
428428
return tag === 'col';
429429
case 'tbody':
@@ -437,7 +437,7 @@ function isStartTagMandatory(optionalEndTag, tag) {
437437
case 'colgroup':
438438
return optionalEndTag === 'colgroup';
439439
case 'tbody':
440-
return tableSectionTags(optionalEndTag);
440+
return tableSectionTags.has(optionalEndTag);
441441
}
442442
return false;
443443
}
@@ -456,25 +456,25 @@ function canRemovePrecedingTag(optionalEndTag, tag) {
456456
return tag === optionalEndTag;
457457
case 'dt':
458458
case 'dd':
459-
return descriptionTags(tag);
459+
return descriptionTags.has(tag);
460460
case 'p':
461-
return pBlockTags(tag);
461+
return pBlockTags.has(tag);
462462
case 'rb':
463463
case 'rt':
464464
case 'rp':
465-
return rubyTags(tag);
465+
return rubyTags.has(tag);
466466
case 'rtc':
467-
return rtcTag(tag);
467+
return rtcTag.has(tag);
468468
case 'option':
469-
return optionTag(tag);
469+
return optionTag.has(tag);
470470
case 'thead':
471471
case 'tbody':
472-
return tableContentTags(tag);
472+
return tableContentTags.has(tag);
473473
case 'tfoot':
474474
return tag === 'tbody';
475475
case 'td':
476476
case 'th':
477-
return cellTags(tag);
477+
return cellTags.has(tag);
478478
}
479479
return false;
480480
}
@@ -744,7 +744,7 @@ function uniqueId(value) {
744744
return id;
745745
}
746746

747-
const specialContentTags = createMapFromString('script,style');
747+
const specialContentTags = new Set(['script', 'style']);
748748

749749
async function createSortFns(value, options, uidIgnore, uidAttr) {
750750
const attrChains = options.sortAttributes && Object.create(null);
@@ -788,7 +788,7 @@ async function createSortFns(value, options, uidIgnore, uidAttr) {
788788
currentTag = '';
789789
},
790790
chars: async function (text) {
791-
if (options.processScripts && specialContentTags(currentTag) &&
791+
if (options.processScripts && specialContentTags.has(currentTag) &&
792792
options.processScripts.indexOf(currentType) > -1) {
793793
await scan(text);
794794
}
@@ -996,15 +996,15 @@ async function minifyHTML(value, options, partialMarkup) {
996996
tag = options.name(tag);
997997
currentTag = tag;
998998
charsPrevTag = tag;
999-
if (!inlineTextTags(tag)) {
999+
if (!inlineTextTags.has(tag)) {
10001000
currentChars = '';
10011001
}
10021002
hasChars = false;
10031003
currentAttrs = attrs;
10041004

10051005
let optional = options.removeOptionalTags;
10061006
if (optional) {
1007-
const htmlTag = htmlTags(tag);
1007+
const htmlTag = htmlTags.has(tag);
10081008
// <html> may be omitted if first thing inside is not comment
10091009
// <head> may be omitted if first thing inside is an element
10101010
// <body> may be omitted if first thing inside is not space, comment, <meta>, <link>, <script>, <style> or <template>
@@ -1059,7 +1059,7 @@ async function minifyHTML(value, options, partialMarkup) {
10591059
if (parts.length > 0) {
10601060
buffer.push(' ');
10611061
buffer.push.apply(buffer, parts);
1062-
} else if (optional && optionalStartTags(tag)) {
1062+
} else if (optional && optionalStartTags.has(tag)) {
10631063
// start tag must never be omitted if it has any attributes
10641064
optionalStartTag = tag;
10651065
}
@@ -1100,18 +1100,18 @@ async function minifyHTML(value, options, partialMarkup) {
11001100

11011101
if (options.removeOptionalTags) {
11021102
// <html>, <head> or <body> may be omitted if the element is empty
1103-
if (isElementEmpty && topLevelTags(optionalStartTag)) {
1103+
if (isElementEmpty && topLevelTags.has(optionalStartTag)) {
11041104
removeStartTag();
11051105
}
11061106
optionalStartTag = '';
11071107
// </html> or </body> may be omitted if not followed by comment
11081108
// </head> may be omitted if not followed by space or comment
11091109
// </p> may be omitted if no more content in non-</a> parent
11101110
// except for </dt> or </thead>, end tags may be omitted if no more content in parent element
1111-
if (htmlTags(tag) && optionalEndTag && !trailingTags(optionalEndTag) && (optionalEndTag !== 'p' || !pInlineTags(tag))) {
1111+
if (htmlTags.has(tag) && optionalEndTag && !trailingTags.has(optionalEndTag) && (optionalEndTag !== 'p' || !pInlineTags.has(tag))) {
11121112
removeEndTag();
11131113
}
1114-
optionalEndTag = optionalEndTags(tag) ? tag : '';
1114+
optionalEndTag = optionalEndTags.has(tag) ? tag : '';
11151115
}
11161116

11171117
if (options.removeEmptyElements && isElementEmpty && canRemoveElement(tag, attrs)) {
@@ -1126,7 +1126,7 @@ async function minifyHTML(value, options, partialMarkup) {
11261126
buffer.push('</' + tag + '>');
11271127
}
11281128
charsPrevTag = '/' + tag;
1129-
if (!inlineTags(tag)) {
1129+
if (!inlineTags.has(tag)) {
11301130
currentChars = '';
11311131
} else if (isElementEmpty) {
11321132
currentChars += '|';
@@ -1136,7 +1136,7 @@ async function minifyHTML(value, options, partialMarkup) {
11361136
chars: async function (text, prevTag, nextTag) {
11371137
prevTag = prevTag === '' ? 'comment' : prevTag;
11381138
nextTag = nextTag === '' ? 'comment' : nextTag;
1139-
if (options.decodeEntities && text && !specialContentTags(currentTag)) {
1139+
if (options.decodeEntities && text && !specialContentTags.has(currentTag)) {
11401140
text = decodeHTML(text);
11411141
}
11421142
if (options.collapseWhitespace) {
@@ -1165,7 +1165,7 @@ async function minifyHTML(value, options, partialMarkup) {
11651165
}
11661166
trimTrailingWhitespace(tagIndex - 1, 'br');
11671167
}
1168-
} else if (inlineTextTags(prevTag.charAt(0) === '/' ? prevTag.slice(1) : prevTag)) {
1168+
} else if (inlineTextTags.has(prevTag.charAt(0) === '/' ? prevTag.slice(1) : prevTag)) {
11691169
text = collapseWhitespace(text, options, /(?:^|\s)$/.test(currentChars));
11701170
}
11711171
}
@@ -1182,7 +1182,7 @@ async function minifyHTML(value, options, partialMarkup) {
11821182
text = collapseWhitespace(text, options, false, false, true);
11831183
}
11841184
}
1185-
if (options.processScripts && specialContentTags(currentTag)) {
1185+
if (options.processScripts && specialContentTags.has(currentTag)) {
11861186
text = await processScript(text, options, currentAttrs);
11871187
}
11881188
if (isExecutableScript(currentTag, currentAttrs)) {
@@ -1200,13 +1200,13 @@ async function minifyHTML(value, options, partialMarkup) {
12001200
optionalStartTag = '';
12011201
// </html> or </body> may be omitted if not followed by comment
12021202
// </head>, </colgroup> or </caption> may be omitted if not followed by space or comment
1203-
if (compactTags(optionalEndTag) || (looseTags(optionalEndTag) && !/^\s/.test(text))) {
1203+
if (compactTags.has(optionalEndTag) || (looseTags.has(optionalEndTag) && !/^\s/.test(text))) {
12041204
removeEndTag();
12051205
}
12061206
optionalEndTag = '';
12071207
}
12081208
charsPrevTag = /^\s*$/.test(text) ? prevTag : 'comment';
1209-
if (options.decodeEntities && text && !specialContentTags(currentTag)) {
1209+
if (options.decodeEntities && text && !specialContentTags.has(currentTag)) {
12101210
// Escape any `&` symbols that start either:
12111211
// 1) a legacy named character reference (i.e. one that doesn't end with `;`)
12121212
// 2) or any other character reference (i.e. one that does end with `;`)
@@ -1259,11 +1259,11 @@ async function minifyHTML(value, options, partialMarkup) {
12591259
if (options.removeOptionalTags) {
12601260
// <html> may be omitted if first thing inside is not comment
12611261
// <head> or <body> may be omitted if empty
1262-
if (topLevelTags(optionalStartTag)) {
1262+
if (topLevelTags.has(optionalStartTag)) {
12631263
removeStartTag();
12641264
}
12651265
// except for </dt> or </thead>, end tags may be omitted if no more content in parent element
1266-
if (optionalEndTag && !trailingTags(optionalEndTag)) {
1266+
if (optionalEndTag && !trailingTags.has(optionalEndTag)) {
12671267
removeEndTag();
12681268
}
12691269
}

0 commit comments

Comments
 (0)