import type { CodemodPlugin } from 'vue-metamorph' import type { TransformOpts } from '.' import { splitClassName } from './transform-css-vars' export function transformTwPrefix(opts: TransformOpts): CodemodPlugin { return { type: 'codemod', name: 'add prefix to tailwind classes', transform({ scriptASTs, sfcAST, utils: { traverseScriptAST, traverseTemplateAST, astHelpers } }) { let transformCount = 0 const { config } = opts const CLASS_IDENTIFIER = ['class', 'classes'] if (!config.tailwind?.prefix) return transformCount for (const scriptAST of scriptASTs) { traverseScriptAST(scriptAST, { visitCallExpression(path) { if (path.node.callee.type === 'Identifier' && path.node.callee.name === 'cva') { const nodes = path.node.arguments nodes.forEach((node) => { // cva(base, ...) if (node.type === 'Literal' && typeof node.value === 'string') { node.value = applyPrefix(node.value, config.tailwind.prefix) transformCount++ } else if (node.type === 'ObjectExpression') { node.properties.forEach((node) => { // cva(..., { variants: { ... } }) if (node.type === 'Property' && node.key.type === 'Identifier' && node.key.name === 'variants') { const nodes = astHelpers.findAll(node, { type: 'Literal' }) nodes.forEach((node) => { if (typeof node.value === 'string') { node.value = applyPrefix(node.value, config.tailwind.prefix) transformCount++ } }) } }) } }) } return this.traverse(path) }, }) } if (sfcAST) { traverseTemplateAST(sfcAST, { enterNode(node) { if (node.type === 'VAttribute' && node.key.type === 'VDirectiveKey') { if (node.key.argument?.type === 'VIdentifier') { if (CLASS_IDENTIFIER.includes(node.key.argument.name)) { const nodes = astHelpers.findAll(node, { type: 'Literal' }) nodes.forEach((node) => { if (!['BinaryExpression', 'Property'].includes(node.parent?.type ?? '') && typeof node.value === 'string') { node.value = applyPrefix(node.value, config.tailwind.prefix) transformCount++ } }) } } } // handle class attribute without binding else if (node.type === 'VLiteral' && typeof node.value === 'string') { if (CLASS_IDENTIFIER.includes(node.parent.key.name)) { node.value = `"${applyPrefix(node.value.replace(/"/g, ''), config.tailwind.prefix)}"` transformCount++ } } }, leaveNode() { }, }) } return transformCount }, } } export function applyPrefix(input: string, prefix: string = '') { const classNames = input.split(' ') const prefixed: string[] = [] for (const className of classNames) { const [variant, value, modifier] = splitClassName(className) if (variant) { modifier ? prefixed.push(`${variant}:${prefix}${value}/${modifier}`) : prefixed.push(`${variant}:${prefix}${value}`) } else { modifier ? prefixed.push(`${prefix}${value}/${modifier}`) : prefixed.push(`${prefix}${value}`) } } return prefixed.join(' ') } export function applyPrefixesCss(css: string, prefix: string) { const lines = css.split('\n') for (const line of lines) { if (line.includes('@apply')) { const originalTWCls = line.replace('@apply', '').trim() const prefixedTwCls = applyPrefix(originalTWCls, prefix) css = css.replace(originalTWCls, prefixedTwCls) } } return css }