80 lines
3.0 KiB
JavaScript
80 lines
3.0 KiB
JavaScript
|
|
/**
|
||
|
|
* Clean up overrides.css:
|
||
|
|
* 1. Remove BOM character
|
||
|
|
* 2. Remove `html body #root ` specificity prefix from selectors
|
||
|
|
* 3. Remove `html body ` specificity prefix
|
||
|
|
* 4. Deduplicate doubled class selectors: .foo.foo → .foo
|
||
|
|
* 5. Deduplicate doubled attribute selectors: [attr="val"][attr="val"] → [attr="val"]
|
||
|
|
* 6. Remove all `!important` declarations
|
||
|
|
* 7. Compress multiple consecutive blank lines
|
||
|
|
*/
|
||
|
|
|
||
|
|
import { readFileSync, writeFileSync } from "node:fs";
|
||
|
|
|
||
|
|
const filePath = process.argv[2];
|
||
|
|
if (!filePath) {
|
||
|
|
console.error("Usage: node cleanup-css-overrides.mjs <file.css>");
|
||
|
|
process.exit(1);
|
||
|
|
}
|
||
|
|
|
||
|
|
let content = readFileSync(filePath, "utf-8");
|
||
|
|
|
||
|
|
// 0. Strip BOM
|
||
|
|
content = content.replace(/^\uFEFF/, "");
|
||
|
|
|
||
|
|
// 1. Remove `html body #root ` prefix (with space before the next selector)
|
||
|
|
content = content.replace(/^(\s*)html\s+body\s+#root\s+/gm, "$1");
|
||
|
|
|
||
|
|
// 2. Remove `html body ` prefix (remaining ones without #root)
|
||
|
|
content = content.replace(/^(\s*)html\s+body\s+/gm, "$1");
|
||
|
|
|
||
|
|
// 3. Deduplicate doubled class selectors: .foo.foo → .foo
|
||
|
|
// Must not match across word boundaries incorrectly
|
||
|
|
content = content.replace(/\.([\w-]+)(\.\1)\b/g, ".$1");
|
||
|
|
|
||
|
|
// 4. Deduplicate doubled attribute selectors: [data-tool="clone"][data-tool="clone"] → [data-tool="clone"]
|
||
|
|
// Use known patterns
|
||
|
|
const attrDedup = [
|
||
|
|
[/\[data-tool="clone"\]\[data-tool="clone"\]/g, '[data-tool="clone"]'],
|
||
|
|
[/\[data-tool="kit"\]\[data-tool="kit"\]/g, '[data-tool="kit"]'],
|
||
|
|
[/\[data-tool="video"\]\[data-tool="video"\]/g, '[data-tool="video"]'],
|
||
|
|
];
|
||
|
|
for (const [pat, repl] of attrDedup) {
|
||
|
|
content = content.replace(pat, repl);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 4b. Generic dedup for any other doubled attribute patterns that slipped through
|
||
|
|
// Match [name] or [name="val"] followed by an exact copy
|
||
|
|
content = content.replace(/\[(\w[\w-]*(?:="[^"]*")?)\]\1/g, "[$1]");
|
||
|
|
|
||
|
|
// 5. Remove ` !important` — strip the space and the keyword, keeping the semicolon or newline
|
||
|
|
content = content.replace(/\s*!important/g, "");
|
||
|
|
|
||
|
|
// 6. Fix artifacts:
|
||
|
|
// - " ; " or " ;; " → "; "
|
||
|
|
content = content.replace(/;\s*;/g, ";");
|
||
|
|
// - Lone semicolon before closing brace: ";\n}" → "\n}" (just remove trailing ;)
|
||
|
|
content = content.replace(/;(\s*\})/g, "$1");
|
||
|
|
// - But keep "; " inside rule blocks — only clean up empty declarations
|
||
|
|
|
||
|
|
// 7. Don't collapse " ;}" — we already handled this above
|
||
|
|
// If after removing !important we have style: value; } → keep it as is
|
||
|
|
|
||
|
|
// 8. Compress multiple blank lines (>2 consecutive newlines) to at most one
|
||
|
|
content = content.replace(/\n{3,}/g, "\n\n");
|
||
|
|
|
||
|
|
// 9. Remove trailing whitespace on lines
|
||
|
|
content = content.replace(/[ \t]+$/gm, "");
|
||
|
|
|
||
|
|
// 10. Ensure single trailing newline
|
||
|
|
content = content.trimEnd() + "\n";
|
||
|
|
|
||
|
|
writeFileSync(filePath, content, "utf-8");
|
||
|
|
|
||
|
|
// Stats
|
||
|
|
const importantCount = (content.match(/!important/g) || []).length;
|
||
|
|
const lines = content.split("\n").length;
|
||
|
|
console.log(`Processed: ${filePath}`);
|
||
|
|
console.log(` Lines: ${lines}`);
|
||
|
|
console.log(` !important remaining: ${importantCount}`);
|