自定义babel插件检测Vue组件中未使用的data

老项目经历了多人迭代,组件中也存在大量废弃的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属性已被删除

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值