资讯专栏INFORMATION COLUMN

Babel.js插件开发之二 - 开始编写

focusj / 2061人阅读

摘要:完整专题上一篇已经介绍了编写插件所需要了解的基础知识,这篇我们就开始编写插件了。如字面意思,他们分别代表了节点数据父节点群数据。看到这里你已经可以动手开始尝试写一个插件了。使用上述文件目录结构为在中编写程序。

  

完整专题:http://www.codefrom.com/p/Babel.js

上一篇已经介绍了编写babel.js插件所需要了解的基础知识,这篇我们就开始编写babel.js插件了。

第一篇传送门: Babel.js插件开发之一 - Babel与AST

开始

新建一个新的ES6项目,创建目录结构如下的项目:

YourProject/
    |- src/
    |   |- index.es6
    |- js/
    |
    |- app.js

进入到 YourProject 并安装babel开发模块 babel-core

$ cd path/to/YourProject/
$ npm install babel-core

之后目录结构如下:

YourProject/
    |- src/
    |- js/
    |- node_modules/
    |   |- babel-core/
    |       |- ...
    |- app.js

新建插件目录

cd node_modules/
mkdir babel-plugin-test

并且新建目录下的nodejs模块文件,之后目录结构如下:

fileYourProject/
    |- src/
    |- js/
    |- node_modules/
    |   |- babel-core/
    |   |- babel-plugin-test/
    |       |- index.js
    |       |- package.json
    |- app.js

接下来我们就可以在 index.js 中编写插件了。

转换
  

由于AST中节点类型众多,我在这里就讲如何通过如上文档中的个别常用的类型进行转换,其他的都是类似的。

  

PS: 最近Babel.js更新了5.6+的API,支持用ES6编写,也换了新的转换器接口= = 可素他们自己官方的栗子都跑不起来= =,放弃,之后弄明白再换上新借口的版本,现在依然按照可用的例子进行讲解。

首先 创建一个入口: 在新创建的 index.js 中添加:

javascript"use strict";

module.exports = function(babel) {
    var t = babel.types; // AST模块
    var imports = {}; 
    // 如果你有自己的模块组织方式,用这里把模块名和路径记录下来。

    var moduleRoot = ""; // 你其他的自定义变量

    // module_name写你的插件名称,但并不在调用时使用
    return new babel.Transformer("module_name", {
        // 这里写你的转换逻辑 [逻辑区域]
    });
};

在AST中,我们可以把整个程序看成一个 Program 例如

var a = 42;
var b = 5;
var c = a + b;

其AST树长这样:(图 1)

program 是它的根节点。

于是我们可以在上面的逻辑区域添加以下代码:

Program: function(node) {
},

除了 Program 常用的还有:

Class (对类的处理方法)

NewExpression (对new新建对象表达式的处理)

CallExpression (对调用函数表达式的处理)

MethodDefinition (对类成员方法的处理)

... (等等)

它们都有三个传入参数: nodeparentscope

  

注: 你可以通过调试查看他们的具体构造。

如字面意思,他们分别代表了: 节点数据、 父节点、群数据。其中,节点数据 node 是操作一条语句主要的参数~。

节点数据就是该节点的属性以及内容,其具体的数据格式可以看我在第一篇相关概念中最后提到的两篇文档:

  

ES5: https://github.com/estree/estree/blob/master/spec.md - 文档 1
ES6: https://github.com/estree/estree/blob/master/es6.md - 文档 2

例如,上面图1中的那颗树的 Program 的节点数据,文档 1中相关描述如下:

Program

interface Program <: Node {
    type: "Program";
    body: [ Statement ];
}

我们可以看到其 node 参数是一个 Node 类型的数据,包含了两个属性:公共属性 type 代表节点类别,body 代表其内容,这里是一个子节点的列表,列表中有三个VariableDeclaration 代表程序中的三条语句,其类型也是 Node

