本文將從下面幾個問題來回答對babel對理解:
1. 什么是bable;
2. bable的工作原理是什么媒区;
3. babel應該怎么使用、任何配置舶得;
一、什么是babel:
Babel is a toolchain that is mainly used to convert ECMAScript 2015+ code into a backwards compatible version of JavaScript in current and older browsers or environments.
我們已經(jīng)能夠熟練地使用 es2015+ 的語法咱娶。但是對于瀏覽器來說缭贡,可能和它們還不夠熟悉,我們得讓瀏覽器理解它們破衔,這就需要 Babel.
Babel 是一個通用的多功能 JavaScript 編譯器清女,但與一般編譯器不同的是它只是把同種語言的高版本規(guī)則轉(zhuǎn)換為低版本規(guī)則,而不是輸出另一種低級機器可識別的代碼晰筛,并且在依賴不同的拓展插件下可用于不同形式的靜態(tài)分析嫡丙。(靜態(tài)分析:指在不需要執(zhí)行代碼的前提下對代碼進行分析以及相應處理的一個過程忠售,主要應用于語法檢查、編譯迄沫、代碼高亮、代碼轉(zhuǎn)換卦方、優(yōu)化羊瘩、壓縮等等)
二、bable的工作原理是什么:
可以理解babel本質(zhì)也是一個編譯器盼砍,只是這個編譯器輸出對不是二進制機器碼而是在瀏覽器能運行對js語法. 所以更準確點說他是一個轉(zhuǎn)譯器尘吗,它工作分為3個步驟:
1. 解析 Parse
2. 轉(zhuǎn)換 Transform
3. Generate
- 解析 Parse
將代碼解析生成抽象語法樹( 即AST ),也就是計算機理解我們代碼的方式(擴展:一般來說每個 js 引擎都有自己的 AST浇坐,比如熟知的 v8睬捶,chrome 瀏覽器會把 js 源碼轉(zhuǎn)換為抽象語法樹,再進一步轉(zhuǎn)換為字節(jié)碼或機器代碼)近刘,而 babel 則是通過 babylon 實現(xiàn)的 擒贸。簡單來說就是一個對于 JS 代碼的一個編譯過程,進行了詞法分析與語法分析的過程觉渴。
談到AST就必須理解js引擎是如何理解我們代碼的介劫,簡答來說是兩個步驟:1、分詞; 2案淋、語意分析座韵。
1、分詞:
將整個代碼字符串分割成語法單元數(shù)組(token)
JS 代碼中的語法單元主要指如標識符(if/else踢京、return誉碴、function)、運算符瓣距、括號黔帕、數(shù)字、字符串旨涝、空格等等能被解析的最小單元蹬屹。比如下面的代碼生成的語法單元數(shù)組如下:
var answer = 6 * 7;
=>
//分詞token
[
{
"type": "Keyword",
"value": "var"
},
{
"type": "Identifier",
"value": "answer"
},
{
"type": "Punctuator",
"value": "="
},
{
"type": "Numeric",
"value": "6"
},
{
"type": "Punctuator",
"value": "*"
},
{
"type": "Numeric",
"value": "7"
},
{
"type": "Punctuator",
"value": ";"
}
]
2、語意分析:
語義分析則是將得到的詞匯進行一個立體的組合白华,確定詞語之間的關(guān)系慨默。考慮到編程語言的各種從屬關(guān)系的復雜性弧腥,語義分析的過程又是在遍歷得到的語法單元組厦取,相對而言就會變得更復雜。
簡單來說語義分析既是對語句和表達式識別管搪,這是個遞歸過程虾攻,在解析中铡买,babel 會在解析每個語句和表達式的過程中設置一個暫存器,用來暫存當前讀取到的語法單元霎箍,如果解析失敗奇钞,就會返回之前的暫存點,再按照另一種方式進行解析漂坏,如果解析成功景埃,則將暫存點銷毀,不斷重復以上操作顶别,直到最后生成對應的語法樹谷徙。
{
"type": "Program",
"body": [
{
"type": "VariableDeclaration",
"declarations": [
{
"type": "VariableDeclarator",
"id": {
"type": "Identifier",
"name": "answer"
},
"init": {
"type": "BinaryExpression",
"operator": "*",
"left": {
"type": "Literal",
"value": 6,
"raw": "6"
},
"right": {
"type": "Literal",
"value": 7,
"raw": "7"
}
}
}
],
"kind": "var"
}
],
"sourceType": "script"
}
- 轉(zhuǎn)換 Transform
對于 AST 進行變換一系列的操作,babel 接受得到 AST 并通過 babel-traverse 對其進行遍歷驯绎,在此過程中進行添加完慧、更新及移除等操作
- 生成 Generate
將變換后的 AST 再轉(zhuǎn)換為JS代碼, 使用到的模塊是 babel-generator。
而 babel-core 模塊則是將三者結(jié)合使得對外提供的API做了一個簡化剩失。
此外需要注意的是屈尼,babel 只是轉(zhuǎn)譯新標準引入的語法,比如ES6箭頭函數(shù):而新標準引入的新的原生對象赴叹,部分原生對象新增的原型方法鸿染,新增的 API 等(Proxy、Set 等), 這些事不會轉(zhuǎn)譯的乞巧,需要引入對應的 polyfill 來解決涨椒。
而我們編寫的 babel 插件則主要專注于第二步轉(zhuǎn)換過程的工作,專注于對于代碼的轉(zhuǎn)化規(guī)則的拓展绽媒,解析與生成的偏底層相關(guān)操作則有對應的模塊支持蚕冬,在此我們理解它主要做了什么即可。
經(jīng)過babel轉(zhuǎn)換后高版本js語法就變成了ECMAScript 2015+ 的代碼是辕,支持在舊的瀏覽器上運行囤热,如:
// es2015 的 const 和 arrow function
const add = (a, b) => a + b;
let a =1;
let b ={a};
// Babel 轉(zhuǎn)譯后
var add = function add(a, b) {
return a + b;
};
var a = 1;
var b = { a: a };
三、babel該如何使用及配置:
目前JS沒有統(tǒng)一的構(gòu)建構(gòu)建获三、平臺旁蔼,所以babel支持對各種工具對基gulp、webpack疙教、node
開始使用babel之前我們首先理解幾個babel文件:
babel-cli 是一種在命令行下使用Babel編譯工具
babel-register 在node下運行babel的小工具
babel-polyfill Babel一起使用的polyfill是babel-polyfill
babele-runtime 轉(zhuǎn)換api工具
babel-core Babel 的核心代碼
babel-loader 解析 js 的 loader
babel-preset-react 用于解析 jsx 語法
babel-preset-env 取代之前的 babel-preset-es2015 babel-preset-es2016 等包棺聊,解析 ES6 等語法
babel-preset-stage-0 同類的還有 stage-1、stage-2贞谓、stage-3限佩,stage-0 包含es7的解析內(nèi)容,且同時包含 stage-1/2/3 的所有功能
* 以上是babel6,如果需要使用babel7的話需要增加@前綴,@babel-core
-
babel-cli
從名字就可以理解祟同,這個是一個命令行工具作喘,運行在node環(huán)境,能將es6或者es7語法的js文件轉(zhuǎn)換為es5文件晕城。
$ npm install --global babel-cli
$ babel example.js --out-file compiled.js
# 或
$ babel example.js -o compiled.js
-
babel-register
babel-cli會把一個文件轉(zhuǎn)換成一個es5文件執(zhí)行泞坦,但是如果你想直接執(zhí)行這個文件:
$ node index.js
直接來運行它是不會使用 Babel 來編譯的,這個時候就需要使用babel-register砖顷,首先在項目中創(chuàng)建 register.js 文件并添加如下代碼:
require("babel-register");
require("./index.js");
這樣做可以把 Babel 注冊到 Node 的模塊系統(tǒng)中并開始編譯其中 require 的所有文件暇矫。
現(xiàn)在我們可以使用 register.js 來代替 node index.js 來運行了。
$ node register.js
-
babel-polyfill與babele-runtime
Babel默認只轉(zhuǎn)換新的JavaScript語法择吊,而不轉(zhuǎn)換新的API。 例如槽奕,Iterator几睛、Generator、Set粤攒、Maps所森、Proxy、Reflect夯接、Symbol焕济、Promise 等全局對象,以及一些定義在全局對象上的方法(比如 Object.assign)都不會轉(zhuǎn)譯盔几。 如果想使用這些新的對象和方法晴弃,則需要為當前環(huán)境提供一個polyfill
目前最常用的配合Babel一起使用的polyfill是babel-polyfill,它會”加載整個polyfill庫”逊拍,針對編譯的代碼中新的API進行處理上鞠,并且在代碼中插入一些幫助函數(shù)。
babel-polyfill解決了Babel不轉(zhuǎn)換新API的問題芯丧,但是直接在代碼中插入幫助函數(shù)芍阎,會導致污染了全局環(huán)境,并且不同的代碼文件中包含重復的代碼缨恒,導致編譯后的代碼體積變大谴咸。 (比如:上述的幫助函數(shù)_defineProperty有可能在很多的代碼模塊文件中都會被插入)
Babel為了解決這個問題,提供了單獨的包babel-runtime用以提供編譯模塊的工具函數(shù)骗露, 啟用插件babel-plugin-transform-runtime后岭佳,Babel就會使用babel-runtime下的工具函數(shù),上述的代碼就會變成這樣
-
babel-core
以上都是基于cli或者node環(huán)境時使用babel椒袍,如果你想通過代碼的方式實現(xiàn)babel驼唱,這個時候就需要使用babel-core
$ npm install babel-core
var babel = require("babel-core");
字符串形式的 JavaScript 代碼可以直接使用 babel.transform 來編譯。
babel.transform("code();", options);
// => { code, map, ast }
配置 Babel
目前為止通過運行 Babel 自己我們并沒能“翻譯”代碼驹暑,而僅僅是把代碼從一處拷貝到了另一處玫恳。
這是因為我們還沒告訴 Babel 要做什么辨赐。
你可以通過安裝插件(plugins)或預設(presets,也就是一組插件來指示 Babel 去做什么事情京办。
-
.babelrc
在我們告訴 Babel 該做什么之前掀序,我們需要創(chuàng)建一個配置文件。你需要做的就是在項目的根路徑下創(chuàng)建 .babelrc 文件惭婿。然后輸入以下內(nèi)容作為開始:
{
"presets": [],
"plugins": []
}
這個文件就是用來讓 Babel 做你要它做的事情的配置文件不恭。
-
plugins
上面說過babel本身是不會去翻譯代碼,需要插件告訴babel需要做什么财饥,比如你需要翻譯箭頭函數(shù)就需要transform-es2015-arrow-functions插件换吧。
-
presets(預設)
不想自己動手組合插件?沒問題钥星!preset 可以作為 Babel 插件的組合沾瓦,甚至可以作為可以共享的 options 配置。
官方針對常用環(huán)境編寫了一些 preset:
babel/preset-env
babel/preset-flow
babel/preset-react 翻譯jsx文件
babel/preset-typescript
babel-preset-react-app creat-react-app
babel-preset-es2015 es6=>es5
babel-preset-stage-x 提案階段語法
-
presets與plugins差別
舉個例子谦炒, babel-preset-es2015 包含了 transform-es2015-arrow-functions(編譯箭頭函數(shù))贯莺、transform-es2015-block-scoping(編譯 const、let)宁改、transform-es2015-classes(編譯class)等幾十個插件
如果不使用 preset缕探,如果想要用完整的 es6 語法,使用者可能需要自己配置幾十個 plugins还蹲。
-
babel-preset-env
單獨講一下babel-preset-env 這個預設插件爹耗,
為了讓開發(fā)者能夠盡早用上新的JS特性,babel團隊開發(fā)了babel-preset-latest谜喊。這個preset比較特殊鲸沮,它是多個preset的集合(es2015+),并且隨著ECMA規(guī)范的更新更增加它的內(nèi)容锅论。
隨著時間的推移讼溺,babel-preset-latest 包含的插件越來越多,這帶來了如下問題:
1最易、加載的插件越來越多怒坯,編譯速度會越來越慢;
2藻懒、隨著用戶瀏覽器的升級剔猿,ECMA規(guī)范的支持逐步完善,編譯至低版本規(guī)范的必要性在減少(比如ES6 -> ES5)嬉荆,多余的轉(zhuǎn)換不單降低執(zhí)行效率归敬,還浪費帶寬。
因為上述問題的存在,babel官方推出了babel-preset-env插件汪茧。它可以根據(jù)開發(fā)者的配置椅亚,按需加載插件。
需要支持的平臺:比如node舱污、瀏覽器等呀舔。
需要支持的平臺的版本:比如支持node@6.1等。