supports.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. let featureQueries = require('caniuse-lite/data/features/css-featurequeries.js')
  2. let { feature } = require('caniuse-lite')
  3. let { parse } = require('postcss')
  4. let Browsers = require('./browsers')
  5. let brackets = require('./brackets')
  6. let Value = require('./value')
  7. let utils = require('./utils')
  8. let data = feature(featureQueries)
  9. let supported = []
  10. for (let browser in data.stats) {
  11. let versions = data.stats[browser]
  12. for (let version in versions) {
  13. let support = versions[version]
  14. if (/y/.test(support)) {
  15. supported.push(browser + ' ' + version)
  16. }
  17. }
  18. }
  19. class Supports {
  20. constructor(Prefixes, all) {
  21. this.Prefixes = Prefixes
  22. this.all = all
  23. }
  24. /**
  25. * Return prefixer only with @supports supported browsers
  26. */
  27. prefixer() {
  28. if (this.prefixerCache) {
  29. return this.prefixerCache
  30. }
  31. let filtered = this.all.browsers.selected.filter(i => {
  32. return supported.includes(i)
  33. })
  34. let browsers = new Browsers(
  35. this.all.browsers.data,
  36. filtered,
  37. this.all.options
  38. )
  39. this.prefixerCache = new this.Prefixes(
  40. this.all.data,
  41. browsers,
  42. this.all.options
  43. )
  44. return this.prefixerCache
  45. }
  46. /**
  47. * Parse string into declaration property and value
  48. */
  49. parse(str) {
  50. let parts = str.split(':')
  51. let prop = parts[0]
  52. let value = parts[1]
  53. if (!value) value = ''
  54. return [prop.trim(), value.trim()]
  55. }
  56. /**
  57. * Create virtual rule to process it by prefixer
  58. */
  59. virtual(str) {
  60. let [prop, value] = this.parse(str)
  61. let rule = parse('a{}').first
  62. rule.append({ prop, value, raws: { before: '' } })
  63. return rule
  64. }
  65. /**
  66. * Return array of Declaration with all necessary prefixes
  67. */
  68. prefixed(str) {
  69. let rule = this.virtual(str)
  70. if (this.disabled(rule.first)) {
  71. return rule.nodes
  72. }
  73. let result = { warn: () => null }
  74. let prefixer = this.prefixer().add[rule.first.prop]
  75. prefixer && prefixer.process && prefixer.process(rule.first, result)
  76. for (let decl of rule.nodes) {
  77. for (let value of this.prefixer().values('add', rule.first.prop)) {
  78. value.process(decl)
  79. }
  80. Value.save(this.all, decl)
  81. }
  82. return rule.nodes
  83. }
  84. /**
  85. * Return true if brackets node is "not" word
  86. */
  87. isNot(node) {
  88. return typeof node === 'string' && /not\s*/i.test(node)
  89. }
  90. /**
  91. * Return true if brackets node is "or" word
  92. */
  93. isOr(node) {
  94. return typeof node === 'string' && /\s*or\s*/i.test(node)
  95. }
  96. /**
  97. * Return true if brackets node is (prop: value)
  98. */
  99. isProp(node) {
  100. return (
  101. typeof node === 'object' &&
  102. node.length === 1 &&
  103. typeof node[0] === 'string'
  104. )
  105. }
  106. /**
  107. * Return true if prefixed property has no unprefixed
  108. */
  109. isHack(all, unprefixed) {
  110. let check = new RegExp(`(\\(|\\s)${utils.escapeRegexp(unprefixed)}:`)
  111. return !check.test(all)
  112. }
  113. /**
  114. * Return true if we need to remove node
  115. */
  116. toRemove(str, all) {
  117. let [prop, value] = this.parse(str)
  118. let unprefixed = this.all.unprefixed(prop)
  119. let cleaner = this.all.cleaner()
  120. if (
  121. cleaner.remove[prop] &&
  122. cleaner.remove[prop].remove &&
  123. !this.isHack(all, unprefixed)
  124. ) {
  125. return true
  126. }
  127. for (let checker of cleaner.values('remove', unprefixed)) {
  128. if (checker.check(value)) {
  129. return true
  130. }
  131. }
  132. return false
  133. }
  134. /**
  135. * Remove all unnecessary prefixes
  136. */
  137. remove(nodes, all) {
  138. let i = 0
  139. while (i < nodes.length) {
  140. if (
  141. !this.isNot(nodes[i - 1]) &&
  142. this.isProp(nodes[i]) &&
  143. this.isOr(nodes[i + 1])
  144. ) {
  145. if (this.toRemove(nodes[i][0], all)) {
  146. nodes.splice(i, 2)
  147. continue
  148. }
  149. i += 2
  150. continue
  151. }
  152. if (typeof nodes[i] === 'object') {
  153. nodes[i] = this.remove(nodes[i], all)
  154. }
  155. i += 1
  156. }
  157. return nodes
  158. }
  159. /**
  160. * Clean brackets with one child
  161. */
  162. cleanBrackets(nodes) {
  163. return nodes.map(i => {
  164. if (typeof i !== 'object') {
  165. return i
  166. }
  167. if (i.length === 1 && typeof i[0] === 'object') {
  168. return this.cleanBrackets(i[0])
  169. }
  170. return this.cleanBrackets(i)
  171. })
  172. }
  173. /**
  174. * Add " or " between properties and convert it to brackets format
  175. */
  176. convert(progress) {
  177. let result = ['']
  178. for (let i of progress) {
  179. result.push([`${i.prop}: ${i.value}`])
  180. result.push(' or ')
  181. }
  182. result[result.length - 1] = ''
  183. return result
  184. }
  185. /**
  186. * Compress value functions into a string nodes
  187. */
  188. normalize(nodes) {
  189. if (typeof nodes !== 'object') {
  190. return nodes
  191. }
  192. nodes = nodes.filter(i => i !== '')
  193. if (typeof nodes[0] === 'string') {
  194. let firstNode = nodes[0].trim()
  195. if (
  196. firstNode.includes(':') ||
  197. firstNode === 'selector' ||
  198. firstNode === 'not selector'
  199. ) {
  200. return [brackets.stringify(nodes)]
  201. }
  202. }
  203. return nodes.map(i => this.normalize(i))
  204. }
  205. /**
  206. * Add prefixes
  207. */
  208. add(nodes, all) {
  209. return nodes.map(i => {
  210. if (this.isProp(i)) {
  211. let prefixed = this.prefixed(i[0])
  212. if (prefixed.length > 1) {
  213. return this.convert(prefixed)
  214. }
  215. return i
  216. }
  217. if (typeof i === 'object') {
  218. return this.add(i, all)
  219. }
  220. return i
  221. })
  222. }
  223. /**
  224. * Add prefixed declaration
  225. */
  226. process(rule) {
  227. let ast = brackets.parse(rule.params)
  228. ast = this.normalize(ast)
  229. ast = this.remove(ast, rule.params)
  230. ast = this.add(ast, rule.params)
  231. ast = this.cleanBrackets(ast)
  232. rule.params = brackets.stringify(ast)
  233. }
  234. /**
  235. * Check global options
  236. */
  237. disabled(node) {
  238. if (!this.all.options.grid) {
  239. if (node.prop === 'display' && node.value.includes('grid')) {
  240. return true
  241. }
  242. if (node.prop.includes('grid') || node.prop === 'justify-items') {
  243. return true
  244. }
  245. }
  246. if (this.all.options.flexbox === false) {
  247. if (node.prop === 'display' && node.value.includes('flex')) {
  248. return true
  249. }
  250. let other = ['order', 'justify-content', 'align-items', 'align-content']
  251. if (node.prop.includes('flex') || other.includes(node.prop)) {
  252. return true
  253. }
  254. }
  255. return false
  256. }
  257. }
  258. module.exports = Supports