Polyfill 策略與 bundle 大小的爭鬥

Polyfill 允許我們在舊版本的環境使用新穎的 API ,但是錯誤配置 Polyfill 的方式,只會讓 Modern Browser 白白浪費網路資源,那麼在高流、一刻不能遲、一個 User 也不能少的生產環境有什麼樣的策略呢?

realdennis
8 min readDec 27, 2019

寫在文前:如果你從來沒發現 IE11 看不懂超多 ES6 語法 or 你壓根可以控制 User browser 的版本(如 Hybrid App),那麼這篇對你的幫助可能不大。

法一、僅在 App 開始前 import 對應的 Polyfill

如果我們知道 Feature code 裡頭只有用到 ProxyObject.keysArray.from

當我們明確知道使用的語法哪些是需要 Polyfill 時,我們可以僅 import 對應的 Polyfill 進來,多數的 Polyfill 會像是這樣:

if (!Array.from) {
Array.from = (function () {
/* Using the ES5 method to implement this method... */
}
}

(關於 Array.from 的詳細 Polyfill,請查看此處。)

沒錯,Polyfill 在開始前其實會確認該語法是不是早就存在。(JavaScript 如此動態到令人吐槽的特性,在解決跨版本語法問題時,竟顯得特別友好,哈。

於是乎,你可以載入 Proxy Object.keysArray.from ,又不會讓你的 modern browser 用戶的 Built-in API 爛掉。

But,如果你今天要載入的 Polyfill 一狗票又很大的時候…

你的 Chrome user 這時會私訊你:“What the fuck?我開你們網站前置作業一堆是怎樣?而且第一行 if 就走了內,Hello?”

法二、Server-side 決定 polyfill.js

當你的 Team 全部都是沒經歷 IE 時代的小哥哥與小姐姐,這時除了需要 built-time 先轉譯的 Syntax (如箭頭函數)以外,其他 ES6 API 通通要 Polyfill 時…

如同上面那方法最後說的,你的 Chrome user 如果按下 F12 會很想打電話到你的分機把你幹死

這時你就會開始想:我能不能 by browser 決定最終 user 看到的 polyfill.js 內容?

《金融時報》(Financial Times)工程師們想到這樣的解法:

讓我們在 Server-side 先偷瞄一下使用者的瀏覽器是啥,再把對應的 Polyfill 回傳回來…這樣做在 IE11 就能拿到完整的 Polyfill ,而像是 Chrome 這樣的瀏覽器,我們就給他一個全空的 script 也就無需多餘的流量了!

這就是 Financial-Times 所開源的 Polyfill service 核心精神 https://github.com/Financial-Times/polyfill-service

Polyfill.io is a service which makes web development less frustrating by selectively polyfilling just what the browser needs. Polyfill.io reads the User-Agent header of each request and returns polyfills that are suitable for the requesting browser.

法三、Client-side dynamic polyfill

當你的使用者不知道裝了什麼牛逼的瀏覽器,結果 user-agent 亂成一團,或是大家都以為你看得懂 Array.from ,但是在其他腳本 or extension 跑完後被 pollution 成undefined…

FYI 第一種方法非常的肥大,對 modern browser 相當不友好。

這時有另外一個想法:如果我們只把判定 API 是否存在的 code 放進來,跑完一輪判定後,再從 client-side 去拉需要 Polyfill 的 patch 回來呢?

這樣的做法相當有趣,並且一定程度解決了第一個方法所造成的問題。而我是在 Web Component 的社群發現這樣子的 Practice 。

在 Web Component 中,browser 一定要看懂的幾個 API:customElementshadowDomHTMLTemplateElementPromiseArray.from…族繁不及備載。

而他們的 polyfill loader 的介紹這樣寫到: The webcomponents-loader.js is a client-side loader that dynamically loads the minimum polyfill bundle, using feature detection.

實際上是怎麼做到的呢?

L107–114:是feature detection,但是 detect 到的時候不是直接去執行 polyfill (那時根本沒有載入), 而是 push 到一個名為 polyfills 的 array 裡頭。

L141–155:一系列的 Polyfills array 所記錄缺少的,透過 script 標籤插入到 browser 中實現動態載入。

(有沒有一種果然魔術被破解後感覺:啊原來就這樣啊…的感覺,哈。)

休息一下,在下文中我們會比較不同做法,以及探討如何實現在生產環境。

Comparison

你現在可能心想:“gan,你說了那麼多,所以到底哪個好?法三嗎?還是二?

其實這個問題還真的不一定!每一個方法都有其好與壞,以及 implement 到生產環境的做法,以下針對其分析。

看似肥大的法一,在前提不一樣時未必是爛方法

我們都知道 babel 、 autoprefixer 可以透過 browserlist 決定要 Patch 的 property prefix、 method 。

當你的 Project 在後台 User 的 browser 都是非常新穎、或是跟著 mobile OS 在走的時候,其實你的 Browserlist 設好,拉進來的 Polyfill 可能非常的小,小到不需要花費 Effort 去優化

優秀的 Server-side solution 如何整合進現有的 App

你可以直接使用 Polyfill service,或是自己 build 一個起來(http://localhost:8080/v3/polyfill.min.js)。

思路如下:

法一、建立一個 microservice 給 polyfill service,在生產環境使用 .min.js ,將其 service 的位置放在 Site 的 head 裡頭。

法二、建立一個 internal-service,在 render 前 forward user request去轉打,並且 async 幹進去 App code 的頂端。

優點:在 Client 端,對這些操作甚至完全無感(Client 無需 feature detect),直接拿到對應的 Polyfill 。

缺點:Server loading 顯然比較重,且不同架構各有優缺。

新穎的 Client-loader 與其優缺

將不同的 polyfill code spliting 到 assets 中,並且在 Client-side 做 feature detect 去決定要拉去的 assets 進來。

缺點:請求數過多、任一腳本 timeout 就會 crash、風險上升。

優點:對於 modern browser 完全無感、無 server logic、透過 HTTP/2 去 parallel 拉 polyfill assets 速度上升。

尾聲

我認為複雜且多層次的設計,其實背後代表著對於 User 的廣度、深度、效能上的競賽

如同副標題所說的,若需要支援的 Browser 多、又需要顧及每一 User 的體驗、一點速度都不能掉,那麼自然在一件 “直接載入就完事了” 的問題上就需要深度的探討。

僅支援 modern browser 與需要小量 Polyfill 的客群,自然是選擇法一。

極端的 Case 下,我認為法二與法三同時 implement 進去完全沒有問題,站在 “一點 User 都不能少” 的前提下,付出一點 Architecture Design 的 Effort 與 Client-side 低成本的 feature detection fallback, 我認為可能是相當值得的。

最後,常常有人說前端開發只是調調樣式、碰不到架構設計,你瞧這麼一個聽起來簡單到不行的小問題(多數人往往會一兩句話回答),背後是不是滿滿的設計與考量呢!

--

--