shadcn-vue/packages/cli/src/utils/transformers/transform-tw-prefix.ts

108 lines
3.6 KiB
TypeScript

import type { CodemodPlugin } from 'vue-metamorph'
import { splitClassName } from './transform-css-vars'
import type { TransformOpts } from '.'
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
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 === 'Literal' && typeof node.value === 'string') {
if (!['BinaryExpression', 'Property'].includes(node.parent?.type ?? '')) {
node.value = applyPrefix(node.value, config.tailwind.prefix)
transformCount++
}
}
// handle class attribute without binding
else if (node.type === 'VLiteral' && typeof node.value === 'string') {
if (node.parent.key.name === 'class') {
node.value = `"${applyPrefix(node.value, 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
}