专栏名称: 寒东设计师
前端工程师
目录
相关文章推荐
参考消息  ·  中方驳斥鲁比奥涉华言论 ·  15 小时前  
参考消息  ·  以军公布调查:承认全面失败 ·  18 小时前  
世界说  ·  中蒙新跨境铁路历时20年终落地 ... ·  2 天前  
参考消息  ·  北极!美俄正“秘密谈判” ·  昨天  
参考消息  ·  将加拿大从“五眼”除名?白宫高官回应 ·  2 天前  
51好读  ›  专栏  ›  寒东设计师

入门babel--实现一个es6的class转换器

寒东设计师  · 掘金  ·  · 2018-04-03 08:20

正文

入门babel--实现一个es6的class转换器

babel是一个转码器,目前开发react、vue项目都要使用到它。它可以把es6+的语法转换为es5,也可以转换JSX等语法等,实际上他能通过自定义插件的方式完成任意转换。
我们在项目中都是通过配置插件和预设(多个插件的集合)来转换特定代码,例如env、stage-0等。那么这些库是如何实现的呢,下面就通过一个小例子探究一下--把es6的 class 转换为es5。

文章结构:

webpack环境配置

大家应该都配置过babel-core这个loader,实际上它的作用只是提供babel的核心Api,我们的代码转换其实都是通过插件来实现的。
接下来我们不用第三方的插件,自己实现一个es6类转换插件。先执行以下几步初始化一个项目:

  • npm install webpack webpack-cli babel-core -D
  • 新建一个webpack.config.js
  • 配置webpack.config.js

如果我们的插件名字想叫transform-class,需要在webpack配置中做如下配置:

接下来我们在node_modules中新建一个babel-plugin-transform-class的文件夹来写插件的逻辑(如果是真实项目,你需要编写这个插件并发布到npm仓库),如下图:

红色区域是我新建的文件夹,它上面是一个标准的插件的项目结构,为了方便我的插件只写了核心的index.js文件。

如何编写bable插件

babel插件其实是通过AST(抽象语法树)实现的。
babel帮助我们把js代码转换为AST,然后允许我们修改,最后再把它转换成js代码。
那么就涉及到两个问题:js代码和AST之间的映射关系是什么?如何替换或者新增AST?

好,先介绍一个工具: astexplorer.net :

这个工具可以把一段代码转换为AST:

如图,我们写了一个es6的类,然后网页的右边帮我们生成了一个AST,其实就是把每一行代码变成了一个对象,这样我们就实现了一个映射。

再介绍一个文档: babel-types :

这是创建AST节点的Api文档。
比如,我们想创建一个类,先到astexplorer.net中转换,发现类对应的AST类型是 ClassDeclaration 。好,我们去文档中搜索,发现调用下面的api就可以创建这样一个节点:

同理,创建其他节点也是一样的道理。有了上面这两个东西,我们就可以做任何转换了。

下面我们开始真正编写一个插件,分为以下几步:

  • 在index.js中export一个函数
  • 函数中返回一个对象,对象有一个visitor参数(必须叫visitor)
  • 通过astexplorer.net查询出 class 对应的AST节点为 ClassDeclaration
  • 在vistor中设置一个捕获函数 ClassDeclaration ,意思是我要捕获js代码中所有 ClassDeclaration 节点
  • 编写逻辑代码,完成转换

上面的步骤对应成代码:

module.exports = function ({ types: t }) {
    return {
        visitor: {
            ClassDeclaration(path) {
                //在这里完成转换
            }
        }
    };
}

代码中有两个参数,第一个 {types:t} 东西是从参数中解构出变量t,它其实就是babel-types文档中的t(下图红框),我们就是用这个 t 创建节点:

第二个参数 path ,它是捕获到的节点对应的信息,我们可以通过 path.node 获得这个节点的AST,在这个基础上进行修改就能完成了我们的目标。

如何把es6的class转换为es5的类

上面都是预备工作,真正的逻辑从现在才开始,我们先考虑两个问题:
  1. 我们要做如下转换,首先把es6的类,转换为es5的类写法(也就是普通函数),我们观察到,很多代码是可以复用的,包括函数名字、函数内部的代码块等。

  1. 如果不定义class中的 constructor 方法,JavaScript引擎会自动为它添加一个空的 constructor() 方法,这需要我们做兼容处理。
接下来我们开始写代码,思路是:
  • 拿到老的AST节点
  • 创建一个数组用来盛放新的AST节点(虽然原class只是一个节点,但是替换后它会被若干个函数节点取代)
  • 初始化默认的 constructor 节点(上文提到,class中有可能没有定义constructor)
  • 循环老节点的AST对象(会循环出若干个函数节点)
  • 判断节点的类型是不是 constructor ,如果是,通过老数据创建一个普通函数节点,并更新默认 constructor 节点
  • 处理其余不是 constructor 的节点,通过老数据创建 prototype 类型的函数,并放到 es5Fns
  • 循环结束,把 constructor 节点也放到 es5Fns
  • 判断es5Fns的长度是否大于1,如果大于1使用 replaceWithMultiple 这个API更新AST
module.exports = function ({ types: t }) {
    return {
        visitor: {
            ClassDeclaration(path) {
                //拿到老的AST节点
                let node = path.node
                let className = node.id.name
                let classInner = node.body.body
                //创建一个数组用来成盛放新生成AST
                let es5Fns = []
                //初始化默认的constructor节点
                let newConstructorId = t.identifier(className)
                let constructorFn = t.functionDeclaration(newConstructorId, [t.identifier('')], t.blockStatement([]), false






请到「今天看啥」查看全文