ES6 在2015正式發佈已經多年。最新瀏覽器們逼近100% 的支持率,但爲了少數用戶體驗,咱們極可能須要兼容IE9。 Babel 默認只轉碼 ES6 的新語法(syntax),而不轉換新的 API,好比 Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise 等全局對象,以及一些定義在全局對象上的方法(好比 Object.assign、Array.from)都不會轉碼,這時咱們就須要提供polyfill。vue
剛接觸 babel 的同窗可能都認爲在使用了 babel 後就能夠無痛的使用 ES6 了,以後被各類 undefined 的報錯無情打臉。一句話歸納, babel 的編譯不會作 polyfill。那麼 polyfill 是指什麼呢? 翻譯: 一種用於衣物、牀具等的填充材料node
const foo = (a, b) => {
return Object.assign(a, b);
};
複製代碼
當咱們寫出上面這樣的代碼,交給 babel 編譯時,咱們獲得了:react
"use strict";
var foo = function foo(a, b) {
return Object.assign(a, b);
};
複製代碼
箭頭 function 被編譯成了普通函數,但丫的 Object.assign
還沒變身,而它做爲 ES6 的新方法,並不能在IE9等瀏覽器上。爲何不把 Object.assign 編譯成 (Object.assign||function() { /*...*/})
這樣的替代方法呢?好問題!編譯爲了保證正確的語義,只轉換語法而不是去增長或修改原有的屬性和方法。因此 babel 不處理 Object.assign
反卻是最正確的作法。而處理這些方法的方案則稱爲 polyfill。webpack
這個問題最原始解決思路是缺什麼補什麼,babel 提供了一系列 transform 的插件來解決這個問題,例如針對 Object.assign,咱們可使用 babel-plugin-transform-object-assign:git
npm i babel-plugin-transform-object-assign
# in .babelrc
{
"presets": ["latest"],
"plugins": ["transform-object-assign"]
}
複製代碼
方便你嘗試,這裏準備了一些測試的代碼。編譯以前的代碼,咱們獲得了:github
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
var foo = exports.foo = function foo(a, b) {
return _extends(a, b);
};
複製代碼
babel-plugin-transform-object-assign 在咱們用到 Object.assign 方法以前使用ES5或更早的寫法替換了。看上去效果不錯,但細細考究一下會發現這樣的問題:web
// another.js
export const bar = (a, b) => Object.assign(a, b);
// index.js
import { bar } from './another';
export const foo = (a, b) => Object.assign(a, b);
複製代碼
被編譯成了:chrome
/***/ index.js:
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.foo = undefined;
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
var _another = __webpack_require__(212);
var foo = exports.foo = function foo(a, b) {
return _extends(a, b);
};
/***/ }),
/***/ another.js:
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
var bar = exports.bar = function bar(a, b) {
return _extends(a, b);
};
/***/ })
複製代碼
plugin-transform 的引用是 module 級別的,意味着在多個 module 使用時會重複的引用,這在多文件的項目裏可能帶來災難。且也不想一個個的去添加須要用的 plugin,若是能自動引入該多好。npm
前面提到問題主要在於方法的引入方式是內聯的,直接插入了一行代碼從而沒法優化。鑑於這樣的考慮,babel 提供了 babel-plugin-transform-runtime,從一個統一的地方 core-js 自動引入對應的方法。redux
npm i -D babel-plugin-transform-runtime
npm i babel-runtime
# .babelrc
{
"presets": ["latest"],
"plugins": ["transform-runtime"]
}
複製代碼
一切就緒,編譯時它會自動引入你用到的方法。但自動就意味着不必定精確
export const foo = (a, b) => Object.assign(a, b);
export const bar = (a, b) => {
const o = Object;
const c = [1, 2, 3].includes(3);
return c && o.assign(a, b);
};
複製代碼
會編譯成:
var _assign = __webpack_require__(214);
var _assign2 = _interopRequireDefault(_assign);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var foo = exports.foo = function foo(a, b) {
return (0, _assign2.default)(a, b);
};
var bar = exports.bar = function bar(a, b) {
var o = Object;
var c = [1, 2, 3].includes(3);
return c && o.assign(a, b);
};
複製代碼
foo 中的 assign 會被替換成 require 來的方法,而 bar 中這樣非直接調用的方式則無能爲力了。同時,由於 babel-plugin-transform-runtime 依然不是全局生效的,所以實例化的對象方法則不能被 polyfill,好比 [1,2,3].includes
這樣依賴於全局 Array.prototype.includes
的調用依然沒法使用。
上面兩種 polyfill 方案共有的缺陷在於做用域。所以 babel 直接提供了經過改變全局來兼容 es2015 全部方法的 babel-polyfill,安裝 babel-polyfill 後你只須要在main.js加一句 import 'babel-polyfill'
即可引入它,若是使用了 webpack 也能夠直接在 entry 中添加 babel-polyfill 的入口。
import 'babel-polyfill';
export const foo = (a, b) => Object.assign(a, b);
複製代碼
加入 babel-polyfill 後,打包好的 pollyfill.js 一會兒增長到了 251kb(未壓縮),(建議感興趣的同窗把代碼拉下來運行一下,以後提到的全部方式也均可以看到打包結果)搜索一下 polyfill.js 不難找到這樣的全局修改:
//polyfill
`$export($export.S + $export.F, 'Object', {assign: __webpack_require__(79)});
複製代碼
babel-polyfill 在項目代碼前插入全部的 polyfill 代碼,爲你的程序打造一個完美的 es2015 運行環境。babel 建議在網頁應用程序裏使用 babel-polyfill,只要不在乎它略有點大的體積(min 後 86kb),直接用它確定是最穩妥的。值得注意的是,由於 babel-polyfill 帶來的改變是全局的,因此無需屢次引用,也有可能所以產生衝突,因此最好仍是把它抽成一個 common module,放在項目 的 vendor 裏,或者乾脆直接抽成一個文件放在 cdn 上。
若是你是在開發一個庫或者框架,那麼 babel-polyfill 的體積就有點大了,尤爲是在你實際使用的只有一個 Object.assign
的狀況下。更可怕的是對於一個庫來講,改變全局環境是使不得的。誰也不但願使用了你的庫,還附帶了一家老少的 polyfill 改變了全局對象。這時不污染全局環境的 babel-plugin-transform-runtime 纔是最合適的。
回到應用開發。經過babel-runtime
自動識別代碼引入 polyfill 來優化不太靠譜,那是否是就無從優化了呢?並非。還記得 babel 推薦使用的 babel-preset-env 麼?它能夠根據指定目標環境判斷須要作哪些編譯。babel-preset-env 也支持針對指定目標環境選擇須要的 polyfill 了,只需引入 babel-polyfill,並在 babelrc 中聲明 useBuiltIns,babel 會將引入的 babel-polyfill 自動替換爲所需的 polyfill。
# .babelrc
{
"presets": [
["env", {
"modules": false,
"targets": {
"browsers": ["last 2 versions", "safari >= 7", "IE >= 9" ],
"node": "current", // 自動採用你當前用來運行 Babel 的 Node.js 版本
"modules": false
},
"useBuiltIns": "entry", // entry usage false
include: []
}],
"stage-2"
],
"plugins": ["transform-vue-jsx", "transform-runtime"],
"env": {
"test": {
"presets": ["env", "stage-2"],
"plugins": ["transform-vue-jsx", "transform-es2015-modules-commonjs", "dynamic-import-node"]
}
}
}
複製代碼
對比 "IE >= 9" 和 "chrome >= 59" 環境下編譯後的文件大小:
Asset Size Chunks
polyfill.js 252 kB 0 [emitted] [big]
ie9.js 189 kB 1 [emitted]
chrome.js 30.5 kB 2 [emitted]
transform-runtime.js 17.3 kB 3 [emitted]
transform-plugins.js 3.48 kB 4 [emitted]
複製代碼
在目前 IE9 的需求下能節省到將近 30%,但想不到瀏覽器之神 chrome 也還須要 30kb 的 polyfill,多是爲了修正那些 v8 的一些細小的規範問題吧。
以上本應該已經夠用了,但本質上仍是讓那些願意使用最新瀏覽器的優質用戶們作了犧牲。聰明的你可能已經想到了一種優化方案,針對瀏覽器來選擇 polyfill。沒錯!polyfill.io 給出的一項服務。
你能夠嘗試在不一樣的瀏覽器下請求 https://cdn.polyfill.io/v2/polyfill.js
這個文件,服務器會判斷瀏覽器 UA 返回不一樣的 polyfill 文件,你所要作的僅僅是在頁面上引入這個文件,polyfill 這件事就自動以最優雅的方式解決了。更加讓人喜悅的是,polyfill.io 不旦提供了 cdn 的服務,也開源了本身的實現方案 polyfill-service。簡單配置一下,即可擁有本身的 polyfill service 了。
看上去一切都很美好,但在使用以前還請你多考慮一下。polyfill.io 面對國內奇葩的瀏覽器環境能不能把 UA 算準,若是缺失了 polyfill 還有沒有什麼補救方案,也許都是你須要考慮的。但不管如何,這是個優秀的想法和方案,或許將來也會有更多的網站採用 polyfill.io 的思路的。好比 theguardian 和 redux 做者 Dan 在 create-react-app 上的提議(雖然沒被接受哈~)。