Last active
February 5, 2025 10:36
-
-
Save iamgideonidoko/4d27c3bc94d622cd0fb1ae9148835619 to your computer and use it in GitHub Desktop.
CONVERT TAILWIND CSS (V3) PREDEFINED & ARBITRARY CLASSES (WITH REM) FROM 10px BASE TO 16px BASE
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
// CONVERT TAILWIND CSS (V3) PREDEFINED & ARBITRARY CLASSES (WITH REM) FROM 10px BASE TO 16px BASE | |
/* eslint-disable no-console */ | |
/* eslint-disable @typescript-eslint/no-var-requires */ | |
import { readdirSync, statSync, readFileSync, writeFileSync } from 'fs'; | |
import { join, dirname } from 'path'; | |
import { fileURLToPath } from 'url'; | |
const __dirname = dirname(fileURLToPath(import.meta.url)); | |
/* | |
* Caveat: | |
* Predefined Classes like scroll-* are ingored | |
* Arbitrary Classes like .*-\[.*3rem.*\].* are not captured so handle or convert manually | |
*/ | |
const scaleFactor = 10 / 16; | |
const allPredefinedValues = [ | |
0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 16, 20, 24, 28, 32, 36, 40, 44, | |
48, 52, 56, 60, 64, 72, 80, 96, | |
]; | |
const classesRegex = | |
/(text|leading|tracking|w|h|p|pt|pb|pl|pr|ps|pe|px|py|m|mt|mb|ml|mr|ms|me|mx|my|gap|gap-x|gap-y|top|right|bottom|left|start|end|inset|inset-x|inset-y|basis|space-x|space-y|size|indent)-(\d*\.?\d+|\[(\-?\d*\.?\d+)rem\])(?=\s|'|"|`)/g; | |
// Special case for rounded predefined classes | |
const roundedClassesRegex = | |
/(rounded|rounded-s|rounded-t|rounded-r|rounded-b)(?:-(sm|md|lg|xl|2xl|3xl|\[(\-?\d*\.?\d+)rem\]))?(?=\s|'|"|`)/g; | |
// Values in rem | |
const roundedPredefinedValues = { | |
sm: 0.125, | |
default: 0.25, | |
md: 0.375, | |
lg: 0.5, | |
xl: 0.75, | |
'2xl': 1, | |
'3xl': 1.5, | |
}; | |
/** | |
* @param {string} match | |
* @param {string} prefix (capture group 1) | |
* @param {string|undefined} predefinedValue (capture group 2) | |
* @param {string|undefined} arbitraryValue (capture group 3) | |
* @return {string} | |
*/ | |
const roundedClassesReplacer = (match, prefix, predefinedValue, arbitraryValue) => { | |
/** @type {number|undefined} */ | |
const arbitraryValueToUse = | |
arbitraryValue !== undefined | |
? arbitraryValue | |
: predefinedValue | |
? roundedPredefinedValues[predefinedValue] | |
: roundedPredefinedValues.default; | |
if (arbitraryValueToUse === undefined) { | |
return match; | |
} | |
const newArbitraryValue = +(arbitraryValueToUse * scaleFactor).toFixed(2); | |
const possiblyPredefinedValue = Object.entries(roundedPredefinedValues).find( | |
([, v]) => v === arbitraryValueToUse, | |
)?.[0]; | |
return ( | |
prefix + | |
(possiblyPredefinedValue && possiblyPredefinedValue !== 'default' | |
? `-${possiblyPredefinedValue}` | |
: '') + | |
(!possiblyPredefinedValue ? `-[${newArbitraryValue}rem]` : '') | |
); | |
}; | |
/** | |
* @param {string} prefix | |
* @param {number} arbitraryValue | |
* @param {boolean|undefined} predefined | |
* @return {[string, string]} | |
*/ | |
const getReplaceInfoFromArbitraryValue = (prefix, arbitraryValue, predefined) => { | |
const newArbitraryValue = +(arbitraryValue * scaleFactor).toFixed(2); | |
const possiblyPredefinedValue = +(newArbitraryValue * 4).toFixed(2); | |
const standaloneArbitraryRegex = /\[(\-?\d*\.?\d+)rem\]/g; | |
const isPredefinedValue = | |
allPredefinedValues.includes(possiblyPredefinedValue) && prefix !== 'text'; | |
/** @type {RegExp} */ | |
let replaceRegex = predefined === true ? `${+(arbitraryValue * 4).toFixed(2)}` : arbitraryValue; | |
let replaceValue = newArbitraryValue; | |
if (isPredefinedValue) { | |
replaceRegex = standaloneArbitraryRegex; | |
replaceValue = possiblyPredefinedValue; | |
} else if (predefined === true) { | |
replaceValue = `[${replaceValue}rem]`; | |
} | |
return [replaceRegex, replaceValue]; | |
}; | |
/** | |
* @param {string} match | |
* @param {string} prefix | |
* @param {string} predefinedValue (capture group 1) | |
* @param {string|undefined} arbitraryValue (capture group 2) | |
* @return {string} | |
*/ | |
const classesReplacer = (match, prefix, predefinedValue, arbitraryValue) => { | |
/** @type {number} */ | |
let arbitraryValueToUse; | |
if (arbitraryValue !== undefined) { | |
arbitraryValueToUse = parseFloat(arbitraryValue); | |
} else { | |
// Predefined value is correct here | |
arbitraryValueToUse = parseFloat(predefinedValue) / 4; | |
} | |
return match.replace( | |
...getReplaceInfoFromArbitraryValue(prefix, arbitraryValueToUse, arbitraryValue === undefined), | |
); | |
}; | |
const processFiles = (dir) => { | |
readdirSync(dir).forEach((file) => { | |
const fullPath = join(dir, file); | |
if (statSync(fullPath).isDirectory()) { | |
processFiles(fullPath); | |
} else if ( | |
file.endsWith('.js') || | |
file.endsWith('.jsx') || | |
file.endsWith('.ts') || | |
file.endsWith('.tsx') | |
) { | |
let content = readFileSync(fullPath, 'utf8'); | |
const updatedContent = content | |
.replace(classesRegex, classesReplacer) | |
.replace(roundedClassesRegex, roundedClassesReplacer); | |
if (content !== updatedContent) { | |
writeFileSync(fullPath, updatedContent, 'utf8'); | |
console.log(`Updated: ${fullPath}`); | |
} | |
} | |
}); | |
}; | |
processFiles(join(__dirname, './src')); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment