1. 程式人生 > 其它 >【轉】babel那些事兒

【轉】babel那些事兒

從前,一提到新東西,我的反應就是相容性好不好,如果不能滿足產品經理的需求,就還是用保守的方式實現吧。畢竟前端開發是一件很靈活的事,怎麼寫都行,至於為何會用某種方法,一定是綜合考慮相容性,效能,使用者體驗,開發成本等因素後再說。相容性和新事物有時就像魚和熊掌不可兼得,必須權衡利弊,做一個決斷。但是ECMAScript 2015不一定要等所有瀏覽器都支援後才可以用,於是你會想到一個工具babel,它可以助ECMAScript 2015一臂之力,讓處於某個stage的且尚未納入es規範的特性提前支援,因為babel能夠把不同stage的es,轉成瀏覽器識別的js。
在學babel前,我有一個這樣的疑問,為啥說到babel,一般會提到es5+?他倆有啥關係?babel是伴隨ECMAScript 2015出現的嗎?如果不是,它比ECMAScript 2015出生早,還是晚呢?

感覺問題很多,就像破案一樣,疑點重重,越走越深,但是主方向不會忘。我有時會問自己,我是搞清它的歷史背景呢,還是趕緊敲命令列,立刻做專案?內心的躁動告訴我,反正有時間研究,索性我就挖地三尺,弄清babel的祖墳在哪兒。
看了babel的官方文件後,我恍然大悟,原來babel不是TC39的作品啊!!!如果說ECMAScript的爹孃是TC39,那麼babel的爹是james kyle,一個澳大利亞工程師,曾就職於Facebook。總之,babel和ECMAScript 2015沒有什麼血緣關係,babel也不是ECMAScript 2015出生的附屬品,babel5.0釋出的比ECMAScript 2015早。

一、babel版本有哪些?
2015年1月12日,6to5和esnext開源;
2015年2月15日,6to5重新命名為babel;
2015年3月31日,babel 5.0釋出;
2015年10月30日,babel 6.0釋出。

二、babel的用途
babel是一個JavaScript編譯器。
1、ES6轉成ES5
預設情況下,babel自帶了一組ES2015語法轉化器,這些轉化器能把ES2015轉為ES5,讓你現在就使用最新的JavaScript語法,而不用等待瀏覽器提供支援。在babel中,預設啟用stage-2及以上的proposal。如果有人說“不同stage的babel”,這種說法是錯誤的,因為babel沒有所謂的stage,babel就是一個編譯器。至於babel的配置檔案引數中為什麼會牽扯到stage,是由於babel要知道把哪個stage的es5+轉換成瀏覽器能識別的js,而TC39 Process會告訴babel是哪個stage。
2、jsx檔案轉成js檔案
babel可以把React的JSX語法轉成普通JavaScript語法。
3、babel由外掛組成,可組合拼裝。
4、babel支援source map,幫助除錯。

三、babel的工作原理
1、解析
用babylon對原始碼(比如ES6)進行詞法解析,得到AST;
2、轉換
用babel-traverse對AST樹進行遍歷轉換,得到新的AST樹;
3、生成
用babel-generator通過AST樹生成目的碼(比如ES5)。

四、babel的配置
babel的配置檔案是.babelrc,存放在專案的根目錄下。使用babel的第一步,就是配置這個檔案。什麼是.babelrc檔案呢?熟悉linux的同學一定知道,rc結尾的檔案通常代表執行時自動載入的檔案,配置等等,類似bashrc,zshrc。同樣babelrc在這裡也是有同樣的作用,而且在babel6中,這個檔案必不可少。
該檔案用來設定轉碼規則和外掛,基本格式如下。

{
  "presets": [],
  "plugins": []
}

1、轉碼規則
presets欄位設定轉碼規則,轉碼規則可以告訴babel去處理什麼語法。

A、babel-preset-es2015
這是ES2015(最新版本的JavaScript標準,也叫做ES6)的轉碼規則。使用它後,babel可以將ES6語法轉碼為普通 JavaScript(即ES5)語法。

# 安裝ES2015轉碼規則
$ npm install --save-dev babel-preset-es2015

B、babel-preset-react
這是react的轉碼規則。使用它後,babel可以將react語法轉碼為普通JavaScript語法。

