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>
48 lines
1.2 KiB
JavaScript
48 lines
1.2 KiB
JavaScript
/**
|
|
* 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.");
|
|
}
|