Created
February 4, 2022 23:39
-
-
Save loicknuchel/4275a06736f834709a2dbc43a98b65ab to your computer and use it in GitHub Desktop.
Check if tailwind classes are correctly generated
This file contains hidden or 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
/* | |
Using tailwind 3 CLI when generating dynamic classes can sometimes be quite painful as you never know which classes may be missing. | |
Not anymore with this script! | |
It gets your stylesheets, then all your classes in HTML and print classes that are present in HTML but not in stylesheets. | |
*/ | |
function identifyMissingClasses() { | |
getStyles().then(styles => { | |
const classes = getClassCounts(document.getElementsByTagName('html')[0]) | |
const matches = matchClassesInStyles(styles, classes) | |
const misses = filterMissingClasses(matches) | |
console.log(`${Object.keys(misses).length} missing classes in styles`, misses) | |
}) | |
} | |
function getStyles() { | |
const urls = Array.from(document.querySelectorAll('link[rel=stylesheet]')).map(l => l.href) | |
return Promise.all(urls.map(url => fetch(url).then(r => r.text()))).then(styles => styles.join('\n')) | |
} | |
function getClassCounts(node, classCounts = {}) { | |
node.classList.forEach(clazz => classCounts[clazz] ? classCounts[clazz]++ : classCounts[clazz] = 1) | |
Array.from(node.children).forEach(child => getClassCounts(child, classCounts)) | |
return classCounts | |
} | |
function matchClassesInStyles(styles, classCounts) { | |
const res = {} | |
Object.keys(classCounts).forEach(clazz => { | |
const selector = classToCssSelector(clazz) | |
const matches = [...(styles.matchAll(new RegExp(`${escapeRegex(selector)} \{[^}]+}`, 'g')))].map(r => r[0]) | |
res[clazz] = { count: classCounts[clazz], matches } | |
}) | |
return res | |
} | |
function classToCssSelector(clazz) { | |
const [value, state] = clazz.split(':').reverse() | |
let className = value | |
.replaceAll('.', '\\.') // px-2.5 => .px-2\.5 | |
.replaceAll('/', '\\/') // h-1/2 => .h-1\/2 | |
let extension = className.startsWith('placeholder-') ? '::placeholder' : '' // placeholder-gray-500 => .placeholder-gray-500::placeholder | |
className = className.startsWith('space-') ? className + ' > :not([hidden]) ~ :not([hidden])' : className // space-x-3 => .space-x-3 > :not([hidden]) ~ :not([hidden]) | |
className = className.startsWith('divide-') ? className + ' > :not([hidden]) ~ :not([hidden])' : className // divide-gray-200 => .divide-gray-200 > :not([hidden]) ~ :not([hidden]) | |
switch (state) { | |
case 'hover': return `.hover\\:${className}:hover${extension}` // hover:to-indigo-700 => .hover\:to-indigo-700:hover | |
case 'focus': return `.focus\\:${className}:focus${extension}` // focus:ring-1 => .focus\:ring-1:focus | |
case 'focus-within': return `.focus-within\\:${className}:focus-within${extension}` // focus-within:outline-none => .focus-within\:outline-none:focus-within | |
case 'disabled': return `.disabled\\:${className}:disabled${extension}` // disabled:border-gray-200 => .disabled\:border-gray-200:disabled | |
case undefined: return `.${className}${extension}` // mt-6 => .mt-6 | |
default: return `.${state}\\:${className}${extension}` // md:absolute => .md\:absolute | |
} | |
} | |
function escapeRegex(source) { | |
return source.replace(/[-[/\]{}()*+?.,\\^$|#\s]/g, '\\$&') | |
} | |
function filterMissingClasses(matches) { | |
const res = {} | |
Object.keys(matches).forEach(key => { | |
if (matches[key].matches.length === 0) { | |
res[key] = matches[key] | |
} | |
}) | |
return res | |
} | |
identifyMissingClasses() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment