Skip to content

Instantly share code, notes, and snippets.

@msandrini
Created February 25, 2022 09:46
Show Gist options
  • Save msandrini/34c1453471e53d41e33a57ab6f2ec57a to your computer and use it in GitHub Desktop.
Save msandrini/34c1453471e53d41e33a57ab6f2ec57a to your computer and use it in GitHub Desktop.
/**
* Taken from: https://github.com/marceloucker/postcss-prefixer
* and adapted to receive a third parameter in options
* to restrict plugin action by import name match (restrictByName)
* Note: it uses the package css-selector-tokenizer
*
* Specified in postcss.config.cjs
*/
const postcss = require('postcss')
const Tokenizer = require('css-selector-tokenizer')
function itMatchesOne(arr, term) {
return arr.some((i) => term.search(i) >= 0)
}
function parseAttrSelector(node) {
const { content } = node
const regex =
/(^class|^id)([*^?~|$=]*)+(?:("\s*)([^"\\]*?(?:\\.[^"\\]*)*?)(\s*")|('\s*)([^'\\]*?(?:\\.[^'\\]*)*?)(\s*'))/i
const [type, operator, head, classes, foot] = content.split(regex).filter((part) => part)
return {
type,
operator,
head,
classes: classes ? classes.split(' ').map((c) => c.replace(/"|'/g, '')) : [],
foot
}
}
function attrStringify({ type, operator, head, classes, foot }) {
return `${type}${operator || ''}${head || ''}${classes.join(' ')}${foot || ''}`
}
const prefixNode = (node, prefix) => {
if (['class', 'id'].includes(node.type)) {
return Object.assign({}, node, { name: `${prefix}${node.name}` })
}
if (['attribute'].includes(node.type) && node.content) {
const { type, operator, head, classes, foot } = parseAttrSelector(node)
if (!['class', 'id'].includes(type)) return node
return Object.assign({}, node, {
content: attrStringify({
type,
operator,
head,
classes: classes.map((cls) => `${prefix}${cls}`),
foot
})
})
}
return node
}
const iterateSelectorNodes = (selector, options) =>
Object.assign({}, selector, {
nodes: selector.nodes.map((node) => {
if (['selector', 'nested-pseudo-class'].includes(node.type)) {
return iterateSelectorNodes(node, options)
}
if (itMatchesOne(options.ignore, Tokenizer.stringify(node))) return node
return prefixNode(node, options.prefix)
})
})
module.exports = (options) => {
return {
postcssPlugin: 'postcss-prefixer',
Once(root, { result }) {
const { prefix, ignore } = Object.assign(
{},
{
prefix: '',
ignore: []
},
options
)
if (typeof prefix !== 'string') {
throw new Error('@postcss-prefix: prefix option should be of type string.')
}
if (!Array.isArray(ignore)) {
throw new Error('@postcss-prefix: ignore options should be an Array.')
}
if (!prefix.length) return
if (root.source.input.file.includes(options.restrictByName)) {
root.walkRules(rule => {
const parsed = Tokenizer.parse(rule.selector)
const selector = iterateSelectorNodes(parsed, { prefix, ignore })
/* eslint no-param-reassign: "off" */
rule.selector = Tokenizer.stringify(selector)
})
}
}
}
}
module.exports.postcss = true
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment