快速写一个babel插件

es6/7/8的出现,给咱们带来了不少方便,可是浏览器并不怎么支持,目前chrome应该是支持率最高的,因此为了兼容咱们只能把代码转成es5,这应该算是咱们最初使用babel的一个原因,随着业务的开发,咱们会有不少本身定制化的需求,单纯的bebel并不能解决咱们全部的问题,因此babel插件应用而来,本文将会采用较为通俗的语言来描述如何快速写一个babel插件。node

1、babel的做用

babel的做用其实就是一个转换器,把咱们的代码转成浏览器能够运行的代码,相似于加工厂的概念。解析代码都是一个文件一个文件的处理,把代码读出来,而后通过处理,再输出,在处理的过程当中每一个文件的代码其实就是个大的字符串。可是咱们要把有些语法修改,好比let定义变量改为var定义,很明显用字符串替换是不现实的,这里babel是把代码转成ast语法树,而后通过一系列操做以后再转成字符串输出,git

2、ast分析

那么什么是ast语法树呢?
好比代码var a = 12对应的就是下面的ast语法树,是否是很懵?就这么简单的代码弄出这么多东西
image.png
先来个我的官方解释,ast->Abstract Syntax Tree,也叫抽象语法树,就是对代码进行词法分析以后再进行语法分析弄出来的东西,能够理解为代码执行前的编译过程,毕竟运行代码的不是咱们,因此要变成机器但是别的东东。
简单分析下var a = 12这一句,首先咱们知道这条语句是定义变量,定义了a,而且赋值12(非配内存什么的这里就不说了,跟理解ast没啥用处),而后咱们在对应看那个ast语法树,开始的program就是根节点,不用管,而后是body,应该是到重点了,紧接着咱们就看到了image.pnges6

这句VariableDeclaration字面意思就是变量声名,是否是跟咱们以前的分析对应上了,至于接下来为啥又有个declarations数组,也很好理解,咱们声明变量是否是能够用逗号隔开同时写多个,就像var a = 12, b = 16;,而他们在同一条声明语句中,因此就用数组来表示了。再看具体的一个
image.png
一个id,一个init,也很直观的看出,id就是咱们的变量名,init就是咱们的值,而后咱们看到有三个key在每一个花括号中都是一直在重复出现,就是type/start/end,type就是类型,start是代码起始位置,end是结束位置,关于type这里多介绍点。github

type

这里咱们把每一个花括号叫作一个节点,每一个节点表明了代码中的一部分,变量名,而后是所赋的值,type表示了每一块节点所表示的类型,那么这些类型有那些呢,babel type这里有详细介绍,固然有不少种类型,这里也没法给你们一一讲解(我也不知道全部的),可是咱们须要知道的只是咱们要改变的代码是什么类型的节点,介绍个网址 AST explorer,在这个页面中咱们只须要把代码写进去,就会展现出代码的ast语法树,上面截图就是来自于这个网站。chrome

3、写插件

基础的东西讲了写,下面说下具体如何写插件。数组

插件格式

image.png
这是一个插件的基本格式,一个函数,参数是babel,这里咱们用到的是types这个属性,因此只把它写出来,而后就是返回一个对象,key是visiter,而后里面又是个对象,可是key是咱们熟悉的东西,就是一个babel-types类型,而后是一个箭头函数,函数有两个参数,path表示路径,state表示状态。
visitor字面意思就是访问者,这里也是这个意思,也就是咱们要访问哪一个类型的节点,这里是个CallExpression,字面意思就是调用表达式,相似于handle(),path参数表示当前节点的位置,包含的主要是当前节点(node)内容以及父节点(parent)内容,state先无论,有了这些咱们就能够去修改代码了。浏览器

一个简单的插件

咱们先来一个简单的插件,要求是把全部定义变量名为a的换成是b
首先咱们要找到定义变量的地方,而后判断变量名是否是a,若是是就把它替换成b,思路就是这样。开始动手,首先把这一句放到 AST explorer这个网站中,鼠标选中这一句代码,右侧就会展现出这句代码转成ast的样子
image.png
咱们看到这是一个变量定义的语句,因此咱们要找的节点类型就是VariableDeclarator,因此写成以下babel

visitor: {
    VariableDeclarator: (path, state) => {
         //code
    }
}

而后咱们要判断他的变量名是否是a,能够从ast中看到VariableDeclarator的id属性就是变量名部分,因此咱们只要判断id的name属性是否是a就能够了ide

//访问的是当前节点,因此操做对象是path.node
if(path.node.id.name === 'a'){
    //code
}

有人可能会想这里是否是直接path.node.id.name = 'b'就能够了,若是你是操做object,那你就对了,不过这里是ast语法树,因此想改变某个值,就是用对应的ast来替换,因此咱们要把id是a的ast换成b的ast,那么b的ast怎么建立呢?很简单,最外层的函数参数咱们引入了types,就是用这个来构建,替换的类型是个Identifierimage.png因此咱们也要构建b的Identifier,写起来就是t.Identifier('b'),因此咱们的插件最终就是:函数

module.exports = function(babel){
    let t = babel.types;
    return {
        visitor: {
            VariableDeclarator(path, state) {
                if(path.node.id.name == 'a'){
                    path.node.id = t.Identifier('b')
                }
            }
            }
      }
}

因此咱们写插件的时候只须要如下几个步骤就能够完成:
1.确认咱们要修改的节点类型(把代码复制到ast explorer 中,一一对应)
2.找到修改的属性是哪一个(这里咱们修改id属性)
3.根据旧的ast构建新的ast语句并替换(把b构建成ast语句替换原来的属性)

构建ast

这里的一个难点就是如何构建ast,代码有不少种类型,咱们要修改就须要构建各类各样类型的ast,这里咱们结合AST explorerbabel type来快速构建,首先你要知道咱们要构建的ast是什么样的,因此把代码放到AST explorer中,咱们就能够在右侧看到它的ast树,而后再根据节点类型,参考babel type的使用方法一层一层的构建。
下面举个例子:
image.png
咱们要构建声明语句,第一层节点类型是VariableDeclaration,因此要写这个类型的ast,看下babel-type中怎么用
image.png
照着写t.variableDeclaration('const', [declarators]),kind是const,后面是个数组,每一项是个VariableDeclarator。
咱们再看ast,下一层就是个VariableDeclarator,仍是去查下这个怎么用image.png一个id,一个init,id就是变量名,init就是要赋的值,因此写出来就是t.variableDeclarator('info', initExpression),是这样吗?要记住每一项都是ast,因此info也要构建成ast,看下ast树中这个id是啥样的,他是个identifier,而后使用方法是image.png
因此id就是t.identifier('info'),init表达式有点复杂,是个对象,因此继续上面的步骤,写出来以下

t.objectExpression([t.objectProperty(
            t.identifier('name'),
            t.stringLiteral('Steven'),
            false,
            false,
            null
        )])

连起来就是

t.variableDeclaration('let', [t.variableDeclarator(t.identifier('info'), t.objectExpression([t.objectProperty(
            t.identifier('name'),
            t.stringLiteral('Steven'),
            false,
            false,
            null
        )]))])

咱们来验证下
image.png
包括以前的把a换成b
image.png显然是对的,插件中内容是
image.png

4、总结

写插件最快的方法就是,对照上面推荐的两个网站一层一层的构建ast,而后作想要的操做。在GitHub上有全面的介绍写插件的各个属性及方法,看这里教程

相关文章
相关标签/搜索