Created
September 5, 2019 16:25
-
-
Save tomhodgins/f7f74313d25d89026d17e4a6baa3d8e0 to your computer and use it in GitHub Desktop.
Convert vanilla CSS with a custom at-rule for element queries into either Caffeinated Style Sheets (using @supports) that can be read client-side in the browser to control JS plugins, or output JavaScript output that calls the plugins directly. Run with Deno: deno preprocess-element-queries.js input-css.css 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
@--element div and (min-width: 500) and (max-width: 1000) { | |
:--self { | |
background-color: lime; | |
} | |
} |
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
@supports (--element("div", {"minWidth":"500","maxWidth":"1000"})) { | |
[--self] { background-color: lime; } | |
} |
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
(() => { | |
jsincss(event => [ | |
element("div", {"minWidth":"500","maxWidth":"1000"}, | |
`[--self] { background-color: lime; }` | |
) | |
].join('')) | |
})() |
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
import * as parseCSS from '../parse-css/index.js' | |
import jsincss from '../jsincss/index.vanilla.js' | |
import element from '../jsincss-element-query/index.vanilla.js' | |
const kebabToCamel = (string = '') => string.replace(/-([a-z])/g, (string, match) => match.toUpperCase()) | |
const data = new TextDecoder('utf-8').decode( | |
Deno.readFileSync(Deno.args[1]) | |
) | |
const output = Deno.args[2] || 'js' | |
const complete = Deno.args[3] || null | |
// Parse Queries | |
const result = parseCSS.parseAStylesheet(data).value.reduce( | |
(queries, rule) => { | |
if ( | |
rule.type === 'AT-RULE' | |
&& rule.name === '--element' | |
) { | |
const query = { | |
selector: '', | |
conditions: {}, | |
rules: [] | |
} | |
const firstAnd = rule.prelude.findIndex(({tokenType, value}) => | |
tokenType === 'IDENT' && value === 'and' | |
) | |
// Extract selector | |
query.selector = rule.prelude.slice(0, firstAnd) | |
.map(token => token.toSource()) | |
.join('') | |
.trim() | |
// Extract conditions | |
rule.prelude.slice(firstAnd, -1) | |
.filter(({type, name}) => type === 'BLOCK' && name === '(') | |
.map(({value}) => { | |
let colon = value.findIndex(({tokenType}) => tokenType === ':') | |
query.conditions[ | |
kebabToCamel( | |
value.slice(0, colon) | |
.map(token => token.toSource()) | |
.join('') | |
.trim() | |
) | |
] = value.slice(colon + 1, value.length) | |
.map(token => token.toSource()) | |
.join('') | |
.trim() | |
return query.conditions | |
}) | |
// Extract rules | |
query.rules = parseCSS.parseAListOfRules( | |
rule.value.value | |
).map(rule => { | |
const selectorStart = rule.prelude.findIndex((token, index, list) => | |
token.tokenType === ':' | |
&& list[index + 1] | |
&& list[index + 1].tokenType === 'IDENT' | |
&& list[index + 1].value === '--self' | |
) | |
if (selectorStart !== -1) { | |
rule.prelude.splice( | |
selectorStart, | |
2, | |
parseCSS.parseAComponentValue('[--self]'), | |
) | |
} | |
return rule.toSource() | |
}) | |
queries.push(query) | |
} | |
return queries | |
}, | |
[] | |
) | |
// Output as JavaScript: (jsincss + plugin + usage) | |
if (output === 'js') { | |
console.log( | |
[ | |
`(() => {\n`, | |
// jsincss | |
complete ? ` const jsincss = ${ | |
jsincss | |
.toString() | |
.split('\n') | |
.map(line => line.trim() === '' ? '' : ` ${line}`) | |
.join('\n') | |
.trimStart() | |
}\n\n` : '', | |
// plugin | |
complete ? ` const element = ${ | |
element | |
.toString() | |
.split('\n') | |
.map(line => ` ${line}`) | |
.join('\n') | |
.trimStart() | |
}\n\n` : '', | |
// usage | |
` jsincss(event => [ | |
${result.map(query => | |
` element(${JSON.stringify(query.selector)}, ${JSON.stringify(query.conditions)}, | |
\`${query.rules}\` | |
)`).join(`,\n `)} | |
].join('')) | |
})()` | |
].join('') | |
) | |
} | |
// Output as a Caffeinated Style Sheet: @supports (--element()) {} | |
if (output === 'css') { | |
console.log( | |
result.map(query => | |
`@supports (--element(${JSON.stringify(query.selector)}, ${JSON.stringify(query.conditions)})) { | |
${query.rules} | |
}\n` | |
).join('') | |
) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment