diff options
Diffstat (limited to '@linaria/packages/babel/src/evaluators/visitors')
3 files changed, 122 insertions, 0 deletions
diff --git a/@linaria/packages/babel/src/evaluators/visitors/JSXElement.ts b/@linaria/packages/babel/src/evaluators/visitors/JSXElement.ts new file mode 100644 index 0000000..89a0ccd --- /dev/null +++ b/@linaria/packages/babel/src/evaluators/visitors/JSXElement.ts @@ -0,0 +1,56 @@ +import { types as t } from '@babel/core'; +import type { NodePath } from '@babel/traverse'; +import type { Function, JSXElement as JSXElementNode } from '@babel/types'; + +function getFunctionName(path: NodePath<Function>): string | null { + if (path.isClassMethod() && t.isIdentifier(path.node.key)) { + return path.node.key.name; + } + + return null; +} + +export default function JSXElement(path: NodePath<JSXElementNode>) { + // JSX can be safely replaced on an empty fragment because it is unnecessary for styles + const emptyFragment = t.jsxFragment( + t.jsxOpeningFragment(), + t.jsxClosingFragment(), + [] + ); + + // We can do even more + // If that JSX is a result of a function, we can replace the function body. + const scopePath = path.scope.path; + if (scopePath.isFunction()) { + const emptyBody = t.blockStatement([t.returnStatement(emptyFragment)]); + + // Is it not just a function, but a method `render`? + if (getFunctionName(scopePath) === 'render') { + const decl = scopePath.findParent((p) => p.isClassDeclaration()); + + // Replace the whole component + if (decl?.isClassDeclaration()) { + decl.replaceWith(t.functionDeclaration(decl.node.id, [], emptyBody)); + + return; + } + } + + const body = scopePath.get('body'); + if (Array.isArray(body)) { + throw new Error( + `A body of a function is expected to be a single element but an array was returned. It's possible if JS syntax has been changed since that code was written.` + ); + } + + const node: typeof scopePath.node = { + ...scopePath.node, + body: emptyBody, + params: [], + }; + + scopePath.replaceWith(node); + } else { + path.replaceWith(emptyFragment); + } +} diff --git a/@linaria/packages/babel/src/evaluators/visitors/ProcessCSS.ts b/@linaria/packages/babel/src/evaluators/visitors/ProcessCSS.ts new file mode 100644 index 0000000..6f737a1 --- /dev/null +++ b/@linaria/packages/babel/src/evaluators/visitors/ProcessCSS.ts @@ -0,0 +1,20 @@ +/** + * This visitor replaces css tag with the generated className + * + */ + +import { types as t } from '@babel/core'; +import type { NodePath } from '@babel/traverse'; +import type { TaggedTemplateExpression } from '@babel/types'; +import getLinariaComment from '../../utils/getLinariaComment'; + +export default function ProcessCSS(path: NodePath<TaggedTemplateExpression>) { + if (t.isIdentifier(path.node.tag) && path.node.tag.name === 'css') { + const [, , , className] = getLinariaComment(path); + if (!className) { + return; + } + + path.replaceWith(t.stringLiteral(className)); + } +} diff --git a/@linaria/packages/babel/src/evaluators/visitors/ProcessStyled.ts b/@linaria/packages/babel/src/evaluators/visitors/ProcessStyled.ts new file mode 100644 index 0000000..a9e38c5 --- /dev/null +++ b/@linaria/packages/babel/src/evaluators/visitors/ProcessStyled.ts @@ -0,0 +1,46 @@ +/** + * This visitor replaces styled components with metadata about them. + * CallExpression should be used to match styled components. + * Works out of the box for styled that wraps other component, + * styled.tagName are transformed to call expressions using @babel/plugin-transform-template-literals + * @babel/plugin-transform-template-literals is loaded as a prest, to force proper ordering. It has to run just after linaria. + * It is used explicitly in extractor, and loaded as a part of `prest-env` in shaker + */ + +import { types as t } from '@babel/core'; +import type { NodePath } from '@babel/traverse'; +import type { CallExpression } from '@babel/types'; +import { expression } from '@babel/template'; +import getLinariaComment from '../../utils/getLinariaComment'; + +const linariaComponentTpl = expression( + `{ + displayName: %%displayName%%, + __linaria: { + className: %%className%%, + extends: %%extends%% + } + }` +); + +export default function ProcessStyled(path: NodePath<CallExpression>) { + const [type, , displayName, className] = getLinariaComment(path); + if (!className) { + return; + } + + if (type === 'css') { + path.replaceWith(t.stringLiteral(className)); + return; + } + + path.replaceWith( + linariaComponentTpl({ + className: t.stringLiteral(className), + displayName: displayName ? t.stringLiteral(displayName) : null, + extends: t.isCallExpression(path.node.callee) + ? path.node.callee.arguments[0] + : t.nullLiteral(), + }) + ); +} |