做者:sorryccjavascript
日期:2019-01-29 01:31:04html
原文地址: github.com/sorrycc/blo…前端
blog: gitissue.com/issues/5c4f…java
任何一個小知識點,深挖下去,也是很是有意思的。node
A polyfill, or polyfiller, is a piece of code (or plugin) that provides the technology that you, the developer, expect the browser to provide natively. Flattening the API landscape if you will.git
咱們但願瀏覽器提供一些特性,可是沒有,而後咱們本身寫一段代碼來實現他,那這段代碼就是補丁。es6
好比 IE11 不支持 Promise,而咱們又須要在項目裏用到,寫了這樣的代碼:github
<script>
Promise.resolve('bar')
.then(function(foo) {
document.write(foo);
});
</script>
複製代碼
這時在 IE 下運行就會報錯了,ajax
而後在此以前加上補丁,chrome
<script src="https://cdn.jsdelivr.net/npm/promise-polyfill@8/dist/polyfill.min.js"></script>
<script>
Promise.resolve('bar')
.then(function(foo) {
document.write(foo);
});
</script>
複製代碼
刷新瀏覽器,就能夠正常運行了,
若是你是一個 3 年陳 + 的前端,應該會有據說過 shim、sham、es5-shim 和 es6-shim 等等如今看起來很古老的補丁方式。
那麼,shim 和 sham 是啥?又有什麼區別?
在 shim 和 sham 以後,還有一種補丁方式是引入包含全部語言層補丁的 babel-polyfill.js
。好比:
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/7.2.5/polyfill.js"></script>
複製代碼
而後就 es六、es7 特性隨便寫了。
但缺點是,babel-polyfill 包含全部補丁,無論瀏覽器是否支持,也無論你的項目是否有用到,都全量引了,因此若是你的用戶全都不差流量和帶寬(好比內部應用),盡能夠用這種方式。
如今尚未銀彈,各類方案百花齊放。
babel-polyfill 包含全部補丁,那我只須要支持某些瀏覽器的某些版本,是否有辦法只包含這些瀏覽器的補丁?這就是 @babel/preset-env
+ useBuiltins: entry
+ targets
配置的方案。
咱們先在入口文件裏引入 @babel/polyfill
,
import '@babel/polyfill';
複製代碼
而後配置 .babelrc,添加 preset @babel/preset-env
,並設置 useBuiltIns
和 targets
,
{
"presets": [
["@babel/env", {
useBuiltIns: 'entry',
targets: { chrome: 62 }
}]
]
}
複製代碼
useBuiltIns: entry
的含義是找到入口文件裏引入的 @babel/polyfill
,並替換爲 targets 瀏覽器/環境須要的補丁列表。
替換後的內容,好比:
import "core-js/modules/es7.string.pad-start";
import "core-js/modules/es7.string.pad-end";
...
複製代碼
這樣就只會引入 chrome@62 及以上所須要的補丁,什麼 Promise 之類的都不會再打包引入。
是否是很好用?
😄
有什麼問題?
🤔
細細想一想,其實還有很多問題,
@babel/preset-env
有提供 exclude 的配置,若是我配置了 exclude,後面是否得當心翼翼地確保不要用到 exclude 掉的特性chrome: 62, ie: 9
,那意味着 chrome 62 也得載入 ie 9 相關的補丁,這也是一份冗餘傳統的手動打補丁方案雖然低效,但直觀有用。有些很是在意性能的場景,好比咱們公司的部分無線 H5 業務,他們寧肯犧牲效率也要追求性能。因此他們的補丁方案是手動引入 core-js/modules 下的文件,缺啥加啥就好。
注意:
前面的手動引入解決的是特性列表的問題,有了特性列表,要作到按需下載,就須要用到在線的補丁服務了。目前最流行的應該就是 polyfill.io,提供的是 cdn 服務,有些站點在用,例如 spectrum.chat/。另外,polyfil… 還開源了 polyfill-service 供咱們本身搭建使用。
使用上,好比:
<script src="https://polyfill.io/v3/polyfill.min.js?features=default%2CPromise"></script>
複製代碼
而後在 Chrome@71 下的輸出是:
/* Disable minification (remove `.min` from URL path) for more info */
複製代碼
啥都沒有,由於 Promsie 特性 Chrome@71 已經支持了。
關於補丁方案的將來,我以爲按需特性探測 + 在線補丁纔是終極方案。
按需特性探測保證特性的最小集;在線補丁作按需下載。
按需特性探測能夠用 @babel/preset-env
配上 targets
以及試驗階段的 useBuiltIns: usage
,保障特性集的最小化。之因此說是將來,由於 JavaScript 的動態性,語法探測不太可能探測出全部特性,但上了 TypeScript 以後可能會好一些。另外,要注意一個前提是 node_modules 也須要走 babel 編譯,否則 node_modules 下用到的特性會探測不出來。
在線補丁能夠用相似前面介紹的 polyfill.io/ 提供的方案,讓瀏覽器只下載必要的補丁,一般大公司用的話會部署一份到本身的 cdn 上。(阿里好像有團隊部署了,但一時間想不起地址了。)
不該該。 好比項目了依賴了 a 和 b,a 和 b 都包含 Promise 的補丁,就會有冗餘。因此組件不該該包含補丁,補丁應該由項目決定。
一般不須要作特殊處理,可是有些語言特性的實現會須要引入額外的 helper 方法。
好比:
console.log({ ...a });
複製代碼
編譯後是:
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
console.log(_objectSpread({}, a));
複製代碼
而後咱們會有不少文件,每一個文件都引入一遍 helper 方法,會有不少冗餘。因此咱們一般會使用 @babel/plugin-transform-runtime 來複用這些 helper 方法。
在 .babelrc
裏配置:
{
"plugins": [
"@babel/transform-runtime"
]
}
複製代碼
編譯後是:
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _objectSpread2 = _interopRequireDefault(require("@babel/runtime/helpers/objectSpread"));
console.log((0, _objectSpread2.default)({}, a));
複製代碼
因此,組件編譯只要確保沒有冗餘的 helper 方法就行了。
core-js 提供了兩種補丁方式。
core-js/library
,經過 helper 方法的方式提供core-js/module
,經過覆蓋全局變量的方式提供舉個例子,
import '@babel/polyfill';
Promise.resolve('foo');
複製代碼
.babelrc
配:
{
"presets": [
["@babel/env", {
"useBuiltIns": "entry",
"targets": {
"ie": 9
}
}]
]
}
複製代碼
編譯結果是:
require("core-js/modules/es6.promise");
require("core-js/modules/es7.promise.finally");
// 此處省略數十個其餘補丁...
Promise.resolve('foo');
複製代碼
而後把文件內容換成:
// import '@babel/polyfill';
Promise.resolve('foo');
複製代碼
.babelrc
配:
{
"plugins": [
["@babel/transform-runtime", {
"corejs": 2
}]
]
}
複製代碼
編譯結果是:
var _interopRequireDefault = require("@babel/runtime-corejs2/helpers/interopRequireDefault");
var _promise = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/promise"));
_promise.default.resolve('foo');
複製代碼
而後 @babel/runtime-corejs2/core-js/promise
的內容是:
module.exports = require("core-js/library/fn/promise");
複製代碼
目前推薦是用 core-js/modules
,由於 node_modules 不走 babel 編譯,因此 core-js/library
的方式沒法爲依賴庫提供補丁。
手動引入,好比 Intl.js、URL 等。可是得當心有些規範後續加入 ecmascript 以後可能的冗餘,好比 URL。
關注公衆號,發現更多精彩內容。