Last active
May 7, 2024 15:19
-
-
Save thomaswilburn/b32f7b4104b11daaebdd5dc95e5cde04 to your computer and use it in GitHub Desktop.
Convert CSS to nested CSS
This file contains 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
import { parse } from "https://deno.land/x/[email protected]/mod.ts"; | |
var file = Deno.args[0]; | |
var input = await Deno.readTextFile(file); | |
var parsed = parse(input, { value: true }); | |
var root = { | |
rules: [], | |
nest: {} | |
}; | |
var notNested = []; | |
function spaces(count = 0) { | |
return "".padStart(count, " "); | |
} | |
function parseSelector(selector) { | |
var buffer = ""; | |
var chunks = []; | |
var inPseudo = false; | |
for (var i = 0; i < selector.length; i++) { | |
var letter = selector[i]; | |
switch (letter) { | |
case "(": | |
case ")": | |
inPseudo = letter == "("; | |
buffer += letter; | |
break; | |
case " ": | |
if (inPseudo) { | |
buffer += letter; | |
} else { | |
if (buffer) chunks.push(buffer); | |
buffer = ""; | |
} | |
break; | |
case "+": | |
case ">": | |
case "~": | |
buffer = chunks.pop() + " "; | |
buffer += letter + " "; | |
while (selector[++i] == " " && i < selector.length); | |
i--; | |
break; | |
default: | |
buffer += letter; | |
} | |
} | |
if (buffer) { | |
chunks.push(buffer); | |
} | |
return chunks; | |
} | |
for (var rule of parsed.stylesheet.rules) { | |
// currently we skip media queries or keyframes and selector lists | |
// at some point it would be nice to handle these | |
if (!rule.selectors) { | |
notNested.push(rule); | |
continue; | |
} | |
var parts; | |
if (rule.selectors.length > 1) { | |
parts = [rule.selectors.join(", ")]; | |
} else { | |
parts = parseSelector(rule.selectors[0]); | |
} | |
var branch = root; | |
for (var part of parts) { | |
// check to see if we can `&` match on anything | |
var splitIndex = part.search(/\w[\[.#]/) + 1; | |
var prefix = part.slice(0, splitIndex); | |
if (prefix) { | |
for (var possible in branch.nest) { | |
if (possible == prefix) { | |
part = "&" + part.slice(splitIndex); | |
branch = branch.nest[prefix]; | |
} | |
} | |
} | |
if (!branch.nest[part]) { | |
branch.nest[part] = { | |
rules: [], | |
nest: {} | |
} | |
} | |
branch = branch.nest[part]; | |
} | |
branch.rules.push(rule); | |
} | |
function writeDeclarations(rule, pad = "") { | |
for (var { name, value } of rule.declarations) { | |
output += `${pad}${name}: ${value};\n` | |
} | |
} | |
var output = ""; | |
function walk(node, context = "", indentation = 0) { | |
var pad = spaces(indentation); | |
for (var rule of node.rules) { | |
writeDeclarations(rule, pad); | |
} | |
for (var selector in node.nest) { | |
var merged = context + " " + selector; | |
var branch = node.nest[selector]; | |
if (node != root && selector.match(/^[a-z]/i)) { | |
selector = "& " + selector; | |
} | |
output += "\n" + pad + selector + " {\n"; | |
walk(branch, context = merged, indentation + 2); | |
output += pad + "}\n"; | |
} | |
} | |
walk(root, 0); | |
function writeRule(rule, indentation = 0) { | |
var outer = spaces(indentation); | |
var inner = spaces(indentation + 2); | |
var selector = rule.selectors.join(", "); | |
output += `\n${outer}${selector} {\n`; | |
writeDeclarations(rule, inner); | |
output += `${outer}}\n`; | |
} | |
output += "\n/*==== excluded from nesting ===*/\n"; | |
for (var other of notNested) { | |
if (other.declarations) { | |
writeRule(other); | |
} else if (other.rules) { | |
output += `\n@${other.type} ${other.name} {`; | |
for (var rule of other.rules) { | |
writeRule(rule, 2); | |
} | |
output += "}\n"; | |
} | |
} | |
console.log(output); |
This would be a great website.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Run with
deno run pigeon.js inputfile.css
.