babel插件分析-编写你的第一个插件

前言

  最为一个前端开发者,应该没有人不知道babel是什么,但针对不大熟悉的同学,在这里我还是简单介绍一下:JavaScript 的版本标准更新频繁,从2015年开始,就以每年一个版本的速度更新(该版本为es6),每个版本标准都会推出一些新的语法糖(装饰器语法,动态加载等),极大地提升了编程体验;在下个版本推出前,社区也会提出一些提案,涉及未来es版本的最新特性(可选链式调用等)。这些新的特性或者语法糖并不为当前的浏览器引擎所识别。因此就需要一个类似polyfill的中间工具,将这些新的特性编译为降级语法,在功能不变的前提下,支持浏览器引擎。而babel就是这个最出名的polyfill工具,成为事实上的前端生态重要组成部分。

关于AST

  在大型项目中,通常有大佬完成babel配置,作为搬砖仔只需要写业务代码就可以了。但是如果你并不满足于成为页面仔,那就需要对这个重要的底层工具有一定的深度了解。babel之所以能够替我们完成语法的转换,是因为其内置工具(基于ESTree)对我们的源码进行了静态分析,将js源码解析成为了以路径(path)和节点(node)作为基础元素的抽象语法树(AST,计算机科学中的概念),针对js语法所允许的每一种写法,都有与之对应的描述方式与操作api,通过暴露出来的api,即可对源码进行替换,拼接,压缩等操作,输出转换后的代码。举一个最简单的例子,使用let关键字声明一个变量a:

let a = 1;

通过AST工具转换后,可以得到如下的结构(限于篇幅,没有截取全部内容,顺便安利一个ast分析工具astexplorer,链接https://astexplorer.net/ 输入代码后,可直接在右侧获取该代码解析出的对象)

抽象语法树.png

variableDeclaration说明这个节点是一个变量声明,kind:let说明这个变量是let类型,id和init分别指代变量的名字和变量的初始值,所有的js预发都会被解析为类似的形式,其他的语法会被解析成什么样子,大家可以自行实验。获取了抽象语法树,接下来的问题就是如何对其进行改造,满足我们的要求。babel插件的写法并不难,其本质上就是一个函数,该函数返回一个对象,该对象包含visitor属性,该属性定义了在对ast进行遍历的过程中,对哪些节点进行处理:

module.exports = function() {
    return {
        visitor: {
            Identifier() {
                console.log("here is identifier");
            }
        }
    }
}

上面就是一个简单的babel插件,表示在遍历的过程中,每遇到一个标识符,就打印一句提示。identifier是babel内置的js语法描述标识,类似的还有MemberExpression 、FunctionDeclaration 等,表示在进入成员变量或者函数声明节点时要进行的操作。

示例

接下来是一个稍微复杂一些的例子:
源文件1:

function square(n) {
    return n * n;
}

将上述代码进行ast分析(使用astexplorer):


源文件1 部分ast.png

插件内容:

module.exports = function({types: t}) {
    return {
        visitor: {
            BinaryExpression(path) {
                if (path.node.operator === '*') {
                    path.replaceWith(
                        t.binaryExpression('**', path.node.left, t.NumericLiteral(2))
                      );
                }
            }
        }
    }
}

输出内容(webpack打包之后):

eval("function square(n) {\n  return Math.pow(n, 2);\n}\n\n//# sourceURL=webpack:///./index.js?");

通常情况下,一个babels是一个接受babel实例的函数,types是@babel/core包中的内容,在插件的编写过程中非常常用,故而此处使用解构赋值,以后使用命名的t变量指代types。插件的功能是遍历语法树,进入每一个二元表达式,当二元表达式的操作符是‘*’时,将其替换为一个新的二元表达式'n ** 2',此处babel自己还进行了转码,故而最终输出内容是Math.pow(n, 2)。完整代码附在文末仓库中。编写babel插件的大致流程就是:首先对你要转化或处理的目标代码进行ast分析,找出其中的关键节点和特征表达式;之后对要处理的节点进行对应的修改或删除等操作,查找api中对应的方法。

再来个例子,替换函数中的变量名,原函数如下:

function square(n) {
    return n * n;
}

插件内容:

module.exports = function({types: t}) {
    return {
        visitor: {
            FunctionDeclaration(path) {
                path.scope.rename("n", "x");
              }
        }
    }
}

输出内容:

eval("function square(x) {\n  return x * x;\n}\n\n//# sourceURL=webpack:///./index.js?");

可以看到,源码中函数变量n变成了x,事实上,在babel插件中也有作用域的概念,即scope,可以用来保存局部状态,具体api详见文档。babel的文档太过繁杂,学习成本较高,对于二次开发不太友好,要进行一些骚操作还是要小心谨慎,最好先从babel官方的一些插件的源码开始阅读学习。笔者后续也会带来一篇balei插件源码分析的文章。
填坑文章:plugin-proposal-optional-chaining源码分析
————————————————————————————————————
参考文献:
babel插件写法官方文档:https://github.com/jamiebuilds/babel-handbook/blob/master/translations/zh-Hans/plugin-handbook.md
babel types api文档:https://www.babeljs.cn/docs/babel-types
AST explorer官网:https://astexplorer.net/
文中例子的代码仓库:https://github.com/dianluyuanli-wp/myBabel

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

推荐阅读更多精彩内容