Skip to content

Commit b6b9736

Browse files
author
Daniel Del Core
committed
add import alias/member expression support
1 parent 86aa210 commit b6b9736

File tree

5 files changed

+120
-66
lines changed

5 files changed

+120
-66
lines changed

packages/babel-plugin/src/__tests__/index.test.ts

+60-1
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ describe('babel plugin', () => {
114114
`);
115115
});
116116

117-
it('should transform React.cloneElement css prop', () => {
117+
it('should transform cloneElement css prop', () => {
118118
const actual = transform(`
119119
import { cloneElement } from 'react';
120120
import { css } from '@compiled/react';
@@ -144,6 +144,65 @@ describe('babel plugin', () => {
144144
`);
145145
});
146146

147+
it('should transform cloneElement css prop with aliased import', () => {
148+
const actual = transform(`
149+
import { cloneElement as CE } from 'react';
150+
import { css } from '@compiled/react';
151+
152+
const MyDiv = ({ children }) => {
153+
return CE(children, { css: css({ fontSize: 12 }) });
154+
};
155+
`);
156+
157+
expect(actual).toMatchInlineSnapshot(`
158+
"/* File generated by @compiled/babel-plugin v0.0.0 */
159+
import * as React from "react";
160+
import { ax, ix, CC, CS } from "@compiled/react/runtime";
161+
import { cloneElement as CE } from "react";
162+
const _ = "._1wyb1fwx{font-size:12px}";
163+
const MyDiv = ({ children }) => {
164+
return (
165+
<CC>
166+
<CS>{[_]}</CS>
167+
{CE(children, {
168+
className: ax(["_1wyb1fwx"]),
169+
})}
170+
</CC>
171+
);
172+
};
173+
"
174+
`);
175+
});
176+
177+
it('should transform React.cloneElement css prop', () => {
178+
const actual = transform(`
179+
import React from 'react';
180+
import { css } from '@compiled/react';
181+
182+
const MyDiv = ({ children }) => {
183+
return React.cloneElement(children, { css: css({ fontSize: 12 }) });
184+
};
185+
`);
186+
187+
expect(actual).toMatchInlineSnapshot(`
188+
"/* File generated by @compiled/babel-plugin v0.0.0 */
189+
import { ax, ix, CC, CS } from "@compiled/react/runtime";
190+
import React from "react";
191+
const _ = "._1wyb1fwx{font-size:12px}";
192+
const MyDiv = ({ children }) => {
193+
return (
194+
<CC>
195+
<CS>{[_]}</CS>
196+
{React.cloneElement(children, {
197+
className: ax(["_1wyb1fwx"]),
198+
})}
199+
</CC>
200+
);
201+
};
202+
"
203+
`);
204+
});
205+
147206
// TODO Removing import React from 'react' breaks this test
148207
it('should preserve comments at the top of the processed file before inserting runtime imports', () => {
149208
const actual = transform(`

packages/babel-plugin/src/babel-plugin.ts

+31-3
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,26 @@ const findClassicJsxPragmaImport: Visitor<State> = {
6666
},
6767
};
6868

69+
const findReactImportSpecifier: Visitor<State> = {
70+
ImportSpecifier(path, state) {
71+
const specifier = path.node;
72+
73+
t.assertImportDeclaration(path.parent);
74+
if (path.parent.source.value !== 'react') {
75+
return;
76+
}
77+
78+
if (
79+
(specifier.imported.type === 'StringLiteral' &&
80+
specifier.imported.value === 'cloneElement') ||
81+
(specifier.imported.type === 'Identifier' && specifier.imported.name === 'cloneElement')
82+
) {
83+
state.reactImports = state.reactImports || {};
84+
state.reactImports.cloneElement = specifier.local.name;
85+
}
86+
},
87+
};
88+
6989
export default declare<State>((api) => {
7090
api.assertVersion(7);
7191

@@ -125,6 +145,7 @@ export default declare<State>((api) => {
125145

126146
// Handle classic JSX pragma, if it exists
127147
path.traverse<State>(findClassicJsxPragmaImport, this);
148+
path.traverse<State>(findReactImportSpecifier, this);
128149

129150
if (!file.ast.comments) {
130151
return;
@@ -297,9 +318,16 @@ export default declare<State>((api) => {
297318
state: State
298319
) {
299320
if (
300-
t.isCallExpression(path.node) &&
301-
t.isIdentifier(path.node.callee) &&
302-
path.node.callee.name === 'cloneElement'
321+
(t.isCallExpression(path.node) &&
322+
t.isIdentifier(path.node.callee) &&
323+
path.node.callee.name === state.reactImports?.cloneElement) ||
324+
// handle member expression React.cloneElement
325+
(t.isCallExpression(path.node) &&
326+
t.isMemberExpression(path.node.callee) &&
327+
t.isIdentifier(path.node.callee.object) &&
328+
path.node.callee.object.name === 'React' &&
329+
t.isIdentifier(path.node.callee.property) &&
330+
path.node.callee.property.name === 'cloneElement')
303331
) {
304332
visitCloneElementPath(path as NodePath<t.CallExpression>, {
305333
context: 'root',

packages/babel-plugin/src/clone-element/index.ts

+7-59
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,12 @@ import * as t from '@babel/types';
33

44
import type { Metadata } from '../types';
55
import { buildCompiledCloneElement } from '../utils/build-compiled-component';
6-
import { buildCssVariables } from '../utils/build-css-variables';
76
import { buildCss } from '../utils/css-builders';
87
import { getRuntimeClassNameLibrary } from '../utils/get-runtime-class-name-library';
98
import { resolveIdentifierComingFromDestructuring } from '../utils/resolve-binding';
109
import { transformCssItems } from '../utils/transform-css-items';
1110
import type { CSSOutput } from '../utils/types';
1211

13-
/**
14-
* Handles style prop value. If variables are present it will replace its value with it
15-
* otherwise will add undefined.
16-
*
17-
* @param variables CSS variables prop to be placed as inline styles
18-
* @param path Any Expression path
19-
*/
20-
const handleStyleProp = (variables: CSSOutput['variables'], path: NodePath<t.Expression>) => {
21-
const styleValue = variables.length
22-
? t.objectExpression(buildCssVariables(variables))
23-
: t.identifier('undefined');
24-
25-
path.replaceWith(styleValue);
26-
};
27-
2812
/**
2913
* Extracts styles from an expression.
3014
*
@@ -128,9 +112,13 @@ export const visitCloneElementPath = (path: NodePath<t.CallExpression>, meta: Me
128112
// find ancestor cloneElement callExpression
129113
const ancestorPath = path.findParent(
130114
(p) =>
131-
p.isCallExpression() &&
132-
t.isIdentifier(p.node.callee) &&
133-
p.node.callee.name === 'cloneElement'
115+
(p.isCallExpression() &&
116+
t.isIdentifier(p.node.callee) &&
117+
p.node.callee.name === meta.state.reactImports?.cloneElement) ||
118+
(p.isCallExpression() &&
119+
t.isMemberExpression(p.node.callee) &&
120+
t.isIdentifier(p.node.callee.property) &&
121+
p.node.callee.property.name === 'cloneElement')
134122
) as NodePath<t.CallExpression>;
135123

136124
if (!ancestorPath) {
@@ -140,44 +128,4 @@ export const visitCloneElementPath = (path: NodePath<t.CallExpression>, meta: Me
140128
ancestorPath.replaceWith(buildCompiledCloneElement(ancestorPath.node, builtCss, meta));
141129
},
142130
});
143-
144-
// // Second pass to replace all usages of `style`.
145-
// path.traverse({
146-
// Expression(path) {
147-
// if (t.isIdentifier(path.node)) {
148-
// if (path.parentPath.isProperty()) {
149-
// return;
150-
// }
151-
152-
// // style={style}
153-
// if (path.node.name === 'style' && path.scope.hasOwnBinding('style')) {
154-
// handleStyleProp(collectedVariables, path);
155-
// }
156-
157-
// // style={style} rename prop
158-
// if (path.scope.hasOwnBinding(path.node.name)) {
159-
// const binding = path.scope.getBinding(path.node.name)?.path.node;
160-
161-
// if (
162-
// !!resolveIdentifierComingFromDestructuring({
163-
// name: 'style',
164-
// node: binding as t.Expression,
165-
// })
166-
// ) {
167-
// handleStyleProp(collectedVariables, path);
168-
// }
169-
// }
170-
// } else if (t.isMemberExpression(path.node)) {
171-
// // filter out invalid calls like dontexist.style
172-
// if (t.isIdentifier(path.node.object) && !path.scope.hasOwnBinding(path.node.object.name)) {
173-
// return;
174-
// }
175-
176-
// // style={props.style}
177-
// if (t.isIdentifier(path.node.property) && path.node.property.name === 'style') {
178-
// handleStyleProp(collectedVariables, path);
179-
// }
180-
// }
181-
// },
182-
// });
183131
};

packages/babel-plugin/src/types.ts

+16
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,22 @@ export interface State extends PluginPass {
138138
cssMap?: string[];
139139
};
140140

141+
/**
142+
* Returns the name of the cloneElement import specifier if it is imported.
143+
* If an alias is used, the alias will be returned.
144+
*
145+
* E.g:
146+
*
147+
* ```
148+
* import { cloneElement as myCloneElement } from 'react';
149+
* ```
150+
*
151+
* Returns `myCloneElement`.
152+
*/
153+
reactImports?: {
154+
cloneElement?: string;
155+
};
156+
141157
usesXcss?: boolean;
142158

143159
importedCompiledImports?: {

packages/babel-plugin/src/utils/build-compiled-component.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -168,9 +168,12 @@ export const buildCompiledCloneElement = (
168168
(prop) => t.isObjectProperty(prop) && t.isIdentifier(prop.key) && prop.key.name === 'className'
169169
);
170170

171-
if (classNameProperty && t.isObjectProperty(classNameProperty)) {
172-
const classNameExpression = getExpression(classNameProperty.value);
173-
const values: t.Expression[] = classNames.concat(classNameExpression);
171+
if (
172+
classNameProperty &&
173+
t.isObjectProperty(classNameProperty) &&
174+
t.isIdentifier(classNameProperty.value)
175+
) {
176+
const values: t.Expression[] = classNames.concat(classNameProperty.value);
174177

175178
classNameProperty.value = t.callExpression(t.identifier(getRuntimeClassNameLibrary(meta)), [
176179
t.arrayExpression(values),

0 commit comments

Comments
 (0)