Fix production class extraction by replacing template-literal classNames with cn()
Tailwind v4's static extractor missed classes adjacent to ${} in template
literals (e.g. `pb-8${...}`), causing missing styles in production builds.
Migrated all dynamic classNames to cn() and added a check script to prevent
regressions.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
47
scripts/check-cn-classnames.mjs
Normal file
47
scripts/check-cn-classnames.mjs
Normal file
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Ban template-literal classNames in TSX files.
|
||||
*
|
||||
* Tailwind v4's production content extractor does static analysis on source
|
||||
* files to discover utility classes. Template literals like
|
||||
* className={`foo ${bar}`}
|
||||
* can cause the extractor to miss classes adjacent to `${`, leading to
|
||||
* styles that work in dev (JIT) but break in production.
|
||||
*
|
||||
* Rule: always use cn() for dynamic class composition instead.
|
||||
*/
|
||||
|
||||
import { execSync } from "node:child_process";
|
||||
import { readFileSync } from "node:fs";
|
||||
|
||||
const PATTERN = /className\s*=\s*\{`/;
|
||||
|
||||
function findFiles() {
|
||||
return execSync("git ls-files -- '*.tsx'", { encoding: "utf-8" })
|
||||
.trim()
|
||||
.split("\n")
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
let errors = 0;
|
||||
|
||||
for (const file of findFiles()) {
|
||||
const lines = readFileSync(file, "utf-8").split("\n");
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
if (PATTERN.test(lines[i])) {
|
||||
console.error(
|
||||
`${file}:${i + 1}: className uses template literal — use cn() instead`,
|
||||
);
|
||||
errors++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (errors > 0) {
|
||||
console.error(
|
||||
`\n${errors} template-literal className(s) found. Use cn() for dynamic classes.`,
|
||||
);
|
||||
process.exit(1);
|
||||
} else {
|
||||
console.log("No template-literal classNames found.");
|
||||
}
|
||||
Reference in New Issue
Block a user