Babel是一個JavaScript編譯器,不只能將當前運行環境不支持的JavaScript語法(例如ES六、ES7等)編譯成向下兼容的可用語法(例如ES3或ES5),這其中會涉及新語法的轉換和缺失特性的修補;還支持語法擴展,從而能隨時隨地的使用JSX、TypeScript等語法。目前最新版本是7.4,自從6.0以來,Babel被分解的更加模塊化,各類轉譯功能都以插件的形式分離出來,可按本身的需求,靈活配置。node
在7.0版本中,對Babel的包作了一次大調整,統一改爲域級包,將原先以「babel-」爲前綴的包遷移到@babel的命名空間,例如@babel/core、@babel/cli等。這種模塊化的設計,既能區分是不是官方發佈的,也能避免命名衝突。react
若是要以編程的方式使用Babel,那麼能夠經過@babel/core實現,安裝命令以下所示。git
npm install --save-dev @babel/core
在引入該包後,就能在JavaScript文件中直接編譯代碼、文件或AST。以編譯代碼爲例,可選擇的方法有三個,以下所示。es6
var babel = require("@babel/core"); babel.transform(code, options, function(err, result) { console.log(result); // => { code, map, ast } }); babel.transformSync(code, options) // => { code, map, ast } babel.transformAsync(code, options) // => Promise<{ code, map, ast }>
雖然transform()用於異步編譯,transformSync()用於同步編譯,可是它們獲得的結果相同,都是一個由轉換後的代碼(code)、源碼映射信息(map)和抽象語法樹(ast)組成的對象。而transformAsync()獲得的結果與前面兩個方法不一樣,它返回一個包含code、map和ast的Promise對象。這些方法的第二個options參數,可用於傳遞配置信息,具體可參考官方文檔。github
若是要編譯文件或AST,那麼也有相似的三個方法可供選擇。除了能編譯以外,利用這個包還能把代碼解析成AST(即傳入一段代碼,獲得一個AST)以便其它插件對其進行語法分析,可以使用的方法有parse()、parseSync()和parseAsync()。正則表達式
@babel/cli是一個Babel內置的命令行工具,可經過命令來編譯文件,其安裝以下所示。chrome
npm install --save-dev @babel/cli
假設有一個src.js文件,其內容以下代碼所示,用到了ES6中的箭頭函數。typescript
let fn = () => true;
在運行下面的命令後,能夠看到輸出和輸入的代碼相同,這是由於此時還未指定任何轉譯插件。npm
./node_modules/.bin/babel src.js
若是當前的npm版本是5.2以上,那麼可將./node_modules/.bin縮短爲npx,以下所示。編程
npx babel src.js
在babel中,有多個可用的參數,下面只列舉其中的四個,其他參數的用法可參考官方文檔。
npx babel src --out-dir dist npx babel src.js --out-file dist.js npx babel src.js --out-file dist.js --plugins=@babel/plugin-transform-classes,@babel/transform-modules-amd npx babel src.js --out-file dist.js --presets=@babel/preset-env,@babel/flow
(1)「--out-dir」參數可編譯整個src目錄下的文件並輸出到dist目錄中。
(2)「--out-file」參數可編譯src.js文件並輸出到dist.js文件中。
(3)「--plugins」參數可指定編譯過程當中要使用的插件,多個插件用逗號隔開。
(4)「--presets」參數可指定編譯過程當中要使用的預設,多個預設用逗號隔開。
在package.json文件中,能夠經過scripts字段聲明腳本命令。若是想要簡化babel命令,那麼能夠將它們遷移到scripts字段中,以下所示。
{ "scripts": { "compile": "npx babel src.js --out-file dist.js" } }
如今要編譯目錄的話,只要執行下面的這條npm命令便可。
npm run compile
在Babel中,能夠將各類命令的參數集中到一個配置文件中,而可配置的文件包括babel.config.js、.babelrc和package.json。
1)babel.config.js
這是Babel 7最新引入的配置文件,存在於根目錄中(即package.json文件所在的目錄)。它不只能以編程的方式聲明全局生效的配置參數,還能利用overrides字段對不一樣的子目錄進行鍼對性的配置,從而就能避免爲相關目錄建立一個.babelrc文件了。在下面的示例中,爲test目錄單獨配置了預設(presets)。
module.exports = { presets: [...], overrides: [{ test: ["./test"], presets: [...] }] };
當運行下面兩條命令進行編譯時,第一條讀取的是最外層的預設,第二條讀取的是overrides中的預設。
npx babel src.js
npx babel ./test/src.js
2).babelrc
babel.config.js並非.babelrc的替代品,.babelrc文件用於局部配置(以下代碼所示),可放置於全部目錄中。若是當前目錄沒有.babelrc文件,那麼就會往上查找直至找到爲止。
{ "presets": [...] }
注意,若是.babelrc的後綴是「.js」(即.babelrc.js),那麼在文件中能夠經過JavaScript配置參數。
3)package.json
在package.json文件中,能夠聲明一個babel字段,其值就是.babelrc文件中的配置參數,以下所示。
{ "babel": { "presets": [...] } }
注意,不能讓.babelrc和聲明過babel字段的package.json處在相同的目錄中。
4)配置函數或方法
前面三種都是用單獨的文件來配置Babel的參數,其實還能夠經過相關的函數或方法來達到相同地目的。在下面的示例中,引入gulp-babel包後就能經過獲得的babel()函數來配置Babel的參數。
let babel = require("gulp-babel");
babel({
presets: [...]
});
Babel的編譯可分爲三個階段:解析、轉換和生成,而插件(Plugin)在轉換過程當中起到了相當重要的做用。藉助Babel的插件可將解析完成的AST按照特定的要求進行處理,而後再輸出生成的代碼。
1)插件類型
Babel中的插件分爲兩種類型:語法和轉換。語法插件只容許Babel解析成特定類型的語法,例如JSX、Flow等。轉換插件經常使用來編譯ES六、ES七、React和Modules等,因爲能自動開啓相應的語法插件,所以不用顯式的指定兩種插件。以以前的箭頭函數爲例,若是要將其編譯成全部瀏覽器都能識別的形式,那麼就得安裝相應的插件,以下所示。
npm install --save-dev @babel/plugin-transform-arrow-functions
在.babelrc文件中的配置以下所示。
{ "plugins": ["@babel/plugin-transform-arrow-functions"] }
在配置文件中,不只能指定插件的相對或絕對路徑,還可省略插件名稱中的「babel-plugin-」前綴。假設有個插件名叫@org/babel-plugin-name,那麼它的相對路徑和簡寫名稱以下所示。
{ "plugins": [ "../@org/babel-plugin-name", "@org/name" ] }
2)執行順序
插件的執行順序會受配置時所處的位置的影響,具體規則以下所列,其中預設是指官方預先設計的一組插件集,本質上仍然是插件。
(1)插件執行在預設以前。
(2)插件會按順序從前日後執行。
(3)預設與插件相反,從後往前執行。
如下面的配置信息爲例,在插件中,先執行@babel/plugin-transform-arrow-functions,後執行@babel/plugin-transform-classes。而在預設中,先執行@babel/preset-react,後執行@babel/preset-env。
{ "plugins": ["@babel/plugin-transform-arrow-functions", "@babel/plugin-transform-classes"], "presets": ["@babel/preset-env", "@babel/preset-react"] }
3)插件參數
若是要配置插件的參數,那麼得在插件名稱後增長一個參數對象,而且寫成數組的形式,以下所示。
{ "plugins": [ ["@babel/plugin-transform-arrow-functions", { "spec": true }], ["@babel/plugin-transform-classes", { "loose": true }] ] }
預設也能接收參數,其寫法與之相似。
若是在配置文件中一個一個的聲明插件,那麼不只會讓該文件變得巨大,並且還不免會有所遺漏。官方爲了不此類問題,引入了預設(Preset)的概念。預設相似於生活中的套餐,每一個套餐會集合不一樣的插件,從而可以一次性安裝各種插件,而且還可共享配置的參數。
1)@babel/preset-env
此預設不只集合了最新的ES語法(即編譯指定的ES標準),還能配置所要支持的平臺(例如Node、Chrome等)和版本,以及按需加載插件,其安裝命令以下所示。
npm install --save-dev @babel/preset-env
下面是一個預設的配置示例,支持Chrome 58和IE 11,以及超過市場份額5%的瀏覽器。還有許多其它可供選擇的配置參數,具體可翻閱官方文檔。
{ presets: [ [ "@babel/preset-env", { targets: { chrome: "58", ie: "11", browsers: "> 5%" } } ] ] }
除了@babel/preset-env以外,官方還給出了其它實用的預設,例如@babel/preset-flow、@babel/preset-react和@babel/preset-typescript等。
與插件相似,預設也能指定相對或絕對路徑,只不過它省略的前綴是「babel-preset-」。假設有個預設名叫@org/babel-preset-name,那麼它的相對路徑和簡寫名稱以下所示。
{ "presets": [ "../@org/babel-preset-name", "@org/name" ] }
2)stage-x
這是一組實驗性質的預設,囊括了處於提案階段的標準,TC39委員會將提案分爲5個階段,如表2所示。
表2 提案的五個階段
階段 | 描述 | 預設 |
Stage 0 | 設想(Strawman),由TC39的成員或已經註冊成TC39貢獻者的會員提出的想法 | @babel/preset-stage-0 |
Stage 1 | 建議(Proposal),產生一個正式的提案,肯定提案負責人 | @babel/preset-stage-1 |
Stage 2 | 草案(Draft),規範的第一個版本,包含新特性的語法和語義,接下來只接受增量修改 | @babel/preset-stage-2 |
Stage 3 | 候選(Candidate),完成規範,得到用戶反饋,接下來只有出現重大問題才作修改 | @babel/preset-stage-3 |
Stage 4 | 完成(Finished),將新規範歸入到標準中,準備發佈 | 不存在,由於它就是已發佈的標準預設,例如ES六、ES7等 |
以上4個階段的預設存在着依賴關係,階段靠前的依賴階段靠後的(即數字小的包含數字大的),例如@babel/preset-stage-0依賴@babel/preset-stage-1。
因爲這些提案階段的預設不太穩定,頗有可能會被TC39委員會除名或變動,而且混合使用在配置上容易出錯,所以從Babel 7開始,它們都將被廢棄。
雖然env預設(@babel/preset-env)能統一JavaScript的新語法(即高版本編譯成低版本),可是沒法支持內置的新方法或新對象,例如Promise、Array.of()等。爲此,Babel引入了的Polyfill技術(所有打包在@babel/polyfill中),將所缺的特性添加到全局對象中或內置對象的原型上,彌補env預設的不足,從而模擬出完整的ES6+語法和特性。
@babel/polyfill包含兩個模塊:regenerator-runtime和core-js,前者用於編譯生成器與異步函數(async和await),後者用於處理其它兼容性問題。@babel/polyfill的安裝命令以下所示,注意,使用的參數是--save而不是--save-dev,由於須要在源碼以前先執行Polyfill。
npm install --save @babel/polyfill
1)useBuiltIns
在env預設中,存在一個與@babel/polyfill有緊密聯繫的useBuiltIns參數,它有三個關鍵字可供選擇,分別是false、entry和usage,具體說明以下所列。
(1)false:默認值,不開啓Polyfill,顯式的配置以下所示。
{ presets: [ ["@babel/preset-env", { useBuiltIns: false }] ] }
(2)entry:加載運行環境(可在targets參數中聲明)所需的Polyfill,下面是一個使用Promise的例子。
require("@babel/polyfill"); new Promise();
當useBuiltIns參數的值爲entry時,這段代碼在編譯後,就會引入許多不相干的JavaScript文件,形成資源的浪費,以下所示。
require("core-js/modules/es6.promise"); require("core-js/modules/es6.array.fill"); require("core-js/modules/es6.math.trunc"); require("core-js/modules/es6.string.fixed"); ...... new Promise();
(3)usage:自動加載源碼所需的Polyfill,仍然以Promise爲例,以下代碼所示,不用再顯式的引入@babel/polyfill。
new Promise();
當useBuiltIns參數的值爲usage時,這段代碼在編譯後,就會只引入須要的JavaScript文件,以下所示。
require("core-js/modules/es6.promise"); require("core-js/modules/es6.object.to-string"); new Promise();
@babel/runtime與@babel/polyfill相似,也是用來加強env預設的,只是它以非侵入式的輔助函數來填補平臺所沒有的特性,其安裝命令以下所示。
npm install --save @babel/runtime
在Babel 7中還引入了一個@babel/runtime-corejs2,它比@babel/runtime多包含一個core-js模塊,即可以編譯Promise、Symbol等。接下來以ES6的類爲例,以下所示。
class People {
name() {}
}
在將People類編譯後,獲得的結果以下所示,省略了三個函數中的邏輯代碼。
"use strict"; function _classCallCheck(instance, Constructor) { } function _defineProperties(target, props) { } function _createClass(Constructor, protoProps, staticProps) { } var People = /*#__PURE__*/ function () { function People() { _classCallCheck(this, People); } _createClass(People, [{ key: "name", value: function name() {} }]); return People; }();
1)@babel/plugin-transform-runtime
因爲編譯生成的輔助函數會滯留在所使用的文件中,所以文件越多冗餘的函數就越多。若是人工分離,那工做量將巨大,所以Babel提供了能自動將它們分離的@babel/plugin-transform-runtime插件,其安裝命令以下所示。
npm install --save-dev @babel/plugin-transform-runtime
仍然以ES6的People類爲例,先在配置文件中將其聲明,以下所示。
{ plugins: ["@babel/plugin-transform-runtime"] }
而後再將類編譯,三個輔助函數就能經過引入的方式獲得,以下所示。
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
在Babel中有兩種方式實現動態編譯,分別是@babel/register和@babel/node,它們的安裝命令以下所示。
npm install --save-dev @babel/register npm install --save-dev @babel/node
雖然動態編譯省去了手動操做,可是會損耗程序的速度和性能,所以不適合生產環境。
1)@babel/register
在將其安裝後,就會爲Node.js的require()函數增長一個鉤子。每當經過require()加載後綴爲.es六、.es、.jsx、.mjs或.js的文件時,就能自動對其進行Babel編譯。接下來用一個例子來演示@babel/register的用法,首先建立一個src.js的文件,其代碼以下所示。
module.exports = () => true;
而後建立一個default.js文件,引入@babel/register和以前的src.js,以下所示。
require("@babel/register"); var code = require("./src.js"); console.log(code.toString());
最後在命令行工具中輸入「node default.js」,打印出編譯後的匿名函數,以下所示。
function () { return true; }
在@babel/register中,若是須要使用Polyfill,那麼得逐個引入。還要注意,它默認不會編譯node_modules目錄中的模塊,可是能夠經過ignore參數修改忽略規則來覆蓋其行爲。
ignore參數是一個由正則表達式和函數組成的數組(以下代碼所示),若是要讓文件不被編譯,那麼得將其路徑與正則表達式匹配或函數返回true,其中函數的參數就是文件路徑。
require("babel-register")({ ignore: [ /regex/, function(filepath) { return true; } ] });
另外還有一個only參數,其值也是一個數組,一樣也用來設置文件不被編譯的條件。只是其規則與ignore參數正好相反,即路徑與正則表達式不匹配或函數返回false時,纔不編譯。
@babel/register還能夠接收其它參數,例如extensions、cache等,而且會合並配置文件中的信息,做爲其參數傳遞進來。
2)@babel/node
在Babel 7以前,@babel/cli會自帶babel-node命令,但以後,官方將其拆成一個單獨的包:@babel/node。與@babel/register不一樣,@babel/node不須要調整源碼,直接一個命令就能實現動態編譯,以下代碼所示,其中src就是以前的src.js文件。
npx babel-node src