htmlnano
Version:
Modular HTML minifier, built on top of the PostHTML
114 lines (108 loc) • 3.55 kB
JavaScript
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = collapseAttributeWhitespace;
var _collapseAttributeWhitespace = require("./collapseAttributeWhitespace.cjs");
// class, rel, ping
const validOptions = new Set(['frequency', 'alphabetical']);
const processModuleOptions = options => {
if (options === true) return 'alphabetical';
return validOptions.has(options) ? options : false;
};
class AttributeTokenChain {
constructor() {
this.freqData = new Map(); // <attrValue, frequency>[]
}
addFromNodeAttrsArray(attrValuesArray) {
attrValuesArray.forEach(attrValue => {
if (this.freqData.has(attrValue)) {
this.freqData.set(attrValue, this.freqData.get(attrValue) + 1);
} else {
this.freqData.set(attrValue, 1);
}
});
}
createSortOrder() {
let _sortOrder = [...this.freqData.entries()];
_sortOrder.sort((a, b) => b[1] - a[1]);
this.sortOrder = _sortOrder.map(i => i[0]);
}
sortFromNodeAttrsArray(attrValuesArray) {
const resultArray = [];
if (!this.sortOrder) {
this.createSortOrder();
}
this.sortOrder.forEach(k => {
if (attrValuesArray.includes(k)) {
resultArray.push(k);
}
});
return resultArray;
}
}
/** Sort values inside list-like attributes (e.g. class, rel) */
function collapseAttributeWhitespace(tree, options, moduleOptions) {
const sortType = processModuleOptions(moduleOptions);
if (sortType === 'alphabetical') {
return sortAttributesWithListsInAlphabeticalOrder(tree);
}
if (sortType === 'frequency') {
return sortAttributesWithListsByFrequency(tree);
}
// Invalid configuration
return tree;
}
function sortAttributesWithListsInAlphabeticalOrder(tree) {
tree.walk(node => {
if (!node.attrs) {
return node;
}
Object.keys(node.attrs).forEach(attrName => {
const attrNameLower = attrName.toLowerCase();
if (!_collapseAttributeWhitespace.attributesWithLists.has(attrNameLower)) {
return;
}
const attrValues = node.attrs[attrName].split(/\s/);
node.attrs[attrName] = attrValues.sort((a, b) => {
return typeof a.localeCompare === 'function' ? a.localeCompare(b) : a - b;
}).join(' ');
});
return node;
});
return tree;
}
function sortAttributesWithListsByFrequency(tree) {
const tokenChainObj = {}; // <attrNameLower: AttributeTokenChain>[]
// Traverse through tree to get frequency
tree.walk(node => {
if (!node.attrs) {
return node;
}
Object.entries(node.attrs).forEach(([attrName, attrValues]) => {
const attrNameLower = attrName.toLowerCase();
if (!_collapseAttributeWhitespace.attributesWithLists.has(attrNameLower)) {
return;
}
tokenChainObj[attrNameLower] = tokenChainObj[attrNameLower] || new AttributeTokenChain();
tokenChainObj[attrNameLower].addFromNodeAttrsArray(attrValues.split(/\s/));
});
return node;
});
// Traverse through tree again, this time sort the attribute values
tree.walk(node => {
if (!node.attrs) {
return node;
}
Object.entries(node.attrs).forEach(([attrName, attrValues]) => {
const attrNameLower = attrName.toLowerCase();
if (!_collapseAttributeWhitespace.attributesWithLists.has(attrNameLower)) {
return;
}
if (tokenChainObj[attrNameLower]) {
node.attrs[attrName] = tokenChainObj[attrNameLower].sortFromNodeAttrsArray(attrValues.split(/\s/)).join(' ');
}
});
return node;
});
}
;