假设我们定义了一种模块化的方法(类似AMD的requirejs),我们将整个程序包裹在一个 test.defineModule(function(){/* block */}) 方法中。

那我们可以这样构建Program:

"use strict";

module.exports = function(babel) {
    var t = babel.types; // AST模块
    return new babel.Transformer("module_name", {
        // 这里写你的转换逻辑 [逻辑区域]
        Program: function(node) {
            var moduleFunction = t.functionExpression( // [1]
                t.identifier(""), // [2]
                [],  // [3]
                t.blockStatement(node.body) // [4]
            );
            var moduleDefine = t.callExpression( // [5]
                t.identifier("test.defineModule"),  // [6]
                [moduleFunction] // [7]
            );
            var moduleExpression = t.expressionStatement(moduleDefine); // [8]
            node.body = [moduleExpression]; // [9]
            return node; // [10]
        }
    });
};

这里你只定义了Program的转换机制,因此其他的转换还是会按照默认的方式进行转换。

按照这种机制,上面的AST树的示例程序就被转换成下面这样了:

"use strict";

test.defineModule(function () {
  var a = 42;
  var b = 5;
  var c = a + b;
});

下面我们来逐行分析一下(以逻辑的顺序):

  

[5] 新建一个函数调用 moduleDefine
[6] 这个被调用函数的名字叫做 "test.defineModule" 即: test.defineModule()
[8] 整个函数调用是一个完整的表达式 moduleExpression ,即: test.defineModule();
接下来我们需要向这个函数调用中填入参数列表
[7] 这个参数列表中有一个参数 moduleFunction
1 moduleFunction 是一个函数
[2] 这个函数的名称是 "",即: 这是一个匿名函数
[3] 这个函数的参数列表为空
[4] 这个函数的内容块的内容是原本Program节点的内容
[9] 把原有的Program节点的内容替换成新的
[10] 返回这个改动,当然你是直接在原本的对象实例上改动的,不返回也可以

同样你也可以重新定义 ImportDeclarationExportDeclaration,其结构略微与普通节点有所不同,例如:

ImportDeclaration: function(node) {
    node.specifiers.forEach(function(specifier){
        if(specifier.type == "ImportDefaultSpecifier"){
            imports[specifier.local.name] = node.source.value;
        }
    });
    this.dangerouslyRemove();
},

ExportDeclaration: function(){
    this.dangerouslyRemove();
}

其作用为:将export 和import的相关转换都删掉,并且将import的值和路径都记录下来,可以在其他的转换中用到,又或者直接在ImportDeclaration 中直接对import的变量进行操作,例如:

import $ from "jquery"

我们希望转化成

var $ = test.requireModule("jquery");

并将其放入模块内:

ImportDeclaration: function(node) {
    var self = this;
    node.specifiers.forEach(function(specifier){
        if(specifier.type == "ImportDefaultSpecifier"){
            //imports[specifier.local.name] = node.source.value;
            var requireCall = t.callExpression(
                t.identifier("test.requireModule"), 
                [t.literal(node.source.value)]
            );
            var assignEx = t.assignmentExpression(
                "=", 
                t.identifier(specifier.local.name), 
                requireCall
            );
            self.insertAfter(t.expressionStatement(assignEx));
        }
    });
    this.dangerouslyRemove();
},

将其假如之前的test.defineModule的转换中,则我们发现

import $ from "jquery"

var a = 42;
var b = 5;
var c = a + b;

被转换为了:

"use strict";

test.defineModule(function () {
    $ = test.requireModule("jquery");

    var a = 42;
    var b = 5;
    var c = a + b;
});

ImportDeclaration 在上述文档2中的描述为:

interface ImportDeclaration <: Node {
    type: "ImportDeclaration";
    specifiers: [ ImportSpecifier | ImportDefaultSpecifier | ImportNamespaceSpecifier ];
    source: Literal;
}

