文章總結的時間是2017/11/20
本文是為了梳理Babel配置及使用而整理,因為看過使用Babel配置項目和文章,存在項目插件使用混亂、文章各種照搬、插件使用聽風是雨、插件升級文章內容不再適用的問題。這里就目前最新使用的配置組合進行整理,涉及的插件包括以下三個:
- @babel/preset-env(^7.0.0-beta.32)
- @babel/preset-stage-x(7.0.0-beta.32), x-0,1,2,3
- @babel/polyfill(^7.0.0-beta.32)
@babel/preset-env
特性
替換之前所有babel-presets-es20xx
插件
A Babel preset that compiles ES2015+ down to ES5 by automatically determining the Babel plugins and polyfills you need based on your targeted browser or runtime environments.
也就是說,這是一個能根據運行環境為代碼做相應的編譯,@babel/preset-env
的推出是為了解解決個性化輸出目標代碼的問題,通過browserslist
語法解析需要支持的目標環境,根據環境將源代碼轉義到目標代碼,可以實現代碼精準適配。
此外,@babel/preset-env
不包含state-x
一些列插件,只支持最新推出版本的JavaScript語法(state-4),關于state-x
后面會介紹。
更進一步說明,請參考Dr. Axel Rauschmayer的介紹。這個插件對特殊平臺的開發有很大幫助,比如:Electron、大屏、移動端(只考慮webkit)等。
替換@babel/plugin-transform-runtime
的使用
@babel/plugin-transform-runtime
插件是為了解決:
- 多個文件重復引用相同helpers(幫助函數)-> 提取運行時
- 新API方法全局污染 -> 局部引入
這個插件推薦在編寫library/module時使用。當然,以上問題可通過設置useBuiltIns
搞定。
支持實例方法按需引入
傳統方式是手動從core-js
引入需要的ES6+特性,
require('core-js/fn/set');
require('core-js/fn/array/from');
require('core-js/fn/array/find-index');
...
...
或者一股腦全部引入:
import '@babel/polyfill'
// or
require('@babel/polyfill')
同樣,以上問題可通過設置useBuiltIns
搞定。
使用說明
默認情況下,@babel/preset-env
的效果和@babel/preset-latest
一樣,雖然上面的說明有提到polyfills,但是也需要在配置中設置useBuiltIns
才會生效。
主要配置
targets
設置支持環境,支持的key
包括:chrome, opera, edge, firefox, safari, ie, ios, android, node, electron。
例如:
{
"presets": [
["@babel/env", {
"targets": {
"node": "current",
"chrome": 52,
"browsers": ["last 2 versions", "safari 7"]
}
}]
]
}
其中,browserslist
可在package.json
中配置,這個和設置CSS的Autoprefixer一致,配置優先級如下:
targets.browsers > package.json/browserslist
此外,browserslist
的配置滿足最大匹配原則,比如需要同時支持在 IE 8 和 Chrome 55 下運行,則preset-env
提供所有 IE 8 下需要的插件,即使 Chrome 55 可能不需要。
modules
選項用于模塊轉化規則設置,可選配置包括:"amd" | "umd" | "systemjs" | "commonjs" | false, 默認使用 "commonjs"。即,將代碼中的ES6的import
轉為require
。
如果你當前的webpack構建環境是2.x/3.x,推薦將modules
設置為false
,即交由 Webpack 來處理模塊化,通過其 TreeShaking 特性將有效減少打包出來的 JS 文件大小。這部分參考這里的回答:ECMAScript 6 的模塊相比 CommonJS 的require (...)有什么優點?
useBuiltIns
A way to apply @babel/preset-env for polyfills (via @babel/polyfill).
可選值包括:"usage" | "entry" | false, 默認為 false,表示不對 polyfills 處理,這個配置是引入 polyfills 的關鍵。
"useBuiltIns":"usage"
在文件需要的位置單獨按需引入,可以保證在每個bundler中只引入一份。例如:
In
a.js
var a = new Promise();
b.js
var b = new Map();
Out (if environment doesn't support it)
import "core-js/modules/es6.promise";
var a = new Promise();
import "core-js/modules/es6.map";
var b = new Map();
Out (if environment supports it)
var a = new Promise();
var b = new Map();
!!注意!!
當前模式類似于@babel/plugin-transform-runtime
,polyfill局部使用,制造一個沙盒環境,不造成全局污染,但是如上配置后,@babel/preset-env
能按需引入新實例方法,例如:
"foobar".includes("foo")
而@babel/plugin-transform-runtime
不行,需要自行從core-js
中按需引入。我測試的情況和下面這篇文章所說完全不一致。
原文:https://zhuanlan.zhihu.com/p/29506685
當 useBuiltIns 設置為 usage 時,Babel 會在你使用到 ES2015+ 新特性時,自動添加 babel-polyfill 的引用,并且是 partial 級別的引用。
請注意: usage 的行為類似 babel-transform-runtime,不會造成全局污染,因此也會不會對類似 Array.prototype.includes() 進行 polyfill。
"useBuiltIns":"entry"
在項目入口引入一次(多次引入會報錯)
import "@babel/polyfill"
// or
require("@babel/polyfill")
插件@babel/preset-env
會將把@babel/polyfill
根據實際需求打散,只留下必須的,例如:
In
import "@babel/polyfill";
Out (different based on environment)
import "core-js/modules/es6.promise";
import "core-js/modules/es7.string.pad-start";
import "core-js/modules/es7.string.pad-end";
import "core-js/modules/es7.array.includes";
除了新API,也可以是實例方法,不過最終包體積比使用
"useBuiltIns":"usage"
大30kb左右(因項目而異)。
"useBuiltIns": false
不在代碼中使用polyfills,表現形式和@babel/preset-latest
一樣,當使用ES6+語法及API時,在不支持的環境下會報錯。
@babel/preset-stage-x
這里需要說明下ES特性支持的提案,不同階段的提案支持的內容不同,其中stage-4
階段提案中的特性將會在未來發布。
關于各個Stage的說明參考這里。上面介紹的@babel/preset-env
或者@babel/preset-latest
就是下面提到的stage-4
。
The TC39 categorizes proposals into the following stages:
- Stage 0 - Strawman: just an idea, possible Babel plugin.
- Stage 1 - Proposal: this is worth working on.
- Stage 2 - Draft: initial spec.
- Stage 3 - Candidate: complete spec and initial browser implementations.
- Stage 4 - Finished: will be added to the next yearly release.
Stage的包含順序是:左邊包含右邊全部特性,即stage-0包含右邊 1 / 2 / 3 的所有插件。
stage-0 > ~1 > ~2 > ~3 > ~4:
疑問
1. 這個和@babel/preset-env
的區別
@babel/preset-env
會根據預設的瀏覽器兼容列表從stage-4
選取必須的plugin,也就是說,不引入別的stage-x
,@babel/preset-env
將只支持到stage-4
。
建議
1. 如果是React用戶,建議配到@babel/preset-stage-0
其中的兩個插件對于寫JSX很有幫助。
- transform-do-expressions:if/else三目運算展開
- transform-function-bind:this綁定
2. 通常使用建議配到@babel/preset-stage-2
插件包括:
- syntax-dynamic-import: 動態import
- transform-class-properties:用于 class 的屬性轉化
- transform-object-rest-spread:用來處理 rest spread
- transform-async-generator-functions:用來處理 async 和 await
@babel/polyfill
這個插件是對core-js和regenerator-runtime的再次封裝,在@babel/preset-env
中的useBuiltIns: entry
用到,代碼不多。
if (global._babelPolyfill) {
throw new Error("only one instance of @babel/polyfill is allowed");
}
global._babelPolyfill = true;
import "core-js/shim";
import "regenerator-runtime/runtime";
core-js/shim
shim only: Only includes the standard methods.
只包含了納入標準的API及實例化方法,例如下列常見的。注意,這里沒有generator/async,如果需要,那就安裝插件@babel/preset-stage-3
(或者0 / 1 / 2)
...
require('./modules/es6.string.trim');
require('./modules/es6.string.includes');
require('./modules/es7.array.includes');
require('./modules/es6.promise');
...
regenerator-runtime/runtime
Standalone runtime for Regenerator-compiled generator and async functions.
主要是給generator/async做支持的插件。
總結
這里需要理解下三個概念:
- 最新ES 語法:比如,箭頭函數
- 最新ES API:,比如,Promise
- 最新ES 實例方法:比如,String.protorype.includes
@babel/preset-env
默認支持語法轉化,需要開啟useBuiltIns
配置才能轉化API和實例方法。
此外,不管是寫項目還是寫Library/Module,使用@babel/preset-env
并正確配置就行。多看英文原稿說明,中文總結看看就好,別太當真。