1 | let featureQueries = require('caniuse-lite/data/features/css-featurequeries.js')
|
2 | let { feature } = require('caniuse-lite')
|
3 | let { parse } = require('postcss')
|
4 |
|
5 | let Browsers = require('./browsers')
|
6 | let brackets = require('./brackets')
|
7 | let Value = require('./value')
|
8 | let utils = require('./utils')
|
9 |
|
10 | let data = feature(featureQueries)
|
11 |
|
12 | let supported = []
|
13 | for (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 |
|
23 | class Supports {
|
24 | constructor(Prefixes, all) {
|
25 | this.Prefixes = Prefixes
|
26 | this.all = all
|
27 | }
|
28 |
|
29 | |
30 |
|
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 |
|
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 |
|
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 |
|
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 |
|
101 |
|
102 | isNot(node) {
|
103 | return typeof node === 'string' && /not\s*/i.test(node)
|
104 | }
|
105 |
|
106 | |
107 |
|
108 |
|
109 | isOr(node) {
|
110 | return typeof node === 'string' && /\s*or\s*/i.test(node)
|
111 | }
|
112 |
|
113 | |
114 |
|
115 |
|
116 | isProp(node) {
|
117 | return (
|
118 | typeof node === 'object' &&
|
119 | node.length === 1 &&
|
120 | typeof node[0] === 'string'
|
121 | )
|
122 | }
|
123 |
|
124 | |
125 |
|
126 |
|
127 | isHack(all, unprefixed) {
|
128 | let check = new RegExp(`(\\(|\\s)${utils.escapeRegexp(unprefixed)}:`)
|
129 | return !check.test(all)
|
130 | }
|
131 |
|
132 | |
133 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
302 | module.exports = Supports
|