钉钉前端团队原创,点击右上角关注我们,了解更多前端技术
作者: 烛象
引言
在JavaScript蓬勃发展的今天,ES6/7、typescript已经成为代码编写的标配。
上一篇文章,我们介绍了钉钉IDL和自动生成typescript定义的工具,本文将会介绍AST相关js知识:babel。
关于babel
一句话阐述什么是babel:
babel是一个主要用于将ES2015+版本的代码编译成向下兼容(比如ES5/ES3)js版本的编译器。
// Babel Input: ES2015 arrow function
[1, 2, 3].map((n) => n + 1);
// Babel Output: ES5 equivalent
[1, 2, 3].map(function(n) {
return n + 1;
});
复制代码
结合实际使用场景,我们接触到的babel使用方式一般为
- .babelrc/babel.config.json (babel配置文件)
- babel-loader (webpack/rollup等)
然而,.babelrc的每一块配置后面究竟代表着babel怎样的处理方式,这个估计很少有人能讲得清楚。
babel知识体系
1、主要组件
- 源代码到AST: babel/parser(前身为babylon)
依赖acorn/acorn-jsx,用于将源码(如ES2015代码) 编译成 AST(抽象语法树)
- AST到输出代码: babel/generator
用于将AST转换为最终代码,根据不同的参数option,实现代码功能(比如sourceMap的实现)
2、结构转换
- 对AST实现颗粒化改造: @babel/traverse
通过AST节点遍历,方便使用方对AST节点进行逻辑重组。
import * as parser from "@babel/parser";
import traverse from "@babel/traverse";
// 源代码
const code = `function square(n) {
return n * n;
}`;
const ast = parser.parse(code);
traverse(ast, {
enter(path) {
// 节点转换
if (path.isIdentifier({ name: "n" })) {
path.node.name = "x";
}
}
});
// 新代码 function square(x) {\n return x * x;\n}
console.log(generate(ast))
复制代码
3、工具组件
- 代码行列定位: @babel/code-frame
对代码行列进行定位
import { codeFrameColumns } from '@babel/code-frame';
const rawLines = `class Foo {
constructor()
}`;
const location = { start: { line: 2, column: 16 } };
const result = codeFrameColumns(rawLines, location, { /* options */ });
console.log(result);
// 结果如下
1 | class Foo {
> 2 | constructor()
| ^
3 | }
复制代码
- 运行时优化(比如重复代码等): @babel/runtime(包含regenerator-runtime)
对代码重复率进行优化,比如优化如下class语法转换
class Circle {}
function _classCallCheck(instance, Constructor) {
//...
}
var Circle = function Circle() {
_classCallCheck(this, Circle);
};
复制代码
通过@babel/runtime,将class语法以模块化的方式替换具体的实现,以达到减少重复代码的目的。
var _classCallCheck = require("@babel/runtime/helpers/classCallCheck");
var Circle = function Circle() {
_classCallCheck(this, Circle);
};
复制代码
- 代码模板: @babel/template
模板代码,可以对比printf、Mustache的语法
import template from "@babel/template";
import generate from "@babel/generator";
import * as t from "@babel/types";
const buildRequire = template(`
var %%importName%% = require(%%source%%);
`);
const ast = buildRequire({
importName: t.identifier("myModule"),
source: t.stringLiteral("my-module"),
});
// 最终的代码变为var myModule = require("my-module");
console.log(generate(ast).code);
复制代码
除了以上几个大件之外,babel工具体系还有types、helpers等来优化AST和generate最终代码。接下来要分享的是babel中非常重要的一环: plugins和presets。
4、babel plugins和 babel presets
babel作为编译器,它对代码的转换工作全部依赖于plugins(插件)。如果开发者连babelrc都没有配置的话,代码转换将什么都不做。
对于babel来说plugins的作用是转换代码。然而,js语法何其多,箭头函数、class语法、async/await等等,这么多语法需要非常庞大的插件体系。对于开发者来说,极其不友好。
babel在插件的概念基础上新增了个插件列表的概念,叫做presets(预设)。 比如@babel/preset-stage-0,代表的是支持stage-0语法的插件列表,通过presets解放了开发者极大的配置babel的工作量。
关于babel-polyfill
对于babel7.4.0,这个库官方已经废弃了,取而代之的是core-js/stable
和regenerator-runtime/runtime
。 对于很多babel-polyfill的使用方而言,这个库确实对bundle的最终大小产生了影响。官方推荐的是采用@babel/preset-env和useBuiltIns这个option配合起来,以便只引入你所需要的polyfill。
来看下这个能力有多酷炫:
var a = new Promise();
=转换为=>
import "core-js/modules/es.promise";
var a = new Promise();
var b = new Map();
=转换为=>
import "core-js/modules/es.map";
var b = new Map();
复制代码
写在后面
关于babel更多的知识,欢迎大家移步babel官网。 希望读完本文你有一定的收获。
招人时间:
钉钉前端团队社招全面开启,求各路技术达人加入。
简历投递邮箱: xiaogang.hxg@alibaba-inc.com
笔者钉钉号: huangxiaogang
扫码关注更多钉钉技术
😊钉钉欢迎扫码关注《钉钉技术圈》
😊欢迎扫码关注《钉钉大前端团队》微信公众号
来源:oschina
链接:https://my.oschina.net/u/4383937/blog/3220075