@twstyled/babel-preset
Version:
Babel plugin for twstyled -- the full-featured Tailwind CSS + CSS in JS Compiler
241 lines (240 loc) • 11.1 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.withLoc = exports.assureImport = exports.combineExpressions = exports.prefixTemplateLiteral = exports.wrapExpression = exports.asTemplateLiteralFromArray = exports.asTemplateLiteral = void 0;
const StarterRegExp = /^\s*(@tailwind)?\s*/;
const EndingRegExp = /\s*;?\s*$/;
function asTemplateLiteral({ types: t }, item) {
let css;
if (t.isStringLiteral(item)) {
css = t.templateLiteral([
withLoc(t.templateElement({ raw: item.value, cooked: item.value }, true), item.loc)
], []);
}
else if (t.isJSXExpressionContainer(item)) {
if (t.isTemplateLiteral(item.expression)) {
css = item.expression;
}
else if (t.isTaggedTemplateExpression(item.expression) &&
t.isIdentifier(item.expression.tag) &&
item.expression.tag.name === 'css') {
css = item.expression.quasi;
}
else if (t.isObjectExpression(item.expression)) {
throw new Error('Not yet implemented -- object expression in css or tw attribute');
}
else if (t.isJSXEmptyExpression(item.expression)) {
css = t.templateLiteral([withLoc(t.templateElement({ raw: '', cooked: '' }, true), item.loc)], []);
}
else if (t.isStringLiteral(item.expression) ||
t.isNumericLiteral(item.expression)) {
css = t.templateLiteral([
withLoc(t.templateElement({
raw: `${item.expression.value}`,
cooked: `${item.expression.value}`
}, false), item.expression.loc)
], []);
}
else {
css = t.templateLiteral([
withLoc(t.templateElement({ raw: '', cooked: '' }, false), item.expression.loc),
withLoc(t.templateElement({ raw: '', cooked: '' }, true), item.expression.loc)
], [item.expression]);
}
}
else {
throw new Error('unable to transcribe css');
}
return css;
}
exports.asTemplateLiteral = asTemplateLiteral;
function asTemplateLiteralFromArray({ types: t }, prefix, items) {
const quasis = [];
const expressions = [];
items.forEach((item) => {
if (item === null) {
return;
}
if (t.isSpreadElement(item)) {
throw new Error('spread element not implemented');
}
quasis.push(withLoc(t.templateElement({
raw: prefix,
cooked: prefix
}, false), item.loc));
expressions.push(item);
});
quasis.push(withLoc(t.templateElement({
raw: prefix,
cooked: prefix
}, false), items[0].loc));
quasis[quasis.length - 1].tail = true;
return t.templateLiteral(quasis, expressions);
}
exports.asTemplateLiteralFromArray = asTemplateLiteralFromArray;
/**
* Utility function to wrap a given expression that might appear in a JSX tag
* <Component twstyled="bg-blue-500" /> or <Component twstyled="{`bg-blue-500 ${mixin}`}" /> etc.
* The function returns a tagged template literal wrapped with the specified starting and ending strings
* <Component className={css`bg-blue-500;`} /> or <Component className={css`bg-blue-500 ${mixin};`} />
* @param param0 Babel
* @param value The expression to wrap
* @param tag The target tag of the tagged template literal, e.g., css
* @param startString The string to insert at the beginning e.g. "@tailwind "
* @param endString The string to insert at the end e.g, ";"
*/
function wrapExpression({ types: t }, node, tag, startReplacement = '@tailwind ', endReplacement = ';') {
let result;
if (t.isTaggedTemplateExpression(node)) {
result = asWrappedTaggedTemplateExpression({ types: t }, node.quasi, tag, startReplacement, endReplacement);
}
else if (t.isStringLiteral(node)) {
result = t.taggedTemplateExpression(t.identifier(tag), t.templateLiteral([
withLoc(t.templateElement(replaceQuasi(replaceQuasi({ raw: node.value }, StarterRegExp, startReplacement), EndingRegExp, endReplacement), true), node.loc)
], []));
}
else if (t.isJSXExpressionContainer(node)) {
/** noop */
const expression = node.expression;
if (t.isTemplateLiteral(expression)) {
result = asWrappedTaggedTemplateExpression({ types: t }, expression, tag, startReplacement, endReplacement);
}
else if (t.isTaggedTemplateExpression(expression) &&
expression.tag.name === tag) {
result = asWrappedTaggedTemplateExpression({ types: t }, expression.quasi, tag, startReplacement, endReplacement);
}
else if (t.isJSXEmptyExpression(expression)) {
result = t.stringLiteral('');
} /* t.isExpression(expression) */
else {
/**
* Emmbed original expression a new one
* e.g, <Componenent classname={css`@tailwind ${anytag`bg-blue-500`};`} />
* * e.g, <Componenent classname={css`@tailwind ${anyfn('bg-blue-500')};`} />
*/
result = t.taggedTemplateExpression(t.identifier(tag), t.templateLiteral([
withLoc(t.templateElement({
raw: startReplacement,
cooked: startReplacement
}), expression.loc),
withLoc(t.templateElement({
raw: endReplacement,
cooked: endReplacement
}), expression.loc)
], [expression]));
}
}
else {
result = t.stringLiteral('');
}
return result;
}
exports.wrapExpression = wrapExpression;
function replaceQuasi(value, pattern, replacement) {
const raw = value.raw.replace(pattern, replacement);
const cooked = value.cooked ? value.cooked.replace(pattern, replacement) : raw;
return { raw, cooked };
}
function prefixTemplateLiteral({ types: t }, templateLiteral, startReplacement, endReplacement) {
const quasis = templateLiteral.quasis;
quasis[0] = withLoc(t.templateElement(replaceQuasi(quasis[0].value, StarterRegExp, startReplacement), quasis[0].tail), quasis[0].loc);
quasis[quasis.length - 1] = withLoc(t.templateElement(replaceQuasi(quasis[quasis.length - 1].value, EndingRegExp, endReplacement), true), quasis[quasis.length - 1].loc);
return t.templateLiteral(quasis, templateLiteral.expressions);
}
exports.prefixTemplateLiteral = prefixTemplateLiteral;
function asWrappedTaggedTemplateExpression({ types: t }, templateLiteral, tag, startReplacement, endReplacement) {
return t.taggedTemplateExpression(t.identifier(tag), prefixTemplateLiteral({ types: t }, templateLiteral, startReplacement, endReplacement));
}
/**
* Utility function to wrap a given expression that might appear in a JSX tag
* <Component twstyled="bg-blue-500" /> or <Component twstyled="{`bg-blue-500 ${mixin}`}" /> etc.
* The function returns a tagged template literal wrapped with the specified starting and ending strings
* <Component className={css`bg-blue-500;`} /> or <Component className={css`bg-blue-500 ${mixin};`} />
* @param param0 Babel
* @param value The expression to wrap
* @param tag The target tag of the tagged template literal, e.g., css
* @param startString The string to insert at the beginning e.g. "@tailwind "
* @param endString The string to insert at the end e.g, ";"
*/
function combineExpressions({ types: t }, leftExpression, rightExpression) {
if (t.isStringLiteral(leftExpression) && t.isStringLiteral(rightExpression)) {
// 1. string - string
return t.stringLiteral(`${leftExpression.value} ${rightExpression.value}`);
}
else if (t.isStringLiteral(leftExpression)) {
if (t.isJSXEmptyExpression(rightExpression)) {
// 2. string - empty
return leftExpression;
}
else {
// 3. string - JSXexpression
return t.jSXExpressionContainer(t.templateLiteral([
withLoc(t.templateElement({
raw: `${leftExpression.value} `,
cooked: `${leftExpression.value} `
}, false), leftExpression.loc),
withLoc(t.templateElement({ raw: '' }, true), rightExpression.loc)
], [rightExpression.expression]));
}
}
else {
if (t.isStringLiteral(rightExpression)) {
// 4. taggedtemplate - string
const templateLiteral = t.templateLiteral([
withLoc(t.templateElement({ raw: '' }, false), leftExpression.loc),
withLoc(t.templateElement({
raw: ` ${rightExpression.value}`,
cooked: ` ${rightExpression.value}`
}, true), rightExpression.loc)
], [leftExpression]);
templateLiteral.loc = rightExpression.loc;
return t.jSXExpressionContainer(templateLiteral);
}
else if (t.isJSXEmptyExpression(rightExpression)) {
// 5. taggedtemplate - empty
return t.jSXExpressionContainer(leftExpression);
}
else {
// 6. taggedtemplate - JSXExpressionContainer
const templateLiteral = t.templateLiteral([
withLoc(t.templateElement({ raw: '' }, false), leftExpression.loc),
withLoc(t.templateElement({ raw: ' ', cooked: ' ' }, false), leftExpression.loc),
withLoc(t.templateElement({ raw: '' }, true), rightExpression.loc)
], [
leftExpression,
rightExpression.expression
]);
templateLiteral.loc = rightExpression.loc;
return t.jSXExpressionContainer(templateLiteral);
}
}
}
exports.combineExpressions = combineExpressions;
function assureImport({ types: t }, state, importName, importSource) {
const program = state.file.path;
let localIdentifier;
if (!state.importLocalNames[importName]) {
localIdentifier =
importName === 'css' || importName === 'styled'
? t.identifier(importName) // linaria disallows renaming of css tag
: program.scope.generateUidIdentifier(importName);
const styledImportSpecifier = t.importSpecifier(localIdentifier, t.identifier(importName));
if (!state.importDeclaration) {
// no declaration at all
program.unshiftContainer('body', t.importDeclaration([styledImportSpecifier], t.stringLiteral(importSource)));
}
else {
state.importDeclaration.node.specifiers.unshift(styledImportSpecifier);
}
state.importLocalNames[importName] = localIdentifier.name;
}
else {
localIdentifier = t.identifier(state.importLocalNames[importName]);
}
return localIdentifier;
}
exports.assureImport = assureImport;
function withLoc(element, loc) {
element.loc = { start: loc === null || loc === void 0 ? void 0 : loc.start, end: loc === null || loc === void 0 ? void 0 : loc.end };
return element;
}
exports.withLoc = withLoc;