老项目经历了多人迭代,组件中也存在大量废弃的data,写个自定义的babel插件,自动删除未使用的data。Babel 是 JavaScript中广泛使用的转译工具,Babel 的核心工作原理就是基于 AST 进行代码转换。
介绍AST语法树的关键概念
1.visitor(访问器)
作用:遍历和修改 AST 的入口
2.Program 节点
对应整个文件/模块的根节点
visitor: {
Program: { /* 处理整个文件 */ },
ObjectProperty(path) { /* 处理对象属性 */ },
MemberExpression(path) { /* 处理属性访问 */ }
}
3.常见 AST 节点类型
ObjectProperty(path) { /* 处理对象属性 */ },
MemberExpression(path) { /* 处理属性访问 */ }
FunctionDeclaration (path) { /* 处理函数声明 */ }
VariableDeclarator(path) { /* 处理变量声明 */ }
//Vue中常用
ExportDefaultDeclaration(){ /* 处理 Vue 组件导出的选项对象*/}
ObjectMethod (){ /* 检测组件选项方法 (需配合其他节点使用) */}
CallExpression { /*处理全局组件注册 */}
4.核心类型检查方法
isObjectExpression
// 检测对象字面量
const obj = {
key: 'value' // 这个对象字面量是 ObjectExpression
};
// 在插件中的判断
// 当 path 指向 { key: 'value' } 时返回 true
path.isObjectExpression()
isObjectMethod
export default {
methods: {
// 检测对象方法
showMessage() { //这个方法是 ObjectMethod
console.log('Hello')
}
}
}
// 在插件中的判断
// 当 path 指向 showMessage() {} 时返回 true
path.isObjectMethod()
isIdentifier
const userId = 123 // "userId" 是 Identifier
// 在插件中检测变量名
path.isIdentifier() // 当 path 指向 userId 时返回 true
isMemberExpression
// 检测属性访问
this.userInfo.name // "this.userInfo.name" 是 MemberExpression
vueComponent.$router // "$router" 访问也是 MemberExpression
// 在插件中的判断
path.isMemberExpression() // 当 path 指向上述表达式时返回 true
isFunctionDeclaration
// 检测函数声明
function fetchData() {
// 这是 FunctionDeclaration
}
// 在插件中的判断
path.isFunctionDeclaration()
调试 工具 AST Explorer,这个工具可以直观展示代码如何被解析为 AST 节点,帮助大家更好地理解插件的工作原理。
开发自定义插件
在babel.config.js中配置
"plugins": [
'./plugins/vue-unused-data.js'
]
创建vue-unused-data.js 文件
module.exports = function() {
return {
visitor: {
Program: {
// 初始化存储容器
enter() {
this.dataProperties = new Set(); // 存储所有 data 属性名称
this.usedProperties = new Set(); // 存储已使用的属性名称
this.unusedPropsToRemove = new Set(); // 存储待删除的节点路径
},
// 最终处理阶段
exit(path) {
// 二次遍历定位需要删除的节点
path.traverse({
ObjectProperty: (p) => {
// 判断是否满足删除条件:是 data 属性且未被使用
if (isDataProperty(p) &&
!this.usedProperties.has(p.node.key.name) &&
this.dataProperties.has(p.node.key.name)) {
this.unusedPropsToRemove.add(p);
}
}
});
// 批量删除未使用属性节点
this.unusedPropsToRemove.forEach(p => p.remove());
// 输出控制台警告
const unused = [...this.dataProperties].filter(p => !this.usedProperties.has(p));
if (unused.length > 0) {
console.warn(`未使用的 data 属性: ${unused.join(', ')}`);
}
}
},
// 处理对象属性(收集 data 属性)
ObjectProperty(path) {
if (isDataProperty(path)) {
// 记录 data 属性名称
this.dataProperties.add(path.node.key.name);
}
},
// 处理成员表达式(收集属性使用情况)
MemberExpression(path) {
// 捕获 this.xxx 形式的属性访问
if (path.get('object').isThisExpression() && path.node.property) {
this.usedProperties.add(path.node.property.name);
}
}
}
};
// 判断是否是 data 方法返回对象的属性
function isDataProperty(path) {
return path.parentPath.isObjectExpression() && // 父节点是对象表达式
path.findParent(p => p.isObjectMethod() && p.node.key.name === 'data');
}
};
测试代码
export default {
data() {
return {
usedProp: '已使用属性',
unusedProp: '未使用属性',
imgSrc: '',
aa: '',
bb: '',
bb1: '',
bb2: '',
};
},
methods: {
testMethod() {
console.log(this.usedProp);
}
}
};
查看调试后的结果,unusedProp属性已被删除