# 安裝react轉碼規則
$ npm install --save-dev babel-preset-react

C、babel-preset-stage-x
這是ES7不同階段語法提案的轉碼規則。使用它後,babel可以將ES7不同階段語法轉碼為普通JavaScript語法。

# 安裝ES7不同階段語法提案的轉碼規則(共有4個階段),選裝一個
$ npm install --save-dev babel-preset-stage-0
$ npm install --save-dev babel-preset-stage-1
$ npm install --save-dev babel-preset-stage-2
$ npm install --save-dev babel-preset-stage-3

以上每種預設都依賴於緊隨的後期階段預設。例如,babel-preset-stage-1依賴babel-preset-stage-2,後者又依賴babel-preset-stage-3。
最終,在.babelrc檔案這樣寫。

{
  "presets": [
    "es2015",
    "stage-0"
  ],
  "plugins": []
}

D、babel-preset-env
這是最新的預設外掛,包含es2015,es2016,es2017和latest等,不包含react,polyfill。

$ npm install --save-dev babel-preset-env

最終,在.babelrc檔案這樣寫,是不是比上面簡潔了很多?

{
  "presets": ["env"],
  "plugins": []
}

如果與webpack一起使用,需要再配置下webpack.config.js檔案。

module: {
  rules: [
    {
      test: /\.js$/,
      exclude: /(node_modules|bower_components)/,
      use: {
        loader: 'babel-loader'
      }
    }
  ]
}

2、外掛
A、babel-plugin-transform-runtime
babel-polyfill可以控制你自己寫的檔案,但是不能很好的處理第三方library,因為polyfill會汙染原來的全域性環境,容易發生衝突,這時候babel-runtime閃電登場。
babel-runtime是一個提供了regenerator、core-js和helpers的執行時庫。
babel-plugin-transform-runtime外掛依賴babel-runtime,babel-runtime是真正提供runtime環境的包,也就是說transform-runtime外掛是把js程式碼中使用到的新原生物件和靜態方法轉換成對runtime實現包的引用。
a.1、利弊
可以用babel-runtime/core-js匯出的物件和方法替代ES6引入的新原生物件和靜態方法,避免全域性汙染;
可以用babel-runtime/regenerator匯出的函式取代generators或async函式;
可以把多餘程式碼提取到babel-runtime/helps中,減少體積。
由於runtime不會汙染全域性空間,所以例項方法是無法工作的,這就需要polyfill啦。

a.2、安裝

# babel-plugin-transform-runtime用於開發環境
$ npm install --save-dev babel-plugin-transform-runtime

# babel-runtime用於生產環境
$ npm install --save babel-runtime

a.3、配置.babelrc檔案

{
  "plugins": [
    ["transform-runtime", {
      "helpers": false,
      "polyfill": false,
      "regenerator": true,
      "moduleName": "babel-runtime"
    }]
  ]
}

a.4、使用

// ES6程式碼
var sym = Symbol();
// 通過transform-runtime轉換後的ES5+runtime程式碼 
var _symbol = require("babel-runtime/core-js/symbol");
var sym = (0, _symbol.default)();

五、babel-core,新語法轉碼
1、安裝

$ npm install --save-dev babel-core 

2、使用

var babel = require("babel-core");

A、如果是字串形式的JavaScript程式碼,可以使用transform編譯。
babel.transform("code();", options);  // { code, map, ast }

B、如果是檔案,非同步編譯使用transformFile。
babel.transformFile("filename.js", options, function(err, result) {
  result; // { code, map, ast }
});
C、如果是檔案,同步編譯使用transformFileSync。
babel.transformFileSync("filename.js", options);  // { code, map, ast }

D、要是已經有一個babel AST(抽象語法樹)了就可以直接從AST進行轉換。
babel.transformFromAst(ast, code, options); // { code, map, ast }

六、babel-polyfill,新Api轉碼
babel預設只轉換新的JavaScript句法,而不轉換新的API,比如Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise等全域性物件,以及一些定義在全域性物件上的方法(比如Object.assign)都不會轉碼。babel-polyfill正好可以轉碼新的API,polyfill和runtime都是對core-js和regenerator的再封裝,方便使用。

