Skip to content

Instantly share code, notes, and snippets.

@thomaswilburn
Last active May 7, 2024 15:19
Show Gist options
  • Save thomaswilburn/b32f7b4104b11daaebdd5dc95e5cde04 to your computer and use it in GitHub Desktop.
Save thomaswilburn/b32f7b4104b11daaebdd5dc95e5cde04 to your computer and use it in GitHub Desktop.
Convert CSS to nested CSS
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);
@thomaswilburn
Copy link
Author

Run with deno run pigeon.js inputfile.css.

@TimChinye
Copy link

This would be a great website.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment