eslint规则:auto-import-components

Posted on 2024-05-25 22:43  生之不止,思之不息  阅读(36)  评论(0)    收藏  举报

为了实现这个需求,需要创建一个自定义的 ESLint 规则,该规则将会解析 .vue 文件中的 template 块,检查自定义组件是否在 components 中注册,如果没有,则根据配置添加相应的 import 语句,并注册到 components 中。这个规则可以使用 eslint-plugin-vue 提供的工具来实现。

下面是实现这个自定义 ESLint 规则的步骤:

  1. 创建一个新的 ESLint 插件。
  2. 在插件中创建一个规则文件,该文件会定义我们需要的规则逻辑。
  3. 配置该规则以解析 .vue 文件,并在需要的时候自动添加 import 语句和组件注册。
// index.js
module.exports = {
  rules: {
    'auto-import-components': require('./rules/auto-import-components')
  }
};

// rules/auto-import-components.js
const parser = require('vue-eslint-parser');
const fs = require('fs');

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description: 'Auto import and register components in Vue files',
      category: 'Best Practices',
      recommended: false
    },
    fixable: 'code',
    schema: [
      {
        type: 'array',
        items: {
          type: 'object',
          properties: {
            tag: { type: 'string' },
            import: { type: 'string' }
          },
          required: ['tag', 'import'],
          additionalProperties: false
        }
      }
    ]
  },
  create(context) {
    const options = context.options[0] || [];

    return {
      'Program:exit'(node) {
        if (context.getFilename().endsWith('.vue')) {
          const sourceCode = context.getSourceCode();
          const template = sourceCode.ast.templateBody;

          if (template) {
            const usedComponents = new Set();
            parser.AST.traverse(template, {
              enter(node) {
                if (node.type === 'VElement' && node.name !== 'template') {
                  usedComponents.add(node.rawName);
                }
              }
            });

            const script = sourceCode.ast.body.find(node => node.type === 'ExportDefaultDeclaration');
            if (script) {
              const componentsNode = script.declaration.properties.find(prop => prop.key.name === 'components');
              const components = componentsNode ? componentsNode.value.properties.map(prop => prop.key.name) : [];

              const fixes = [];
              usedComponents.forEach(tag => {
                const component = options.find(opt => opt.tag === tag);
                if (component && !components.includes(tag)) {
                  if (!componentsNode) {
                    fixes.push(fixer => fixer.insertTextAfter(
                      script.declaration,
                      `\ncomponents: { ${tag} },`
                    ));
                  } else {
                    fixes.push(fixer => fixer.insertTextAfter(
                      componentsNode.value.properties[componentsNode.value.properties.length - 1],
                      `, ${tag}`
                    ));
                  }
                  fixes.push(fixer => fixer.insertTextBefore(
                    script.declaration,
                    `${component.import}\n`
                  ));
                }
              });

              if (fixes.length > 0) {
                context.report({
                  node: script,
                  message: 'Auto import and register components.',
                  fix(fixer) {
                    return fixes.map(f => f(fixer));
                  }
                });
              }
            }
          }
        }
      }
    };
  }
};

在使用这个 ESLint 插件之前,您需要将其添加到 ESLint 配置中,并提供相应的配置:

// .eslintrc.js
module.exports = {
  plugins: ['path/to/your/plugin'],
  rules: {
    'your-plugin/auto-import-components': ['error', [
      { tag: 'BaseIcon', import: 'import BaseIcon from "@/components/BaseIcon"' },
      // 添加其他组件的配置
    ]]
  }
};

这样,当您在 .vue 文件中使用了配置中定义的组件但没有引入时,ESLint 将会自动修复这些问题,添加相应的 import 语句并在 components 中注册组件。

针对组件多次注册的问题:
如果初始文件中没有 components 属性,并且模板中有多个未注册的组件,根据前面的实现逻辑,的确会导致每个未注册的组件都会重新添加一个 components 属性。这显然不是我们想要的行为。

我们需要确保在初次添加 components 属性时,只添加一次,并且将所有未注册的组件都注册进去。以下是修改后的规则逻辑,确保只会添加一个 components 属性,并且将所有需要的组件注册进去。

// rules/auto-import-components.js
const parser = require('vue-eslint-parser');

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description: 'Auto import and register components in Vue files',
      category: 'Best Practices',
      recommended: false
    },
    fixable: 'code',
    schema: [
      {
        type: 'array',
        items: {
          type: 'object',
          properties: {
            tag: { type: 'string' },
            import: { type: 'string' }
          },
          required: ['tag', 'import'],
          additionalProperties: false
        }
      }
    ]
  },
  create(context) {
    const options = context.options[0] || [];

    return {
      'Program:exit'(node) {
        if (context.getFilename().endsWith('.vue')) {
          const sourceCode = context.getSourceCode();
          const template = sourceCode.ast.templateBody;

          if (template) {
            const usedComponents = new Set();
            parser.AST.traverse(template, {
              enter(node) {
                if (node.type === 'VElement' && node.name !== 'template') {
                  usedComponents.add(node.rawName);
                }
              }
            });

            const script = sourceCode.ast.body.find(node => node.type === 'ExportDefaultDeclaration');
            if (script) {
              let componentsNode = script.declaration.properties.find(prop => prop.key.name === 'components');
              const components = componentsNode ? componentsNode.value.properties.map(prop => prop.key.name) : [];

              const newComponents = [];
              const importStatements = [];

              usedComponents.forEach(tag => {
                const component = options.find(opt => opt.tag === tag);
                if (component && !components.includes(tag)) {
                  newComponents.push(tag);
                  importStatements.push(component.import);
                }
              });

              if (newComponents.length > 0) {
                const fixes = [];

                importStatements.forEach(importStatement => {
                  fixes.push(fixer => fixer.insertTextBefore(
                    script.declaration,
                    `${importStatement}\n`
                  ));
                });

                if (!componentsNode) {
                  fixes.push(fixer => fixer.insertTextAfter(
                    script.declaration,
                    `\ncomponents: { ${newComponents.join(', ')} },`
                  ));
                } else {
                  fixes.push(fixer => fixer.insertTextAfter(
                    componentsNode.value.properties[componentsNode.value.properties.length - 1],
                    `, ${newComponents.join(', ')}`
                  ));
                }

                context.report({
                  node: script,
                  message: 'Auto import and register components.',
                  fix(fixer) {
                    return fixes.map(f => f(fixer));
                  }
                });
              }
            }
          }
        }
      }
    };
  }
};

这个版本的规则确保在初次添加 components 属性时,只会添加一次,并且将所有需要的组件都注册进去。如果 components 属性已经存在,则会在现有的属性中追加新组件。这将避免多个 components 属性的问题。

博客园  ©  2004-2026
浙公网安备 33010602011771号 浙ICP备2021040463号-3