Last active
December 20, 2019 04:57
-
-
Save tomhodgins/6438b539245b6521c4fcb06b075caa6a to your computer and use it in GitHub Desktop.
Nest CSS rules inside custom properties starting with a triple-dash, ---, which represents the containing selector in the new nested rule! https://tomhodgins.com/demo/nesting/
This file contains hidden or 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
// deno nesting-deno.js 'a { color: red; ---\ b: { color: green }; }' | |
// deno nesting-deno.js --allow-read path/to/stylesheet.css | |
import * as parseCSS from 'https://tomhodgins.github.io/parse-css/index.js' | |
let file = Deno.args.slice(1)[0] | |
let css = file | |
try { | |
Deno.statSync(file) | |
css = new TextDecoder('utf-8').decode( | |
Deno.readFileSync(file) | |
) | |
} catch (error) {} | |
function expandNestedStylesheet(string, propertyName = '-') { | |
const output = [] | |
const stringify = (list = [], joiner = '') => list | |
.map(token => token.toSource()) | |
.join(joiner) | |
function expandNestedRule(rule) { | |
const output = [] | |
const declarations = parseCSS.parseAListOfDeclarations( | |
stringify(rule.value.value) | |
) | |
if (declarations.length) { | |
const filteredDeclarations = declarations.filter(({name}) => | |
name.startsWith(`--${propertyName}`) === false | |
) | |
if (filteredDeclarations.length) { | |
output.push( | |
parseCSS.parseARule(` | |
${stringify(rule.prelude)} { | |
${stringify(filteredDeclarations, ';')} | |
} | |
`) | |
) | |
} | |
// Process nested properties | |
declarations.forEach(declaration => { | |
if (declaration.name.startsWith(`--${propertyName}`)) { | |
output.push( | |
...expandNestedRule( | |
parseCSS.parseARule( | |
parseCSS.parseACommaSeparatedListOfComponentValues( | |
stringify(rule.prelude).trim() | |
).map(splitSelector => | |
parseCSS.parseACommaSeparatedListOfComponentValues( | |
declaration.name.slice(2 + propertyName.length) | |
).map(selector => stringify(splitSelector) + stringify(selector)).join(', ') | |
).join(', ') | |
+ stringify(declaration.value) | |
) | |
) | |
) | |
} | |
}) | |
} else { | |
output.push(rule) | |
} | |
return output | |
} | |
function expandNestedAtRule(rule) { | |
if ( | |
rule.value | |
&& rule.value.value | |
) { | |
const output = [] | |
const firstColon = rule.value.value.findIndex(({tokenType}) => tokenType === ':') | |
const firstAt = rule.value.value.findIndex(({tokenType}) => tokenType === 'AT-KEYWORD') | |
const firstBlock = rule.value.value.findIndex(({type}) => type === 'BLOCK') | |
let rules = [] | |
if ( | |
[firstAt, firstColon].every(index => index < firstBlock) | |
) { | |
rules = parseCSS.parseAListOfRules( | |
stringify(rule.value.value) | |
) | |
} | |
if (rules.length) { | |
rules.forEach(child => { | |
output.push( | |
expandNestedStylesheet(child.toSource()) | |
) | |
}) | |
rule.value.value = parseCSS.parseAListOfComponentValues( | |
output.join('\n') | |
) | |
} | |
} | |
return rule | |
} | |
// Process rules in stylesheet | |
parseCSS.parseAStylesheet(string).value.forEach(rule => { | |
if (rule.type === 'QUALIFIED-RULE') { | |
output.push(...expandNestedRule(rule)) | |
} | |
if (rule.type === 'AT-RULE') { | |
output.push(expandNestedAtRule(rule)) | |
} | |
}) | |
return stringify(output, '\n') | |
} | |
console.log( | |
expandNestedStylesheet(css) | |
) |
This file contains hidden or 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
// mkdir node_modules && cd node_modules && git clone [email protected]:tomhodgins/parse-css.git | |
// node nesting-node.js 'a { color: red; ---\ b: { color: green }; }' | |
// node nesting-node.js path/to/stylesheet.css | |
const parseCSS = require('parse-css/index.cjs.js') | |
const fs = require('fs') | |
let file = process.argv.slice(2)[0] | |
let css = file | |
if (fs.existsSync(file)) { | |
css = fs.readFileSync(file).toString() | |
} | |
function expandNestedStylesheet(string, propertyName = '-') { | |
const output = [] | |
const stringify = (list = [], joiner = '') => list | |
.map(token => token.toSource()) | |
.join(joiner) | |
function expandNestedRule(rule) { | |
const output = [] | |
const declarations = parseCSS.parseAListOfDeclarations( | |
stringify(rule.value.value) | |
) | |
if (declarations.length) { | |
const filteredDeclarations = declarations.filter(({name}) => | |
name.startsWith(`--${propertyName}`) === false | |
) | |
if (filteredDeclarations.length) { | |
output.push( | |
parseCSS.parseARule(` | |
${stringify(rule.prelude)} { | |
${stringify(filteredDeclarations, ';')} | |
} | |
`) | |
) | |
} | |
// Process nested properties | |
declarations.forEach(declaration => { | |
if (declaration.name.startsWith(`--${propertyName}`)) { | |
output.push( | |
...expandNestedRule( | |
parseCSS.parseARule( | |
parseCSS.parseACommaSeparatedListOfComponentValues( | |
stringify(rule.prelude).trim() | |
).map(splitSelector => | |
parseCSS.parseACommaSeparatedListOfComponentValues( | |
declaration.name.slice(2 + propertyName.length) | |
).map(selector => stringify(splitSelector) + stringify(selector)).join(', ') | |
).join(', ') | |
+ stringify(declaration.value) | |
) | |
) | |
) | |
} | |
}) | |
} else { | |
output.push(rule) | |
} | |
return output | |
} | |
function expandNestedAtRule(rule) { | |
if ( | |
rule.value | |
&& rule.value.value | |
) { | |
const output = [] | |
const firstColon = rule.value.value.findIndex(({tokenType}) => tokenType === ':') | |
const firstAt = rule.value.value.findIndex(({tokenType}) => tokenType === 'AT-KEYWORD') | |
const firstBlock = rule.value.value.findIndex(({type}) => type === 'BLOCK') | |
let rules = [] | |
if ( | |
[firstAt, firstColon].every(index => index < firstBlock) | |
) { | |
rules = parseCSS.parseAListOfRules( | |
stringify(rule.value.value) | |
) | |
} | |
if (rules.length) { | |
rules.forEach(child => { | |
output.push( | |
expandNestedStylesheet(child.toSource()) | |
) | |
}) | |
rule.value.value = parseCSS.parseAListOfComponentValues( | |
output.join('\n') | |
) | |
} | |
} | |
return rule | |
} | |
// Process rules in stylesheet | |
parseCSS.parseAStylesheet(string).value.forEach(rule => { | |
if (rule.type === 'QUALIFIED-RULE') { | |
output.push(...expandNestedRule(rule)) | |
} | |
if (rule.type === 'AT-RULE') { | |
output.push(expandNestedAtRule(rule)) | |
} | |
}) | |
return stringify(output, '\n') | |
} | |
console.log( | |
expandNestedStylesheet(css) | |
) |
This file contains hidden or 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
a { | |
level: 1; | |
---\ b: { | |
level: 2; | |
---\ c: { | |
level: 3; | |
---\ d: { | |
level: 4; | |
---\ e: { | |
level: 5; | |
---\ f: { | |
level: 6; | |
---\ g: { | |
level: 7; | |
---\ h: { | |
level: 8; | |
---\ i: { | |
level: 9; | |
---\ j: { | |
level: 10; | |
---\ k: { | |
level: 11; | |
---\ l: { | |
level: 12; | |
---\ m: { | |
level: 13; | |
---\ n: { | |
level: 14; | |
---\ o: { | |
level: 15; | |
---\ p: { | |
level: 16; | |
---\ q: { | |
level: 17; | |
---\ r: { | |
level: 18; | |
---\ s: { | |
level: 19; | |
---\ t: { | |
level: 20; | |
---\ u: { | |
level: 21; | |
---\ v: { | |
level: 22; | |
---\ w: { | |
level: 23; | |
---\ x: { | |
level: 24; | |
---\ y: { | |
level: 25; | |
---\ z: { | |
level: 26; | |
}; | |
}; | |
}; | |
}; | |
}; | |
}; | |
}; | |
}; | |
}; | |
}; | |
}; | |
}; | |
}; | |
}; | |
}; | |
}; | |
}; | |
}; | |
}; | |
}; | |
}; | |
}; | |
}; | |
}; | |
}; | |
} |
This file contains hidden or 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
a { level: 1 } | |
a b { level: 2 } | |
a b c { level: 3 } | |
a b c d { level: 4 } | |
a b c d e { level: 5 } | |
a b c d e f { level: 6 } | |
a b c d e f g { level: 7 } | |
a b c d e f g h { level: 8 } | |
a b c d e f g h i { level: 9 } | |
a b c d e f g h i j { level: 10 } | |
a b c d e f g h i j k { level: 11 } | |
a b c d e f g h i j k l { level: 12 } | |
a b c d e f g h i j k l m { level: 13 } | |
a b c d e f g h i j k l m n { level: 14 } | |
a b c d e f g h i j k l m n o { level: 15 } | |
a b c d e f g h i j k l m n o p { level: 16 } | |
a b c d e f g h i j k l m n o p q { level: 17 } | |
a b c d e f g h i j k l m n o p q r { level: 18 } | |
a b c d e f g h i j k l m n o p q r s { level: 19 } | |
a b c d e f g h i j k l m n o p q r s t { level: 20 } | |
a b c d e f g h i j k l m n o p q r s t u { level: 21 } | |
a b c d e f g h i j k l m n o p q r s t u v { level: 22 } | |
a b c d e f g h i j k l m n o p q r s t u v w { level: 23 } | |
a b c d e f g h i j k l m n o p q r s t u v w x { level: 24 } | |
a b c d e f g h i j k l m n o p q r s t u v w x y { level: 25 } | |
a b c d e f g h i j k l m n o p q r s t u v w x y z { level: 26 } |
This file contains hidden or 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
h1, h2, h3, h4, h5, h6 { | |
---\ \+: { | |
---\ h1\,\ h2\,\ h3\,\ h4\,\ h5\,\ h6: { | |
margin-top: .5em; | |
}; | |
}; | |
} |
This file contains hidden or 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
h1 + h1, | |
h1 + h2, | |
h1 + h3, | |
h1 + h4, | |
h1 + h5, | |
h1 + h6, | |
h2 + h1, | |
h2 + h2, | |
h2 + h3, | |
h2 + h4, | |
h2 + h5, | |
h2 + h6, | |
h3 + h1, | |
h3 + h2, | |
h3 + h3, | |
h3 + h4, | |
h3 + h5, | |
h3 + h6, | |
h4 + h1, | |
h4 + h2, | |
h4 + h3, | |
h4 + h4, | |
h4 + h5, | |
h4 + h6, | |
h5 + h1, | |
h5 + h2, | |
h5 + h3, | |
h5 + h4, | |
h5 + h5, | |
h5 + h6, | |
h6 + h1, | |
h6 + h2, | |
h6 + h3, | |
h6 + h4, | |
h6 + h5, | |
h6 + h6 { | |
margin-top: 0.5em; | |
} |
This file contains hidden or 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
1 { | |
---a\,b: { | |
color: cyan; | |
} | |
} | |
2, 3 { | |
---c: { | |
color: magenta; | |
} | |
} | |
4, 5 { | |
---d\,e: { | |
color: yellow; | |
} | |
} |
This file contains hidden or 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
1a, | |
1b { | |
color: cyan; | |
} | |
2c, | |
3c { | |
color: magenta; | |
} | |
4d, | |
4e, | |
5d, | |
5e { | |
color: yellow; | |
} |
This file contains hidden or 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
@media print { | |
a { | |
color: black; | |
text-decoration: underline; | |
---\:\:after: { | |
content: ' [link: ' attr(href) ' ]'; | |
}; | |
} | |
} |
This file contains hidden or 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
@media print { | |
a { | |
color: black; | |
text-decoration: underline; | |
} | |
a::after { | |
content: ' [link: ' attr(href) ' ]'; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment