文章概览

主要包括:Babel如何进行转码、插件编写的入门基础、实例讲解如何编写插件。

阅读本文前,需要读者对Babel插件如何使用、配置有一定了解,可以参考笔者之前的文章

本文所有例子可以在 笔者的github 找到,欢迎访问笔者博客获取更多相关文章。

Babel运行阶段

首先来了解Babel转码的过程分三个阶段:分析(parse)、转换(transform)、生成(generate)。

其中,分析、生成阶段由Babel核心完成,而转换阶段,则由Babel插件完成,这也是本文的重点。

分析

Babel读入源代码,经过词法分析、语法分析后,生成抽象语法树(AST)

parse(sourceCode) => AST

转换

经过前一阶段的代码分析,Babel得到了AST。在原始AST的基础上,Babel通过插件,对其进行修改,比如新增、删除、修改后,得到新的AST。

transform(AST, BabelPlugins) => newAST

生成

通过前一阶段的转换,Babel得到了新的AST,然后就可以逆向操作,生成新的代码。

generate(newAST) => newSourceCode

插件基础入门

典型的Babel插件结构,如下代码所示。

export default function({ types: babelTypes }) {
return {
visitor: {
Identifier(path, state) {},
ASTNodeTypeHere(path, state) {}
}
};
};

需要关注的内容如下:

  • babelType:类似lodash那样的工具集,主要用来操作AST节点,比如创建、校验、转变等。举例:判断某个节点是不是标识符(identifier)。
  • path:AST中有很多节点,每个节点可能有不同的属性,并且节点之间可能存在关联。path是个对象,它代表了两个节点之间的关联。你可以在path上访问到节点的属性,也可以通过path来访问到关联的节点(比如父节点、兄弟节点等)
  • state:代表了插件的状态,你可以通过state来访问插件的配置项。
  • visitor:Babel采取递归的方式访问AST的每个节点,之所以叫做visitor,只是因为有个类似的设计模式叫做访问者模式,不用在意背后的细节。
  • Identifier、ASTNodeTypeHere:AST的每个节点,都有对应的节点类型,比如标识符(Identifier)、函数声明(FunctionDeclaration)等,可以在visitor上声明同名的属性,当Babel遍历到相应类型的节点,属性对应的方法就会被调用,传入的参数就是path、state。

极简插件实例

在本例子中,我们实现一个毫无意义的插件:将所有名称为bad的标识符,转成good。完整代码在这里

首先,安装项目依赖。

npm init -f
npm install --save-dev babel-cli

接着,创建插件。判断标识符的名称是否是bad,如果是则替换成good。

// plugin.js
module.exports = function({ types: babelTypes }) {
return {
name: "deadly-simple-plugin-example",
visitor: {
Identifier(path, state) {
if (path.node.name === 'bad') {
path.node.name = 'good';
}
}
}
};
};

源码前的源代码:

// index.js
let bad = true;

运行转码命令:

npx babel --plugins ./plugin.js index.js

输出转码结果:

// index.js
let good = true;

插件配置

插件可以有自己的配置项。我们修改前面的例子,看下在Babel插件中如何获取配置项。完整代码在这里

首先,我们新建 .babelrc,传入配置项。

{
"plugins": [ ["./plugin", {
"bad": "good",
"dead": "alive"
}] ]
}

然后,修改插件代码。我们从 state.opts 中获取到配置参数。

// plugin.js
module.exports = function({ types: babelTypes }) {
return {
name: "deadly-simple-plugin-example",
visitor: {
Identifier(path, state) {
let name = path.node.name;
if (state.opts[name]) {
path.node.name = state.opts[name];
}
}
}
};
};

修改需要转换的代码:

// index.js
let bad = true;
let dead = true;

运行转码命令 npx babel index.js,转码结果如下:

// index.js
let good = true;
let alive = true;

复杂插件例子:替换process.env.NODE_ENV

下面,来看一个稍微复杂一点但比较实用的例子:替换 process.env.NODE_ENV。示例完整代码可以在 这里找到,参考了这个插件

在很多开源项目中,我们经常会看到类似下面的代码,对这些代码,需要在构建阶段进行处理,比如进行替换。

// index.js
if ( process.env.NODE_ENV === 'development' ) {
console.log('我是程序猿小卡');
}

下面,我们创建一个叫做 node-env-replacer 的插件,代码如下,下面会对插件代码进行讲解。

