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 nodeimport { 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`// 处理templateconst { result: templateResult, handlePropArray: handleProp, hasChange: templateHasChange, jsRepalceKeysArray: jsRepalceKeys } = await templateTransformer(template)jsRepalceKeysArray[path] = jsRepalceKeyshandlePropArray[path] = handleProp// resultCode += templateResultresultCode += `${templateResult}\n`changeArr.push(templateHasChange)antdv_v3_v4[path] = handleProp// 处理scriptfor (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 = pathconst 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 = 0for (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: stringcontent: 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 as ElementNode).children.length ? (item as ElementNode).children[0].loc.source : ''tempInfo.type = 'setup'tempInfo.content = scriptSetup} else {script = (item as ElementNode).children.length ? (item as ElementNode).children[0].loc.source : ''tempInfo.type = 'optionapi'tempInfo.content = script}scriptInfo.push(tempInfo)} else if (item.type === 1 && item.tag === 'style') {otherContent += item.loc.source ?? ''}})return {headerComment,template,scriptHead,script,scriptSetup,otherContent,scriptInfo}
}
处理vue 文件template部分
transformer – template.js
import { VueTransform } from '../utils/transform';
import {ElementNode,type AttributeNode,type DirectiveNode
} from '@vue/compiler-dom'
// import { containsChinese, isI18nKeyPattern, validateValue, i18nKeyPattern, containsAntdvProps } from '../utils/regex'
// import { handlePropsTransform } from '../utils/antdv3_4';
import { changedComponentPropsMap, componentTuple, handlePropsTransform } from '../utils/antdv3_4'export async function templateTransformer(rawContent: string) {const handlePropArray: Object[] = []const jsRepalceKeysArray: Object[] = []let hasChange = falselet deletePropsIndex: number[] //存储已存在const transform = new VueTransform({onelement(element: ElementNode) {const tag = element.tagconst antdvComponentName = componentTuple.includes(tag) ? tag : tag.slice(2).split('-').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join('');if (element.props && element.props.length && (componentTuple.includes(antdvComponentName))) {const props = element.props as anydeletePropsIndex = []for (const prop of props) {switch (prop.type) {case 6:if (changedComponentPropsMap[antdvComponentName][prop.name]) {onAntdvReplaceAttr(prop, changedComponentPropsMap[antdvComponentName], antdvComponentName)}breakcase 7: // Vue 指令if (prop.exp) {if (prop.arg && prop.arg.content && changedComponentPropsMap[antdvComponentName][prop.arg.content]) {onAntdvReplaceDirective(prop, changedComponentPropsMap[antdvComponentName], antdvComponentName, element)}}break}}if (deletePropsIndex.length) {//必须倒序,数组的末尾开始删除,这样不会影响到未处理的元素的索引deletePropsIndex.reverse().forEach(idx => {element.props.splice(idx, 1)})}}},})// key='value'function onAntdvReplaceAttr(node: AttributeNode | any, config?: any, antdvComponentName?: string) {let obj = {}let NewPropName: { NewPropName: string; propSubKeyValue: string; } | stringlet oldProp = ''// 处理情况二:NewPropName = handlePropsTransform(node.name, config)oldProp = node.name + ''if (NewPropName === oldProp) return nodeif (NewPropName !== 'v-if') {/** <a-select-- dropdownClassName="my-select-popup"++ popupClassName="my-select-popup"/>**/node.name = NewPropNameif (node.nameLoc)node.nameLoc.source = NewPropName} else {/**--<a-tag visible> tag</a-tag> ++ <a-tag v-if="visible">tag</a-tag> */delete node.nameLocdelete node.valuenode.type = 7node.name = 'if'node.exp = {type: 4,loc: node.loc,content: oldProp,isStatic: false,constType: 0,}node.arg = nullnode.modifiers = []}obj[antdvComponentName as string] = {[oldProp]: NewPropName}handlePropArray.push(obj)hasChange = true}// :key='value'function onAntdvReplaceDirective(node: DirectiveNode | any, config: any, antdvComponentName: string, Element: ElementNode,): void {let obj = {}let NewPropName = ''let oldProp = ''let propSubKeyValue = ''if (!node.arg) returnlet result = node.arg.content ? handlePropsTransform(node.arg.content, config, node.exp, jsRepalceKeysArray) : ''oldProp = node.arg.content + ''if (result === node.arg.content) returnif (typeof result === 'string') {// 处理情况一:// (1):// -- <a-modal :visible="visible">content</a-modal>// ++ <a-modal :open="visible">content</a-modal>NewPropName = resultif (NewPropName === 'v-if') {// (2):// --<a-tag :visible="visible"> tag</a-tag> // ++ <a-tag v-if="visible">tag</a-tag>node.name = 'if'delete node.rawNamenode.arg = nullnode.modifiers = []}} else if (result.propSubKeyValue) {propSubKeyValue = result.propSubKeyValueNewPropName = result.NewPropNameif (node.exp) {const index = Element.props.findLastIndex((prop: any) => prop.arg && prop.arg.content === NewPropName)if (index > -1) {//(3)// <a-slider// --:tooltip.sync="{open2:visible2}"// --:tooltipPlacement.sync="visible"//++ :tooltip.sync="{open2:visible2,open:visible}"// :visible="visible"/>const prop = Element.props[index] as DirectiveNode | anyif (prop.arg && prop.arg.content === NewPropName) {//匹配内容if (prop.exp.content.startsWith('{') && prop.exp.content.endsWith('}')) {//将新增的内容加到最新需要变更的属性node.exp.content = `{${prop.exp.content.slice(1, -1)},${propSubKeyValue}:${node.exp.content}}`node.exp.loc.source = `{${prop.exp.loc.source.slice(1, -1)},${propSubKeyValue}:${node.exp.loc.source}}`//删除旧的propdeletePropsIndex.push(index)}}} else {// (4):// -- <a-slider :tooltipVisible="visible" />// ++ <a-slider :tooltip="{ open: visible }" />node.exp.content = `{${propSubKeyValue}:${node.exp.content}}`node.exp.loc.source = `{${propSubKeyValue}:${node.exp.loc.source}}`}}}if (node.arg || node.rawName) {node.arg.content = NewPropNamenode.arg.loc.source = NewPropNamenode.rawName = NewPropName !== 'v-if' ? `:${NewPropName}` : NewPropName}obj[antdvComponentName] = {[oldProp]: NewPropName}handlePropArray.push(obj)hasChange = true}const ast = transform.parser(rawContent)// AST转templateconst result = transform.generate(ast)// console.log(handlePropArray);return {result: result,hasChange,handlePropArray,jsRepalceKeysArray}
}
处理vue script部分
这里也可以用于vue项目中.js或者.ts文件处理
import { parse } from '@babel/parser'
import _traverse from '@babel/traverse'
import _generate from '@babel/generator'
import { changedComponentJsMap } from '../utils/antdv3_4'// https://github.com/babel/babel/issues/13855
// @ts-ignore
const traverse = _traverse.default || _traverse
// @ts-ignore
const generate = _generate.default || _generateexport enum JavaScriptCodeType {Vanilla,Vue2,Vue2Composition,Vue3,Vue3Composition
}export async function javascriptTransformer(rawContent: string, type: JavaScriptCodeType, jsRepalceKeysArray?: Object[]) {let hasChange = falseconst handleArray: string[] = []const ast = parse(rawContent, {sourceType: 'unambiguous',plugins: ['jsx', 'typescript']})traverse(ast, {// 处理情况:// const arr = [{// title: 'Name',// dataIndex: 'name',// --filterDropdownVisible: visible,// ++filterDropdownOpen: visible,// visible: filterDropdown// }];ArrayExpression(path) { //确定arraypath.node.elements && path.node.elements.forEach((element: any) => {if (element.type === 'ObjectExpression') {//确定对象if (element.properties && element.properties.length) {element.properties.forEach(prop => {if (prop.type === 'ObjectProperty') {//确定keyconst keyName = prop.key.name || prop.key.value;if (jsRepalceKeysArray && jsRepalceKeysArray.length) {jsRepalceKeysArray.forEach(obj => {if (obj[keyName]) {prop.key.name = obj[keyName]hasChange = true}})} else if (changedComponentJsMap[keyName]) {prop.key.name = changedComponentJsMap[keyName].replacerhasChange = true}}})}}})},})const output = generate(ast, {jsescOption: {// 中文不转unicodeminimal: true,quotes: 'single'}})// console.log(output.code);return {result: output.code,handleArray,hasChange}
}
utils–transform.ts(主要转换函数)
将template转AST 区分节点类型进行不同处理
import {type AttributeNode,// HTML 元素的属性节点。type DirectiveNode,//Vue 指令节点type ExpressionNode,//表达式节点,可以是在模板中使用的各种表达式,如插值表达式、指令表达式等type RootNode,//模板的根节点type TemplateChildNode,//模板中的子节点,可以是文本节点、元素节点等type TextNode,//文本节点baseParse, ElementNode,//解析 Vue 模板字符串,返回一个RootNode,代表模板的抽象语法树(AST)SimpleExpressionNode//表示简单表达式节点,通常是一个常量或变量引用
} from '@vue/compiler-dom'
import { escapeHtml } from './excapeRe'export interface Options {onelement?: (node: ElementNode) => voidontext?: (node: TextNode) => TextNodeonattr?: (node: AttributeNode) => AttributeNodeondirective?: (node: DirectiveNode,) => DirectiveNodeoninterpolation?: (node: SimpleExpressionNode) => SimpleExpressionNode
}export class VueTransform {options: Options | undefined_lastStartLine: number = 1_lastColumn: number = 1_voidTag = ['img', 'br', 'hr']constructor(options?: Options) {this.options = options}parser(template: string) { //将template转成AST树// console.log(JSON.stringify(baseParse(template, {// isVoidTag: (tag: string) => {// return this._voidTag.includes(tag)// }// })))return baseParse(template, { //将template转成AST树isVoidTag: (tag: string) => {//判断给定的标签是否是一个 “空标签”(void tag)return this._voidTag.includes(tag)}})}generate(tree: RootNode) {// 表示root,这一层不解析if (tree.type === 0) {this._lastStartLine = tree.loc.start.linethis._lastColumn = 1}return this.html(tree.children)}html(tree: TemplateChildNode[]): string {let result = ''for (const node of tree) {// 根据给定的列数和当前列信息,返回相应数量的空格result += this.createNewline(this._lastStartLine, node.loc.start.line)this._lastStartLine = node.loc.start.line// 根据起始行和结束行的差值,返回相应数量的换行符,并在有换行时重置列信息。result += this.createNewsColumns(node.loc.start.column)this._lastColumn = node.loc.end.column// 包装$tswitch (node.type) {case 1: // ELEMENTthis.onElement(node)result += `<${node.tag}`if (node.props && node.props.length) {this._lastColumn = node.loc.start.column + `<${node.tag}`.lengthresult += this.attrs(node.props, node)}if (node.isSelfClosing) {result += this.createNewline(this._lastStartLine, node.loc.end.line)this._lastStartLine = node.loc.end.lineif (!node.props.length) {// 当标签无属性,则计算结束标签与标签之间的column 标签结束位置 = 标签起始位置 + 标签长度 + 空格this._lastColumn = node.loc.start.column + `<${node.tag}`.length}// 取当前节点的结束column时,没算上 /> 所占的字符长度,故需要减去 2result += this.createNewsColumns(node.loc.end.column - 2)this._lastColumn = node.loc.end.columnresult += '/>'} else {// TODO VUE解析出来的 ast 信息不全// 如果此时非自闭合标签结束符 单起一行,在 ast 中无法记录该信息,故无法还原此时的换行情况// 不过总体行数不会影响,副作用:自闭合标签结束符单起一行的样式丢失,该children计算会多出一行result += '>'}if (node.children && node.children.length) {result += this.html(node.children)}if (!node.isSelfClosing && !this._voidTag.includes(node.tag)) {result += this.createNewline(this._lastStartLine, node.loc.end.line)this._lastStartLine = node.loc.end.lineresult += this.createNewsColumns(node.loc.start.column)this._lastColumn = node.loc.end.columnresult += `</${node.tag}>`}breakcase 2: // TEXTresult += escapeHtml(this.onText(node)?.content || node.content)breakcase 3: // COMMENTresult += `<!--${escapeHtml(node.content)}-->`breakcase 5: // INTERPOLATION 插值 {{'中文'}} ${'中文'}// TODO 此处 {{ 括号难以还原位置,暂不处理if (node.content.type === 4) {this.onInterpolation(node.content)}result += `{{ ${this.attrValue(node.content)} }}`break}}return result}attrs(props: Array<AttributeNode | DirectiveNode>, node: ElementNode): string {let attr = ''for (const prop of props) {attr += this.createNewline(this._lastStartLine, prop.loc.start.line)this._lastStartLine = prop.loc.end.line// 重置 lastColumnattr += this.createNewsColumns(prop.loc.start.column)this._lastColumn = prop.loc.end.columnswitch (prop.type) {case 6: // ATTRIBUTE key="value" let val = ''if (prop.value) {this.onAttr(prop)val = `="${this.attrValue(prop.value)}"`}attr += `${prop.name}${val}`breakcase 7: // DIRECTIVE :key='value'if (prop.exp) {this.onDirective(prop)}let modifiers = ''if (prop.modifiers) {prop.modifiers.forEach(modifier => {modifiers += `.${modifier.content}`})}if (prop.name === 'slot') {// slot 统一解析成 #xxxif (prop.arg) {attr += `#${this.attrValue(prop.arg)}`} else {attr += `v-${prop.name}`}if (prop.exp) {attr += `="${this.attrValue(prop.exp)}"`}} else if (prop.name === 'bind') {if (prop.arg) {// 如果参数名存在,bind 统一解析成 :xxx="xxx" 的模式attr += `:${this.attrValue(prop.arg)}${modifiers}="${this.attrValue(prop.exp)}"`} else {attr += `v-bind="${this.attrValue(prop.exp)}"`}} else if (prop.name === 'on') {// 事件绑定统一解析成 @xxx=xxxif (prop.exp) {attr += `@${this.attrValue(prop.arg)}${modifiers}="${this.attrValue(prop.exp)}"`} else {attr += `@${this.attrValue(prop.arg)}${modifiers}`}} else {if (prop.exp) {attr += `v-${prop.name}${modifiers}${prop.arg ? ':' + this.attrValue(prop.arg) : ''}="${this.attrValue(prop.exp)}"`} else {attr += `v-${prop.name}${modifiers}${prop.arg ? ':' + this.attrValue(prop.arg) : ''}`}}break}}return attr}attrValue(value: TextNode | ExpressionNode | undefined): string {if (!value) return ''let val = ''try {val += this.createNewline(this._lastStartLine, value.loc.start.line)this._lastStartLine = value.loc.end.line// lastColumn = value.loc.end.columnswitch (value.type) {case 2: // TEXTval += escapeHtml(value.content)breakcase 4: // SIMPLE_EXPRESSIONval += value.contentbreak}} catch (error) {console.log(error)}return val}onElement(element: ElementNode) {return this.options?.onelement && this.options.onelement(element)}onText(node: TextNode): TextNode {return this.options?.ontext && this.options.ontext(node) || node}onAttr(node: AttributeNode): AttributeNode {return this.options?.onattr && this.options.onattr(node) || node}onDirective(node: DirectiveNode): DirectiveNode {return this.options?.ondirective && this.options.ondirective(node,) || node}onInterpolation(node: SimpleExpressionNode): SimpleExpressionNode {return this.options?.oninterpolation && this.options.oninterpolation(node) || node}// onAntdvReplace(node: DirectiveNode, config?: any, props?: Array<AttributeNode | DirectiveNode>, antdvComponentName?: string, type?: number): DirectiveNode {// return this.options?.onAntdvReplace && this.options.onAntdvReplace(node, config, props, antdvComponentName, type) || node// }// onAntdv(oldProp: string, newProp: string, antdvComponentName: string) {// this.options?.onAntdv && this.options.onAntdv(oldProp, newProp, antdvComponentName)// }createNewsColumns(num: number) {return ' '.repeat(Math.max(num - this._lastColumn, 0))}createNewline(start: number, end: number) {// return ''const lines = Math.max(end - start, 0)// 没换行后重新初始化列信息if (lines > 0) {this._lastColumn = 1}return '\n'.repeat(lines)}}
utils— antdv3_4
antdv升级模块
//antdv prop 版本升级部分
const changedComponentPropsMap = {AutoComplete: {dropdownClassName: {action: 'rename',replacer: 'popupClassName',},},Cascader: {dropdownClassName: {action: 'rename',replacer: 'popupClassName',},},Select: {dropdownClassName: {action: 'rename',replacer: 'popupClassName',},},TreeSelect: {dropdownClassName: {action: 'rename',replacer: 'popupClassName',},},// 处理 compound components: TimePicker.RangePickerTimePicker: {dropdownClassName: {action: 'rename',replacer: 'popupClassName',},},// 处理 compound components: DatePicker.RangePickerDatePicker: {dropdownClassName: {action: 'rename',replacer: 'popupClassName',},},RangePicker: {dropdownClassName: {action: 'rename',replacer: 'popupClassName',},},Mentions: {dropdownClassName: {action: 'rename',replacer: 'popupClassName',},},Drawer: {visible: {action: 'rename',replacer: 'open',},className: {action: 'rename',replacer: 'rootClassName',},style: {action: 'rename',replacer: 'rootStyle',},},Modal: {visible: {action: 'rename',replacer: 'open',},},Dropdown: {visible: {action: 'rename',replacer: 'open',},},Tooltip: {visible: {action: 'rename',replacer: 'open',},},Tag: {visible: {action: 'remove',},},Slider: {tipFormatter: {action: 'rename',replacer: 'tooltip.formatter',},tooltipPlacement: {action: 'rename',replacer: 'tooltip.placement',},tooltipVisible: {action: 'rename',replacer: 'tooltip.open',},},Table: {columns: {filterDropdownVisible: {action: 'rename',replacer: 'filterDropdownOpen',},filterDropdown: {action: 'rename',replacer: 'filterDropdownVisible3',},}},
};
//antdv js 版本升级部分
const changedComponentJsMap = {filterDropdownVisible: {action: 'rename',replacer: 'filterDropdownOpen',},
}
// 将map对象key转成数组 [ DatePicker, RangePicker]
const componentTuple = Object.keys(changedComponentPropsMap)
//处理需要升级组件 prop 部分
function handlePropsTransform(propNode, componentConfig, attrValue?: any, jsRepalceKeysArray?: string[] | any): string | { NewPropName: string, propSubKeyValue: string } {let hasChanged = false;let NewPropName = ''let propSubKeyValue = ''let exp = ''Object.keys(componentConfig).forEach((propName) => {if (!Object.keys(componentConfig[propName]).includes('action')) {Object.keys(componentConfig[propName]).forEach(key => {//只有匹配到colums就添加到js需要变更属性当中const config = componentConfig[propName][key]jsRepalceKeysArray.push({ [key]: config.replacer })if (attrValue?.content.indexOf(key + ':') >= 0) {//处理5:// <a-table// :data="[]"// :columns="[// {// title: 'Name',// dataIndex: 'name',// --filterDropdownVisible: visible,// ++filterDropdownVisible: visible,// },// ]"// />if (config.action === 'rename') {attrValue.content = attrValue.content.replace(new RegExp(`\\b${key}:`, 'g'), `${config.replacer}:`);}}})return propNode} else {const { action, replacer } = componentConfig[propName];if (action === 'rename' && replacer) {if (replacer.includes('.')) {const [propKey, propSubKey] = replacer.split('.');if (propNode === propName) {propSubKeyValue = propSubKeyNewPropName = propKeyhasChanged = true;return { NewPropName, propSubKeyValue }}} else {if (propNode === propName) {NewPropName = replacerhasChanged = true;return NewPropName}}}if (action === 'remove') {NewPropName = 'v-if'hasChanged = true;return NewPropName}}});// console.log(1, hasChanged, 2, propSubKeyValue, 3, NewPropName, 4, exp, 5, propNode, 6);return hasChanged ? (propSubKeyValue ? { NewPropName, propSubKeyValue } : exp ? { exp } : NewPropName) : propNode
}export { changedComponentPropsMap, changedComponentJsMap, componentTuple, handlePropsTransform }
utils–excapeRe.ts
node默认转换<;为> ,排除这部分处理,不让他转换
const escapeRE = /["'&<>]/export function escapeHtml(string: unknown): string {const str = '' + stringconst match = escapeRE.exec(str)if (!match) {return str}let html = ''let escaped: stringlet index: numberlet lastIndex = 0for (index = match.index; index < str.length; index++) {switch (str.charCodeAt(index)) {case 34: // "escaped = '"'breakcase 38: // &escaped = '&'breakcase 39: // 'escaped = '''breakcase 60: // <escaped = '<'breakcase 62: // >escaped = '>'breakdefault:continue}if (lastIndex !== index) {html += str.slice(lastIndex, index)}lastIndex = index + 1html += escaped}return lastIndex !== index ? html + str.slice(lastIndex, index) : html
}// https://www.w3.org/TR/html52/syntax.html#comments
const commentStripRE = /^-?>|<!--|-->|--!>|<!-$/gexport function escapeHtmlComment(src: string): string {return src.replace(commentStripRE, '')
}export const cssVarNameEscapeSymbolsRE: RegExp =/[ !"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/gexport function getEscapedCssVarName(key: string,doubleEscape: boolean,
): string {return key.replace(cssVarNameEscapeSymbolsRE, s =>doubleEscape ? (s === '"' ? '\\\\\\"' : `\\\\${s}`) : `\\${s}`,)
}