插播一段,一直好奇polyfill是個啥東西,簡單查了下,程式碼填充,也可譯作相容性補丁,類似軟墊片。

1、安裝

$ npm install --save-dev babel-polyfill

2、使用

//方案A,在your.js中引入
import 'babel-polyfill';

//方案B,在node.js中引入
require('babel-polyfill');

//方案C,設定webpack.config.js
module.exports = {
  entry: ["babel-polyfill", "./app/js"]
};

七、babel-cli,命令列轉碼
1、安裝

$ npm install --save-dev babel-cli

2、命令列

# 轉碼結果輸出到標準輸出
$ babel example.js

# 轉碼結果寫入一個檔案
# --out-file 或 -o 引數指定輸出檔案
$ babel example.js --out-file compiled.js
# 或者
$ babel example.js -o compiled.js

# 整個目錄轉碼
# --out-dir 或 -d 引數指定輸出目錄
$ babel src --out-dir lib
# 或者
$ babel src -d lib

# -s 引數生成source map檔案
$ babel src -d lib -s

3、命令列優化
可以像上面一樣直接輸入命令完成轉碼,也可以修改package.json檔案,然後執行npm run build。

"scripts": {
  "build": "babel src -d lib"
}

八、babel-register,require實時轉碼
通過繫結node.js的require來自動轉譯require引用的js程式碼檔案。
babel-register模組改寫require命令,為它加上一個鉤子。此後,每當使用require載入.js、.jsx、.es和.es6字尾名的檔案,就會先用babel進行轉碼。
1、安裝

$ npm install --save-dev babel-register

2、使用

//必須先載入babel-register
require("babel-register");
require("./index.js");

3、利弊
這種方式不需要手動對index.js轉碼。
babel-register只會對require命令載入的檔案轉碼,而不會對當前檔案轉碼。另外,由於它是實時轉碼,所以只適合在開發環境使用。

九、sublime外掛轉碼es6

1、安裝外掛
在sublime中,安裝babel外掛。
2、全域性安裝babel-cli包

cnpm install -g babel-cli

3、配置sublime中的babel外掛

preferences -> Package Settings -> Babel -> Settings-Default,開啟檔案,按照下面的引數配置。
{
  "debug": false,
  "use_local_babel": true,
  "node_modules": {
    "windows": "C:/Users/Administrator/AppData/Roaming/npm/node_modules/babel-cli/node_modules",
    "linux": "/usr/lib/node_modules",
    "osx": "/usr/local/lib/node_modules"
  },
  "options": {}
}

4、配置.babelrc檔案
在當前目錄配置好.babelrc檔案,定義轉碼規則,在當前目錄放置好依賴包。

{
  "presets": ["es2015"],
  "plugins": []
}

5、執行轉換
寫一個es6.js檔案,按下快捷鍵Ctrl+Shift+P,輸入babel,選擇babel transform執行,會自動生成一個匿名檔案。很詭異的是,這個檔案自動儲存了,但不是在當前目錄,也不是在sublime的包目錄裡,具體在哪,我也不清楚,我只知道這不是我想要的結果。我希望能夠在儲存es6.js檔案時,自動生成期望目錄的es5.js檔案,而且名稱一致,檔案實際存在,不是無名檔案。後面找到優化方案,再回來補充

tools -> Babel -> Babel Transform,也可以執行轉換。

十、線上轉碼
babel提供一個repl線上編譯器,可以線上將ES6程式碼轉為ES5程式碼。轉換後的程式碼,可以直接作為ES5程式碼插入網頁執行。

十一、總結
看了上面的介紹,你會用babel了嗎?如果還有點迷惑,搞不清東南西北,看這裡就對啦。
1、宣告轉換規則
babel的配置檔案.babelrc列舉了一些要用到的東西。比如babel要用env,就把env寫進去,宣告要處理哪些語法。
2、進行轉換操作
幕後高人則是babel-core,babel-polyfill或者babel-cli,他們負責把原始檔轉換成目標檔案。
安裝了babel-core和babel-polyfill,並在你的原始檔中引用,頁面載入的時候,就會自動實現轉換。或者安裝babel-cli,在命令列輸入命令,手動轉換。