Skip to content

Instantly share code, notes, and snippets.

@sachin-hg
Created January 20, 2023 07:23
Show Gist options
  • Save sachin-hg/96f016ffe3891f05f78970ac58570043 to your computer and use it in GitHub Desktop.
Save sachin-hg/96f016ffe3891f05f78970ac58570043 to your computer and use it in GitHub Desktop.
// inspired from https://github.com/callstack/linaria/blob/master/packages/server/src/collect.ts
const postcss = require('postcss')
const htmlTagNames = require('./htmlTagNames')
module.exports = function collect (css) {
const basic = postcss.root()
const map = {}
const stylesheet = postcss.parse(css)
const getClassNameRoot = rule => {
// Only check class names selectors
let match = rule.selector && rule.selector.match(/\.[a-zA-Z\d_-]+/g)
if (match) {
const className = match
.filter(x => /^\.([A-Z_]\w{2,5}|[a-zA-Z_]\w?|_\w{6,})$/g.test(x))
.map(x => x.replace('.', ''))[0]
if (className && !htmlTagNames[className]) {
let root = map[className]
if (!root) {
root = postcss.root()
map[className] = root
}
return root
}
}
}
const handleAtRule = rule => {
rule.each(childRule => {
// can have a keyframes -> which can be ignored -> handled separately
// can have font-face -> which wont match any className and would be moved to 'basic'
// multiple className declarations -> each of which should be moved into its own @media declaration
// and associated with the classname
if (childRule.name === 'keyframes') {
return
}
const root = getClassNameRoot(childRule)
const cloned = rule.clone()
cloned.nodes = [childRule]
if (root) {
// need to add childRule in media query
root.append(cloned)
} else {
basic.append(cloned)
}
})
}
stylesheet.walkAtRules('font-face', rule => {
/**
* @font-face rules may be defined also in CSS conditional groups (eg. @media)
* we want only handle those from top-level, rest will be handled in stylesheet.walkRules
*/
if (rule.parent?.type === 'root') {
basic.append(rule)
}
})
const walkedAtRules = new Set()
stylesheet.walkRules(rule => {
if (
rule.parent &&
'name' in rule.parent &&
rule.parent.name === 'keyframes'
) {
return
}
if (rule.parent?.type === 'atrule') {
if (!walkedAtRules.has(rule.parent)) {
handleAtRule(rule.parent)
walkedAtRules.add(rule.parent)
}
return
}
const root = getClassNameRoot(rule)
if (root) {
root.append(rule)
} else {
basic.append(rule)
}
})
const basicAnimations = new Set()
basic.walkDecls(/animation/, decl => {
basicAnimations.add(decl.value.split(' ')[0])
})
const animationMap = {
// animationName: [animationDeclaration]
}
stylesheet.walkAtRules('keyframes', rule => {
if (basicAnimations.has(rule.params)) {
basic.append(rule)
} else {
let animations = animationMap[rule.params]
if (!animations) {
animations = []
animationMap[rule.params] = animations
}
animations.push(rule)
}
})
Object.keys(map).forEach(className => {
const root = map[className]
root.walkDecls(/animation/, decl => {
const animationName = decl.value.split(' ')[0]
if (!basicAnimations.has(animationName)) {
const animations = animationMap[animationName] || []
animations.forEach(animation => {
root.append(animation)
})
}
})
map[className] = root.toString()
})
return {
map,
basic: basic.toString()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment