UNPKG

htmlnano

Version:

Modular HTML minifier, built on top of the PostHTML

115 lines (112 loc) 3.7 kB
const validOptions = new Set([ 'frequency', 'alphabetical' ]); function resolveSortType(options) { if (options === true) return 'alphabetical'; if (options === false) return false; return validOptions.has(options) ? options : false; } class AttributeTokenChain { addFromNodeAttrs(nodeAttrs) { Object.keys(nodeAttrs).forEach((attrName)=>{ const attrNameLower = attrName.toLowerCase(); if (this.freqData.has(attrNameLower)) { this.freqData.set(attrNameLower, this.freqData.get(attrNameLower) + 1); } else { this.freqData.set(attrNameLower, 1); } }); } createSortOrder() { const _sortOrder = [ ...this.freqData.entries() ]; _sortOrder.sort((a, b)=>{ const freqDiff = b[1] - a[1]; if (freqDiff !== 0) return freqDiff; return a[0].localeCompare(b[0]); }); this.sortOrder = _sortOrder.map((i)=>i[0]); } sortFromNodeAttrs(nodeAttrs) { const newAttrs = {}; // Convert node.attrs attrName into lower case while preserving originals. const loweredNodeAttrs = {}; Object.entries(nodeAttrs).forEach(([attrName, attrValue])=>{ const attrNameLower = attrName.toLowerCase(); if (!loweredNodeAttrs[attrNameLower]) { loweredNodeAttrs[attrNameLower] = { name: attrName, value: attrValue }; } }); if (!this.sortOrder) { this.createSortOrder(); } const seen = new Set(); this.sortOrder.forEach((attrNameLower)=>{ // The attrName inside "sortOrder" has been lowered const originalAttr = loweredNodeAttrs[attrNameLower]; if (originalAttr != null) { newAttrs[originalAttr.name] = originalAttr.value; seen.add(attrNameLower); } }); Object.entries(loweredNodeAttrs).forEach(([attrNameLower, originalAttr])=>{ if (!seen.has(attrNameLower)) { newAttrs[originalAttr.name] = originalAttr.value; } }); return newAttrs; } constructor(){ /** <attr, frequency> */ this.freqData = new Map(); this.sortOrder = null; } } /** Sort attibutes */ const mod = { default (tree, options, moduleOptions) { const sortType = resolveSortType(moduleOptions); if (sortType === 'alphabetical') { return sortAttributesInAlphabeticalOrder(tree); } if (sortType === 'frequency') { return sortAttributesByFrequency(tree); } // Invalid configuration return tree; } }; function sortAttributesInAlphabeticalOrder(tree) { tree.walk((node)=>{ if (!node.attrs) { return node; } node.attrs = Object.fromEntries(Object.entries(node.attrs).sort(([attrA], [attrB])=>attrA.localeCompare(attrB))); return node; }); return tree; } function sortAttributesByFrequency(tree) { const tokenchain = new AttributeTokenChain(); // Traverse through tree to get frequency tree.walk((node)=>{ if (!node.attrs) { return node; } tokenchain.addFromNodeAttrs(node.attrs); return node; }); // Traverse through tree again, this time sort the attributes tree.walk((node)=>{ if (!node.attrs) { return node; } node.attrs = tokenchain.sortFromNodeAttrs(node.attrs); return node; }); return tree; } export { mod as default };