21 分鐘精通前端 Polyfill 方案
今天是 2017 年 7 月 7 日,es2015 正式發布已經兩年了。但最新的瀏覽器們逼近 100% 的支持率對我們好像並沒有什麼卵用,為了少數用戶的體驗,我們很可能需要兼容 IE9。感謝 babel 的編譯,讓我們完美的提前使用上了 const,let 和 arrow function。可也許你還是面對著不敢直接使用 或是 的難題?
babel 和 polyfill
剛接觸 babel 的同學一開始可能都認為在使用了 babel 後就可以無痛的使用 es2015 了,之後被各種 undefined 的報錯無情打臉。一句話概括, babel 的編譯不會做 polyfill。那麼 polyfill 是指什麼呢?
const foo = (a, b) => { return Object.assign(a, b); };
當我們寫出上面這樣的代碼,交給 babel 編譯時,我們得到了:
"use strict"; var foo = function foo(a, b) { return Object.assign(a, b); };
arrow function 被編譯成了普通的函數,但仔細一看 還牢牢的站在那裡,而它作為 es2015 的新方法,並不能運行在相當多的瀏覽器上。為什麼不把 Object.assign 編譯成 這樣的替代方法呢?好問題!編譯為了保證正確的語義,只能轉換語法而不是去增加或修改原有的屬性和方法。所以 babel 不處理 Object.assign 反倒是最正確的做法。而處理這些方法的方案則被稱為 polyfill。
babel-plugin-transform-xxx
解決這個問題最原始的思路是缺什麼補什麼,babel 提供了一系列 transform 的插件來解決這個問題,例如針對 ,我們可以使用 babel-plugin-transform-object-assign:
yarn add babel-plugin-transform-object-assign # in .babelrc { "presets": ["latest"], "plugins": ["transform-object-assign"] }
方便你嘗試,這裡準備了一些測試的代碼。編譯之前的代碼,我們得到了:
babel-plugin-transform-object-assign 在 module 之前替換了我們用到的 Object.assign 方法。看上去效果不錯,但細細考究一下會發現這樣的問題:
// 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);
被編譯成了:
/***/ 211: /***/ (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
transform 的引用是 module 級別的,這意味著在多個 module 使用時會帶來重複的引用,這在多文件的項目里可能帶來災難。另外,你可能也並不想一個個的去添加自己要用的 plugin,如果能自動引入該多好。
babel-runtime & babel-plugin-transform-runtime
前面提到問題主要在於方法的引入方式是內聯的,直接插入了一行代碼從而無法優化。鑒於這樣的考慮,babel 提供了 babel-plugin-transform-runtime,從一個統一的地方core-js自動引入對應的方法。
安裝和使用的方法同樣不複雜:
yarn add -D babel-plugin-transform-runtime yarn add babel-runtime # .babelrc { "presets": ["latest"], "plugins": ["transform-runtime"] }
首先需要安裝開發時的依賴 。同時還需要安裝生產環境的依賴 。是否要在生產環境也依賴它取決於你發布代碼的方式,簡單點直接放在 dependency 里總沒錯。一切就緒,編譯時它會自動引入你用到的方法。但自動就意味著不一定精確:
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,比如 這樣依賴於全局 的調用依然無法使用。
babel-polyfill
上面兩種 polyfill 方案共有的缺陷在於作用域。因此 babel 直接提供了通過改變全局來兼容 es2015 所有方法的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 , );
babel-polyfill 在項目代碼前插入所有的 polyfill 代碼,為你的程序打造一個完美的 es2015 運行環境。babel 建議在網頁應用程序里使用 babel-polyfill,只要不在意它略有點大的體積(min 後 86kb),直接用它肯定是最穩妥的。值得注意的是,因為 babel-polyfill 帶來的改變是全局的,所以無需多次引用,也有可能因此產生衝突,所以最好還是把它抽成一個 common module,放在項目 的 vendor 里,或者乾脆直接抽成一個文件放在 cdn 上。
如果你是在開發一個庫或者框架,那麼 babel-polyfill 的體積就有點大了,尤其是在你實際使用的只有一個 的情況下。更可怕的是對於一個庫來說,改變全局環境是使不得的。誰也不希望使用了你的庫,還附帶了一家老小的 polyfill 改變了全局對象。這時不污染全局環境的 babel-plugin-transform-runtime 才是最合適的。
babel-preset-env
回到應用開發。通過自動識別代碼引入 polyfill 來優化看來是不太靠譜的,那是不是就無從優化了呢?並不是。還記得 babel 推薦使用的 babel-preset-env 么?它可以根據指定目標環境判斷需要做哪些編譯。而在張克炎大神的建議下,babel-preset-env 也支持針對指定目標環境選擇需要的 polyfill 了,只需引入 babel-polyfill,並在 babelrc 中聲明 useBuiltIns,babel 會將引入的 babel-polyfill 自動替換為所需的 polyfill。
# .babelrc { "presets": [ ["env", { "targets": { "browsers": ["IE >= 9"] }, "useBuiltIns": true }] ] }
對比 "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 的一些細小的規範問題吧。(當我嘗試調大瀏覽器範圍時,發現始終停留在 189kb 以內,還沒細究相比完整的 polyfill 少掉了什麼,如果有高手知道的歡迎解答)
polyfill.io
以上這樣對你來說應該已經夠用了,但本質上還是讓那些願意使用最新瀏覽器的優質用戶們做了犧牲。聰明的你可能已經想到了一種優化方案,針對瀏覽器來選擇 polyfill。沒錯!polyfill.io便是基於這個思路給出的一項服務。
你可以嘗試在不同的瀏覽器下請求 這個文件,伺服器會判斷瀏覽器 UA 返回不同的 polyfill 文件,你所要做的僅僅是在頁面上引入這個文件,polyfill 這件事就自動以最優雅的方式解決了。更加讓人喜悅的是,polyfill.io 不旦提供了 cdn 的服務,也開源了自己的實現方案polyfill-service。簡單配置一下,便可擁有自己的 polyfill service 了。
看上去一切都很美好,但在使用之前還請你多考慮一下。polyfill.io 面對國內奇葩的瀏覽器環境能不能把 UA 算準,如果缺失了 polyfill 還有沒有什麼補救方案,也許都是你需要考慮的。但無論如何,這是個優秀的想法和方案,我想未來也會有更多的網站採用 polyfill.io 的思路的。比如theguardian和redux 作者 Dan 在 create-react-app 上的提議(雖然沒被接受哈~)。
※我是這麼把美業產品做失敗的
※使用NIO的內存映射計算超大文件的MD5
※用戶體驗設計從業者有沒有所謂的 35 歲中年危機?
※慧川智能發布首款視頻理解API,要讓AI真正取代「剪片子」的人類?
※德國電子政務通信系統組件存在多個嚴重漏洞可導致政府交換數據泄露
TAG:推酷 |
※MIDO 美度Baroncelli 2118限量表
※Brand News丨Viennois jewelry,Chinese jewelry design brand NO.21
※Vol.215 兜豆靚Youlina
※vivo X21i A 跑分現身 Geekbench:搭載 Helio P60
※Forever21首頁分析
※Biselyngbyolide B及其C21-C22 Z-異構體的全合成
※21世紀孤獨生活指南×Kovanen&Vivian Maier&Amélie
※hyojin☆【圖片】180321-孔曉振出席sjyp 18 summerpre-fall
※【IMiss愛蜜社】Vol.212 Yannie
※Episode 321.冷的火雞Turkey
※iPhone X的勁敵vivo X21登場;華為p20lite已經開箱,好看
※【IMiss愛蜜社】Vol.213 許諾Sabrina
※i3 8121U發布!CannonLake登陸Intel ARK
※vivo X21i上架,改用聯發科Helio P60
※刷起!vivo X21升級Android P公測版
※5月21日 Arena AfterParty 全球舞朝競技場賽後派對
※Android P宣布秘境開啟,vivo X21成為首批嘗鮮者
※江詩丹頓 Vacheron Constantin 全新歷史名作系列美國1921小型號
※iPhone7plus和vivox21不考慮價格,入手哪個更好?
※最全介紹:Logitech G213 Prodigy RGB幻彩鍵盤G403滑鼠