@merkur/plugin-css-scrambler
Version:
Merkur plugin for scrambling CSS classes.
95 lines (88 loc) • 2.97 kB
JavaScript
;
var core = require('@merkur/core');
var classnames = require('classnames');
const CLASSNAME_CHARS = ('abcdefghijklmnopqrstuvwxyz' + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + '-').split('');
const EXTENDED_CLASSNAME_CHARS = (CLASSNAME_CHARS.join('') + '0123456789').split('');
function numberToCssClass(number) {
if (number < CLASSNAME_CHARS.length) {
return CLASSNAME_CHARS[number];
}
// we have to "shift" the number to adjust for the gap between base53 and
// base64 encoding
number += EXTENDED_CLASSNAME_CHARS.length - CLASSNAME_CHARS.length;
let className = '';
while (number >= CLASSNAME_CHARS.length) {
className = EXTENDED_CLASSNAME_CHARS[number % EXTENDED_CLASSNAME_CHARS.length] + className;
number = Math.floor(number / EXTENDED_CLASSNAME_CHARS.length);
}
if (number) {
className = CLASSNAME_CHARS[number - 1] + className;
} else {
className = '_' + className;
}
return className;
}
/**
* @param {Array<Array<string>, Array<string>>=} hashingTable
* @return {function(...?(string|Object<string, boolean>))}
*/
function scramblerFactory(hashingTable) {
let uniqueHash, prefixes, mainParts;
const prefixTable = hashingTable && new Map();
const mainPartTable = hashingTable && new Map();
if (hashingTable) {
[uniqueHash, prefixes, mainParts] = hashingTable;
for (let i = 0; i < prefixes.length; i++) {
prefixTable.set(prefixes[i], numberToCssClass(i));
}
for (let i = 0; i < mainParts.length; i++) {
mainPartTable.set(mainParts[i], numberToCssClass(i));
}
}
return (widget, ...args) => {
const classNamesSource = classnames(...args);
if (!hashingTable) {
return {
className: classNamesSource
};
}
const processedClasses = classNamesSource.split(/\s+/).map(className => {
const parts = className.split('-');
const prefix = parts[0];
const mainPart = parts.slice(1).join('-');
if (!prefixTable.has(prefix) || !mainPartTable.has(mainPart)) {
return className;
}
return `${prefixTable.get(prefix)}_${mainPartTable.get(mainPart)}_${uniqueHash}`;
}).join(' ');
return {
className: processedClasses,
'data-class': classNamesSource
};
};
}
function cssScramblePlugin() {
return {
async setup(widget, widgetDefinition) {
const {
classnameHashtable
} = widgetDefinition;
widget.$in.classNameScrambler = scramblerFactory(classnameHashtable);
widget.cn = (...args) => widget.$in.classNameScrambler(...args).className;
return widget;
},
async create(widget) {
core.hookMethod(widget, 'info', infoHook);
return widget;
}
};
}
async function infoHook(widget, originalInfo, ...rest) {
const result = await originalInfo(...rest);
return {
classnameHashtable: widget.classnameHashtable,
...result
};
}
exports.cssScramblePlugin = cssScramblePlugin;
exports.numberToCssClass = numberToCssClass;