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,重新转回原文件过程

## 项目结构![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/7203466177fc4b1398d21bb31b83b487.png)
将打包后的dist上传到node,在本地安装。建议安装全局,方便全局使用。
安装:

npm install @my-cli -g

检查是否安装成功

 bdi-cli  --help . 

使用: < path > 替换成项目绝对路径

bdi-cli --antdv <path>

如何获取项目路径

  1. 配置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默认转换&lt;为> ,排除这部分处理,不让他转换

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 = '&quot;'breakcase 38: // &escaped = '&amp;'breakcase 39: // 'escaped = '&#39;'breakcase 60: // <escaped = '&lt;'breakcase 62: // >escaped = '&gt;'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}`,)
}

思路流程图

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/458168.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

<<机器学习实战>>15-26节笔记:逻辑回归参数估计、梯度下降及优化、模型评价指标

梯度下降缺点&#xff1a;有可能有鞍点&#xff08;如果不是凸函数的时候&#xff09;&#xff0c;不一定能找到最小值解决方法&#xff1a;随机梯度下降&#xff08;选一条数据&#xff09;和小批量梯度下降&#xff08;选几条数据这两个解决方法又会带来新问题&#xff0c;比…

机器视觉-相机、镜头、光源(总结)

目录 1、机器视觉光源概述 2、光源的作用 3、光谱 4、工业场景常见光源 4.1、白炽灯 4.2、卤素灯 4.3、 荧光灯 4.4、LED灯 4.5、激光灯 5、光源的基本性能 5.1、光通量 5.2、光效率 5.3、发光强度 5.4、光照度 5.5、均匀性 5.6、色温 5.7、显色性 6、基本光学…

2024年信息化管理与计算技术研讨会 (ICIMCT 2024)--分会场

目录 重要信息 大会简介 荣誉主席 主讲嘉宾 征稿主题 会议日程 参会方式 重要信息 大会时间&#xff1a;2024年11月15-17日 大会地点&#xff1a;中国-成都 大会官网&#xff1a; http://www.icbar.net/ 大会简介 2024年信息化管理与计算技术研讨会 (ICIMCT 2024)为…

JAVA基础:集合 (学习笔记)

集合 什么是集合&#xff1f; 一种引用数据类型&#xff0c;可以存储多个数据 为什么要学习集合&#xff1f; 数组的缺点&#xff1a; &#xff08;1&#xff09;空间长度不可以改变。 &#xff08;2&#xff09;没办法获得数组中真实的元素个数。 &#xff08;3&#xff…

【Android】perfetto使用学习

在开发者选项中的系统跟踪里抓取的perfetto文件是保存在/data/local/traces 里的 adb pull /data/local/traces ./ 主线程中的执行是受vsync信号控制的&#xff0c;即间隔调用的 如果写一个while线程&#xff0c;一直使用cpu&#xff0c;是怎样的呢&#xff0c;这里我们来试验一…

asp.net core 入口 验证token,但有的接口要跳过验证

asp.net core 入口 验证token,但有的接口要跳过验证 在ASP.NET Core中&#xff0c;你可以使用中间件来验证token&#xff0c;并为特定的接口创建一个属性来标记是否跳过验证。以下是一个简化的例子&#xff1a; 创建一个自定义属性来标记是否跳过验证&#xff1a; public clas…

【算法】递归系列:206.反转链表(两种递归实现)

目录 1、题目链接 2、题目介绍 3、解法 递归法&#xff08;从前往后递归&#xff09; 从后往前递归 4、代码 递归法&#xff08;从前往后递归&#xff09; 从后往前递归 1、题目链接 206.反转链表 2、题目介绍 3、解法 递归法&#xff08;从前往后递归&#xff09; 递归…

OpenIPC开源FPV之Ardupilot配置

OpenIPC开源FPV之Ardupilot配置 1. 源由2. 问题3. 分析3.1 MAVLINK_MSG_ID_RAW_IMU3.2 MAVLINK_MSG_ID_SYS_STATUS3.3 MAVLINK_MSG_ID_BATTERY_STATUS3.4 MAVLINK_MSG_ID_RC_CHANNELS_RAW3.5 MAVLINK_MSG_ID_GPS_RAW_INT3.6 MAVLINK_MSG_ID_VFR_HUD3.7 MAVLINK_MSG_ID_GLOBAL_P…

基于rk356x u-boot版本功能分析及编译相关(二)

🎏技术驱动源于热爱,祝各位学有所成。 文章目录 build.sh脚本分析make.sh编译脚本分析接上,rk3568的u-boot编译在 基于rk356x u-boot版本功能分析及编译相关(一)已有描述,下面针对编译脚本进行分析,在编译之前都进行了哪些工作。 build.sh脚本分析 在编译目录下执行…

