最近在作一次Babel6升級Babel7的操做,把升級的過程和關於babel的配置進行一次總結。前端
Babel 是一個工具鏈,主要用於將 ECMAScript 2015+ 版本的代碼轉換爲向後兼容的 JavaScript 語法,以便可以運行在當前和舊版本的瀏覽器或其餘環境中。node
其實目前前端開發,各類項目模版,你也不須要關心babel的配置,隨便拉下來一個就能運行,可是要作定製化的處理仍是要把babel搞懂。android
@babel/cli
是Babel的命令行工具,咱們通常用不到,由於咱們一般都是用babel-loader
,裏邊使用的是@babel/core
的api形式,咱們只須要關心Babel的配置,若是有須要在編譯階段對代碼進行處理 也能夠寫本身的插件,可是大部分場景是須要咱們把Babel的配置搞清楚。ios
Babel6的階段 最經常使用的是.babelrc
,可是如今Babel7支持了更多格式:git
const RELATIVE_CONFIG_FILENAMES = [".babelrc", ".babelrc.js", ".babelrc.cjs", ".babelrc.mjs", ".babelrc.json"];
和package.json
files with a "babel"
key。github
配置文件的格式以下:web
{ "presets": [ [ "@babel/preset-env", { "modules": "commonjs" } ] ], "plugins": [ [ "@babel/plugin-transform-runtime", { "corejs": 3 } ], "@babel/plugin-syntax-dynamic-import", ] } }
更詳細介紹參見Babel Config。chrome
plugins
和preset
配置文件中主要有兩個配置plugins
和preset
,@babel/core
自己對代碼不作任何的轉化,可是提供了對代碼的解析,各類插件在解析過程當中能夠進行代碼的轉換,好比處理箭頭函數的插件@babel/plugin-transform-arrow-functions
等等,因此好比針對ES6語法的解析就須要不少插件,preset
預設就是配置的子集,預設的一套配置,能夠根據參數動態的返回配置。npm
順序問題很重要,好比某一個插件是添加'use strict', 一個插件是刪除'use strict',若是想要刪除成功,就要保證執行順序。
在一個配置裏面json
因此在preset中的插件,確定比外層的插件要後執行。
plugins
和preset
的配置是數組的形式,若是不須要傳參數,最基本的就是字符串名稱,若是須要傳參數,把它寫成數組的形式,數組第一項是字符串名稱,第二項是要傳的參數對象。
@babel/preset-env已經徹底能夠替換
babel-preset-es2015
babel-preset-es2016
babel-preset-es2017
babel-preset-latest
全部stage的preset在Babel v7.0.0-beta.55版本都已經被廢棄了,
stage-x:指處於某一階段的js語言提案
最開始stage的出現是爲了方便開發人員,每一個階段的插件與TC39和社區相互做用,同步更新,用戶能夠直接引用對應stage支持的語法特性。關於廢棄的緣由 總結下來是:
先說下已經有了Babel爲何還要polyfill,Babel默認只轉換新的JavaScript句法(syntax),而不轉換新的API,好比 Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise等全局對象,以及一些定義在全局對象上的方法(好比Object.assign)都不會轉碼。舉個栗子,ES6在Array對象上新增了Array.from方法。babel就不會轉碼這個方法。因此以前咱們都須要引入polyfill。
可是從Babel 7.4.0開始,不推薦使用此軟件包,而直接包括core-js/stable
(包括regenerator-runtime/runtime
polyfill ECMAScript功能)和(須要使用轉譯的生成器函數)。
import "core-js/stable"; import "regenerator-runtime/runtime";
可是最優的方式也不是直接這樣引入,後面講@babel/preset-env
的使用時會有更好的方式。
關於升級,官方提供了工具 babel-upgrade
總結關鍵點以下:
npx babel-upgrade --write --install
,兩個參數,--write
會把更新的配置寫入babel的配置文件中,package.json
中也會更新依賴,可是發現沒有的依賴沒有新增,因此我在更新的時候把配置中依賴的npm包,在package.json
都check了一遍。--install
是會進行一次安裝操做。@babel/preset-env
是Babel推薦的最智能的預設,在使用了 babel-upgrade
升級以後你就能夠看到配置中會有這個預設,由於設個預設集成了經常使用插件和polyfill能力,能夠根據用戶指定的環境尋找對應的插件。
下面對它的關鍵配置項作說明。
string | Array<string> | { [string]: string }
,默認爲{}
。
描述您爲項目支持/目標的環境。
這能夠是與瀏覽器列表兼容的查詢:
`{ "targets": "> 0.25%, not dead" }`
或支持最低環境版本的對象:
`{ "targets": { "chrome": "58", "ie": "11" } }`
實施例的環境中:chrome
,opera
,edge
,firefox
,safari
,ie
,ios
,android
,node
,electron
。
若是未指定目標,則旁註@babel/preset-env
將默認轉換全部ECMAScript 2015+代碼,因此不建議。
"usage"
| "entry"
| false
,默認爲false
。
此選項決定@babel/preset-env
如何處理polyfill的引入。
前面將廢棄polyfill
時 講到了polyfill如今分爲兩個npm包,是這樣引入
import "core-js/stable"; import "regenerator-runtime/runtime";
可是問題是全量引入,增長包體積,因此useBuiltIns選項就是對其進行優化。
當取值"entry"
時,@babel/preset-env
會把全量引入替換爲目標環境特定須要的模塊。
當目標瀏覽器是 chrome 72
時,上面的內容將被 @babel/preset-env
轉換爲
require("core-js/modules/es.array.reduce"); require("core-js/modules/es.array.reduce-right"); require("core-js/modules/es.array.unscopables.flat"); require("core-js/modules/es.array.unscopables.flat-map"); require("core-js/modules/es.math.hypot"); require("core-js/modules/es.object.from-entries"); require("core-js/modules/web.immediate");
當取值"usage"
時,咱們無需手動引入polyfill
文件,@babel/preset-env
在每一個文件的開頭引入目標環境不支持、僅在當前文件中使用的 polyfills。
例如,
const set = new Set([1, 2, 3]); [1, 2, 3].includes(2);
當目標環境是老的瀏覽器例如 ie 11
,將轉換爲
import "core-js/modules/es.array.includes"; import "core-js/modules/es.array.iterator"; import "core-js/modules/es.object.to-string"; import "core-js/modules/es.set"; const set = new Set([1, 2, 3]); [1, 2, 3].includes(2);
當目標是 chrome 72
時不須要導入,由於這個環境不須要 polyfills:
const set = new Set([1, 2, 3]); [1, 2, 3].includes(2);
core-js就是Javascript標準庫的polyfill,@babel/preset-env
的polyfill就依賴於它,因此咱們須要指定使用的core-js的版本,目前最新版本是3。
默認狀況下,僅注入穩定ECMAScript功能的polyfill,若是想使用一些提案的語法,能夠有三種選擇:
useBuiltIns: "entry"
時,能夠直接導入建議填充工具:import "core-js/proposals/string-replace-all"
。使用useBuiltIns: "usage"
時,您有兩種不一樣的選擇:
shippedProposals
選項設置爲true
。這將啓用已經在瀏覽器中發佈一段時間的投標的polyfill和transforms。corejs: { version: 3, proposals: true }
。這樣能夠對所支持的每一個提案進行填充core-js
。我以爲這個選擇有用,由於@babel/preset-env
中內置的插件,咱們沒法在其後執行,好比裏面內置的"@babel/plugin-transform-modules-commonjs"
插件會默認的在全部的模塊上都添加use strict
嚴格模式, 雖然有babel-plugin-remove-use-strict
用於移除use strict
可是因爲執行順序的問題,仍是沒法移除。
第二個問題就是內置插件沒法傳參數的問題。
因此我想到的方法是先exclude排除掉這個插件,而後在外層再添加 這樣就能夠改變執行順序同時也能夠自定義傳參數。
已經有了polyfill,這個包的做用是什麼?主要分兩類:
await
的_asyncToGenerator
和asyncGeneratorStep
,使用了它以後會把這些方法經過@babel/runtime/helpers
中的模塊進行替換。例如代碼
async function a () { await new Promise(function(resolve, reject) { resolve(1) }) }
沒使用以前,編譯結果
require("regenerator-runtime/runtime"); function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } } function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; } function _a() { _a = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee() { return regeneratorRuntime.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: _context.next = 2; return new Promise(function (resolve, reject) { resolve(1); }); case 2: case "end": return _context.stop(); } } }, _callee); })); return _a.apply(this, arguments); }
使用以後
var _regenerator = _interopRequireDefault(require("@babel/runtime-corejs3/regenerator")); var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise")); require("regenerator-runtime/runtime"); var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/asyncToGenerator"));
@babel/preset-env
中引入的polyfill都是直接引入的core-js
下的模塊,它的問題會污染全局變量,好比
"foobar".includes("foo");
編譯後的polyfill是給String.prototype
添加了includes方法,因此會影響全局的String
對象。
require("core-js/modules/es.string.includes");
而使用了@babel/plugin-transform-runtime
後的編譯結果
var _includes = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/includes")); (0, _includes.default)(_context = "foobar").call(_context, "foo");
會把代碼中用的到的方法進行包裝,而不會對全局變量產生影響。
最後是 @babel/plugin-transform-runtime
的配置項,關鍵的是指定 core-js
的版本。
corejs: 2
僅支持全局變量(例如Promise
)和靜態屬性(例如Array.from
),corejs: 3
還支持實例屬性(例如[].includes
)。
默認狀況下,@babel/plugin-transform-runtime
不填充提案。若是您使用corejs: 3
,則能夠經過使用proposals: true
選項啓用此功能。
須要安裝對應的運行時依賴:npm install --save @babel/runtime-corejs3
最後 你能夠基於以上知識已經建立了符合本身團隊開發的preset。
若是以爲有收穫請關注微信公衆號 前端良文 每週都會分享前端開發中的乾貨知識點。