[实践系列] 主要是让咱们经过实践去加深对一些原理的理解。javascript
有兴趣的同窗能够关注 [实践系列] 。 求star求follow~java
Babel 是一个 JavaScript 编译器。他把最新版的javascript编译成当下能够执行的版本,简言之,利用babel就可让咱们在当前的项目中随意的使用这些新最新的es6,甚至es7的语法。node
为了能用可爱的ES678910写代码,咱们必须了解它!git
August 27, 2018 by Henry Zhues6
历经 2 年,4k 屡次提交,50 多个预发布版本以及大量社区援助,咱们很高兴地宣布发布 Babel 7。自 Babel 6 发布以来,已通过了将近三年的时间!发布期间有许多要进行的迁移工做,所以请在发布第一周与咱们联系。Babel 7 是更新巨大的版本:咱们使它编译更快,并建立了升级工具,支持 JS 配置,支持配置 "overrides",更多 size/minification 的选项,支持 JSX 片断,支持 TypeScript,支持新提案等等!github
Babel开发团队这么辛苦的为开源作贡献,为咱们开发者提供更完美的工具,咱们为何不去了解它呢?web
(OS:求求你别更啦.老子学不动啦~)npm
August 27, 2018 by Henry Zhu编程
我想再次介绍下过去几年中 Babel 在 JavaScript 生态系统中所担任的角色,以此展开本文的叙述。
起初,JavaScript 与服务器语言不一样,它没有办法保证对每一个用户都有相同的支持,由于用户可能使用支持程度不一样的浏览器(尤为是旧版本的 Internet Explorer)。若是开发人员想要使用新语法(例如 class A {}),旧浏览器上的用户只会由于 SyntaxError 的错误而出现屏幕空白的状况。
Babel 为开发人员提供了一种使用最新 JavaScript 语法的方式,同时使得他们没必要担忧如何进行向后兼容,如(class A {} 转译成 var A = function A() {})。
因为它能转译 JavaScript 代码,它还可用于实现新的功能:所以它已成为帮助 TC39(制订 JavaScript 语法的委员会)得到有关 JavaScript 提案意见反馈的桥梁,并让社区对语言的将来发展发表本身的看法。
Babel 现在已成为 JavaScript 开发的基础。GitHub 目前有超过 130 万个仓库依赖 Babel,每个月 npm 下载量达 1700 万次,还拥有数百个用户,其中包括许多主要框架(React,Vue,Ember,Polymer)以及著名公司(Facebook,Netflix,Airbnb)等。它已成为 JavaScript 开发的基础,许多人甚至不知道它正在被使用。即便你本身没有使用它,但你的依赖极可能正在使用 Babel。
即便你本身没有使用它,但你的依赖极可能正在使用 Babel。怕不怕 ? 了解不了解 ?
解析步骤接收代码并输出 AST。 这个步骤分为两个阶段:词法分析(Lexical Analysis) 和 语法分析(Syntactic Analysis)。
词法分析阶段把字符串形式的代码转换为 令牌(tokens) 流。
你能够把令牌看做是一个扁平的语法片断数组:
n * n;
复制代码
[
{ type: { ... }, value: "n", start: 0, end: 1, loc: { ... } },
{ type: { ... }, value: "*", start: 2, end: 3, loc: { ... } },
{ type: { ... }, value: "n", start: 4, end: 5, loc: { ... } },
...
]
复制代码
每个 type 有一组属性来描述该令牌:
{
type: {
label: 'name',
keyword: undefined,
beforeExpr: false,
startsExpr: true,
rightAssociative: false,
isLoop: false,
isAssign: false,
prefix: false,
postfix: false,
binop: null,
updateContext: null
},
...
}
复制代码
和 AST 节点同样它们也有 start,end,loc 属性。
语法分析阶段会把一个令牌流转换成 AST 的形式。 这个阶段会使用令牌中的信息把它们转换成一个 AST 的表述结构,这样更易于后续的操做。
简单来讲,解析阶段就是
code(字符串形式代码) -> tokens(令牌流) -> AST(抽象语法树)
复制代码
Babel 使用 @babel/parser 解析代码,输入的 js 代码字符串根据 ESTree 规范生成 AST(抽象语法树)。Babel 使用的解析器是 babylon。
转换步骤接收 AST 并对其进行遍历,在此过程当中对节点进行添加、更新及移除等操做。 这是 Babel 或是其余编译器中最复杂的过程。
Babel提供了@babel/traverse(遍历)方法维护这AST树的总体状态,而且可完成对其的替换,删除或者增长节点,这个方法的参数为原始AST和自定义的转换规则,返回结果为转换后的AST。
代码生成步骤把最终(通过一系列转换以后)的 AST 转换成字符串形式的代码,同时还会建立源码映射(source maps)。
代码生成其实很简单:深度优先遍历整个 AST,而后构建能够表示转换后代码的字符串。
Babel使用 @babel/generator 将修改后的 AST 转换成代码,生成过程能够对是否压缩以及是否删除注释等进行配置,而且支持 sourceMap。
在这以前,你必须对Babel有了基本的了解,下面咱们简单的了解下babel的一些东西,以便于后面开发插件。
babel-core是Babel的核心包,里面存放着诸多核心API,这里说下transform。
transform : 用于字符串转码获得AST 。
//安装
npm install babel-core -D;
import babel from 'babel-core';
/* * @param {string} code 要转译的代码字符串 * @param {object} options 可选,配置项 * @return {object} */
babel.transform(code:String,options?: Object)
//返回一个对象(主要包括三个部分):
{
generated code, //生成码
sources map, //源映射
AST //即abstract syntax tree,抽象语法树
}
复制代码
Babel Types模块是一个用于 AST 节点的 Lodash 式工具库(译注:Lodash 是一个 JavaScript 函数工具库,提供了基于函数式编程风格的众多工具函数), 它包含了构造、验证以及变换 AST 节点的方法。 该工具库包含考虑周到的工具方法,对编写处理AST逻辑很是有用。 传送门
npm install babel-types -D;
import traverse from "babel-traverse";
import * as t from "babel-types";
traverse(ast, {
enter(path) {
if (t.isIdentifier(path.node, { name: "n" })) {
path.node.name = "x";
}
}
});
复制代码
当咱们谈及“进入”一个节点,其实是说咱们在访问它们, 之因此使用这样的术语是由于有一个访问者模式(visitor)的概念。
访问者是一个用于 AST 遍历的跨语言的模式。 简单的说它们就是一个对象,定义了用于在一个树状结构中获取具体节点的方法。 这么说有些抽象因此让咱们来看一个例子。
const MyVisitor = {
Identifier() {
console.log("Called!");
}
};
// 你也能够先建立一个访问者对象,并在稍后给它添加方法。
let visitor = {};
visitor.MemberExpression = function() {};
visitor.FunctionDeclaration = function() {}
复制代码
注意: Identifier() { ... } 是 Identifier: { enter() { ... } } 的简写形式
这是一个简单的访问者,把它用于遍历中时,每当在树中碰见一个 Identifier 的时候会调用 Identifier() 方法。
AST 一般会有许多节点,那么节点直接如何相互关联呢? 咱们可使用一个可操做和访问的巨大可变对象表示节点之间的关联关系,或者也能够用Paths(路径)来简化这件事情。
Path 是表示两个节点之间链接的对象。
在某种意义上,路径是一个节点在树中的位置以及关于该节点各类信息的响应式 Reactive 表示。 当你调用一个修改树的方法后,路径信息也会被更新。 Babel 帮你管理这一切,从而使得节点操做简单,尽量作到无状态。
Paths in Visitors(存在于访问者中的路径)
当你有一个 Identifier() 成员方法的访问者时,你其实是在访问路径而非节点。 经过这种方式,你操做的就是节点的响应式表示(译注:即路径)而非节点自己。
const MyVisitor = {
Identifier(path) {
console.log("Visiting: " + path.node.name);
}
};
复制代码
Babel的插件模块须要咱们暴露一个function,function内返回visitor对象。
//函数参数接受整个Babel对象,这里将它进行解构获取babel-types模块,用来操做AST。
module.exports = function({types:t}){
return {
visitor:{
}
}
}
复制代码
作一个简单的ES6转ES3插件:
1. let,const 声明 -> var 声明
2. 箭头函数 -> 普通函数
复制代码
|-- index.js 程序入口
|-- plugin.js 插件实现
|-- before.js 转化前代码
|-- after.js 转化后代码
|-- package.json
复制代码
首先,咱们先建立一个package.json。
npm init
复制代码
package.json
{
"name": "babelplugin",
"version": "1.0.0",
"description": "create babel plugin",
"main": "index.js",
"scripts": {
"babel": "node ./index.js"
},
"author": "webfansplz",
"license": "MIT",
"devDependencies": {
"@babel/core": "^7.2.2"
}
}
复制代码
能够看到,咱们首先下载了@babel/core做为咱们的开发依赖,而后配置了npm run babel做为开发命令。
index.js
const { transform } = require('@babel/core');
const fs = require('fs');
//读取须要转换的js字符串
const before = fs.readFileSync('./before.js', 'utf8');
//使用babel-core的transform API 和插件进行字符串->AST转化。
const res = transform(`${before}`, {
plugins: [require('./plugin')]
});
// 存在after.js删除
fs.existsSync('./after.js') && fs.unlinkSync('./after.js');
// 写入转化后的结果到after.js
fs.writeFileSync('./after.js', res.code, 'utf8');
复制代码
咱们首先来实现 功能 1. let,const 声明 -> var 声明
let code = 1;
复制代码
咱们经过传送门查看到上面代码对应的AST结构为
咱们能够看到这句声明语句位于VariableDeclaration节点,咱们接下来只要操做VariableDeclaration节点对应的kind属性就能够啦~
before.js
const a = 123;
let b = 456;
复制代码
plugin.js
module.exports = function({ types: t }) {
return {
//访问者
visitor: {
//咱们须要操做的访问者方法(节点)
VariableDeclaration(path) {
//该路径对应的节点
const node = path.node;
//判断节点kind属性是let或者const,转化为var
['let', 'const'].includes(node.kind) && (node.kind = 'var');
}
}
};
};
复制代码
ok~ 咱们来看看效果!
npm run babel
复制代码
after.js
var a = 123;
var b = 456;
复制代码
没错,就是这么吊!!!功能1搞定,接下来实现功能2. 箭头函数 -> 普通函数 (this指向暂不作处理~)
咱们先来看看箭头函数对应的节点是什么?
let add = (x, y) => {
return x + y;
};
复制代码
咱们经过传送门查看到上面代码对应的AST结构为
咱们能够看到箭头函数对应的节点是ArrowFunctionExpression。
接下来咱们再来看看普通函数对应的节点是什么?
let add = function(x, y){
return x + y;
};
复制代码
咱们经过传送门查看到上面代码对应的AST结构为
咱们能够看到普通函数对应的节点是FunctionExpression。
因此咱们的实现思路只要进行节点替换(ArrowFunctionExpression->FunctionExpression)就能够啦。
plugin.js
module.exports = function({ types: t }) {
return {
visitor: {
VariableDeclaration(path) {
const node = path.node;
['let', 'const'].includes(node.kind) && (node.kind = 'var');
},
//箭头函数对应的访问者方法(节点)
ArrowFunctionExpression(path) {
//该路径对应的节点信息
let { id, params, body, generator, async } = path.node;
//进行节点替换 (arrowFunctionExpression->functionExpression)
path.replaceWith(t.functionExpression(id, params, body, generator, async));
}
}
};
};
复制代码
满怀激动的
npm run babel
复制代码
after.js
var add = function (x, y) {
return x + y;
};
复制代码
惊不惊喜 ? 意不意外 ? 你觉得这样就结束了吗 ? 那你就太年轻啦。
咱们常常会这样写箭头函数来省略return。
let add = (x,y) =>x + y;
复制代码
咱们来试试 这样能不能转义
npm run babel
复制代码
GG.控制台飘红~
下面我直接贴下最后的实现,具体缘由我以为读者本身研究或许更有趣~
plugin.js
module.exports = function({ types: t }) {
return {
visitor: {
VariableDeclaration(path) {
const node = path.node;
['let', 'const'].includes(node.kind) && (node.kind = 'var');
},
ArrowFunctionExpression(path) {
let { id, params, body, generator, async } = path.node;
//箭头函数咱们会简写{return a+b} 为 a+b
if (!t.isBlockStatement(body)) {
const node = t.returnStatement(body);
body = t.blockStatement([node]);
}
path.replaceWith(t.functionExpression(id, params, body, generator, async));
}
}
};
};
复制代码
若是以为有帮助到你,请给个star或者follow 支持下做者哈~接下来还会有不少干货哦!!!