Created
January 20, 2023 07:23
-
-
Save sachin-hg/96f016ffe3891f05f78970ac58570043 to your computer and use it in GitHub Desktop.
This file contains 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
// 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