UNPKG

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