// plugin.js
module.exports = function({ types: babelTypes }) {
return {
name: "node-env-replacer",
visitor: {
// 成员表达式
MemberExpression(path, state) {
// 如果 object 对应的节点匹配了模式 "process.env"
if (path.get("object").matchesPattern("process.env")) {
// 这里返回结果为字符串字面量类型的节点
const key = path.toComputedKey();
if ( babelTypes.isStringLiteral(key) ) {
// path.replaceWith( newNode ) 用来替换当前节点
// babelTypes.valueToNode( value ) 用来创建节点,如果value是字符串,则返回字符串字面量类型的节点
path.replaceWith(babelTypes.valueToNode(process.env[key.value]));
}
}
}
}
};
};

插件代码讲解

这次我们处理的是成员表达方式(MemberExpression)。对于MemberExpression,BabelType的定义如下:

MemberExpression 主要是由 object、property、computed、optional 组成的。对于本例子来说,object 是 process.env 对应的节点,property 为 NODE_ENV 对应的节点。

defineType("MemberExpression", {
builder: ["object", "property", "computed", "optional"],
visitor: ["object", "property"],
// ...
});

前面提到,path对应了节点的属性,以及节点的关联关系。path.get("object") 获取到的就是 object(process.env)对应的 path实例。

matchesPattern(pattern) 检查某个节点是否符合某种模式(pattern)。本例子中,path.get("object").matchesPattern("process.env") 检查 object 是否符合 "process.env" 这种模式。比如 成员表达式 process.env.NODE_ENV 为true,而成员表达式 process.hello.NODE_ENV 返回false。

 if (path.get("object").matchesPattern("process.env")) { }

接着,通过 path.toComputedKey() 获取成员表达式的键(key),对于对于MemberExpression,返回的是类型为字符串字面量(stringLiteral)的节点。

const key = path.toComputedKey();

if ( babelTypes.isStringLiteral(key) ) 判断 key 是否为字符串字面量,如果是,则返回true。

path.replaceWith( node ) 方法用来替换节点。babelTypes.valueToNode( value ) 用来创建节点,如果value是字符串,则返回字符串字面量类型的节点。

path.replaceWith(babelTypes.valueToNode(process.env[key.value]));

运行插件

命令如下:

npx babel --plugins ./plugin.js index.js

转换结果:

// index.js
if ('development' === 'development') {
console.log('我是程序猿小卡');
}

小结

Babel的插件入门比较简单,照葫芦画瓢即可。在编写插件过程中,可能会遇到的主要障碍,包括对ECMA规范不了解、对Babel的API不了解。

  1. 对ECMA规范不了解:MemberExpression、FunctionDeclaration、Identifier等都是规范里的术语,如果对规范没有一定的了解,转换代码的时候就不知道如何入手。建议读者稍微了解下ECMA规范。
  2. 对Babel的API不了解:Babel相关API的文档比较少,这会对插件编写造成不小的困难,目前比较好的解决办法,就是参考现有的插件进行修改。

总而言之,就是多看多写多查。

这里再留个小问题,前面插件替换了 process.env.NODE_ENV,如果是下面代码该怎么替换?

process.env['NODE_' + 'ENV'];

相关链接

babel-handbook

ECMA-262/5.1

Babel核心package的文档

热心开发者贡献的文档

