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 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 = false
let deletePropsIndex: number[] //存储已存在
const transform = new VueTransform({
onelement(element: ElementNode) {
const tag = element.tag
const 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 any
deletePropsIndex = []
for (const prop of props) {
switch (prop.type) {
case 6:
if (changedComponentPropsMap[antdvComponentName][prop.name]) {
onAntdvReplaceAttr(prop, changedComponentPropsMap[antdvComponentName], antdvComponentName)
}
break
case 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; } | string
let oldProp = ''
// 处理情况二:
NewPropName = handlePropsTransform(node.name, config)
oldProp = node.name + ''
if (NewPropName === oldProp) return node
if (NewPropName !== 'v-if') {
/**
<a-select
-- dropdownClassName="my-select-popup"
++ popupClassName="my-select-popup"
/>
**/
node.name = NewPropName
if (node.nameLoc)
node.nameLoc.source = NewPropName
} else {
/**
--<a-tag visible> tag</a-tag>
++ <a-tag v-if="visible">tag</a-tag>
*/
delete node.nameLoc
delete node.value
node.type = 7
node.name = 'if'
node.exp = {
type: 4,
loc: node.loc,
content: oldProp,
isStatic: false,
constType: 0,
}
node.arg = null
node.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) return
let result = node.arg.content ? handlePropsTransform(node.arg.content, config, node.exp, jsRepalceKeysArray) : ''
oldProp = node.arg.content + ''
if (result === node.arg.content) return
if (typeof result === 'string') {
// 处理情况一:
// (1):
// -- <a-modal :visible="visible">content</a-modal>
// ++ <a-modal :open="visible">content</a-modal>
NewPropName = result
if (NewPropName === 'v-if') {
// (2):
// --<a-tag :visible="visible"> tag</a-tag>
// ++ <a-tag v-if="visible">tag</a-tag>
node.name = 'if'
delete node.rawName
node.arg = null
node.modifiers = []
}
} else if (result.propSubKeyValue) {
propSubKeyValue = result.propSubKeyValue
NewPropName = result.NewPropName
if (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 | any
if (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}}`
//删除旧的prop
deletePropsIndex.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 = NewPropName
node.arg.loc.source = NewPropName
node.rawName = NewPropName !== 'v-if' ? `:${NewPropName}` : NewPropName
}
obj[antdvComponentName] = {
[oldProp]: NewPropName
}
handlePropArray.push(obj)
hasChange = true
}
const ast = transform.parser(rawContent)
// AST转template
const 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 || _generate
export enum JavaScriptCodeType {
Vanilla,
Vue2,
Vue2Composition,
Vue3,
Vue3Composition
}
export async function javascriptTransformer(rawContent: string, type: JavaScriptCodeType, jsRepalceKeysArray?: Object[]) {
let hasChange = false
const 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) { //确定array
path.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') {//确定key
const 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].replacer
hasChange = true
}
}
})
}
}
})
},
})
const output = generate(ast, {
jsescOption: {
// 中文不转unicode
minimal: 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) => void
ontext?: (node: TextNode) => TextNode
onattr?: (node: AttributeNode) => AttributeNode
ondirective?: (node: DirectiveNode,) => DirectiveNode
oninterpolation?: (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.line
this._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
// 包装$t
switch (node.type) {
case 1: // ELEMENT
this.onElement(node)
result += `<${node.tag}`
if (node.props && node.props.length) {
this._lastColumn = node.loc.start.column + `<${node.tag}`.length
result += this.attrs(node.props, node)
}
if (node.isSelfClosing) {
result += this.createNewline(this._lastStartLine, node.loc.end.line)
this._lastStartLine = node.loc.end.line
if (!node.props.length) {
// 当标签无属性,则计算结束标签与标签之间的column 标签结束位置 = 标签起始位置 + 标签长度 + 空格
this._lastColumn = node.loc.start.column + `<${node.tag}`.length
}
// 取当前节点的结束column时,没算上 /> 所占的字符长度,故需要减去 2
result += this.createNewsColumns(node.loc.end.column - 2)
this._lastColumn = node.loc.end.column
result += '/>'
} 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.line
result += this.createNewsColumns(node.loc.start.column)
this._lastColumn = node.loc.end.column
result += `</${node.tag}>`
}
break
case 2: // TEXT
result += escapeHtml(this.onText(node)?.content || node.content)
break
case 3: // COMMENT
result += `<!--${escapeHtml(node.content)}-->`
break
case 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
// 重置 lastColumn
attr += this.createNewsColumns(prop.loc.start.column)
this._lastColumn = prop.loc.end.column
switch (prop.type) {
case 6: // ATTRIBUTE key="value"
let val = ''
if (prop.value) {
this.onAttr(prop)
val = `="${this.attrValue(prop.value)}"`
}
attr += `${prop.name}${val}`
break
case 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 统一解析成 #xxx
if (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=xxx
if (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.column
switch (value.type) {
case 2: // TEXT
val += escapeHtml(value.content)
break
case 4: // SIMPLE_EXPRESSION
val += value.content
break
}
} 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.RangePicker
TimePicker: {
dropdownClassName: {
action: 'rename',
replacer: 'popupClassName',
},
},
// 处理 compound components: DatePicker.RangePicker
DatePicker: {
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 = propSubKey
NewPropName = propKey
hasChanged = true;
return { NewPropName, propSubKeyValue }
}
} else {
if (propNode === propName) {
NewPropName = replacer
hasChanged = 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 = '' + string
const match = escapeRE.exec(str)
if (!match) {
return str
}
let html = ''
let escaped: string
let index: number
let lastIndex = 0
for (index = match.index; index < str.length; index++) {
switch (str.charCodeAt(index)) {
case 34: // "
escaped = '"'
break
case 38: // &
escaped = '&'
break
case 39: // '
escaped = '''
break
case 60: // <
escaped = '<'
break
case 62: // >
escaped = '>'
break
default:
continue
}
if (lastIndex !== index) {
html += str.slice(lastIndex, index)
}
lastIndex = index + 1
html += escaped
}
return lastIndex !== index ? html + str.slice(lastIndex, index) : html
}
// https://www.w3.org/TR/html52/syntax.html#comments
const commentStripRE = /^-?>|<!--|-->|--!>|<!-$/g
export function escapeHtmlComment(src: string): string {
return src.replace(commentStripRE, '')
}
export const cssVarNameEscapeSymbolsRE: RegExp =
/[ !"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g
export function getEscapedCssVarName(
key: string,
doubleEscape: boolean,
): string {
return key.replace(cssVarNameEscapeSymbolsRE, s =>
doubleEscape ? (s === '"' ? '\\\\\\"' : `\\\\${s}`) : `\\${s}`,
)
}