syntax
199 | if (originalCssValue) {
200 | originalCssValue.expression = stylesObject;
201 | } else {
202 | transformedJsxAttrs.push(
203 | t.jsxAttribute(t.jsxIdentifier("css"), t.jsxExpressionContainer(stylesObject))
204 | );
205 | }
206 | } else {
207 | // if we don't allow using the css prop, use
syntax
208 | let classNameValue;
209 | if (!classNameAttr && !spreadsAttrs.length) {
210 | const cssCall = t.callExpression(getCssFn(), [stylesObject]);
211 | classNameValue = t.jsxExpressionContainer(cssCall);
212 | } else {
213 | let args = [];
214 | if (classNameAttr) {
215 | // if className is already present use
syntax
216 | args.push(classNameAttr.value);
217 | } else {
218 | // if spreads are present use
syntax
219 | spreadsAttrs.forEach(attr => {
220 | args.push(t.memberExpression(attr.argument, t.identifier("className")));
221 | });
222 | }
223 | args.push(stylesObject);
224 | const cxCall = t.callExpression(getCxFn(), args);
225 | classNameValue = t.jsxExpressionContainer(cxCall);
226 | }
227 | if (classNameAttr) {
228 | classNameAttr.value = classNameValue;
229 | } else {
230 | transformedJsxAttrs.push(t.jsxAttribute(t.jsxIdentifier("className"), classNameValue));
231 | }
232 | }
233 | }
234 | return transformedJsxAttrs;
235 | };
236 |
237 | const glamorousVisitor = {
238 | // for each reference to an identifier...
239 | ReferencedIdentifier(path, {getStyledFn, oldName, mode, getCssFn, getCxFn, useJsxPragma}) {
240 | // skip if the name of the identifier does not correspond to the name of glamorous default import
241 | if (path.node.name !== oldName) return;
242 |
243 | switch (path.parent.type) {
244 | // replace `glamorous()` with `styled()`
245 | case "CallExpression": {
246 | const transformedArguments = processGlamorousArgs(path.parent.arguments);
247 | path.parentPath.replaceWith(t.callExpression(getStyledFn(), transformedArguments));
248 | break;
249 | }
250 |
251 | // replace `glamorous.div()` with `styled("div")()`
252 | case "MemberExpression": {
253 | const grandParentPath = path.parentPath.parentPath;
254 | if (t.isCallExpression(grandParentPath.node)) {
255 | grandParentPath.replaceWith(
256 | t.callExpression(
257 | t.callExpression(getStyledFn(), [
258 | t.stringLiteral(grandParentPath.node.callee.property.name),
259 | ]),
260 | fixContentProp(grandParentPath.node.arguments)
261 | )
262 | );
263 | } else {
264 | throw new Error(
265 | `Not sure how to deal with glamorous within MemberExpression @ ${path.node.loc}`
266 | );
267 | }
268 | break;
269 | }
270 |
271 | // replace
with `
`
272 | case "JSXMemberExpression": {
273 | const grandParent = path.parentPath.parent;
274 | const tagName = grandParent.name.property.name.toLowerCase();
275 | grandParent.name = t.identifier(tagName);
276 | if (t.isJSXOpeningElement(grandParent)) {
277 | grandParent.attributes = transformJSXAttributes({
278 | tagName,
279 | jsxAttrs: grandParent.attributes,
280 | mode,
281 | getCssFn,
282 | getCxFn,
283 | useJsxPragma,
284 | });
285 | }
286 | break;
287 | }
288 |
289 | default: {
290 | console.warning("Found glamorous being used in an unkonwn context:", path.parent.type);
291 | }
292 | }
293 | },
294 | };
295 |
296 | return {
297 | name: "glamorousToEmotion",
298 | visitor: {
299 | ImportDeclaration(path, {opts}) {
300 | const {value: libName} = path.node.source;
301 | const mode = MODES[opts.mode] || MODES.withJsxPragma;
302 | if (libName !== "glamorous" && libName !== "glamorous.macro") {
303 | return;
304 | }
305 |
306 | // use "name" as identifier, but only if it's not already used in the current scope
307 | const createUniqueIdentifier = name =>
308 | path.scope.hasBinding(name) ? path.scope.generateUidIdentifier(name) : t.identifier(name);
309 |
310 | // this object collects all the imports we'll need to add
311 | let imports = {};
312 |
313 | const getStyledFn = () => {
314 | if (!imports["@emotion/styled"]) {
315 | imports["@emotion/styled"] = {
316 | default: t.importDefaultSpecifier(createUniqueIdentifier("styled")),
317 | };
318 | }
319 | return imports["@emotion/styled"].default.local;
320 | };
321 |
322 | const getCssFn = () => {
323 | if (!imports["@emotion/core"]) imports["@emotion/core"] = {};
324 | if (!imports["@emotion/core"].css) {
325 | const specifier = t.importSpecifier(t.identifier("css"), createUniqueIdentifier("css"));
326 | imports["@emotion/core"].css = specifier;
327 | }
328 | return imports["@emotion/core"].css.local;
329 | };
330 |
331 | const getCxFn = () => {
332 | if (!imports["emotion"]) imports["emotion"] = {};
333 | if (!imports["emotion"].cx) {
334 | const specifier = t.importSpecifier(t.identifier("cx"), createUniqueIdentifier("cx"));
335 | imports["emotion"].cx = specifier;
336 | }
337 | return imports["emotion"].cx.local;
338 | };
339 |
340 | const useJsxPragma = () => {
341 | if (!imports["@emotion/core"]) imports["@emotion/core"] = {};
342 | if (!imports["@emotion/core"].jsx) {
343 | t.addComment(path.parent, "leading", "* @jsx jsx ");
344 | const specifier = t.importSpecifier(t.identifier("jsx"), createUniqueIdentifier("jsx"));
345 | imports["@emotion/core"].jsx = specifier;
346 | }
347 | };
348 |
349 | // only if the default import of glamorous is used, we're gonna apply the transforms
350 | path.node.specifiers
351 | .filter(s => t.isImportDefaultSpecifier(s))
352 | .forEach(s => {
353 | path.parentPath.traverse(glamorousVisitor, {
354 | getStyledFn,
355 | oldName: s.local.name,
356 | mode,
357 | getCssFn,
358 | getCxFn,
359 | useJsxPragma,
360 | });
361 | });
362 |
363 | /*
364 | `import {Span, Div as StyledDiv} from "glamorous"`
365 | will be represented as `importedTags = {"Span": "span", "StyledDiv": "div"}`
366 | */
367 | const importedTags = {};
368 |
369 | path.node.specifiers
370 | .filter(s => t.isImportSpecifier(s))
371 | .forEach(({imported, local}) => {
372 | const tagName = imported.name.toLowerCase();
373 | if (validElementNames.has(tagName)) {
374 | importedTags[local.name] = tagName;
375 | }
376 | });
377 |
378 | // transform corresponding JSXElements if any html element imports were found
379 | if (Object.keys(importedTags).length) {
380 | path.parentPath.traverse({
381 | "JSXOpeningElement|JSXClosingElement": path => {
382 | const componentIdentifier = path.node.name;
383 | // exclude MemberEpressions
384 | if (!t.isJSXIdentifier(componentIdentifier)) return;
385 |
386 | const targetTagName = importedTags[componentIdentifier.name];
387 | if (!targetTagName) return;
388 |
389 | componentIdentifier.name = targetTagName;
390 | if (t.isJSXOpeningElement(path)) {
391 | path.node.attributes = transformJSXAttributes({
392 | targetTagName,
393 | jsxAttrs: path.node.attributes,
394 | mode,
395 | getCssFn,
396 | getCxFn,
397 | useJsxPragma,
398 | });
399 | }
400 | },
401 | });
402 | }
403 |
404 | const themeProvider = path.node.specifiers.find(
405 | specifier => specifier.local.name === "ThemeProvider"
406 | );
407 |
408 | if (themeProvider) {
409 | path.insertBefore(
410 | t.importDeclaration(
411 | [t.importSpecifier(t.identifier("ThemeProvider"), t.identifier("ThemeProvider"))],
412 | t.stringLiteral("emotion-theming")
413 | )
414 | );
415 | }
416 |
417 | const preactLibs = {
418 | "@emotion/styled": "@emotion/preact-styled",
419 | };
420 |
421 | // if we used any emotion imports, we add them before the glamorous import path
422 | Object.entries(imports).forEach(([rawLibName, libImports]) => {
423 | const libName = (opts.preact && preactLibs[rawLibName]) || rawLibName;
424 | path.insertBefore(
425 | t.importDeclaration(Object.values(libImports), t.stringLiteral(libName))
426 | );
427 | });
428 |
429 | path.remove();
430 | },
431 | },
432 | };
433 | };
434 |
435 | module.exports.MODES = MODES;
436 |
--------------------------------------------------------------------------------