Last active
January 14, 2019 22:03
-
-
Save iocat/314a9a093b0e621284642f4ad6c39907 to your computer and use it in GitHub Desktop.
Stateful variable-length CSS Modules' class name generator (using ECMAScript Generator Function)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// @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