Babel插件开发入门指南的更多相关文章

  1. Babel 入门指南

    Babel 入门指南 ​⚠️ 注意: Babel 可以与很多构建工具(如 Browserify.Grunt.Gulp 等)进行集成.由于本教程选择 Webpack ,所以只讲解与 Webpack 的集 ...

  2. Web API 入门指南 - 闲话安全

    Web API入门指南有些朋友回复问了些安全方面的问题,安全方面可以写的东西实在太多了,这里尽量围绕着Web API的安全性来展开,介绍一些安全的基本概念,常见安全隐患.相关的防御技巧以及Web AP ...

  3. Vue.js 入门指南之“前传”(含sublime text 3 配置)

    题记:关注Vue.js 很久了,但就是没有动手写过一行代码,今天准备入手,却发现自己比菜鸟还菜,于是四方寻找大牛指点,才终于找到了入门的“入门”,就算是“入门指南”的“前传”吧.此文献给跟我一样“白痴 ...

  4. yii2实战教程之新手入门指南-简单博客管理系统

    作者:白狼 出处:http://www.manks.top/document/easy_blog_manage_system.html 本文版权归作者,欢迎转载,但未经作者同意必须保留此段声明,且在文 ...

  5. 【翻译】Fluent NHibernate介绍和入门指南

    英文原文地址:https://github.com/jagregory/fluent-nhibernate/wiki/Getting-started 翻译原文地址:http://www.cnblogs ...

  6. ASP.NET MVC 5 入门指南汇总

    经过前一段时间的翻译和编辑,我们陆续发出12篇ASP.NET MVC 5的入门文章.其中大部分翻译自ASP.NET MVC 5 官方教程,由于本系列文章言简意赅,篇幅适中,从一个web网站示例开始讲解 ...

  7. 一起学微软Power BI系列-官方文档-入门指南(1)Power BI初步介绍

    我们在前一篇文章微软新神器-Power BI,一个简单易用,还用得起的BI产品中,我们初步介绍了Power BI的基本知识.由于Power BI是去年开始微软新发布的一个产品,虽然已经可以企业级应用, ...

  8. 一起学微软Power BI系列-官方文档-入门指南(2)获取源数据

    我们在文章: 一起学微软Power BI系列-官方文档-入门指南(1)Power BI初步介绍中,我们介绍了官方入门文档的第一章.今天继续给大家介绍官方文档中,如何获取数据源的相关内容.虽然是英文,但 ...

  9. 一起学微软Power BI系列-官方文档-入门指南(3)Power BI建模

    我们前2篇文章:一起学微软Power BI系列-官方文档-入门指南(1)Power BI初步介绍 和一起学微软Power BI系列-官方文档-入门指南(2)获取源数据 中,我们介绍了官方入门文档与获取 ...

随机推荐

  1. 消息队列_MSMQ(1)简单了解

    MSMQ (微软消息队列)   MSMQ 百度百科 MicroSoft Message Queuing(微软消息队列)是在多个不同的应用之间实现相互通信的一种异步传输模式,相互通信的应用可以分布于同一 ...

  2. MySql中存储过程中的@变量总是无法执行,提示Parameter '@XXX' must be defined

    一.情形: 在.net调用Mysql时,比如如下的一句SQL,总是无法执行,可是在其它SQL客户端窗口中是能正确执行的. drop procedure if exists AddColumnUnles ...

  3. Dynamics 365 Customer Engagement V9 活动源功能报错的解决方法

    微软动态CRM专家罗勇 ,回复300或者20190120可方便获取本文,同时可以在第一间得到我发布的最新博文信息,follow me!我的网站是 www.luoyong.me . 安装好Dynamic ...

  4. EFCore使用JSON_VALUE查询json对象的值

    EFCore使用JSON_VALUE查询json对象的值 Intro SqlServer 从2016开始支持 JSON 操作,可以使用 JSON_VALUE 查询 JSON 对象的某个属性值,更多介绍 ...

  5. git submodule 删除及更新URL 转载的

    删除一个submodule 1.删除 .gitsubmodule中对应submodule的条目 2.删除 .git/config 中对应submodule的条目 3.执行 git rm --cache ...

  6. Python 日志模块logging

    logging模块: logging是一个日志记录模块,可以记录我们日常的操作. logging日志文件写入默认是gbk编码格式的,所以在查看时需要使用gbk的解码方式打开. logging日志等级: ...

  7. iOS证书配置与管理

    证书: 证书: 命名 特点 团队管理 开发证书 iOS Development 不与App ID对应 表示拥有开发应用的资格 一般只需一个,通过导出p12文件,分发给其他电脑安装: 生产证书 iOS ...

  8. 为什么不使用github的wiki而是使用mkdocs做文档管理?

    为什么不使用github的wiki而是使用mkdocs做文档管理? 目前 KSFramework 是使用mkdocs来做在线文档 而非使用github的wiki,这是为什么呢? 在windows下搭建 ...

  9. 【任务】Python语言程序设计.MOOC学习

    [博客导航] [Python导航] 任务 18年11月29日开始,通过9周时间跨度,投入约50小时时间,在19年1月25日之前,完成中国大学MOOC平台上的<Python语言程序设计>课程 ...

  10. Python 函数初识 (1)

    一.今日主要内容 认识函数 函数:对功能或者动作的封装(定义) 语法: def 函数名字(形参) 函数体 函数的调用格式:函数名(实参) 函数的返回值 关键字:return 终止函数的运行 1.函数内 ...