@twstyled/babel-preset
Version:
Babel plugin for twstyled -- the full-featured Tailwind CSS + CSS in JS Compiler
112 lines (111 loc) • 4.75 kB
JavaScript
;
// Most of this code was taken from @satya164's babel-plugin-css-prop
// and styled-components babel plugin
// @see https://github.com/satya164/babel-plugin-css-prop
// @see https://github.com/styled-components/babel-plugin-styled-components
Object.defineProperty(exports, "__esModule", { value: true });
const util_1 = require("./util");
const TAG_NAME_REGEXP = /^[a-z][a-z\d]*(\-[a-z][a-z\d]*)?$/;
function visitorPreprocessToStyled({ types: t }, path, state, css) {
const styledIdentifier = util_1.assureImport({ types: t }, state, 'styled', '@twstyled/core');
//
// Derive name of replacement Component tag
// div => first unique version of TwCssDiv...
//
const elem = path.parentPath;
const name = getName(elem.node.name, t);
const nameExpression = getNameExpression(elem.node.name, t);
const id = path.scope.generateUidIdentifier('TwCss' + name.replace(/^([a-z])/, (match, p1) => p1.toUpperCase()));
//
// Keep all attributes on JSX element except for the css attribute being processed here
// and rename opening and closing
//
elem.node.attributes = elem.node.attributes.filter((attr) => attr !== path.node);
elem.node.name = t.jSXIdentifier(id.name);
if (elem.parentPath.node.closingElement) {
;
elem.parentPath
.node.closingElement.name = t.jSXIdentifier(id.name);
}
//
// Setup new styled function to inject with original name
// If a React component is used that is not imported, then set up injection
// point immediately after its definition
//
const program = state.file.path;
const { bindings } = program.scope;
let styled;
let injector;
if (TAG_NAME_REGEXP.test(name)) {
// is a generic html element
styled = t.callExpression(styledIdentifier, [t.stringLiteral(name)]);
}
else {
// is a React component
styled = t.callExpression(styledIdentifier, [nameExpression]);
if (bindings[name] && !t.isImportDeclaration(bindings[name].path.parent)) {
injector = (nodeToInsert) => (t.isVariableDeclaration(bindings[name].path.parent)
? bindings[name].path.parentPath
: bindings[name].path).insertAfter(nodeToInsert);
}
}
//
// Make sure that any embedded expressions can be transferred to new component scope
//
if (t.isObjectExpression(css)) {
// object syntax
throw new Error('object syntax is not yet supported in css attributes');
}
else {
// tagged template literal
css.expressions = css.expressions.reduce((acc, expression) => {
const target = t.isMemberExpression(expression)
? expression.object
: expression;
if (Object.keys(bindings).some((key) => bindings[key].referencePaths.find((p) => p.node === target)) ||
t.isFunctionExpression(expression) ||
t.isArrowFunctionExpression(expression)) {
// expression used is in outer scope so can just be transferred as is
acc.push(expression);
}
else {
// expression used needs to be transferred via a new property
const name = path.scope.generateUidIdentifier('_$p_');
elem.node.attributes.push(t.jSXAttribute(t.jSXIdentifier(name.name), t.jSXExpressionContainer(expression)));
const p = t.identifier('p');
const fn = t.arrowFunctionExpression([p], t.memberExpression(p, name));
fn.loc = path.node.loc;
acc.push(fn);
}
return acc;
}, []);
}
if (!injector) {
injector = (nodeToInsert) => program.node.body.push(nodeToInsert);
}
path.remove();
injector(t.variableDeclaration('var', [
t.variableDeclarator(id, t.isObjectExpression(css)
? t.callExpression(styled, [css])
: t.taggedTemplateExpression(styled, css))
]));
}
exports.default = visitorPreprocessToStyled;
function getName(node, t) {
if (t.isJSXMemberExpression(node)) {
return `${getName(node.object, t)}.${node.property.name}`;
}
if (typeof node.name === 'string') {
return node.name;
}
throw new Error(`Cannot infer name from node with type "${node.type}". `);
}
function getNameExpression(node, t) {
if (t.isJSXMemberExpression(node)) {
return t.memberExpression(getNameExpression(node.object, t), t.identifier(node.property.name));
}
if (typeof node.name === 'string') {
return t.identifier(node.name);
}
throw new Error(`Cannot infer name expression from node with type "${node.type}". `);
}