Skip to content

Instantly share code, notes, and snippets.

@tomhodgins
Created September 5, 2019 16:25
Show Gist options
  • Save tomhodgins/f7f74313d25d89026d17e4a6baa3d8e0 to your computer and use it in GitHub Desktop.
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
@--element div and (min-width: 500) and (max-width: 1000) {
:--self {
background-color: lime;
}
}
@supports (--element("div", {"minWidth":"500","maxWidth":"1000"})) {
[--self] { background-color: lime; }
}
(() => {
jsincss(event => [
element("div", {"minWidth":"500","maxWidth":"1000"},
`[--self] { background-color: lime; }`
)
].join(''))
})()
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