-
Notifications
You must be signed in to change notification settings - Fork 2
/
index.js
148 lines (134 loc) · 5.02 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
/**
* Glamorous to Emotion codemod
*
* This babel plugin should migrate any existing codebase
* using React or Preact and glamorous to one using
* emotion (emotion.sh).
*
* It follows the glamorous to emotion migration guide
* found at https://github.com/paypal/glamorous/blob/master/other/EMOTION_MIGRATION.md
*
* You can use it as a babel plugin by adding it to your .babelrc
* under "plugins", or use it as a one-off codemod by using the
* babel cli:
*
* babel [your-source-dir] --plugins=glamorous-to-emotion --presets=react,etc... --out-dir=[your-source-dir]
*
* A demo can be seen at:
* https://astexplorer.net/#/gist/7bc4771564a12c9f93c4904b3934aa1c/latests
*/
module.exports = function(babel) {
const {types: t} = babel;
// glamorous and emotion treat the css attribute "content" differently.
// we need to put it's content inside a string.
// i.e. turn {content: ""} into {content: '""'}
const fixContentProp = glamorousFactoryArguments => {
return glamorousFactoryArguments.map(arg => {
if (t.isObjectExpression(arg)) {
arg.properties = arg.properties.map(
prop =>
prop.key.name === "content"
? {...prop, value: t.stringLiteral(`"${prop.value.value}"`)}
: prop
);
}
// TODO: if `arg` is a function, we might want to inspect its return value
return arg;
});
};
const glamorousVisitor = {
// for each reference to an identifier...
ReferencedIdentifier(path, {getNewName, oldName}) {
// skip if the name of the identifier does not correspond to the name of glamorous default import
if (path.node.name !== oldName) return;
switch (path.parent.type) {
// replace `glamorous()` with `styled()`
case "CallExpression": {
path.node.name = getNewName();
break;
}
// replace `glamorous.div()` with `styled("div")()`
case "MemberExpression": {
const grandParentPath = path.parentPath.parentPath;
if (t.isCallExpression(grandParentPath.node)) {
grandParentPath.replaceWith(
t.callExpression(
t.callExpression(t.identifier(getNewName()), [
t.stringLiteral(grandParentPath.node.callee.property.name),
]),
fixContentProp(grandParentPath.node.arguments)
)
);
} else {
throw new Error(
`Not sure how to deal with glamorous within MemberExpression @ ${path.node.loc}`
);
}
break;
}
// replace <glamorous.Div/> with `<div/>`
case "JSXMemberExpression": {
const grandParent = path.parentPath.parent;
grandParent.name = t.identifier(grandParent.name.property.name.toLowerCase());
if (t.isJSXOpeningElement(grandParent) && grandParent.attributes) {
console.warn(
"The current version of the codemod has only weak support of the '<glamorous.Div>' syntax. It won't transform any attributes."
);
}
break;
}
default: {
console.warning("Found glamorous being used in an unkonwn context:", path.parent.type);
}
}
},
};
return {
name: "glamorousToEmotion",
visitor: {
ImportDeclaration(path, {opts}) {
const {value: libName} = path.node.source;
if (libName !== "glamorous" && libName !== "glamorous.macro") {
return;
}
// use "styled" as new default import, only if there's no such variable in use yet
const newName = path.scope.hasBinding("styled")
? path.scope.generateUidIdentifier("styled").name
: "styled";
let newImports = [];
let useDefaultImport = false;
// only if the traversal below wants to know the newName,
// we're gonna add the default import
const getNewName = () => {
if (!useDefaultImport) {
newImports.push(
t.importDeclaration(
[t.importDefaultSpecifier(t.identifier(newName))],
t.stringLiteral(opts.preact ? "preact-emotion" : "react-emotion")
)
);
useDefaultImport = true;
}
return newName;
};
// only if the default import of glamorous is used, we're gonna apply the transforms
path.node.specifiers.filter(s => t.isImportDefaultSpecifier(s)).forEach(s => {
path.parentPath.traverse(glamorousVisitor, {getNewName, oldName: s.local.name});
});
const themeProvider = path.node.specifiers.find(
specifier => specifier.local.name === "ThemeProvider"
);
if (themeProvider) {
newImports.push(
t.importDeclaration(
[t.importSpecifier(t.identifier("ThemeProvider"), t.identifier("ThemeProvider"))],
t.stringLiteral("emotion-theming")
)
);
}
newImports.forEach(ni => path.insertBefore(ni));
path.remove();
},
},
};
};