htmlnano
Version:
Modular HTML minifier, built on top of the PostHTML
117 lines (113 loc) • 3.76 kB
JavaScript
Object.defineProperty(exports, '__esModule', { value: true });
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;
}
exports.default = mod;