theydy / notebook

记录读书笔记 + 知识整理,vuepress 迁移中 https://theydy.github.io/notebook/
0 stars 0 forks source link

vue-dev-inspector loader #32

Open theydy opened 3 years ago

theydy commented 3 years ago

vue-dev-inspector.js


const loaderUtils = require('loader-utils');
const formatSource = require('./formatSource.js');

module.exports = function (source) {
  try {
    const {
      // 模块所在的根目录
      rootContext: rootPath,
      // 资源文件路径
      resourcePath: filePath,
    } = this;

    /**
     * example:
     * rootPath: /home/xxx/project
     * filePath: /home/xxx/project/src/ooo/xxx.js
     * relativePath: src/ooo/xxx.js
     */
    const relativePath = filePath.slice(rootPath.length + 1);

    const options = loaderUtils.getOptions(this);

    if ((options.exclude || []).length > 0) {
      const isSkip = options.exclude.some((path) => filePath.includes(path));
      if (isSkip) {
        return source;
      }
    }

    const code = formatSource(source, relativePath);
    return code;
  } catch (error) {
    console.error('\nvue-dev-inspector compiler error', error);
    return source;
  }
};

formatSource.js


const compiler = require('@vue/compiler-dom');

function transform(
  ast,
  nodeList,
  options = {
    entry: (node, nodeList) => {},
    out: (node, nodeList) => {},
  }
) {
  const _traverse = (ast, options) => {
    options.entry && options.entry(ast, nodeList);

    ast.children &&
      ast.children.map((node) => {
        _traverse(node, options);
      });

    options.out && options.out(ast, nodeList);
  };
  _traverse(ast, options);
}

function generator(nodeList, source) {
  let code = '';
  // 如果 vue 文件中自定义块,必须写针对自定义块的 rule,否则 vue-loader 会报错,具体看
  // https://theydy.github.io/notebook/vue/other.html#vue-loader-%E8%87%AA%E5%AE%9A%E4%B9%89%E5%9D%97
  let start = 0;

  for (let i = 0; i < nodeList.length; i++) {
    const node = nodeList[i];
    code += source.slice(start, node.insertIndex);
    code += node.desc;

    if (i === nodeList.length - 1) {
      code += source.slice(node.insertIndex);
    } else {
      start = node.insertIndex;
    }
  }

  return code;
}

function vueDevInspectorLoader(source, relativePath) {
  const ast = compiler.parse(source);

  const template = ast.children.find((node) => node.tag === 'template');

  // 如果没有 template 直接返回
  if (!template) return source;

  const nodeList = [];
  transform(template, nodeList, {
    entry: (node, nodeList) => {
      if (!node.tag) return;

      const nodeStart = node.loc.start.offset;
      const hasProps = node.props && node.props.length;

      const insertIndex = !hasProps
        ? nodeStart + node.tag.length + 1
        : node.props[node.props.length - 1].loc.end.offset;

      const target = {
        insertIndex,
        desc: ` data-inspector-line="${node.loc.start.line}" data-inspector-column="${node.loc.start.column}" data-inspector-relative-path="${relativePath}"`,
      };

      nodeList.push(target);
    },
  });

  let code = generator(nodeList, source);
  return code;
}

module.exports = vueDevInspectorLoader;