Skip to content

Commit 74d0d58

Browse files
authored
Merge pull request #172 from styled-components/css-prop
[RFC] Add support for the css prop
2 parents aef7bab + a4ef270 commit 74d0d58

File tree

10 files changed

+493
-1
lines changed

10 files changed

+493
-1
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"version": "1.7.1",
2+
"version": "1.9.0-1",
33
"name": "babel-plugin-styled-components",
44
"description": "Improve the debugging experience and add server-side rendering support to styled-components",
55
"repository": "styled-components/babel-plugin-styled-components",
@@ -27,6 +27,7 @@
2727
},
2828
"dependencies": {
2929
"@babel/helper-annotate-as-pure": "^7.0.0",
30+
"@babel/plugin-syntax-jsx": "^7.0.0",
3031
"lodash": "^4.17.10"
3132
},
3233
"resolutions": {

src/index.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,28 @@
1+
import syntax from '@babel/plugin-syntax-jsx'
12
import pureAnnotation from './visitors/pure'
23
import minify from './visitors/minify'
34
import displayNameAndId from './visitors/displayNameAndId'
45
import templateLiterals from './visitors/templateLiterals'
56
import assignStyledRequired from './visitors/assignStyledRequired'
7+
import transpileCssProp from './visitors/transpileCssProp'
68

79
export default function({ types: t }) {
810
return {
11+
inherits: syntax,
912
visitor: {
13+
// These visitors insert newly generated code and missing import/require statements
14+
Program: {
15+
enter(path, state) {
16+
state.required = false
17+
state.items = []
18+
},
19+
exit(path, state) {
20+
path.node.body.push(...state.items)
21+
},
22+
},
23+
JSXAttribute(path, state) {
24+
transpileCssProp(t)(path, state)
25+
},
1026
CallExpression(path, state) {
1127
displayNameAndId(t)(path, state)
1228
pureAnnotation(t)(path, state)

src/utils/options.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,5 @@ export const useTranspileTemplateLiterals = state =>
1212
getOption(state, 'transpileTemplateLiterals')
1313

1414
export const usePureAnnotation = state => getOption(state, 'pure', false)
15+
16+
export const useCssProp = state => getOption(state, 'cssProp', true)

src/visitors/transpileCssProp.js

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
// Most of this code was taken from @satya164's babel-plugin-css-prop
2+
// @see https://github.com/satya164/babel-plugin-css-prop
3+
import { useCssProp } from '../utils/options'
4+
5+
const getName = (node, t) => {
6+
if (typeof node.name === 'string') return node.name
7+
if (t.isJSXMemberExpression(node)) {
8+
return `${getName(node.object, t)}.${node.property.name}`
9+
}
10+
throw path.buildCodeFrameError(
11+
`Cannot infer name from node with type "${
12+
node.type
13+
}". Please submit an issue at github.com/styled-components/babel-plugin-styled-components with your code so we can take a look at your use case!`
14+
)
15+
}
16+
17+
export default t => (path, state) => {
18+
if (!useCssProp(state)) return
19+
if (path.node.name.name !== 'css') return
20+
21+
// Insert require('styled-components') if it doesn't exist yet
22+
const { bindings } = path.findParent(p => p.type === 'Program').scope
23+
if (!state.required) {
24+
if (!bindings.styled) {
25+
state.items.push(
26+
t.importDeclaration(
27+
[t.importDefaultSpecifier(t.identifier('styled'))],
28+
t.stringLiteral('styled-components')
29+
)
30+
)
31+
}
32+
state.required = true
33+
}
34+
35+
const elem = path.parentPath
36+
const name = getName(elem.node.name, t)
37+
const id = path.scope.generateUidIdentifier(
38+
'Styled' + name.replace(/^([a-z])/, (match, p1) => p1.toUpperCase())
39+
)
40+
41+
const styled = t.callExpression(t.identifier('styled'), [
42+
/^[a-z]/.test(name) ? t.stringLiteral(name) : t.identifier(name),
43+
])
44+
45+
let css
46+
47+
if (t.isStringLiteral(path.node.value)) {
48+
css = t.templateLiteral(
49+
[t.templateElement({ raw: path.node.value.value }, true)],
50+
[]
51+
)
52+
} else if (t.isJSXExpressionContainer(path.node.value)) {
53+
if (t.isTemplateLiteral(path.node.value.expression)) {
54+
css = path.node.value.expression
55+
} else if (
56+
t.isTaggedTemplateExpression(path.node.value.expression) &&
57+
path.node.value.expression.tag.name === 'css'
58+
) {
59+
css = path.node.value.expression.quasi
60+
} else {
61+
css = t.templateLiteral(
62+
[
63+
t.templateElement({ raw: '' }, false),
64+
t.templateElement({ raw: '' }, true),
65+
],
66+
[path.node.value.expression]
67+
)
68+
}
69+
}
70+
71+
if (!css) return
72+
73+
elem.node.attributes = elem.node.attributes.filter(attr => attr !== path.node)
74+
elem.node.name = t.jSXIdentifier(id.name)
75+
76+
if (elem.parentPath.node.closingElement) {
77+
elem.parentPath.node.closingElement.name = t.jSXIdentifier(id.name)
78+
}
79+
80+
css.expressions = css.expressions.reduce((acc, ex) => {
81+
if (
82+
Object.entries(bindings).some(([, b] /*: any */) =>
83+
b.referencePaths.find(p => p.node === ex)
84+
) ||
85+
t.isFunctionExpression(ex) ||
86+
t.isArrowFunctionExpression(ex)
87+
) {
88+
acc.push(ex)
89+
} else {
90+
const name = path.scope.generateUidIdentifier(`_$p_`)
91+
const p = t.identifier('p')
92+
93+
elem.node.attributes.push(
94+
t.jSXAttribute(t.jSXIdentifier(name.name), t.jSXExpressionContainer(ex))
95+
)
96+
97+
acc.push(t.arrowFunctionExpression([p], t.memberExpression(p, name)))
98+
}
99+
100+
return acc
101+
}, [])
102+
103+
state.items.push(
104+
t.variableDeclaration('var', [
105+
t.variableDeclarator(id, t.taggedTemplateExpression(styled, css)),
106+
])
107+
)
108+
}

test/__snapshots__/index.test.js.snap

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,216 @@ const WrappedComponent = s(Inner).withConfig({
365365
})\`\`;"
366366
`;
367367
368+
exports[`fixtures should transpile css prop 1`] = `
369+
"\\"use strict\\";
370+
371+
var _styledComponents = _interopRequireDefault(require(\\"styled-components\\"));
372+
373+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
374+
375+
function _templateObject3() {
376+
var data = _taggedTemplateLiteral([\\"\\\\n color: \\", \\";\\\\n \\"]);
377+
378+
_templateObject3 = function _templateObject3() {
379+
return data;
380+
};
381+
382+
return data;
383+
}
384+
385+
function _templateObject2() {
386+
var data = _taggedTemplateLiteral([\\"\\\\n color: \\", \\";\\\\n \\"]);
387+
388+
_templateObject2 = function _templateObject2() {
389+
return data;
390+
};
391+
392+
return data;
393+
}
394+
395+
function _templateObject() {
396+
var data = _taggedTemplateLiteral([\\"\\\\n color: blue;\\\\n \\"]);
397+
398+
_templateObject = function _templateObject() {
399+
return data;
400+
};
401+
402+
return data;
403+
}
404+
405+
function _taggedTemplateLiteral(strings, raw) { if (!raw) { raw = strings.slice(0); } return Object.freeze(Object.defineProperties(strings, { raw: { value: Object.freeze(raw) } })); }
406+
407+
/*
408+
* Basic fixtures
409+
*/
410+
var StaticString = function StaticString(p) {
411+
return <_StyledP>A</_StyledP>;
412+
};
413+
414+
var StaticTemplate = function StaticTemplate(p) {
415+
return <_StyledP2>
416+
A
417+
</_StyledP2>;
418+
};
419+
420+
var ObjectProp = function ObjectProp(p) {
421+
return <_StyledP3 _$p_={{
422+
color: 'blue'
423+
}}>A</_StyledP3>;
424+
};
425+
426+
var NoChildren = function NoChildren(p) {
427+
return <_StyledP4 />;
428+
};
429+
430+
var CssHelperProp = function CssHelperProp(p) {
431+
return <_StyledP5>
432+
A
433+
</_StyledP5>;
434+
};
435+
/*
436+
* Dynamic prop
437+
*/
438+
439+
440+
var CustomComp = function CustomComp(p) {
441+
return <_StyledParagraph>H</_StyledParagraph>;
442+
};
443+
444+
var DynamicProp = function DynamicProp(p) {
445+
return <_StyledP6 _$p_2={props.cssText}>H</_StyledP6>;
446+
};
447+
448+
var LocalInterpolation = function LocalInterpolation(p) {
449+
return <_StyledP7 _$p_3={props.bg}>
450+
H
451+
</_StyledP7>;
452+
};
453+
454+
var FuncInterpolation = function FuncInterpolation(p) {
455+
return <_StyledP8>
456+
H
457+
</_StyledP8>;
458+
};
459+
460+
var radius = 10;
461+
462+
var GlobalInterpolation = function GlobalInterpolation(p) {
463+
return <_StyledP9>
464+
H
465+
</_StyledP9>;
466+
};
467+
468+
var LocalCssHelperProp = function LocalCssHelperProp(p) {
469+
return <_StyledP10 _$p_4={p.color}>
470+
A
471+
</_StyledP10>;
472+
};
473+
474+
var DynamicCssHelperProp = function DynamicCssHelperProp(p) {
475+
return <_StyledP11>
476+
A
477+
</_StyledP11>;
478+
};
479+
480+
var CustomCompWithDot = function CustomCompWithDot(p) {
481+
return <_StyledButtonGhost>H</_StyledButtonGhost>;
482+
};
483+
484+
var NestedCompWithDot = function NestedCompWithDot(p) {
485+
return <_StyledButtonGhostNew>H</_StyledButtonGhostNew>;
486+
};
487+
488+
var _StyledP = (0, _styledComponents.default)(\\"p\\")\`flex: 1;\`;
489+
490+
var _StyledP2 = (0, _styledComponents.default)(\\"p\\")\`
491+
flex: 1;
492+
\`;
493+
494+
var _StyledP3 = (0, _styledComponents.default)(\\"p\\")\`\${p => p._$p_}\`;
495+
496+
var _StyledP4 = (0, _styledComponents.default)(\\"p\\")\`flex: 1;\`;
497+
498+
var _StyledP5 = (0, _styledComponents.default)(\\"p\\")\`
499+
color: blue;
500+
\`;
501+
502+
var _StyledParagraph = (0, _styledComponents.default)(Paragraph)\`flex: 1\`;
503+
504+
var _StyledP6 = (0, _styledComponents.default)(\\"p\\")\`\${p => p._$p_2}\`;
505+
506+
var _StyledP7 = (0, _styledComponents.default)(\\"p\\")\`
507+
background: \${function (p) {
508+
return p._$p_3;
509+
}};
510+
\`;
511+
512+
var _StyledP8 = (0, _styledComponents.default)(\\"p\\")\`
513+
color: \${function (props) {
514+
return props.theme.a;
515+
}};
516+
\`;
517+
518+
var _StyledP9 = (0, _styledComponents.default)(\\"p\\")\`
519+
border-radius: \${radius}px;
520+
\`;
521+
522+
var _StyledP10 = (0, _styledComponents.default)(\\"p\\")\`
523+
color: \${function (p) {
524+
return p._$p_4;
525+
}};
526+
\`;
527+
528+
var _StyledP11 = (0, _styledComponents.default)(\\"p\\")\`
529+
color: \${function (props) {
530+
return props.theme.color;
531+
}};
532+
\`;
533+
534+
var _StyledButtonGhost = (0, _styledComponents.default)(Button.Ghost)\`flex: 1\`;
535+
536+
var _StyledButtonGhostNew = (0, _styledComponents.default)(Button.Ghost.New)\`flex: 1\`;"
537+
`;
538+
539+
exports[`fixtures should transpile css prop add import 1`] = `
540+
"\\"use strict\\";
541+
542+
Object.defineProperty(exports, \\"__esModule\\", {
543+
value: true
544+
});
545+
exports.default = void 0;
546+
547+
var _react = _interopRequireDefault(require(\\"react\\"));
548+
549+
var _Card = _interopRequireDefault(require(\\"../../shared/components/Card\\"));
550+
551+
var _config = _interopRequireDefault(require(\\"../../../config\\"));
552+
553+
var _styledComponents = _interopRequireDefault(require(\\"styled-components\\"));
554+
555+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
556+
557+
// @flow
558+
var _default = function _default() {
559+
return <_StyledDiv>
560+
<_Card.default>
561+
<h1>Login or Sign Up</h1>
562+
<p>
563+
<a href={_config.default.API_URI + '/auth/google'}>
564+
Sign up or login with Google
565+
</a>
566+
</p>
567+
</_Card.default>
568+
</_StyledDiv>;
569+
};
570+
571+
exports.default = _default;
572+
573+
var _StyledDiv = (0, _styledComponents.default)(\\"div\\")\`
574+
width: 35em;
575+
\`;"
576+
`;
577+
368578
exports[`fixtures should transpile require default 1`] = `
369579
"const styled_default = require(\\"styled-components\\");
370580
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"presets": [
3+
"@babel/preset-env"
4+
],
5+
"plugins": [
6+
[
7+
"../../../src",
8+
{
9+
"ssr": false,
10+
"displayName": false,
11+
"transpileTemplateLiterals": false,
12+
"minify": false,
13+
"cssProp": true
14+
}
15+
]
16+
]
17+
}

0 commit comments

Comments
 (0)