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.

214 lines
5.2 KiB

2 years ago
  1. let parser = require('postcss-selector-parser')
  2. function parse (str, rule) {
  3. let nodes
  4. let saver = parser(parsed => {
  5. nodes = parsed
  6. })
  7. try {
  8. saver.processSync(str)
  9. } catch (e) {
  10. if (str.includes(':')) {
  11. throw rule ? rule.error('Missed semicolon') : e
  12. } else {
  13. throw rule ? rule.error(e.message) : e
  14. }
  15. }
  16. return nodes.at(0)
  17. }
  18. function replace (nodes, parent) {
  19. let replaced = false
  20. nodes.each(i => {
  21. if (i.type === 'nesting') {
  22. let clonedParent = parent.clone()
  23. if (i.value !== '&') {
  24. i.replaceWith(parse(i.value.replace('&', clonedParent.toString())))
  25. } else {
  26. i.replaceWith(clonedParent)
  27. }
  28. replaced = true
  29. } else if (i.nodes) {
  30. if (replace(i, parent)) {
  31. replaced = true
  32. }
  33. }
  34. })
  35. return replaced
  36. }
  37. function selectors (parent, child) {
  38. let result = []
  39. parent.selectors.forEach(i => {
  40. let parentNode = parse(i, parent)
  41. child.selectors.forEach(j => {
  42. if (j.length) {
  43. let node = parse(j, child)
  44. let replaced = replace(node, parentNode)
  45. if (!replaced) {
  46. node.prepend(parser.combinator({ value: ' ' }))
  47. node.prepend(parentNode.clone())
  48. }
  49. result.push(node.toString())
  50. }
  51. })
  52. })
  53. return result
  54. }
  55. function pickComment (comment, after) {
  56. if (comment && comment.type === 'comment') {
  57. after.after(comment)
  58. return comment
  59. } else {
  60. return after
  61. }
  62. }
  63. function createFnAtruleChilds (bubble) {
  64. return function atruleChilds (rule, atrule, bubbling) {
  65. let children = []
  66. atrule.each(child => {
  67. if (child.type === 'comment') {
  68. children.push(child)
  69. } else if (child.type === 'decl') {
  70. children.push(child)
  71. } else if (child.type === 'rule' && bubbling) {
  72. child.selectors = selectors(rule, child)
  73. } else if (child.type === 'atrule') {
  74. if (child.nodes && bubble[child.name]) {
  75. atruleChilds(rule, child, true)
  76. } else {
  77. children.push(child)
  78. }
  79. }
  80. })
  81. if (bubbling) {
  82. if (children.length) {
  83. let clone = rule.clone({ nodes: [] })
  84. for (let child of children) {
  85. clone.append(child)
  86. }
  87. atrule.prepend(clone)
  88. }
  89. }
  90. }
  91. }
  92. function pickDeclarations (selector, declarations, after, Rule) {
  93. let parent = new Rule({
  94. selector,
  95. nodes: []
  96. })
  97. for (let declaration of declarations) {
  98. parent.append(declaration)
  99. }
  100. after.after(parent)
  101. return parent
  102. }
  103. function atruleNames (defaults, custom) {
  104. let list = {}
  105. for (let i of defaults) {
  106. list[i] = true
  107. }
  108. if (custom) {
  109. for (let i of custom) {
  110. let name = i.replace(/^@/, '')
  111. list[name] = true
  112. }
  113. }
  114. return list
  115. }
  116. module.exports = (opts = {}) => {
  117. let bubble = atruleNames(['media', 'supports'], opts.bubble)
  118. let atruleChilds = createFnAtruleChilds(bubble)
  119. let unwrap = atruleNames(
  120. [
  121. 'document',
  122. 'font-face',
  123. 'keyframes',
  124. '-webkit-keyframes',
  125. '-moz-keyframes'
  126. ],
  127. opts.unwrap
  128. )
  129. let preserveEmpty = opts.preserveEmpty
  130. return {
  131. postcssPlugin: 'postcss-nested',
  132. Rule (rule, { Rule }) {
  133. let unwrapped = false
  134. let after = rule
  135. let copyDeclarations = false
  136. let declarations = []
  137. rule.each(child => {
  138. if (child.type === 'rule') {
  139. if (declarations.length) {
  140. after = pickDeclarations(rule.selector, declarations, after, Rule)
  141. declarations = []
  142. }
  143. copyDeclarations = true
  144. unwrapped = true
  145. child.selectors = selectors(rule, child)
  146. after = pickComment(child.prev(), after)
  147. after.after(child)
  148. after = child
  149. } else if (child.type === 'atrule') {
  150. if (declarations.length) {
  151. after = pickDeclarations(rule.selector, declarations, after, Rule)
  152. declarations = []
  153. }
  154. if (child.name === 'at-root') {
  155. unwrapped = true
  156. atruleChilds(rule, child, false)
  157. let nodes = child.nodes
  158. if (child.params) {
  159. nodes = new Rule({ selector: child.params, nodes })
  160. }
  161. after.after(nodes)
  162. after = nodes
  163. child.remove()
  164. } else if (bubble[child.name]) {
  165. copyDeclarations = true
  166. unwrapped = true
  167. atruleChilds(rule, child, true)
  168. after = pickComment(child.prev(), after)
  169. after.after(child)
  170. after = child
  171. } else if (unwrap[child.name]) {
  172. copyDeclarations = true
  173. unwrapped = true
  174. atruleChilds(rule, child, false)
  175. after = pickComment(child.prev(), after)
  176. after.after(child)
  177. after = child
  178. } else if (copyDeclarations) {
  179. declarations.push(child)
  180. }
  181. } else if (child.type === 'decl' && copyDeclarations) {
  182. declarations.push(child)
  183. }
  184. })
  185. if (declarations.length) {
  186. after = pickDeclarations(rule.selector, declarations, after, Rule)
  187. }
  188. if (unwrapped && preserveEmpty !== true) {
  189. rule.raws.semicolon = true
  190. if (rule.nodes.length === 0) rule.remove()
  191. }
  192. }
  193. }
  194. }
  195. module.exports.postcss = true