UNPKG

@twstyled/babel-preset

Version:

Babel plugin for twstyled -- the full-featured Tailwind CSS + CSS in JS Compiler

241 lines (240 loc) 11.1 kB
"use strict"; 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;