Skip to content

Commit 38a58f9

Browse files
ChristopherBiscardiKye Hohenberger
authored andcommitted
Configurable imports (#290)
* Custom default import Basically this is a POC that proves that this works without extra configuration. Breaks no existing tests. Only works for `styled` calls. ```js import lol from 'emotion'; lol`color:blue`; ``` * Move ImportDeclaration; state.importedNames Set importedNames on state and merge with babel opts. * rename css in prop, template literal, and function call * change "default" config to "styled" * Move import parsing to `Program` visitor * Remove ImportDeclaration visitor * Remove parseImports * Move import naming logic to `Program` * more tests * preliminary docs * typo fix in docs * more doc fixes * eslint --fix
1 parent e7b36da commit 38a58f9

File tree

9 files changed

+319
-12
lines changed

9 files changed

+319
-12
lines changed

docs/configurable-imports.md

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# Configurable Imports
2+
3+
If you are using ES Module imports (`import styled from
4+
'react-emotion'`) the emotion babel plugin can handle two types of
5+
import renaming.
6+
7+
## Dynamic
8+
9+
```js
10+
import something, { css as cows } from 'react-emotion';
11+
12+
const classes = cows`
13+
color: red;
14+
`
15+
16+
export default something.div`
17+
background: blue;
18+
`
19+
```
20+
21+
## Babel Opts
22+
23+
The emotion babel plugin can also handle using babel options to handle
24+
processing via the `importedNames` key. This is useful for targetting
25+
a prop other than `css` for processing.
26+
27+
```js
28+
{
29+
"plugins": [
30+
["emotion", { "importedNames": { "css": 'cows' }}]
31+
]
32+
}
33+
```
34+
35+
Beware that if you use the babel configuration, you must import as the
36+
same name. In the previous example, we would have to `import { css as
37+
cows } from 'emotion';` then use `cows` to construct the template
38+
literals.
39+
40+
# Use Case
41+
42+
One use case for this functionality is to migrate incrementally from a
43+
styled-jsx application. When compiling the following file with emotion
44+
and styled-jsx.
45+
46+
```js
47+
import styled, { css } from "react-emotion";
48+
49+
export default () => (
50+
<div>
51+
<p>only this paragraph will get the style :)</p>
52+
{/* you can include <Component />s here that include
53+
other <p>s that don't get unexpected styles! */}
54+
<style jsx>{`
55+
p {
56+
color: red;
57+
}
58+
`}</style>
59+
</div>
60+
);
61+
```
62+
63+
The old combination would conflict on the `css` prop that styled-jsx
64+
outputs.
65+
66+
```js
67+
import _JSXStyle from "styled-jsx/style";
68+
import styled, { css } from "react-emotion";
69+
70+
export default (() => <div data-jsx={2648947580}>
71+
<p data-jsx={2648947580}>only this paragraph will get the style :)</p>
72+
{}
73+
<_JSXStyle styleId={2648947580} className={/*#__PURE__*/_css([], [], function createEmotionStyledRules() {
74+
return [{
75+
"p[data-jsx=\"2648947580\"]": {
76+
"color": "red"
77+
}
78+
}];
79+
})} />
80+
</div>);
81+
```
82+
83+
By adding the babel opt config rename as such.
84+
85+
```js
86+
{
87+
"plugins": [
88+
"styled-jsx/babel",
89+
["emotion", { "importedNames": { "css": 'cows' }}]
90+
]
91+
}
92+
```
93+
94+
We can avoid re-compiling the `css` props and instead use `cows` for
95+
our template literals, etc.
96+
97+
```js
98+
import _JSXStyle from "styled-jsx/style";
99+
import styled, { css as cows } from "react-emotion";
100+
101+
export default (() => <div data-jsx={2648947580}>
102+
<p data-jsx={2648947580}>only this paragraph will get the style :)</p>
103+
{}
104+
<_JSXStyle styleId={2648947580} css={"p[data-jsx=\"2648947580\"]{color:red}"} />
105+
</div>);
106+
```
107+

packages/babel-plugin-emotion/src/css-prop.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export default function(path, state, t) {
1010
const attrPath = openElPath.get('name')
1111
const name = attrPath.node.name
1212

13-
if (name === 'css') {
13+
if (name === state.importedNames.css) {
1414
cssPath = attrPath
1515
}
1616

@@ -91,11 +91,13 @@ export default function(path, state, t) {
9191
function getCssIdentifer() {
9292
if (state.opts.autoImportCssProp !== false) {
9393
if (!state.cssPropIdentifier) {
94-
state.cssPropIdentifier = path.scope.generateUidIdentifier('css')
94+
state.cssPropIdentifier = path.scope.generateUidIdentifier(
95+
state.importedNames.css
96+
)
9597
}
9698
return state.cssPropIdentifier
9799
} else {
98-
return t.identifier('css')
100+
return t.identifier(state.importedNames.css)
99101
}
100102
}
101103
function createCssTemplateExpression(templateLiteral) {

packages/babel-plugin-emotion/src/index.js

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,11 @@ export function replaceCssObjectCallExpression(path, identifier, t) {
228228

229229
const visited = Symbol('visited')
230230

231+
const defaultImportedNames = {
232+
styled: 'styled',
233+
css: 'css'
234+
}
235+
231236
export default function(babel) {
232237
const { types: t } = babel
233238

@@ -237,6 +242,30 @@ export default function(babel) {
237242
visitor: {
238243
Program: {
239244
enter(path, state) {
245+
state.importedNames = {
246+
...defaultImportedNames,
247+
...state.opts.importedNames
248+
}
249+
state.file.metadata.modules.imports.forEach(
250+
({ source, imported, specifiers }) => {
251+
if (source.indexOf('emotion') !== -1) {
252+
const importedNames = specifiers
253+
.filter(v => ['default', 'css'].includes(v.imported))
254+
.reduce(
255+
(acc, { imported, local }) => ({
256+
...acc,
257+
[imported === 'default' ? 'styled' : imported]: local
258+
}),
259+
defaultImportedNames
260+
)
261+
state.importedNames = {
262+
...importedNames,
263+
...state.opts.importedNames
264+
}
265+
}
266+
}
267+
)
268+
240269
state.extractStatic =
241270
// path.hub.file.opts.filename !== 'unknown' ||
242271
state.opts.extractStatic
@@ -295,10 +324,10 @@ export default function(babel) {
295324
try {
296325
if (
297326
(t.isCallExpression(path.node.callee) &&
298-
path.node.callee.callee.name === 'styled') ||
327+
path.node.callee.callee.name === state.importedNames.styled) ||
299328
(t.isMemberExpression(path.node.callee) &&
300329
t.isIdentifier(path.node.callee.object) &&
301-
path.node.callee.object.name === 'styled')
330+
path.node.callee.object.name === state.importedNames.styled)
302331
) {
303332
const identifier = t.isCallExpression(path.node.callee)
304333
? path.node.callee.callee
@@ -308,11 +337,15 @@ export default function(babel) {
308337
)
309338
}
310339
if (
311-
path.node.callee.name === 'css' &&
340+
path.node.callee.name === state.importedNames.css &&
312341
!path.node.arguments[1] &&
313342
path.node.arguments[0]
314343
) {
315-
replaceCssObjectCallExpression(path, t.identifier('css'), t)
344+
replaceCssObjectCallExpression(
345+
path,
346+
t.identifier(state.importedNames.css),
347+
t
348+
)
316349
}
317350
} catch (e) {
318351
throw path.buildCodeFrameError(e)
@@ -324,7 +357,7 @@ export default function(babel) {
324357
if (
325358
// styled.h1`color:${color};`
326359
t.isMemberExpression(path.node.tag) &&
327-
path.node.tag.object.name === 'styled'
360+
path.node.tag.object.name === state.importedNames.styled
328361
) {
329362
path.replaceWith(
330363
buildStyledCallExpression(
@@ -338,7 +371,7 @@ export default function(babel) {
338371
} else if (
339372
// styled('h1')`color:${color};`
340373
t.isCallExpression(path.node.tag) &&
341-
path.node.tag.callee.name === 'styled'
374+
path.node.tag.callee.name === state.importedNames.styled
342375
) {
343376
path.replaceWith(
344377
buildStyledCallExpression(
@@ -350,8 +383,13 @@ export default function(babel) {
350383
)
351384
)
352385
} else if (t.isIdentifier(path.node.tag)) {
353-
if (path.node.tag.name === 'css') {
354-
replaceCssWithCallExpression(path, t.identifier('css'), state, t)
386+
if (path.node.tag.name === state.importedNames.css) {
387+
replaceCssWithCallExpression(
388+
path,
389+
t.identifier(state.importedNames.css),
390+
state,
391+
t
392+
)
355393
} else if (path.node.tag.name === 'keyframes') {
356394
replaceCssWithCallExpression(
357395
path,

packages/babel-plugin-emotion/test/__snapshots__/css-prop.test.js.snap

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,15 @@ exports[`babel css prop noClassName 1`] = `
9797
})}></div>;"
9898
`;
9999
100+
exports[`babel css prop redefined-import: basic inline 1`] = `
101+
"import { css as _cows } from \\"emotion\\";
102+
<div className={\\"a\\" + \\" \\" + /*#__PURE__*/_cows([], [], function createEmotionStyledRules() {
103+
return [{
104+
\\"color\\": \\"brown\\"
105+
}];
106+
})}></div>;"
107+
`;
108+
100109
exports[`babel css prop with spread arg in jsx opening tag 1`] = `
101110
"import { css as _css } from \\"emotion\\";
102111
<div className={\\"a\\" + \\" \\" + /*#__PURE__*/_css([], [], function createEmotionStyledRules() {

packages/babel-plugin-emotion/test/__snapshots__/css.test.js.snap

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,12 @@ css({
9494
});"
9595
`;
9696
97+
exports[`babel css extract dynamically renamed-import: basic object support 1`] = `
98+
"import { css as cows } from 'emotion';cows({
99+
'display': '-webkit-box; display: -ms-flexbox; display: flex'
100+
});"
101+
`;
102+
97103
exports[`babel css extract prefixed array of objects 1`] = `
98104
"
99105
css([{
@@ -127,6 +133,12 @@ css({
127133
});"
128134
`;
129135
136+
exports[`babel css extract renamed-import: basic object support 1`] = `
137+
"cows({
138+
'display': '-webkit-box; display: -ms-flexbox; display: flex'
139+
});"
140+
`;
141+
130142
exports[`babel css inline ::placeholder 1`] = `
131143
"
132144
const cls1 = css({
@@ -259,6 +271,43 @@ exports[`babel css inline css basic 1`] = `
259271
});"
260272
`;
261273
274+
exports[`babel css inline css basic 2`] = `
275+
"
276+
/*#__PURE__*/cows([], [widthVar], function createEmotionStyledRules(x0) {
277+
return [{
278+
\\"margin\\": \\"12px 48px\\",
279+
\\"color\\": \\"#ffffff; color: blue\\",
280+
\\"display\\": \\"-webkit-box; display: -ms-flexbox; display: flex\\",
281+
\\"WebkitBoxFlex\\": \\"1\\",
282+
\\"msFlex\\": \\"1 0 auto\\",
283+
\\"flex\\": \\"1 0 auto\\",
284+
\\"@media (min-width: 420px)\\": {
285+
\\"lineHeight\\": \\"40px\\"
286+
},
287+
\\"width\\": x0
288+
}];
289+
});"
290+
`;
291+
292+
exports[`babel css inline css basic 3`] = `
293+
"
294+
import { css as cows } from 'emotion';
295+
/*#__PURE__*/cows([], [widthVar], function createEmotionStyledRules(x0) {
296+
return [{
297+
'margin': '12px 48px',
298+
'color': '#ffffff; color: blue',
299+
'display': '-webkit-box; display: -ms-flexbox; display: flex',
300+
'WebkitBoxFlex': '1',
301+
'msFlex': '1 0 auto',
302+
'flex': '1 0 auto',
303+
'@media (min-width: 420px)': {
304+
'lineHeight': '40px'
305+
},
306+
'width': x0
307+
}];
308+
});"
309+
`;
310+
262311
exports[`babel css inline css random expression 1`] = `
263312
"/*#__PURE__*/css([], [/*#__PURE__*/css([], [], function createEmotionStyledRules() {
264313
return [{

packages/babel-plugin-emotion/test/__snapshots__/styled.test.js.snap

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,3 +317,19 @@ const Figure = styled(\\"figure\\", \\"css-Figure-1mp0w960\\", [{
317317
...defaultText
318318
}]);"
319319
`;
320+
321+
exports[`babel styled component renamed import: inline config rename 1`] = `
322+
"/*#__PURE__*/what(\\"h1\\", \\"css-1jhrj9p0\\", [], [], function createEmotionStyledRules() {
323+
return {
324+
\\"color\\": \\"blue\\"
325+
};
326+
});"
327+
`;
328+
329+
exports[`babel styled component renamed import: inline variable import: no dynamic 1`] = `
330+
"import what from 'emotion'; /*#__PURE__*/what('h1', 'css-nl5a7v0', [], [], function createEmotionStyledRules() {
331+
return {
332+
'color': 'blue'
333+
};
334+
});"
335+
`;

packages/babel-plugin-emotion/test/css-prop.test.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,4 +117,12 @@ describe('babel css prop', () => {
117117
})
118118
expect(code).toMatchSnapshot()
119119
})
120+
121+
test('redefined-import: basic inline', () => {
122+
const basic = '(<div className="a" cows={`color: brown;`}></div>)'
123+
const { code } = babel.transform(basic, {
124+
plugins: [[plugin, { importedNames: { css: 'cows' } }]]
125+
})
126+
expect(code).toMatchSnapshot()
127+
})
120128
})

0 commit comments

Comments
 (0)