快速写一个babel插件

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

一、babel的作用

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

二、ast分析

那么什么是ast语法树呢?
比如代码var a = 12对应的就是下面的ast语法树,是不是很懵?就这么简单的代码弄出这么多东西

image.png

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

这句VariableDeclaration字面意思就是变量声名,是不是跟我们之前的分析对应上了,至于接下来为啥又有个declarations数组,也很好理解,我们声明变量是不是可以用逗号隔开同时写多个,就像var a = 12, b = 16;,而他们在同一条声明语句中,所以就用数组来表示了。再看具体的一个

image.png

一个id,一个init,也很直观的看出,id就是我们的变量名,init就是我们的值,然后我们看到有三个key在每个花括号中都是一直在重复出现,就是type/start/end,type就是类型,start是代码起始位置,end是结束位置,关于type这里多介绍点。

type

这里我们把每个花括号叫做一个节点,每个节点代表了代码中的一部分,变量名,然后是所赋的值,type表示了每一块节点所表示的类型,那么这些类型有那些呢,babel type这里有详细介绍,当然有很多种类型,这里也无法给大家一一讲解(我也不知道所有的),但是我们需要知道的只是我们要改变的代码是什么类型的节点,介绍个网址 AST explorer,在这个页面中我们只需要把代码写进去,就会展示出代码的ast语法树,上面截图就是来自于这个网站。

三、写插件

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

插件格式

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,所以写成如下

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

然后我们要判断他的变量名是不是a,可以从ast中看到VariableDeclarator的id属性就是变量名部分,所以我们只要判断id的name属性是不是a就可以了

//访问的是当前节点,所以操作对象是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,就是用这个来构建,替换的类型是个Identifier

image.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

四、总结

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,294评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,780评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,001评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,593评论 1 289
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,687评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,679评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,667评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,426评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,872评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,180评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,346评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,019评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,658评论 3 323
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,268评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,495评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,275评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,207评论 2 352

推荐阅读更多精彩内容

  • Babel是前端很常用的转码器,更准确地说是转译器,是从源码到源码的转换编译器,例如可以将我们按照ES6标准写的代...
    拉面头_7c92阅读 1,195评论 4 2
  • 1、通过CocoaPods安装项目名称项目信息 AFNetworking网络请求组件 FMDB本地数据库组件 SD...
    阳明先生_X自主阅读 15,979评论 3 119
  • 我是一名专业的保险代理人。这一天,我去菜场买菜,计划跟卖菜的大妈讨论讨论什么是保险。 要从老百姓的口袋里掏钱出来,...
    公子韬韬阅读 568评论 7 19
  • 要远离负能量的人,很重要。最近工作的内容和强度上绝对是资源浪费,但是不得不行。身边的人满脸菜色,失去胶原蛋白的脸真...
    G言阅读 205评论 0 0