写一个webpack插件,让console.log打印文件名和行数

307 阅读1分钟

开发时经常会使用console.log进行调试,如果代码复杂,多个console.log输出往往会分不清其所在位置,因此可以通过一个插件,每次调用console.log时,自动加入其所在文件和代码行数,提升调试效率。

webpack plugin实现

这里是用webpack插件实现的,整体代码如下:

const pluginName = 'ConsoleLogWebpackPlugin';



class ConsoleLogWebpackPlugin {
    apply(compiler) {
        const wp = compiler.webpack;
        compiler.hooks.normalModuleFactory.tap(pluginName, (factory) => {
            factory.hooks.parser.for('javascript/auto')
                .tap(pluginName, (parser, options) => {
                    parser.hooks.call.for('console.log').tap(pluginName, (expr) => {
                        if (!parser.state.module.resource.match(/node_modules/)) {
                            let filename = parser.state.module.resource.split('/').at(-1);
                            const dep1 = new wp.dependencies.ConstDependency(
                                `"${filename + ':' + expr.loc.start.line + ':'}",`,
                                expr.arguments[0].range[0]
                            )
                            dep1.loc = expr.arguments[0].loc
                            parser.state.module.addPresentationalDependency(dep1)
                   
                        }
                    });

                });
        })
    }
}

module.exports = ConsoleLogWebpackPlugin;

开发中的问题

个人感觉实现这个需求最简单的方式是修改ast。通过webpack的js parser,虽然能获取到ast,但并不能修改ast(改了也没效果)。正确的做法是修改webpack编译过程中的dependency。

比如说要删除console.log,则需要在对应位置添加一个空依赖:

   const clearDep = new  wp.dependencies.ConstDependency("", expr.range);
    clearDep.loc = expr.loc;
    parser.state.module.addDependency(clearDep);

不过webpack内部对象都没啥api文档,所以还是建议用babel插件来实现。

babel plugin实现

module.exports = function consoleLogPlugin({ types: t }) {
  return {
    visitor: {
      CallExpression(path, state) {
        const callee = path.get('callee');
        if (
          callee.isMemberExpression() &&
          callee.get('object').isIdentifier({ name: 'console' }) &&
          callee.get('property').isIdentifier({ name: 'log' })
        ) {
          const { line } = path.node.loc.start;
          const filename = state.file.opts.filename.split('/').at(-1);

          const fileAndLineNode = t.stringLiteral(`${filename}:${line}`);
          path.node.arguments.unshift(fileAndLineNode);
        }
      },
    },
  };
};