Skip to content

Commit c0e000a

Browse files
nishp1Kye Hohenberger
authored and
Kye Hohenberger
committed
Prefix css objects (#101)
* Prefix css objects * Fix tests * Handle expressions * Use postcss and autoprefixer * Update doc
1 parent 91b35ad commit c0e000a

File tree

10 files changed

+289
-57
lines changed

10 files changed

+289
-57
lines changed

docs/objects.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
`css` can also take an object or an array of objects as a parameter.
44
This allows you to use your existing object styles in the emotion ecosystem.
5-
Another great benefit is that you can now use [polished](https://polished.js.org/) with emotion!
5+
6+
Another great benefit is that you can now use [polished](https://polished.js.org/) with emotion and styles are prefixed automatically at build time via [postcss](https://github.com/postcss/postcss-js) and [autoprefixer](https://github.com/postcss/autoprefixer/)!
67

78
`styled` can also take objects or functions that return objects as parameters.
89

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,12 @@
2929
"format": "prettier-eslint --write \"src/**/*.js\" \"test/**/*.js\" \"example/**/*.js\" \"jest-utils/**/*.js\""
3030
},
3131
"dependencies": {
32+
"autoprefixer": "^7.1.2",
3233
"@arr/filter.mutate": "^1.0.0",
3334
"@arr/foreach": "^1.0.0",
3435
"@arr/map": "^1.0.0",
3536
"babel-plugin-syntax-jsx": "^6.18.0",
37+
"postcss-js": "^1.0.0",
3638
"styled-components": "2.0.0",
3739
"theming": "^1.0.1",
3840
"touch": "^1.0.0"

src/babel.js

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import fs from 'fs'
22
import { basename } from 'path'
33
import { touchSync } from 'touch'
4+
import postcssJs from 'postcss-js'
5+
import autoprefixer from 'autoprefixer'
46
import { inline, keyframes, fontFace, injectGlobal } from './inline'
57
import cssProps from './css-prop'
68
import createAttrExpression from './attrs'
@@ -102,8 +104,84 @@ function parseDynamicValues (rules, t, options) {
102104
})
103105
}
104106

107+
const visited = Symbol('visited')
108+
105109
export default function (babel) {
106110
const { types: t } = babel
111+
const prefixer = postcssJs.sync([autoprefixer])
112+
113+
function isLiteral (value) {
114+
return (
115+
t.isStringLiteral(value) ||
116+
t.isNumericLiteral(value) ||
117+
t.isBooleanLiteral(value)
118+
)
119+
}
120+
121+
function prefixAst (args) {
122+
if (Array.isArray(args)) {
123+
return args.map(element => prefixAst(element))
124+
}
125+
126+
if (t.isObjectExpression(args)) {
127+
let properties = []
128+
args.properties.forEach(property => {
129+
// nested objects
130+
if (t.isObjectExpression(property.value)) {
131+
const key = t.isStringLiteral(property.key)
132+
? t.stringLiteral(property.key.value)
133+
: t.identifier(property.key.name)
134+
return properties.push(
135+
t.objectProperty(key, prefixAst(property.value))
136+
)
137+
138+
// literal value or array of literal values
139+
} else if (
140+
isLiteral(property.value) ||
141+
(t.isArrayExpression(property.value) &&
142+
property.value.elements.every(isLiteral))
143+
) {
144+
// handle array values: { display: ['flex', 'block'] }
145+
const propertyValue = t.isArrayExpression(property.value)
146+
? property.value.elements.map(element => element.value)
147+
: property.value.value
148+
149+
const style = { [property.key.name]: propertyValue }
150+
const prefixedStyle = prefixer(style)
151+
152+
for (var k in prefixedStyle) {
153+
const key = t.isStringLiteral(property.key)
154+
? t.stringLiteral(k)
155+
: t.identifier(k)
156+
const val = prefixedStyle[k]
157+
let value
158+
159+
if (typeof val === 'number') {
160+
value = t.numericLiteral(val)
161+
} else if (typeof val === 'string') {
162+
value = t.stringLiteral(val)
163+
} else if (Array.isArray(val)) {
164+
value = t.arrayExpression(val.map(i => t.stringLiteral(i)))
165+
}
166+
167+
properties.push(t.objectProperty(key, value))
168+
}
169+
170+
// expressions
171+
} else {
172+
properties.push(property)
173+
}
174+
})
175+
176+
return t.objectExpression(properties)
177+
}
178+
179+
if (t.isArrayExpression(args)) {
180+
return t.arrayExpression(prefixAst(args.elements))
181+
}
182+
183+
return args
184+
}
107185

108186
return {
109187
name: 'emotion', // not required
@@ -147,6 +225,9 @@ export default function (babel) {
147225
cssProps(path, t)
148226
},
149227
CallExpression (path) {
228+
if (path[visited]) {
229+
return
230+
}
150231
if (
151232
(t.isCallExpression(path.node.callee) &&
152233
path.node.callee.callee.name === 'styled') ||
@@ -160,11 +241,17 @@ export default function (babel) {
160241
path.replaceWith(
161242
t.callExpression(t.identifier('styled'), [
162243
tag,
163-
t.arrayExpression(path.node.arguments),
244+
t.arrayExpression(prefixAst(path.node.arguments)),
164245
t.arrayExpression()
165246
])
166247
)
167248
}
249+
250+
if (t.isCallExpression(path.node) && path.node.callee.name === 'css') {
251+
const prefixedAst = prefixAst(path.node.arguments)
252+
path.replaceWith(t.callExpression(t.identifier('css'), prefixedAst))
253+
}
254+
path[visited] = true
168255
},
169256
TaggedTemplateExpression (path, state) {
170257
// in:

test/__snapshots__/css.test.js.snap

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ exports[`css composes 3`] = `
2222
justify-content: center; }.css-cls1-vyoujf-va5xsk { display: -webkit-box; display: -moz-box; display: -ms-flexbox; display: -webkit-flex; display: flex; }"
2323
`;
2424

25-
exports[`css composes with objects 1`] = `"css-q8izmm"`;
25+
exports[`css composes with objects 1`] = `"css-1puxias"`;
2626

27-
exports[`css composes with objects 2`] = `"css-cls2-1qb2ovg-1em6aur css-cls2-1qb2ovg css-q8izmm"`;
27+
exports[`css composes with objects 2`] = `"css-cls2-1qb2ovg-1em6aur css-cls2-1qb2ovg css-1puxias"`;
2828

2929
exports[`css composes with objects 3`] = `
3030
".css-cls1-1gi569l-b877w5 { background: white;
@@ -41,7 +41,7 @@ exports[`css composes with objects 3`] = `
4141
-webkit-justify-content: center;
4242
-ms-flex-pack: center;
4343
-webkit-box-pack: center;
44-
justify-content: center; }.css-cls1-vyoujf-va5xsk { display: -webkit-box; display: -moz-box; display: -ms-flexbox; display: -webkit-flex; display: flex; }.css-1fe3owl{display:flex}.css-q8izmm{display:flex;display:block;width:30px;height:calc(40vw - 50px)}.css-q8izmm:hover{color:blue}.css-q8izmm:after{content:\\" \\";color:red}@media(min-width: 420px){.css-q8izmm{color:green}}"
44+
justify-content: center; }.css-cls1-vyoujf-va5xsk { display: -webkit-box; display: -moz-box; display: -ms-flexbox; display: -webkit-flex; display: flex; }.css-xmm9em{display:-webkit-box;display:-ms-flexbox;display:flex}.css-1puxias{display:-webkit-box;display:-ms-flexbox;display:flex;display:block;width:30px;height:calc(40vw - 50px)}.css-1puxias:hover{color:blue}.css-1puxias:after{content:\\" \\";color:red}@media(min-width: 420px){.css-1puxias{color:green}}"
4545
`;
4646

4747
exports[`css composes with undefined values 1`] = `"css-cls2-1qb2ovg-1em6aur css-cls2-1qb2ovg css-15famh2"`;
@@ -80,4 +80,4 @@ exports[`css handles more than 10 dynamic properties 2`] = `
8080
border: solid 1px red; }"
8181
`;
8282

83-
exports[`css handles objects 1`] = `"css-1fe3owl"`;
83+
exports[`css handles objects 1`] = `"css-xmm9em"`;

test/__snapshots__/react.test.js.snap

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -50,28 +50,28 @@ exports[`styled call expression 1`] = `
5050
`;
5151

5252
exports[`styled component as selector 1`] = `
53-
.css-H1-rs9k70-qz486c {
54-
font-size: 20px;
55-
}
56-
57-
.css-Thing-1kdnbhf-d995d2 {
53+
.css-Thing-1kdnbhf-19sb43k {
5854
display: -webkit-box;
5955
display: -moz-box;
6056
display: -ms-flexbox;
6157
display: -webkit-flex;
6258
display: flex;
6359
}
6460
65-
.css-Thing-1kdnbhf-d995d2 .css-H1-rs9k70 {
61+
.css-Thing-1kdnbhf-19sb43k .css-H1-1t8i2zo {
6662
color: green;
6763
}
6864
65+
.css-H1-1t8i2zo-1c6u60b {
66+
font-size: 20px;
67+
}
68+
6969
<div
70-
className="css-Thing-1kdnbhf-d995d2 css-Thing-1kdnbhf"
70+
className="css-Thing-1kdnbhf-19sb43k css-Thing-1kdnbhf"
7171
>
7272
hello
7373
<h1
74-
className="css-H1-rs9k70-qz486c css-H1-rs9k70"
74+
className="css-H1-1t8i2zo-1c6u60b css-H1-1t8i2zo"
7575
>
7676
This will be green
7777
</h1>
@@ -258,7 +258,7 @@ exports[`styled no dynamic 1`] = `
258258
`;
259259

260260
exports[`styled objects 1`] = `
261-
.css-1x9ygd3 {
261+
.css-1viuxsa {
262262
padding: 10px;
263263
}
264264
@@ -267,7 +267,7 @@ exports[`styled objects 1`] = `
267267
}
268268
269269
<h1
270-
className="some-class css-1x9ygd3 css-1fe3owl"
270+
className="some-class css-1viuxsa css-1fe3owl"
271271
display="flex"
272272
>
273273
hello world

test/babel/__snapshots__/css.test.js.snap

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3-
exports[`babel css extract basic object support 1`] = `"css({ display: 'flex' });"`;
3+
exports[`babel css extract basic object support 1`] = `
4+
"css({
5+
display: ['-webkit-box', '-ms-flexbox', 'flex']
6+
});"
7+
`;
48

59
exports[`babel css extract composes 1`] = `
610
"import './css.test.emotion.css';
@@ -45,7 +49,9 @@ exports[`babel css extract composes no dynamic 2`] = `
4549
exports[`babel css extract composes with objects 1`] = `
4650
"import './css.test.emotion.css';
4751
48-
const cls1 = css({ display: 'flex' });
52+
const cls1 = css({
53+
display: ['-webkit-box', '-ms-flexbox', 'flex']
54+
});
4955
const cls2 = \`\${'css-cls2-k8jueb'} \${cls1}\`;"
5056
`;
5157

@@ -87,6 +93,39 @@ const cls2 = css([\\"css-cls2-16o34vq\\"], [className], function createEmotionRu
8793
});"
8894
`;
8995
96+
exports[`babel css extract prefixed array of objects 1`] = `
97+
"
98+
css([{
99+
borderRadius: '50%',
100+
WebkitBoxSizing: 'border-box',
101+
boxSizing: 'border-box',
102+
display: ['-webkit-box', '-ms-flexbox', 'flex'],
103+
':hover': {
104+
WebkitTransform: 'scale(1.2)',
105+
transform: 'scale(1.2)'
106+
}
107+
}, {
108+
WebkitTransition: '-webkit-transform 400ms ease-in-out',
109+
transition: ['-webkit-transform 400ms ease-in-out', 'transform 400ms ease-in-out', 'transform 400ms ease-in-out, -webkit-transform 400ms ease-in-out']
110+
}]);"
111+
`;
112+
113+
exports[`babel css extract prefixed objects 1`] = `
114+
"
115+
css({
116+
borderRadius: '50%',
117+
WebkitTransition: '-webkit-transform 400ms ease-in-out',
118+
transition: ['-webkit-transform 400ms ease-in-out', 'transform 400ms ease-in-out', 'transform 400ms ease-in-out, -webkit-transform 400ms ease-in-out'],
119+
WebkitBoxSizing: 'border-box',
120+
boxSizing: 'border-box',
121+
display: ['-webkit-box', '-ms-flexbox', 'flex'],
122+
':hover': {
123+
WebkitTransform: 'scale(1.2)',
124+
transform: 'scale(1.2)'
125+
}
126+
});"
127+
`;
128+
90129
exports[`babel css inline composes 1`] = `
91130
"
92131
const cls1 = css(['css-cls1-1q8jsgx'], [], function createEmotionRules() {

test/babel/__snapshots__/styled.test.js.snap

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -224,15 +224,35 @@ const H1 = styled('h1', ['some-class', props => ({
224224
225225
exports[`babel styled component inline objects based on props 1`] = `
226226
"
227-
const H1 = styled('h1', [{ padding: 10 }, props => ({
227+
const H1 = styled('h1', [{
228+
padding: '10px'
229+
}, props => ({
228230
display: props.display
229231
})], []);"
230232
`;
231233
232234
exports[`babel styled component inline objects fn call 1`] = `
233235
"
234236
const H1 = styled('h1', [{
235-
display: 'flex'
237+
display: ['-webkit-box', '-ms-flexbox', 'flex']
238+
}], []);"
239+
`;
240+
241+
exports[`babel styled component inline objects prefixed 1`] = `
242+
"
243+
const H1 = styled('h1', [{
244+
borderRadius: '50%',
245+
WebkitTransition: '-webkit-transform 400ms ease-in-out',
246+
transition: ['-webkit-transform 400ms ease-in-out', 'transform 400ms ease-in-out', 'transform 400ms ease-in-out, -webkit-transform 400ms ease-in-out'],
247+
WebkitBoxSizing: 'border-box',
248+
boxSizing: 'border-box',
249+
display: ['-webkit-box', '-ms-flexbox', 'flex'],
250+
':hover': {
251+
WebkitTransform: 'scale(1.2)',
252+
transform: 'scale(1.2)'
253+
}
254+
}, props => {
255+
padding: props.padding;
236256
}], []);"
237257
`;
238258
@@ -246,7 +266,27 @@ styled(\\"h1\\", [\\"css-byjn67\\"], [SomeComponent], function createEmotionStyl
246266
247267
exports[`babel styled component inline styled. objects 1`] = `
248268
"
249-
const H1 = styled(\\"h1\\", [{ padding: 10 }, props => ({
269+
const H1 = styled(\\"h1\\", [{
270+
padding: \\"10px\\"
271+
}, props => ({
272+
display: props.display
273+
})], []);"
274+
`;
275+
276+
exports[`babel styled component inline styled. objects prefixed 1`] = `
277+
"
278+
const H1 = styled('h1', [{
279+
borderRadius: '50%',
280+
WebkitTransition: '-webkit-transform 400ms ease-in-out',
281+
transition: ['-webkit-transform 400ms ease-in-out', 'transform 400ms ease-in-out', 'transform 400ms ease-in-out, -webkit-transform 400ms ease-in-out'],
282+
WebkitBoxSizing: 'border-box',
283+
boxSizing: 'border-box',
284+
display: ['-webkit-box', '-ms-flexbox', 'flex'],
285+
':hover': {
286+
WebkitTransform: 'scale(1.2)',
287+
transform: 'scale(1.2)'
288+
}
289+
}, props => ({
250290
display: props.display
251291
})], []);"
252292
`;

0 commit comments

Comments
 (0)