如何用 typescript 写一个处理 console 的 babel 插件

点击上方蓝字关注我们!

作者介绍

翟旭光,2019 年 11 月加入 Qunar 机票前端团队,目前在国内基础平台。喜欢工程化和 typescript,喜欢各种提升开发效率的工具,对这方面有浓厚的兴趣,有很多想法待实现。喜欢阅读源码,热衷于探索源码的乐趣,就像在深海探险,会因为偶然发现的一个冷僻的知识点或技巧而兴奋。

技术点介绍

通过这篇文章你可以学到:

  • ts-mocha 和 chai 来写测试用例

  • 如何写一个 babel 插件

  • 如何用 schame-utils 来做 options 校验

  • typescript 双重断言的一个应用场景

  • 如何组织测试代码

前言

console 对象对前端工程师来说是必不可少的 api,开发时我们经常通过它来打印一些信息来调试。但生产环境下 console 有时会引起一些问题。

最近公司内报了一个 bug,console 对象被重写了但是没有把所有的方法都重写,导致了报错,另外考虑到 console 会影响性能,所以最后定的解决方案是把源码中所有的 console 都删掉。

生产环境下删除 console 是没问题的,但是这件事不需要手动去做。在打包过程中,我们会对代码进行压缩,而压缩的工具都提供了删除一些函数的功能,比如 terser 支持 dropconsole 来删除 console.*,也可以通过 purefuncs 来删除某几种 console 的方法。

但是这种方案对我们是不适用的,因为我们既有 react 的项目又有 react-native 的项目,react-native 并不会用 webpack 打包,也就不会用 terser 来压缩。

其实源码到最终代码过程中会经历很多次 ast 的解析,比如 eslint、babel、terser 等,除了 eslint 主要是用来检查 ast,并不会做过多修改,其余的工具都可以来完成修改 ast,删除 console 这件事情。terser 不可以用,那么我们可以考虑用 babel 来做。

而且,我们只是希望在生产环境下删除 console,在开发环境下 console 还是很有用的,如果能扩展一下 console,让它功能更强大,比如支持颜色的打印,支持文件和代码行数的提示就好了。

于是我们就开发了本文介绍的这个插件: babel-plugin-console-transform

安装

演示

先看下效果再讲实现。

比如源码是这样的:

生产环境下转换后的代码:

开发环境下转换后的代码:

运行效果:

生产环境删除了 console,开发环境扩展了一些方法,并且添加了代码行数和颜色等。

接下来是功能的细节还有实现思路。

功能

按照需求,这个插件需要在不同的环境做不同的处理,生产环境可以删除 console,开发环境扩展 console。

生产环境删除 console 并不是全部删除,还需要支持删除指定 name 的方法,比如 log、warn 等,因为有时 console.error 是有用的。而且有的时候根据方法名还不能确定能不能删除,要根据打印的内容来确定是不是要删。

开发环境扩展 console 要求不改变原生的 api,扩展一些方法,这些方法会被转换成原生 api,但是会额外添加一些信息,比如添加代码文件和行数的信息,添加一些颜色的样式信息等。

于是 console-transform 这个插件就有了这样的参数。

其中 env 是指定环境的,可以通过 process.env.NODE_ENV 来设置。

removeMethods 是在生产环境下要删除的方法,可以传一个 name,支持 glob,也就是 *g*是删除所有名字包含 g 的方法;而且可以传一个函数,函数的参数是 console.xxx 的所有参数,插件会根据这个函数的返回值来决定是不是删除该 console.xxx。多个条件的时候,只要有一个生效,就会删。

additionalStyleMethods 里面可以写一些扩展的方法,比如 succes、danger,分别定义了他们的样式。其实插件本身提供了 red、green、orange、blue、bgRed、bgOrange、bgGreen、bgBlue 方法,通过这个参数可以自定义,开发环境 console 可以随意的扩展。

实现

接下来是重头戏,实现思路了。

首先介绍下用到的技术,代码是用 typescript 写的,实现功能是基于 @babel/core,@babel/types,测试代码使用 ts-mocha、chai 写的,代码的 lint 用的 eslint、prettier。

主要逻辑

babel 会把代码转成 ast,插件里可以对对 ast 做修改,然后输出的代码就是转换后的。babel 的插件需要是一个返回插件信息的函数。