二叉树与堆的实现

一 . 概念与结构 在树的概念与结构中树的概念与结构-CSDN博客&#xff0c; 我们发现子结点可以为 0 或者是更多 &#xff0c; 结构较为复杂 &#xff0c; 然后把树的结点个数 加个限制条件 --> 不能超过 2 --> 我们引出了二叉树&#xff0c;在实际运用广 且高效 &#xf…

springboot-springboot官方文档架构

spring官网 >project&#xff1a;spring项目列表&#xff0c;包含了spring一系列框架的List >springboot(也可以换成其他框架)&#xff1a;springboot框架 >learn:显示这个框架的各个版本的reference doc和api doc >某版本的reference doc © 著作权归作者所有…

提示工程(Prompt Engineering)指南(进阶篇)

在 Prompt Engineering 的进阶阶段&#xff0c;我们着重关注提示的结构化、复杂任务的分解、反馈循环以及模型的高级特性利用。随着生成式 AI 技术的快速发展&#xff0c;Prompt Engineering 已经从基础的单一指令优化转向了更具系统性的设计思维&#xff0c;并应用于多轮对话、…

【gRPC】什么是RPC——介绍一下RPC

说起RPC&#xff0c;博主使用CPP手搓了一个RPC项目&#xff0c;RPC简单来说&#xff0c;就是远程过程调用&#xff1a;我们一般在本地传入数据进行执行函数&#xff0c;然后返回一个结果&#xff1b;当我们使用RPC之后&#xff0c;我们可以将函数的执行过程放到另外一个服务器上…

基于python的马尔可夫模型初识

基于python的马尔可夫模型初识 **1.什么是随机过程&#xff1f;****1.1模拟赌徒的毁灭Gamblers Ruin** **2.马尔可夫链(Markov Chains)****2.1马尔可夫链模拟****2.2马尔可夫转移概率图****2.3无记忆性&#xff1a;给定现在&#xff0c;未来独立于过去****2.4 n n n 步转移矩阵…

Python金色流星雨

系列目录 序号直达链接爱心系列1Python制作一个无法拒绝的表白界面2Python满屏飘字表白代码3Python无限弹窗满屏表白代码4Python李峋同款可写字版跳动的爱心5Python流星雨代码6Python漂浮爱心代码7Python爱心光波代码8Python普通的玫瑰花代码9Python炫酷的玫瑰花代码10Python多…

Python图像处理——基于ResNet152的人脸识别签到系统(Pytorch框架)

&#xff08;1&#xff09;数据集制作 本次使用明星做为数据集&#xff0c;首先编写爬虫函数&#xff0c;根据关键字爬取对应的明星&#xff0c;爬取结果保存至data文件夹&#xff0c;并以标签名作为文件名。具体爬取的明星如下&#xff1a; 注&#xff1a;实际应用中&#xf…

linux下gpio模拟spi三线时序

目录 前言一、配置内容二、驱动代码实现三、总结 前言 本笔记总结linux下使用gpio模拟spi时序的方法&#xff0c;基于arm64架构的一个SOC&#xff0c;linux内核版本为linux5.10.xxx&#xff0c;以驱动三线spi(时钟线sclk&#xff0c;片选cs&#xff0c;sdata数据读和写使用同一…

华为鸿蒙HarmonyOS应用开发者高级认证视频及题库答案

华为鸿蒙开发者高级认证的学习资料 1、课程内容涵盖HarmonyOS系统介绍、DevEco Studio工具使用、UI设计与开发、Ability设计与开发、分布式特性、原子化服务卡片以及应用发布等。每个实验都与课程相匹配&#xff0c;帮助加深理解并掌握技能 2、学习视频资料 华为HarmonyOS开发…

Minio文件服务器:SpringBoot实现文件上传

在Minio文件服务器部署成功后(参考上篇文章Minio文件服务器&#xff1a;安装)接下来我们通过SpringBoot框架写一个接口&#xff0c;来实现文件的上传功能&#xff1a;文件通过SpringBoot接口&#xff0c;上传到Minio文件服务器。并且&#xff0c;如果上传的文件是图片类型&…

2025考研各省市网上确认时间汇总!

2025考研各省市网上确认时间汇总&#xff01; 安徽&#xff1a;11月1日至5日 福建&#xff1a;11月1日-11月5日 山东&#xff1a;10月31日9:00至11月5日12:00 新疆&#xff1a;10月31日至11月4日17:00 湖南&#xff1a;11月1日9:00-4日12:00 广东&#xff1a;10月下旬至1…