代码整理之实现一个loader给vue文件加上name

201 阅读2分钟

前言

上家公司的工作是偏基础架构方向的,当时主要技术栈还是vue2 + element-ui,在编写前端工程模板的时候,针对页面缓存这个模块的实现也是常用的vuex + 内置的<keep-alive>组件,开发人员在使用模板的过程中,常常会出现缓存失败的现象,后来发现是在页面中没有配置name属性造成的,所以我决定编写一个工具来帮助开发人员自动加上name属性。

loader实现

关于工具的实现,下面给出了详细代码与注释:

const parseVue = require('@vue/compiler-sfc').parse;
const { parse } = require('@babel/parser');
// const { getOptions } = require('loader-utils');
const t = require('@babel/types');
const generator = require('@babel/generator');
const traverse = require("@babel/traverse");
const convertPath = require('path');

module.exports = function nameLoader(content) {
  // 获取文件路径参数
  let params = new URL(this.resource).searchParams;
  // 获取自定义文件name
  const componentName = params.get('componentName');
  // 获取参数确认是否本地测试,参数从位置为2开始获取,默认参数0为node安装目录,参数1为当前文件目录
  const isDev = process.argv[2] && process.argv[2] === 'dev';
  // vue-loader转化后type值有script|styles|template,只对type为script部分做转化
  if (params.get('type') === 'script' || isDev) {
    // 通过getOptions获取webpack中配置loader时传入的options参数,可以做一些其他定制化配置,webpack5内置了该方法(this.getOptions)
    // const options = getOptions(this) || {};
    // script原内容,vue-loader转换以后不需要再重新parse
    const script = (isDev ? parseVue(content) : content).descriptor.script.loc.source;
    // 转化成ast
    const ast = parse(script, {
      sourceType: 'module'
    });
    const resourcePath = this.resourcePath;
    // 遍历ast
    traverse.default(ast, {
      // 访问export default节点
      ExportDefaultDeclaration(path) {
        // 是否有name字段
        const node = path.node.declaration.properties.find(item => item.key.name === 'name');
        if (!node) {
          // 是否当前节点下第一个property类型的节点
          let isFirst = true;
          // 遍历当前ast节点,找到ObjectExpression对象,(包含.vue文件中定义的选项data,method等)
          path.traverse({
            ObjectExpression(childPath) {
              if (isFirst) {
                // 找到路径并插入name属性
                childPath.traverse({
                  enter(childPath) {
                    if (isFirst) {
                      const basename = componentName ? componentName : convertPath.basename(resourcePath);
                      childPath.insertBefore(t.objectProperty(t.identifier('name'), t.stringLiteral(basename.split('.')[0])));
                      isFirst = false;
                    }
                  }
                })
              }
            }
          })
        }
      }
    })
    
    // ast2code
    const { code } = generator.default(ast);

    // 替换没有name字段的原内容
    const newContent = content.replace(script, code);

    return newContent;
  } else {
    return content;
  }
}

上方关于Ast的操作大家可以把自己的代码拷贝到这个网站 astexplorer.net/ 转化成Ast结构,找到自己想要的属性的位置再进行相关操作。 大家感兴趣的话也可以npm install vue-name-loader,玩一玩儿。