shadcn-vue/packages/cli/src/utils/transformers/transform-tw-prefix.ts
2024-10-14 18:13:46 +08:00

117 lines
4.0 KiB
TypeScript

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
}