釘釘前端團隊原創,點選右上角關注我們,瞭解更多前端技術
作者: 燭象
引言
在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
掃碼關注更多釘釘技術
?釘釘歡迎掃碼關注《釘釘技術圈》
?歡迎掃碼關注《釘釘大前端團隊》微信公眾號