Skip to content

Instantly share code, notes, and snippets.

@iocat
Last active January 14, 2019 22:03
Show Gist options
  • Save iocat/314a9a093b0e621284642f4ad6c39907 to your computer and use it in GitHub Desktop.
Save iocat/314a9a093b0e621284642f4ad6c39907 to your computer and use it in GitHub Desktop.
Stateful variable-length CSS Modules' class name generator (using ECMAScript Generator Function)
// @flow
/**
* Copyright (c) 2019-present, Thanh Ngo.
*
* This source code is licensed under the license found in the
* LICENSE file in the root directory of this source tree.
*/
/** caches class names based on class file and class identifier*/
const CACHE /*: {[string]: string} */ = {}
/**
* Generator that generates valid CSS classes, deterministically.
*
* returns nothing starts with
* 1. hyphen follows by a digit
* 2. double hyphens
* 3. a digit
*/
const classNameGenerator = function*() {
// length is 1
const VALID_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
for(let i = 0; i < VALID_ALPHABET.length; i ++ ){
yield VALID_ALPHABET.charAt(i);
}
// length is now 2 or above
/** maps invalid css prefixes to true */
const EXCLUDING_PREFIXES = ['--', '-0', '-1','-2', '-3', '-4','-5','-6', '-7', '-8', '-9', '0','1','2','3','4','5','6','7','8','9']
.reduce((acc, next) => ({...acc, [next]:true}), {});
/** list of valid css characters in the identifier */
const VALID_SUB_CHARS = '-_0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
/** validates the first two CSS class name characters */
const fstTwoIndsAreValid = (fstInd/*:number*/, sndInd/*: number*/) => {
const fstChar /*: string*/ = VALID_SUB_CHARS.charAt(fstInd);
const sndChar /*: string*/ = VALID_SUB_CHARS.charAt(sndInd);
return !EXCLUDING_PREFIXES[fstChar] && !EXCLUDING_PREFIXES[`${fstChar}${sndChar}`]
}
/** data structure represents the generated class names by indexing the VALID_SUB_CHARS array using pointer item */
let pointers = [0, -1]
while(true) {
/** Increments the pointers array by 1 starting from the right */
for (let i = pointers.length - 1; i >= 0; i --) {
if (i === 0) { /** last iteration */
if(pointers[i] + 1 === VALID_SUB_CHARS.length) {
pointers[i] = 0;
pointers = [0, ...pointers];
break;
} else {
pointers[i]++
break
}
} else {
if(pointers[i] + 1 === VALID_SUB_CHARS.length) {
pointers[i] = 0
continue // next loop increments the left digit
}else{
pointers[i]++
break
}
}
}
if(fstTwoIndsAreValid(pointers[0], pointers[1])) {
yield pointers.reduce(
(next, pointer, ind) => next + VALID_SUB_CHARS.charAt(pointer)
, '');
}
}
}
const CSS_CLASS_GENERATOR = classNameGenerator()
module.exports = function getLocalIdent(
context /*: Object */,
localIdentName /*: string */ ,
localName /*: string*/,
// options
) {
const { resourcePath: cssModule } = context,
className = localName
const gid = `${cssModule}/${className}`
const cached = CACHE[gid]
if (cached) { return cached }
const nextVal = CSS_CLASS_GENERATOR.next()
if(nextVal.done) {
throw new Error('CSS Class generator can\'t ever finish.')
}
let hash = nextVal.value
CACHE[gid] = hash
return hash;
};
module.exports.classNameGenerator = classNameGenerator
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment