I have a following css:
a, a::after, p + .selector, .selector > .my-selector, .selector::before {
}
I want to append [data-123]
to all of the selectors. So something like:
a[data-123], a::after[data-123], p[data-123] + .selector[data-123], .selector[data-123] > .my-selector[data-123], .selector::before[data-123] {
}
How can I do this with postcss or is there any other way to achieve the result at runtime?
This is how Vue does it:
//css-parser.js
import { Root } from "postcss"
import * as postcss from "postcss"
// postcss-selector-parser does have typings but it's problematic to work with.
const selectorParser = require("postcss-selector-parser")
export default postcss.plugin("add-id", options => root => {
const id = options
const keyframes = Object.create(null)
root.each(function rewriteSelector(node) {
if (!node.selector) {
// handle media queries
if (node.type === "atrule") {
if (node.name === "media" || node.name === "supports") {
node.each(rewriteSelector)
} else if (/-?keyframes$/.test(node.name)) {
// register keyframes
keyframes[node.params] = node.params =
node.params + "-" + id
}
}
return
}
node.selector = selectorParser(selectors => {
selectors.each(selector => {
let node = null
// find the last child node to insert attribute selector
selector.each(n => {
// ">>>" combinator
// and /deep/ alias for >>>, since >>> doesn't work in SASS
if (
n.type === "combinator" &&
(n.value === ">>>" || n.value === "/deep/")
) {
n.value = " "
n.spaces.before = n.spaces.after = ""
return false
}
// in newer versions of sass, /deep/ support is also dropped, so add a ::v-deep alias
if (n.type === "pseudo" && n.value === "::v-deep") {
n.value = n.spaces.before = n.spaces.after = ""
return false
}
if (n.type !== "pseudo" && n.type !== "combinator") {
node = n
}
})
if (node) {
node.spaces.after = ""
} else {
// For deep selectors & standalone pseudo selectors,
// the attribute selectors are prepended rather than appended.
// So all leading spaces must be eliminated to avoid problems.
selector.first.spaces.before = ""
}
selector.insertAfter(
node,
selectorParser.attribute({
attribute: id
})
)
})
}).processSync(node.selector)
})
// If keyframes are found in this <style>, find and rewrite animation names
// in declarations.
// Caveat: this only works for keyframes and animation rules in the same
// <style> element.
if (Object.keys(keyframes).length) {
root.walkDecls(decl => {
// individual animation-name declaration
if (/^(-\w+-)?animation-name$/.test(decl.prop)) {
decl.value = decl.value
.split(",")
.map(v => keyframes[v.trim()] || v.trim())
.join(",")
}
// shorthand
if (/^(-\w+-)?animation$/.test(decl.prop)) {
decl.value = decl.value
.split(",")
.map(v => {
const vals = v.trim().split(/\s+/)
const i = vals.findIndex(val => keyframes[val])
if (i !== -1) {
vals.splice(i, 1, keyframes[vals[i]])
return vals.join(" ")
} else {
return v
}
})
.join(",")
}
})
}
})
import hashsum from "hash-sum"
import cssParser from "@/utils/css-parser"
const uniqueHash = "data-e-" + hashsum("my-random-hash")
const processCss = (css, hash) => {
const plugins = [cssParser(hash || uniqueHash)]
const result = postcss(plugins).process(css)
return result.css
}
It works perfectly.