如下, 参数是 babelCore 的 api,里面有很多工具,我们这里只用到了 types 来生成一些 ast 的节点。返回值是一个 PluginObj 类型的对象。

其中 ConsoleTransformState 里面是我们要指定的类型,这是在后面对 ast 处理时需要拿到参数和文件信息时用的。

PluginOptions 是 options的类型,env 是必须,其余两个可选,removeMethods 是一个值为 string 或 Function 的数组,additionalStyleMethods 是一个值为 string 的对象。这都是我们讨论需求时确定的。(其中 file 是获取代码行列数用的,我没找到它的类型,就用了 any。)

返回的插件信息对象有一个 visitor 属性,可以声明对一些节点的处理方式,我们需要处理的是 CallExpression 节点。(关于代码对应的 ast 是什么样的,可以用 astexplorer 这个工具看)。

这个方法就会在处理到 CallExpression 类型的节点时被调用,参数 path 可以拿到一些节点的信息,通过 path.get('callee')拿到调用信息,然后通过 node.type 过滤出 console.xxx() 而不是 xxx()类型的函数调用,也就是 MemberExpression 类型,再通过 callee.node.object 过滤出 console 的方法。

production 下删除 console

接下来就是实现主要功能的时候了。

先看主要逻辑,production 环境下,调用 path.remove(),这样 console 就会被删除,其他环境对 console 的参数(path.node.arguments.)做一些修改,在前面多加一些参数,然后把方法名(callee.node.property.name)改为 log。主要逻辑是这样。

然后细化一下。

production 环境下,当有 removeMethods   参数时,要根据其中的 name 和 funciton 来决定是否删除:

通过把 path.node.arguments 把所有的 args 放到一个数组里,然后来匹配条件。如下,匹配时根据类型是 string 还是 function 决定如何调用。

如果是 function 就把参数作为参数传入,根据返回值确定是否删除,如果是字符串,会用 mimimatch 做 glob 的解析,支持**、 {a,b}等语法。

非 production 下扩展 console

当在非 production 环境下,插件会提供一些内置方法。

结合用户通过 addtionalStyleMethods 扩展的方法,来对代码做转换:

通过 methodName   判断出要扩展的方法,然后在参数(path.node.arguments)中填入一些额外的信息 ,这里就用到了@babel/core 提供的 types(其实是封装了@babel/types 的 api)来生成文本节点了,最后把扩展的方法名都改成 log。

options 的校验

我们逻辑写完了,但是 options 还没有校验,这里可以用 schema-utils 这个工具来校验,通过一个 json 对象来描述解构,然后调用 validate 的 api 来校验。webpack 那么复杂的 options 就是通过这个工具校验的。

schema 如下,对 env、removeMethods、additionalStyleMethods   都是什么格式做了声明。

测试

代码写完了,就到了测试环节,测试的完善度直接决定了这个工具是否可用。

options 的测试就是传入各种情况的 options 参数,看报错信息是否正确。这里有个知识点,因为 options 需要传错,所以肯定类型不符合,使用 as any as PluginOptions 的双重断言可以绕过类型校验。

主要的还是 plugin 逻辑的测试。

@babel/core 提供了   transformFileSync 的 api,可以对文件做处理,我封装了一个工具函数,对输入文件做处理,把结果的内容和另一个输出文件做对比。

fixtures 下按照 production 和其他环境的不同场景分别写了输入文件 actual 和输出文件 expected。比如 production 下测试 drop-all-console、drop-console-by-function 等 case,和下面的测试代码一一对应。

代码里面是对各种情况的测试。

总结

babel-plugin-console-transform 这个插件虽然功能只是处理 console,但细节还是蛮多的,比如删除的时候要根据 name 和 function 确定是否删除,name 支持 glob,非 production 环境要支持用户自定义扩展等等。

技术方面,用了 schema-utils 做 options 校验,用 ts-mocha 结合断言库 chai 做测试,同时设计了一个比较清晰的目录结构来组织测试代码。

麻雀虽小,五脏俱全,希望大家能有所收获。这个插件在我们组已经开始使用,大家也可以使用,有 bug 或者建议可以提 issue 和 pr。

【END】

我来评几句
登录后评论

已发表评论数()

相关站点

热门文章