-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathindex.js
131 lines (109 loc) · 3.91 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
const autoprefix = require('autoprefix')
const cache = {}
function mAutoprefix(name, value) {
const key = `${name}${value}`;
return cache[key] || (cache[key] = autoprefix({ [name]: value }));
}
function prefix(t, path, node) {
let binding
switch (node.type) {
// test: variable outside
case 'Identifier':
binding = path.scope.getBinding(node.name)
if (binding && binding.path.node.type === 'VariableDeclarator') {
// we've already prefixed this object
if (binding.path.data.autoprefixed) return
// track that we've autoprefixed this so we don't do it multiple times
binding.path.data.autoprefixed = true
switch (binding.path.node.init.type) {
// test: object spread
// test: more advanced object spread
case 'CallExpression':
for (const arg of binding.path.node.init.arguments) {
// binding.path.node.init.arguments.forEarch(arg => {
switch (arg.type) {
case 'Identifier':
prefix(t, path, path.scope.getBinding(arg.name).identifier)
break
case 'ObjectExpression':
prefix(t, path, arg)
break
default:
break
}
}
break
case 'ObjectExpression':
prefix(t, path, binding.path.node.init)
break
default:
break
}
}
break
// test: variable outside, key in object
case 'MemberExpression':
binding = path.scope.getBinding(node.object.name)
if (binding && binding.path.node.type === 'VariableDeclarator' && binding.path.node.init.properties) {
// we've already prefixed this object
if (binding.path.data.autoprefixed) return
// track that we've autoprefixed this so we don't do it multiple times
binding.path.data.autoprefixed = true
const subProperty = binding.path.node.init.properties.find(p => p.key.name === node.property.name)
prefix(t, path, subProperty.value)
}
break
// test: inline style
case 'ObjectExpression':
const nextProperties = []
node.properties.forEach(prop => {
// test: spread inline
if (prop.type === 'SpreadProperty') {
prefix(t, path, prop.argument)
nextProperties.push(prop)
} else {
// we don't process computed properties
if (prop.computed || !t.isLiteral(prop.value)) {
nextProperties.push(prop)
return
}
// // ensure that the value is a string
// if (!t.isLiteral(prop.value)) return
// register property as one we'll try and autoprefix
const object = mAutoprefix(prop.key.name, prop.value.value)
for (const key in object) {
// make sure the prefixed value produces valid CSS at all times.
const value = Array.isArray(object[key]) ? object[key].join(`;${key}:`) : object[key]
nextProperties.push(
t.objectProperty(
t.stringLiteral(key),
t.valueToNode(value)
)
)
}
}
})
node.properties = nextProperties
break
default: break
}
}
module.exports = ({ types: t }) => ({
visitor: {
JSXAttribute(path, state) {
const prop = path.node.name.name
if (state.opts.matcher) {
if (!state.opts.matcher.test(prop)) return
} else if (prop !== 'style') {
return
}
// verify this is an object as it's the only type we take
if (!t.isJSXExpressionContainer(path.node.value)) return
// we've already prefixed this object
if (path.data.autoprefixed) return
// track that we've autoprefixed this so we don't do it multiple times
path.data.autoprefixed = true
prefix(t, path, path.node.value.expression)
}
}
})