specifiers 列表中的 specifiers 可能有三种类型,在文档中都有很详细的描述,这里就不多说了。

按照这样的理解,所有的方法都可以通过查看上面的文档 1和文档 2的说明进行改动。

看到这里你已经可以动手开始尝试写一个babel.js插件了。

使用

上述文件目录结构为:

YourProject/
    |- src/
    |- js/
    |- node_modules/
    |   |- babel-core/
    |   |- babel-plugin-test/
    |       |- index.js
    |       |- package.json
    |- app.js

src 中编写es6程序 test.es6

YourProject/
    |- src/
    |   |- test.es6
    |- js/
    |- node_modules/
    |   |- babel-core/
    |   |- babel-plugin-test/
    |       |- index.js
    |       |- package.json
    |- app.js

到YourProject目录下。执行

$ babel src/ -d js/ --plugins babel-plugin-test

则在 js 文件夹中就是你转化好的js文件啦~。

  

还有第三篇是有关英文文档的翻译。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/85823.html

相关文章

  • 手把手教你从零搭建react局部热加载环境

    摘要:有没有办法实现就局部刷新呢当然是有第十步执行为了实现局部热加载,我们需要添加插件。 前言 用了3个多月的vue自认为已经是一名合格的vue框架api搬运工,对于vue的api使用到达了一定瓶颈,无奈水平有限,每每深入底层观赏源码时候都迷失了自己。 遂决定再找个框架学习学习看看能否突破思维局限,加上本人早已对React、RN技术垂涎已久,于是决定找找教程来学习。无奈第一步就卡在了环境搭...

    quietin 评论0 收藏0
  • webpack实战

    摘要:和类似的预处理器还有等。的用处非常多,包括给自动加前缀使用下一代语法等,目前越来越多的人开始用它,它很可能会成为预处理器的最终赢家。 webpack实战 查看所有文档页面:全栈开发,获取更多信息。快马加鞭,加班加点,终于把这个文档整理出来了,顺便深入地学习一番,巩固知识,就是太累人,影响睡眠时间和质量。极客就是想要把事情做到极致,开始了就必须到达终点。 原文链接:webpack实战,原...

    cyrils 评论0 收藏0
  • WebAssembly应用到前端工程(下)—— webpack和webassembly

    摘要:然而在当前以为主要编译工具的实际工程应用中依然存在问题。涉及到的技术主要为模块机制插件编写与插件编写。使用可以参考或,欢迎。上一篇应用到前端工程上模块的编写 在上一篇文章WebAssembly应用到前端工程(上)—— webassembly模块的编写中,完成了@ne_fe/gis模块的编写与发布。然而webassembly在当前以webpack4为主要编译工具的实际工程应用中依然存在问...

    RichardXG 评论0 收藏0
  • 一天快速了解Babel

    摘要:在做项目中一直使用的是脚手架搭建的环境,一直没有仔细的去了解这一工具,这周末抽出一天时间通过官网还有各种博客文章算是了解了一些内容,起码可以在项目中自己完成的配置了。不过好像目前浏览器端对这种诸如之类的方法支持的还不错了。 在做项目中一直使用的是脚手架搭建的环境,一直没有仔细的去了解 babel 这一工具,这周末抽出一天时间通过官网还有各种博客文章算是了解了一些内容,起码可以在项目中自...

    qiangdada 评论0 收藏0
  • 「大概可能也许是」目前最好的 JavaScript 异步方案 async/await

    摘要:使用时也要注意范围和层级。服务端配置服务端使用,最简单的方式是通过。云引擎是推出的服务器端运行环境,支持和环境,功能强大而且目前免费,结合,使原本复杂的开发工作变得简单高效。目前也支持和海外节点,轻松满足你的业务需求。 构建一个应用程序总是会面对异步调用,不论是在 Web 前端界面,还是 Node.js 服务端都是如此,JavaScript 里面处理异步调用一直是非常恶心的一件事情。以...

    Scorpion 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<