| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203 |
- import { PluginCreator, Rule, AtRule } from 'postcss'
- import selectorParser from 'postcss-selector-parser'
- const animationNameRE = /^(-\w+-)?animation-name$/
- const animationRE = /^(-\w+-)?animation$/
- const scopedPlugin: PluginCreator<string> = (id = '') => {
- const keyframes = Object.create(null)
- const shortId = id.replace(/^data-v-/, '')
- return {
- postcssPlugin: 'vue-sfc-scoped',
- Rule(rule) {
- processRule(id, rule)
- },
- AtRule(node) {
- if (
- /-?keyframes$/.test(node.name) &&
- !node.params.endsWith(`-${shortId}`)
- ) {
- // register keyframes
- keyframes[node.params] = node.params = node.params + '-' + shortId
- }
- },
- OnceExit(root) {
- if (Object.keys(keyframes).length) {
- // 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.
- // individual animation-name declaration
- root.walkDecls(decl => {
- if (animationNameRE.test(decl.prop)) {
- decl.value = decl.value
- .split(',')
- .map(v => keyframes[v.trim()] || v.trim())
- .join(',')
- }
- // shorthand
- if (animationRE.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(',')
- }
- })
- }
- }
- }
- }
- const processedRules = new WeakSet<Rule>()
- function processRule(id: string, rule: Rule) {
- if (
- processedRules.has(rule) ||
- (rule.parent &&
- rule.parent.type === 'atrule' &&
- /-?keyframes$/.test((rule.parent as AtRule).name))
- ) {
- return
- }
- processedRules.add(rule)
- rule.selector = selectorParser(selectorRoot => {
- selectorRoot.each(selector => {
- rewriteSelector(id, selector, selectorRoot)
- })
- }).processSync(rule.selector)
- }
- function rewriteSelector(
- id: string,
- selector: selectorParser.Selector,
- selectorRoot: selectorParser.Root
- ) {
- let node: selectorParser.Node | null = null
- let shouldInject = true
- // find the last child node to insert attribute selector
- selector.each(n => {
- // DEPRECATED ">>>" and "/deep/" combinator
- if (
- n.type === 'combinator' &&
- (n.value === '>>>' || n.value === '/deep/')
- ) {
- n.value = ' '
- n.spaces.before = n.spaces.after = ''
- // warn(
- // `the >>> and /deep/ combinators have been deprecated. ` +
- // `Use :deep() instead.`
- // )
- return false
- }
- if (n.type === 'pseudo') {
- const { value } = n
- // deep: inject [id] attribute at the node before the ::v-deep
- // combinator.
- if (value === ':deep' || value === '::v-deep') {
- if (n.nodes.length) {
- // .foo ::v-deep(.bar) -> .foo[xxxxxxx] .bar
- // replace the current node with ::v-deep's inner selector
- let last: selectorParser.Selector['nodes'][0] = n
- n.nodes[0].each(ss => {
- selector.insertAfter(last, ss)
- last = ss
- })
- // insert a space combinator before if it doesn't already have one
- const prev = selector.at(selector.index(n) - 1)
- if (!prev || !isSpaceCombinator(prev)) {
- selector.insertAfter(
- n,
- selectorParser.combinator({
- value: ' '
- })
- )
- }
- selector.removeChild(n)
- } else {
- // DEPRECATED usage in v3
- // .foo ::v-deep .bar -> .foo[xxxxxxx] .bar
- // warn(
- // `::v-deep usage as a combinator has ` +
- // `been deprecated. Use :deep(<inner-selector>) instead.`
- // )
- const prev = selector.at(selector.index(n) - 1)
- if (prev && isSpaceCombinator(prev)) {
- selector.removeChild(prev)
- }
- selector.removeChild(n)
- }
- return false
- }
- // !!! Vue 2 does not have :slotted support
- // ::v-slotted(.foo) -> .foo[xxxxxxx-s]
- // if (value === ':slotted' || value === '::v-slotted') {
- // rewriteSelector(id, n.nodes[0], selectorRoot, true /* slotted */)
- // let last: selectorParser.Selector['nodes'][0] = n
- // n.nodes[0].each(ss => {
- // selector.insertAfter(last, ss)
- // last = ss
- // })
- // // selector.insertAfter(n, n.nodes[0])
- // selector.removeChild(n)
- // // since slotted attribute already scopes the selector there's no
- // // need for the non-slot attribute.
- // shouldInject = false
- // return false
- // }
- // global: replace with inner selector and do not inject [id].
- // ::v-global(.foo) -> .foo
- if (value === ':global' || value === '::v-global') {
- selectorRoot.insertAfter(selector, n.nodes[0])
- selectorRoot.removeChild(selector)
- return false
- }
- }
- if (n.type !== 'pseudo' && n.type !== 'combinator') {
- node = n
- }
- })
- if (node) {
- ;(node as selectorParser.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 = ''
- }
- if (shouldInject) {
- selector.insertAfter(
- // If node is null it means we need to inject [id] at the start
- // insertAfter can handle `null` here
- node as any,
- selectorParser.attribute({
- attribute: id,
- value: id,
- raws: {},
- quoteMark: `"`
- })
- )
- }
- }
- function isSpaceCombinator(node: selectorParser.Node) {
- return node.type === 'combinator' && /^\s+$/.test(node.value)
- }
- scopedPlugin.postcss = true
- export default scopedPlugin
|