跳至主内容
bilipan's Weblog

编写 ESLint 插件(上)

公司楼下的花坛养了不少蓝花丹,它们的花瓣上有粉粉的蓝色,看起来很有吸引力

背景

最近在团队内落地统一规范时,需要保证“每个 SFC 模板至少包含一个 data-xxxv-data-xxx”这类规范。研究了下,可选方案有三种:

第三种方案接入成本低、反馈路径短,是目前工程化实践中的首选。实现手段通常有两种:编写构建插件(vite/webpack)或编写 ESLint 插件。后者配置统一、生态成熟,更利于长期维护,语义上也更符合 ESLint 场景。

实现思路

(ESLint 插件的完整规范可参考官方文档,本文仅聚焦关键步骤。)

ESLint 的核心流程可简化为三步:

  1. 解析器把源码生成 AST
  2. 插件基于 AST 做规则校验
  3. 若命中规则,则上报错误或警告

因此,开发插件的关键在于理解 AST 结构,并针对目标节点编写访问逻辑。

Vue 文件解析

在 Vue 工程里,构建侧常用 @vue/compiler-sfc。编写规则时,应改用 vue-eslint-parser,它专门处理 .vue 单文件,仅解析 <template> 区块并提供 template visitor,可直接在插件中使用。

规则实现示例

需求:模板中至少有一个 DOM 元素或自定义组件带 data-xxx 属性或 v-data-xxx 指令。

module.exports = {
  meta: {
    type: 'problem',
    docs: {
      description: 'Enforce at least one data-xxx or v-data-xxx attribute in template',
      category: 'Best Practices',
      recommended: true
    },
    fixable: null,
    schema: []
  },
  create(context) {
    return {
      /*
       * 只能匹配“指令”属性(v-data-xxx),若要同时检测普通 DOM 属性(如 data-xxx),需再写一条非指令属性选择器:
       * 'VAttribute[directive=false][key.name^="data-"]'(node) { ... }
       * 'VAttribute[directive=true][key.name.name^="data-"]'(node) {...}
       * 改用 VElement 统一检查所有属性,无需区分指令与普通 DOM 属性
      */
      VElement(node) {
        const hasDataAttr = node.startTag.attributes.some(attr => {
          const name = attr.directive ? attr.key.name.name : attr.key.name
          return name.startsWith('data-')
        })
        if (!hasDataAttr) {
          context.report({
            node,
            message: 'Template must contain at least one data-xxx or v-data-xxx attribute'
          })
        }
      }
    }
  }
}

用例测试

ESLint 提供 RuleTester 工具,可在本地模拟完整校验流程:

const { RuleTester } = require('eslint')
const rule = require('./require-data-xxx-attributes')

const tester = new RuleTester({
  parser: require.resolve('vue-eslint-parser')
})

tester.run('require-data-xxx-attributes', rule, {
  valid: [
    { code: '<div data-xxx></div>' },
    { code: '<div v-data-xxx></div>' },
    { code: '<custom data-xxx></custom>' }
  ],
  invalid: [
    { code: '<div></div>', errors: [{ message: 'Template must contain at least one data-xxx or v-data-xxx attribute' }] },
    { code: '<custom></custom>', errors: [{ message: 'Template must contain at least one data-xxx or v-data-xxx attribute' }] }
  ]
})

发布与集成

插件开发完成后,发到 npm 即可。 另外,最好将 eslint 声明为 peerDependencies,毕竟eslint并不是插件的直接依赖。

后续

理解 ESLint 工作原理后,插件本身的编码并不复杂。但在真实业务落地时,仍需解决「仅对 .vue 生效」「组合多条规则」「提供统一配置入口」等细节。这些主题将在下篇展开。