You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

101 lines
3.1 KiB

4 years ago
  1. import { Root } from 'postcss'
  2. import * as postcss from 'postcss'
  3. // postcss-selector-parser does have typings but it's problematic to work with.
  4. const selectorParser = require('postcss-selector-parser')
  5. export default postcss.plugin('add-id', (options: any) => (root: Root) => {
  6. const id: string = options
  7. const keyframes = Object.create(null)
  8. root.each(function rewriteSelector(node: any) {
  9. if (!node.selector) {
  10. // handle media queries
  11. if (node.type === 'atrule') {
  12. if (node.name === 'media' || node.name === 'supports') {
  13. node.each(rewriteSelector)
  14. } else if (/-?keyframes$/.test(node.name)) {
  15. // register keyframes
  16. keyframes[node.params] = node.params = node.params + '-' + id
  17. }
  18. }
  19. return
  20. }
  21. node.selector = selectorParser((selectors: any) => {
  22. selectors.each((selector: any) => {
  23. let node: any = null
  24. // find the last child node to insert attribute selector
  25. selector.each((n: any) => {
  26. // ">>>" combinator
  27. // and /deep/ alias for >>>, since >>> doesn't work in SASS
  28. if (
  29. n.type === 'combinator' &&
  30. (n.value === '>>>' || n.value === '/deep/')
  31. ) {
  32. n.value = ' '
  33. n.spaces.before = n.spaces.after = ''
  34. return false
  35. }
  36. // in newer versions of sass, /deep/ support is also dropped, so add a ::v-deep alias
  37. if (n.type === 'pseudo' && n.value === '::v-deep') {
  38. n.value = n.spaces.before = n.spaces.after = ''
  39. return false
  40. }
  41. if (n.type !== 'pseudo' && n.type !== 'combinator') {
  42. node = n
  43. }
  44. })
  45. if (node) {
  46. node.spaces.after = ''
  47. } else {
  48. // For deep selectors & standalone pseudo selectors,
  49. // the attribute selectors are prepended rather than appended.
  50. // So all leading spaces must be eliminated to avoid problems.
  51. selector.first.spaces.before = ''
  52. }
  53. selector.insertAfter(
  54. node,
  55. selectorParser.attribute({
  56. attribute: id
  57. })
  58. )
  59. })
  60. }).processSync(node.selector)
  61. })
  62. // If keyframes are found in this <style>, find and rewrite animation names
  63. // in declarations.
  64. // Caveat: this only works for keyframes and animation rules in the same
  65. // <style> element.
  66. if (Object.keys(keyframes).length) {
  67. root.walkDecls(decl => {
  68. // individual animation-name declaration
  69. if (/^(-\w+-)?animation-name$/.test(decl.prop)) {
  70. decl.value = decl.value
  71. .split(',')
  72. .map(v => keyframes[v.trim()] || v.trim())
  73. .join(',')
  74. }
  75. // shorthand
  76. if (/^(-\w+-)?animation$/.test(decl.prop)) {
  77. decl.value = decl.value
  78. .split(',')
  79. .map(v => {
  80. const vals = v.trim().split(/\s+/)
  81. const i = vals.findIndex(val => keyframes[val])
  82. if (i !== -1) {
  83. vals.splice(i, 1, keyframes[vals[i]])
  84. return vals.join(' ')
  85. } else {
  86. return v
  87. }
  88. })
  89. .join(',')
  90. }
  91. })
  92. }
  93. })