Skip to content

Commit 733118f

Browse files
authored
Merge pull request #229 from styled-components/changes-revised
fix object syntax css prop, support more types of components & custom elements
2 parents 523a8c4 + 39f502e commit 733118f

File tree

5 files changed

+234
-64
lines changed

5 files changed

+234
-64
lines changed

src/visitors/transpileCssProp.js

Lines changed: 93 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { addDefault } from '@babel/helper-module-imports'
44
import { importLocalName } from '../utils/detectors'
55
import { useCssProp } from '../utils/options'
66

7+
const TAG_NAME_REGEXP = /^[a-z][a-z\d]*(\-[a-z][a-z\d]*)?$/
8+
79
const getName = (node, t) => {
810
if (typeof node.name === 'string') return node.name
911
if (t.isJSXMemberExpression(node)) {
@@ -44,7 +46,7 @@ export default t => (path, state) => {
4446

4547
let styled
4648

47-
if (/^[a-z]/.test(name)) {
49+
if (TAG_NAME_REGEXP.test(name)) {
4850
styled = t.memberExpression(importName, t.identifier(name))
4951
} else {
5052
styled = t.callExpression(importName, [t.identifier(name)])
@@ -70,6 +72,8 @@ export default t => (path, state) => {
7072
path.node.value.expression.tag.name === 'css'
7173
) {
7274
css = path.node.value.expression.quasi
75+
} else if (t.isObjectExpression(path.node.value.expression)) {
76+
css = path.node.value.expression
7377
} else {
7478
css = t.templateLiteral(
7579
[
@@ -90,34 +94,101 @@ export default t => (path, state) => {
9094
elem.parentPath.node.closingElement.name = t.jSXIdentifier(id.name)
9195
}
9296

93-
css.expressions = css.expressions.reduce((acc, ex) => {
94-
if (
95-
Object.keys(bindings).some(key =>
96-
bindings[key].referencePaths.find(p => p.node === ex)
97-
) ||
98-
t.isFunctionExpression(ex) ||
99-
t.isArrowFunctionExpression(ex)
97+
// object syntax
98+
if (t.isObjectExpression(css)) {
99+
/**
100+
* for objects as CSS props, we have to recurse through the object and replace any
101+
* object value scope references with generated props similar to how the template
102+
* literal transform above creates dynamic interpolations
103+
*/
104+
const p = t.identifier('p')
105+
let replaceObjectWithPropFunction = false
106+
107+
css.properties = css.properties.reduce(function propertiesReducer(
108+
acc,
109+
property
100110
) {
101-
acc.push(ex)
102-
} else {
103-
const name = path.scope.generateUidIdentifier('css')
104-
const p = t.identifier('p')
105-
106-
elem.node.attributes.push(
107-
t.jSXAttribute(t.jSXIdentifier(name.name), t.jSXExpressionContainer(ex))
108-
)
109-
110-
acc.push(t.arrowFunctionExpression([p], t.memberExpression(p, name)))
111+
if (t.isObjectExpression(property.value)) {
112+
// recurse for objects within objects (e.g. {'::before': { content: x }})
113+
property.value.properties = property.value.properties.reduce(
114+
propertiesReducer,
115+
[]
116+
)
117+
118+
acc.push(property)
119+
} else if (
120+
// if a non-primitive value we have to interpolate it
121+
[
122+
t.isBigIntLiteral,
123+
t.isBooleanLiteral,
124+
t.isNullLiteral,
125+
t.isNumericLiteral,
126+
t.isStringLiteral,
127+
].every(x => !x(property.value))
128+
) {
129+
replaceObjectWithPropFunction = true
130+
131+
const name = path.scope.generateUidIdentifier('css')
132+
133+
elem.node.attributes.push(
134+
t.jSXAttribute(
135+
t.jSXIdentifier(name.name),
136+
t.jSXExpressionContainer(property.value)
137+
)
138+
)
139+
140+
acc.push(t.objectProperty(property.key, t.memberExpression(p, name)))
141+
} else {
142+
// some sort of primitive which is safe to pass through as-is
143+
acc.push(property)
144+
}
145+
146+
return acc
147+
},
148+
[])
149+
150+
if (replaceObjectWithPropFunction) {
151+
css = t.arrowFunctionExpression([p], css)
111152
}
112-
113-
return acc
114-
}, [])
153+
} else {
154+
// tagged template literal
155+
css.expressions = css.expressions.reduce((acc, ex) => {
156+
if (
157+
Object.keys(bindings).some(key =>
158+
bindings[key].referencePaths.find(p => p.node === ex)
159+
) ||
160+
t.isFunctionExpression(ex) ||
161+
t.isArrowFunctionExpression(ex)
162+
) {
163+
acc.push(ex)
164+
} else {
165+
const name = path.scope.generateUidIdentifier('css')
166+
const p = t.identifier('p')
167+
168+
elem.node.attributes.push(
169+
t.jSXAttribute(
170+
t.jSXIdentifier(name.name),
171+
t.jSXExpressionContainer(ex)
172+
)
173+
)
174+
175+
acc.push(t.arrowFunctionExpression([p], t.memberExpression(p, name)))
176+
}
177+
178+
return acc
179+
}, [])
180+
}
115181

116182
// Add the tagged template expression and then requeue the newly added node
117183
// so Babel runs over it again
118184
const length = program.node.body.push(
119185
t.variableDeclaration('var', [
120-
t.variableDeclarator(id, t.taggedTemplateExpression(styled, css)),
186+
t.variableDeclarator(
187+
id,
188+
t.isObjectExpression(css) || t.isArrowFunctionExpression(css)
189+
? t.callExpression(styled, [css])
190+
: t.taggedTemplateExpression(styled, css)
191+
),
121192
])
122193
)
123194

test/fixtures/transpile-css-prop-all-options-on/code.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,3 +106,30 @@ const CustomCompWithDot = p => <Button.Ghost css="flex: 1">H</Button.Ghost>
106106
const NestedCompWithDot = p => (
107107
<Button.Ghost.New css="flex: 1">H</Button.Ghost.New>
108108
)
109+
110+
const CustomCompWithDotLowerCase = p => (
111+
<button.ghost css="flex: 1">H</button.ghost>
112+
)
113+
114+
const CustomElement = p => <button-ghost css="flex: 1">H</button-ghost>
115+
116+
const globalVar = '"foo"'
117+
const getAfterValue = () => '"bar"'
118+
119+
const ObjectPropMixedInputs = p => {
120+
const color = 'red'
121+
122+
return (
123+
<p
124+
css={{
125+
background: p.background,
126+
color,
127+
textAlign: 'left',
128+
'::before': { content: globalVar },
129+
'::after': { content: getAfterValue() },
130+
}}
131+
>
132+
A
133+
</p>
134+
)
135+
}

test/fixtures/transpile-css-prop-all-options-on/output.js

Lines changed: 57 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,7 @@ var StaticTemplate = function StaticTemplate(p) {
3131
};
3232

3333
var ObjectProp = function ObjectProp(p) {
34-
return <_StyledP3 _css={{
35-
color: 'blue'
36-
}}>A</_StyledP3>;
34+
return <_StyledP3>A</_StyledP3>;
3735
};
3836

3937
var NoChildren = function NoChildren(p) {
@@ -55,11 +53,11 @@ var CustomComp = function CustomComp(p) {
5553
};
5654

5755
var DynamicProp = function DynamicProp(p) {
58-
return <_StyledP6 _css2={props.cssText}>H</_StyledP6>;
56+
return <_StyledP6 _css={props.cssText}>H</_StyledP6>;
5957
};
6058

6159
var LocalInterpolation = function LocalInterpolation(p) {
62-
return <_StyledP7 _css3={props.bg}>
60+
return <_StyledP7 _css2={props.bg}>
6361
H
6462
</_StyledP7>;
6563
};
@@ -79,7 +77,7 @@ var GlobalInterpolation = function GlobalInterpolation(p) {
7977
};
8078

8179
var LocalCssHelperProp = function LocalCssHelperProp(p) {
82-
return <_StyledP10 _css4={p.color}>
80+
return <_StyledP10 _css3={p.color}>
8381
A
8482
</_StyledP10>;
8583
};
@@ -98,6 +96,27 @@ var NestedCompWithDot = function NestedCompWithDot(p) {
9896
return <_StyledButtonGhostNew>H</_StyledButtonGhostNew>;
9997
};
10098

99+
var CustomCompWithDotLowerCase = function CustomCompWithDotLowerCase(p) {
100+
return <_StyledButtonGhost2>H</_StyledButtonGhost2>;
101+
};
102+
103+
var CustomElement = function CustomElement(p) {
104+
return <_StyledButtonGhost3>H</_StyledButtonGhost3>;
105+
};
106+
107+
var globalVar = '"foo"';
108+
109+
var getAfterValue = function getAfterValue() {
110+
return '"bar"';
111+
};
112+
113+
var ObjectPropMixedInputs = function ObjectPropMixedInputs(p) {
114+
var color = 'red';
115+
return <_StyledP12 _css4={p.background} _css5={color} _css6={globalVar} _css7={getAfterValue()}>
116+
A
117+
</_StyledP12>;
118+
};
119+
101120
var _StyledP = _styledComponents["default"].p.withConfig({
102121
displayName: "code___StyledP",
103122
componentId: "sc-7evkve-2"
@@ -111,8 +130,8 @@ var _StyledP2 = _styledComponents["default"].p.withConfig({
111130
var _StyledP3 = _styledComponents["default"].p.withConfig({
112131
displayName: "code___StyledP3",
113132
componentId: "sc-7evkve-4"
114-
})(["", ""], function (p) {
115-
return p._css;
133+
})({
134+
color: 'blue'
116135
});
117136

118137
var _StyledP4 = _styledComponents["default"].p.withConfig({
@@ -134,14 +153,14 @@ var _StyledP6 = _styledComponents["default"].p.withConfig({
134153
displayName: "code___StyledP6",
135154
componentId: "sc-7evkve-8"
136155
})(["", ""], function (p) {
137-
return p._css2;
156+
return p._css;
138157
});
139158

140159
var _StyledP7 = _styledComponents["default"].p.withConfig({
141160
displayName: "code___StyledP7",
142161
componentId: "sc-7evkve-9"
143162
})(["background:", ";"], function (p) {
144-
return p._css3;
163+
return p._css2;
145164
});
146165

147166
var _StyledP8 = _styledComponents["default"].p.withConfig({
@@ -160,7 +179,7 @@ var _StyledP10 = _styledComponents["default"].p.withConfig({
160179
displayName: "code___StyledP10",
161180
componentId: "sc-7evkve-12"
162181
})(["color:", ";"], function (p) {
163-
return p._css4;
182+
return p._css3;
164183
});
165184

166185
var _StyledP11 = _styledComponents["default"].p.withConfig({
@@ -179,3 +198,30 @@ var _StyledButtonGhostNew = (0, _styledComponents["default"])(Button.Ghost.New).
179198
displayName: "code___StyledButtonGhostNew",
180199
componentId: "sc-7evkve-15"
181200
})(["flex:1"]);
201+
202+
var _StyledButtonGhost2 = (0, _styledComponents["default"])(button.ghost).withConfig({
203+
displayName: "code___StyledButtonGhost2",
204+
componentId: "sc-7evkve-16"
205+
})(["flex:1"]);
206+
207+
var _StyledButtonGhost3 = _styledComponents["default"]["button-ghost"].withConfig({
208+
displayName: "code___StyledButtonGhost3",
209+
componentId: "sc-7evkve-17"
210+
})(["flex:1"]);
211+
212+
var _StyledP12 = _styledComponents["default"].p.withConfig({
213+
displayName: "code___StyledP12",
214+
componentId: "sc-7evkve-18"
215+
})(function (p) {
216+
return {
217+
background: p._css4,
218+
color: p._css5,
219+
textAlign: 'left',
220+
'::before': {
221+
content: p._css6
222+
},
223+
'::after': {
224+
content: p._css7
225+
}
226+
};
227+
});

test/fixtures/transpile-css-prop/code.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,9 @@ const CustomCompWithDot = p => <Button.Ghost css="flex: 1">H</Button.Ghost>
9292
const NestedCompWithDot = p => (
9393
<Button.Ghost.New css="flex: 1">H</Button.Ghost.New>
9494
)
95+
96+
const CustomCompWithDotLowerCase = p => (
97+
<button.ghost css="flex: 1">H</button.ghost>
98+
)
99+
100+
const CustomElement = p => <button-ghost css="flex: 1">H</button-ghost>

0 commit comments

Comments
 (0)