Babel 把用最新標準編寫的 JavaScript 代碼向下編譯成能夠在今天隨處可用的版本。 這一過程叫作「源碼到源碼」編譯, 也被稱爲轉換編譯。javascript
15 年 11 月,Babel 發佈了 6.0 版本。相較於前一代 Babel 5,新一代 Babel 更加模塊化, 將全部的轉碼功能以插件的形式分離出去,默認只提供 babel-core。本來只須要裝一個 babel ,如今必須按照本身的需求配置,靈活性提升的同時也提升了使用者的學習成本。html
npm i babel
已經棄用,你能下載到的僅僅是一段 console.warn,告訴你 babel 6 再也不以大雜燴的形式提供轉碼功能了。java
例如,Babel 可以將新的 ES2015 箭頭函數語法:node
let fun = () => console.log('babel')
轉譯爲:react
"use strict"; var fun = function fun() { return console.log('babel'); };
不過 Babel 的用途並不止於此,它支持語法擴展,能支持像 React 所用的 JSX 語法,更重要的是,Babel 的一切都是簡單的插件,誰均可以建立本身的插件,利用 Babel 的所有威力去作任何事情。
再進一步,Babel 自身被分解成了數個核心模塊,任何人均可以利用它們來建立下一代的 JavaScript 工具。webpack
babel-cli
Babel 的 CLI 是一種在命令行下使用 Babel 編譯文件的簡單方法。git
讓咱們先全局安裝它來學習基礎知識。es6
$ npm install --global babel-cli
咱們能夠這樣來編譯咱們的第一個文件:github
$ babel my-file.js
這將把編譯後的結果直接輸出至終端。使用 --out-file 或着 -o 能夠將結果寫入到指定的文件。web
$ 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
babel-core
若是你須要以編程的方式來使用 Babel,可使用 babel-core 這個包。
babel-core 的做用是把 js 代碼分析成 ast ,方便各個插件分析語法進行相應的處理。有些新語法在低版本 js 中是不存在的,如箭頭函數,rest 參數,函數默認值等,這種語言層面的不兼容只能經過將代碼轉爲 ast,分析其語法後再轉爲低版本 js。
首先安裝 babel-core。.
$ npm install babel-core
var babel = require("babel-core");
字符串形式的 JavaScript 代碼能夠直接使用 babel.transform 來編譯。
babel.transform("code();", options); // => { code, map, ast }
若是是文件的話,可使用異步 api:
babel.transformFile("filename.js", options, function(err, result) { result; // => { code, map, ast } });
或者是同步 api:
babel.transformFileSync("filename.js", options); // => { code, map, ast }
還能夠經過babel-register
和babel-node
使用Babel,但因爲這兩種用法不適合生產環境故省略。
你或許已經注意到了,目前爲止經過運行 Babel 本身咱們並沒能「翻譯」代碼,而僅僅是把代碼從一處拷貝到了另外一處。緣由就是從Babel 6之後, 默認的插件被移除, 若是沒有指定一個插件,Babel將會原樣輸出, 不會進行編譯。
你能夠經過安裝插件(plugins)或預設(presets,也就是一組插件)來指示 Babel 去作什麼事情。
插件只是單一的功能,例如
es2015-arrow-functions
es2015-classes
es2015-for-of
es2015-spread
如下是安裝箭頭函數的插件方式
npm install --save-dev babel-plugin-transform-es2015-arrow-functions
若是咱們一個一個引人功能單一的插件的話顯得特別麻煩,一般咱們用的更多的是預設。插件和預設一般寫入到配置文件中。能夠將配置寫入package.json
的‘babel’屬性裏,或者是一個單獨的.babelrc
文件。
.babelrc
在咱們告訴 Babel 該作什麼以前,你須要作的就是在項目的根路徑下建立 .babelrc
文件。而後輸入如下內容做爲開始:
{ "presets": [], "plugins": [] }
這個文件就是用來讓 Babel 作你要它作的事情的配置文件。
babel-preset-es2015
預設 babel-preset 系列打包了一組插件,相似於餐廳的套餐。如 babel-preset-es2015
打包了 es6 的特性,babel-preset-stage-0
打包處於 strawman 階段的語法
咱們須要安裝 "es2015" Babel 預設:
$ npm install --save-dev babel-preset-es2015
咱們修改 .babelrc
來包含這個預設。
{ "presets": [ + "es2015" ], "plugins": [] }
一樣的,還有babel-preset-2016
,babel-preset-2017
。
babel-preset-latest
latest是一個特殊的presets,包括了es2015,es2016,es2017的插件(目前爲止,之後有es2018也會包括進去)。即老是包含最新的編譯插件。
babel-preset-env
上面提到的各類preset的問題就是: 它們都太」重」了, 即包含了過多在某些狀況下不須要的功能. 好比, 現代的瀏覽器大多支持ES6的generator, 可是若是你使用babel-preset-es2015, 它會將generator函數編譯爲複雜的ES5代碼, 這是沒有必要的。但使用babel-preset-env, 咱們能夠聲明環境, 而後該preset就會只編譯包含咱們所聲明環境缺乏的特性的代碼,所以也是比較推薦的方式。
安裝babel-preset-env
npm install babel-preset-env --save-dev
添加配置
{ "presets": ["env"] }
當沒有添加任何的配置選項時,babel-preset-env
默認行爲是和babel-preset-latest
是同樣的。
下面咱們經過一些例子來看babel-preset-env
的配置是如何使用的:
指定支持主流瀏覽器最新的兩個版本以及IE 7+:
"presets": [ [ "env", { "targets": { "browsers": ["last 2 versions", "ie >= 7"] } } ] ] }
支持超過市場份額5%的瀏覽器:
"targets": { "browsers": "> 5%" }
某個固定版本的瀏覽器:
"targets": { "chrome": 56 }
更多的配置請查看官方文檔
babel-preset-stage-x
官方預設(preset), 有兩種,一個是按年份(babel-preset-2017),一個是按階段(babel-preset-stage-0)。 這主要是根據TC39 委員會ECMASCRPIT 發佈流程來制定的。TC39 委員會決定,從2016年開始,每一年都會發佈一個版本,它包括每一年期限內完成的全部功能,同時ECMAScript的版本號也按年份編制,就有了ES2016, ES2017。因此也就有了babel-present-2016, babel-preset-2017, 對每年新增的語法進行轉化。babel-preset-latest 就是把全部es2015, es2016, es2017 所有包含在一塊兒了。
最終在階段 4 被標準正式採納。
如下是4 個不一樣階段的(打包的)預設:
babel-preset-stage-0
babel-preset-stage-1
babel-preset-stage-2
babel-preset-stage-3
注意 stage-4 預設是不存在的由於它就是上面的
es2017
預設。
以上每種預設都依賴於緊隨的後期階段預設,數字越小,階段越靠後,存在依賴關係。也就是說stage-0是包括stage-1的,以此類推。也就是說這些stage包含的特性是比latest更新的特性但還未被寫入標準進行發佈。
使用的時候只須要安裝你想要的階段就能夠了:
$ npm install --save-dev babel-preset-stage-2
而後添加進你的 .babelrc
配置文件。可是要注意若是沒有提供es2017相關的預設,preset-stage-X 這種階段性的預設也不能用。
Babel 幾乎能夠編譯全部時新的 JavaScript 語法,但對於 APIs 來講卻並不是如此。例如: Promise、Set、Map 等新增對象,Object.assign、Object.entries等靜態方法。
爲了達成使用這些新API的目的,社區又有2個實現流派:babel-polyfill和babel-runtime+babel-plugin-transform-runtime。
這兩個模塊功能幾乎相同,就是轉碼新增 api,模擬 es6 環境,但實現方法徹底不一樣。babel-polyfill 的作法是將全局對象統統污染一遍,好比想在 node 0.10 上用 Promise,調用 babel-polyfill 就會往 global 對象掛上 Promise 對象。對於普通的業務代碼沒有關係,但若是用在模塊上就有問題了,會把模塊使用者的環境污染掉。
babel-runtime 的作法是本身手動引入 helper 函數,仍是上面的例子,const Promise = require('babel-runtime/core-js/promise') 就能夠引入 Promise。
但 babel-runtime 也有問題,第一,很不方便,第二,在代碼中中直接引入 helper 函數,意味着不能共享,形成最終打包出來的文件裏有不少重複的 helper 代碼。因此,babel 又開發了 babel-plugin-transform-runtime,這個模塊會將咱們的代碼重寫,如將 Promise 重寫成 _Promise(只是打比方),而後引入_Promise helper 函數。這樣就避免了重複打包代碼和手動引入模塊的痛苦。
babel-polyfill
爲了解決這個問題,咱們使用一種叫作 Polyfill(代碼填充,也可譯做兼容性補丁) 的技術。 簡單地說,polyfill便是在當前運行環境中用來複制(意指模擬性的複製,而不是拷貝)尚不存在的原生 api 的代碼。能讓你提早使用還不可用的 APIs,Array.from
就是一個例子。
Babel 用了優秀的 core-js 用做 polyfill,而且還有定製化的 regenerator 來讓 generators(生成器)和 async functions(異步函數)正常工做。
要使用 Babel polyfill,首先用 npm 安裝它:
$ npm install --save babel-polyfill
而後只須要在文件頂部導入 polyfill 就能夠了:
import "babel-polyfill";
babel-runtime
與 babel-polyfill 同樣,babel-runtime 的做用也是模擬 ES2015 環境。只不過,babel-polyfill 是針對全局環境的,引入它,咱們的瀏覽器就好像具有了規範裏定義的完整的特性 – 雖然原生並未實現。
babel-runtime 更像是分散的 polyfill 模塊,咱們能夠在本身的模塊裏單獨引入,好比 require(‘babel-runtime/core-js/promise’) ,它們不會在全局環境添加未實現的方法,只是,這樣手動引用每一個 polyfill 會很是低效。咱們藉助 Runtime transform 插件來自動化處理這一切。
經過安裝 babel-plugin-transform-runtime
和 babel-runtime
來開始。
$ npm install --save-dev babel-plugin-transform-runtime $ npm install --save babel-runtime
而後更新 .babelrc
:
{ "plugins": [ "transform-runtime", "transform-es2015-classes" ] }
如今,Babel 會把這樣的代碼:
class Foo { method() {} }
編譯成:
import _classCallCheck from "babel-runtime/helpers/classCallCheck"; import _createClass from "babel-runtime/helpers/createClass"; let Foo = function () { function Foo() { _classCallCheck(this, Foo); } _createClass(Foo, [{ key: "method", value: function method() {} }]); return Foo; }();
這樣就不須要把 _classCallCheck
和 _createClass
這兩個助手方法放進每個須要的文件裏去了。
那何時用 babel-polyfill
何時用 babel-runtime
呢?若是你不介意污染全局變量(如上面提到的業務代碼),放心大膽地用 babel-polyfill
;而若是你在寫模塊,爲了不污染使用者的環境,沒的選,只能用 babel-runtime
+ babel-plugin-transform-runtime
。
options
不少預設和插件都有選項用於配置他們自身的行爲。 例如,不少轉換器都有「寬鬆」模式,經過放棄一些標準中的行爲來生成更簡化且性能更好的代碼。
要爲插件添加選項,只須要作出如下更改:
{ "plugins": [ "transform-runtime", - "transform-es2015-classes", + ["transform-es2015-classes", { "loose": true }] ] }
plugins/presets排序:
具體而言,plugins優先於presets進行編譯。
plugins按照數組的index增序(從數組第一個到最後一個)進行編譯。
presets按照數組的index倒序(從數組最後一個到第一個)進行編譯。由於做者認爲大部分會把presets寫成["es2015", "stage-0"]。具體細節能夠看這個。
不多有大型項目僅僅須要 babel,通常都是 babel 配合着 webpack 或 glup 等編譯工具一塊兒上的。
爲了顯出 babel 的能耐,咱們分別配個用 babel-polyfill
和 babel-runtime
、支持 react 的webpack.config.js
先來配使用 babel-runtime
的:
首先安裝:
npm install babel-loader babel-core babel-preset-es2015 babel-plugin-transform-runtime webpack --save-dev
npm install babel-runtime --save
而後配置
module: { loaders: [{ loader: 'babel', test: /\.jsx?$/, include: path.join(__dirname, 'src'), query: { plugins: ['transform-runtime'], presets: [ ["env", { "targets": { "chrome": 52 }, "modules": false, "loose": true }], 'stage-2', 'react' ], } }] }
須要注意的是,babel-runtime
雖然沒有出如今配置裏,但仍然須要安裝,由於 transform-runtime
依賴它。
再來個 babel-polyfill
的:
entry: [ 'babel-polyfill', 'src/index.jsx', ], module: { loaders: [{ loader: 'babel', test: /\.jsx?$/, include: path.join(__dirname, 'src'), query: { presets: [ ["env", { "targets": { "chrome": 52 }, "modules": false, "loose": true }], 'stage-2', 'react', ], } }] }
參考文檔:
http://babeljs.io/
https://github.com/thejamesky...
https://excaliburhan.com/post...
https://icyfish.me/2017/05/18...