vue文件转AST,并恢复成vue文件(适用于antdv版本升级)
vue文件转AST,并恢复成vue文件---antdvV3升级V4
- vue文件转AST,重新转回原文件过程
-
- 如何获取项目路径
- 读取项目文件,判断文件类型
- 分别获取vue文件 template js(vue2和vue3)
- 处理vue 文件template部分
- 处理vue script部分
- utils--transform.ts(主要转换函数)
- utils--- antdv3_4
- utils--excapeRe.ts
- 思路流程图
vue文件转AST,重新转回原文件过程
将打包后的dist上传到node,在本地安装。建议安装全局,方便全局使用。
安装:
npm install @my-cli -g
检查是否安装成功
bdi-cli --help .
使用: < path > 替换成项目绝对路径
bdi-cli --antdv <path>
如何获取项目路径
- 配置bin
#!/usr/bin/env node
import {
program } from 'commander';
import antvUpdateV3ToV4 from '../src/antdv_v3_v4'
program
.name('my-cli')
.description('CLI工具')
.version('1.0')
.option('--antdv <cwd>', 'antdv v3升级v4工具')
program.parse();
const options = program.opts();
if (options.antdv) {
antvUpdateV3ToV4(options.antdv);
}
在脚本的顶部使用 #!/usr/bin/env node 这一行被称为 “shebang”(或 “hashbang”)。它在类 Unix 操作系统中用于指示应使用哪个解释器来执行该脚本。以下是它的作用的详细解释:
**Shebang (#!):**这是一个字符序列,告诉操作系统该文件应该使用某个解释器来执行。
**/usr/bin/env:**这是一个命令,用于定位并运行指定的解释器。使用 env 是一种常见做法,因为它会搜索用户的 PATH 环境变量来找到解释器,从而使脚本在不同系统上更具可移植性,因为解释器可能安装在不同的位置。
**node:**这指定了脚本应该使用 Node.js 解释器来运行。
2.配置package.json
读取项目文件,判断文件类型
index.ts
import {
glob } from 'glob'
import {
readFile, writeFile, access, mkdir } from 'node:fs/promises'
import {
extname } from 'node:path'
import * as pathtool from 'path'
import {
vueParser } from './parser/vue'
import {
templateTransformer } from './transformer/template'
import {
javascriptTransformer, JavaScriptCodeType } from './transformer/javascript'
let antdv_v3_v4: {
[prop: string]: Object[] } = {
}
let jsRepalceKeysArray: {
[prop: string]: {
}[] } = {
}
let handlePropArray: {
[prop: string]: {
}[] } = {
}
async function vueHandler(content: string, path: string) {
console.log('vueHandlerpath: ', path);
let resultCode = ''
let changeArr: boolean[] = []
const {
headerComment, template, scriptInfo, otherContent } = vueParser(content)
// 头部注释
resultCode += `${
headerComment}\n`
// 处理template
const {
result: templateResult, handlePropArray: handleProp, hasChange: templateHasChange, jsRepalceKeysArray: jsRepalceKeys } = await templateTransformer(template)
jsRepalceKeysArray[path] = jsRepalceKeys
handlePropArray[path] = handleProp
// resultCode += templateResult
resultCode += `${
templateResult}\n`
changeArr.push(templateHasChange)
antdv_v3_v4[path] = handleProp
// 处理script
for (const item of scriptInfo) {
const codeType = item.type === 'setup' ? JavaScriptCodeType.Vue3Composition : JavaScriptCodeType.Vue2;
const {
hasChange, result, } = await javascriptTransformer(item.content, codeType, jsRepalceKeys);
resultCode += `\n${
item.head}\n${
result}\n</script>\n`;
changeArr.push(hasChange);
}
resultCode += `\n${
otherContent}\n`
if (changeArr.includes(true)) {
//文件是否有变更,变更重新写入,没有不做操作
const filePath = path
const dir = pathtool.dirname(filePath);
try {
//检查目录是否存在
await access(dir);
} catch (error) {
await mkdir(dir, {
recursive: true });
}
await writeFile(filePath, resultCode)
}
}
const main = async (cwd: string) => {
// 获取文件
const matchFiles = await glob('**/*.{vue,js,ts}', {
cwd,//文件名称(绝对路径)
absolute: true
})
let i = 0
for (const path of matchFiles) {
if (path.includes('locales')) continue
// 读取文件内容
const fileContent = await readFile(path, 'utf-8')
// 获取后缀
const ext = extname(path)
switch (ext) {
case '.vue': {
await vueHandler(fileContent, path)
break
}
}
}
// 生成日志
generateLog(cwd + '/ant3_4.json', handlePropArray, jsRepalceKeysArray)
}
const generateLog = async (cwd, templateObj, jsObj) => {
const result = {
}
for (const filePath in templateObj) {
result[filePath] = {
template: templateObj[filePath],
js: jsObj[filePath]
}
}
await writeFile(cwd, JSON.stringify(result, null, 2))
}
export default main;
分别获取vue文件 template js(vue2和vue3)
parser vue.ts
import {
parse } from '@vue/compiler-dom'
import type {
ElementNode } from '@vue/compiler-dom'
function getScriptHead(props) {
let attr: string[] = []
for (const prop of props) {
let val = ''
if (prop.value) {
val = `="${
prop.value.content}"`
}
attr.push(`${
prop.name}${
val}`)
}
const separator = attr.length === 0 ? '' : ' '
return `<script${
separator + attr.join(' ')}>`
}
function extractHeaderComment(content) {
// 使用正则表达式匹配头部注释
const match = content.match(/<!--[\s\S]*?@Description:[\s\S]*?-->/);
if (match) {
return match[0];
} else {
return '';
}
}
interface ScriptInfo {
type: 'setup' | 'optionapi',
head: string
content: string
}
export function vueParser(rawContent: string) {
const result = parse(rawContent)
let headerComment: string = extractHeaderComment(rawContent)
let template: string = ''
let script: string = ''
let scriptSetup: string = ''
let otherContent: string = ''
let scriptHead: string = ''
const scriptInfo: ScriptInfo[] = []
result.children.forEach((item) => {
if ((item as ElementNode)?.tag === 'template') {
template = item.loc.source
} else if (item.type === 1 && item.tag === 'script') {
const tempInfo:ScriptInfo = {
type: 'setup',
head: getScriptHead(item.props),
content: ''
}
scriptHead = getScriptHead(item.props)
if ((item as ElementNode)?.props?.some?.((prop) => prop.name === 'setup') || item.loc.source.includes('defineComponent')) {
scriptSetup = (item