# 前端效能優化 Day16 Polyfill-less Bundling Script & File Compression [https://ithelp.ithome.com.tw/articles/10275720](https://ithelp.ithome.com.tw/articles/10275720) ## **Polyfill-less Bundling Script & File Compression** **與** **`Build Optimizations` 相關** 過去同個主題的方法 - Code Splitting - Dynamic Import - Tree Shaking --- ### **Polyfill-less Bundling Script** Modern Script 大約指 ES6(ES2015) 版本(或之後)的 JavaScript,因為其定義是可被 「Modern Browser」支援,所以是會持續變動的。只不過 ES6 的存在是一個里程碑,往後的版本雖然陸續都有新增功能,但都不及 ES6 巨大的變動 scope。 ```bash ES6 let、const Template Literals Arrow Functions Destructuring Assignment Promises and Async/Await Module - import、export Classes ...etc ``` > *chat: 目前現代瀏覽器支援到 ES2020 有 90% > *到 can I use 確認,大部分語法都支援 (2024 updated) > Modern JavaScript 與 polyfill 的關聯 ### Transpiling 轉譯的存在是為了讓開發完成的程式碼可以運行在瀏覽器上,不論是多新的 JS 版本。 例如常見的 Babel - 新語法轉譯成舊語法 → 這個行為稱為 polyfill → 經常使程式碼變長 - polyfill 為一種為舊瀏覽器提供的後備機制,補充其不足的功能 - 工作原理:偵測某個功能是否缺少,若缺少的話,用較舊的 JavaScript 版本中已有的功能去實現這個缺少的功能。 - Array forEach 為例 ```jsx // Array forEach 的 polyfill if (!Array.prototype.forEach) { Array.prototype.forEach = function(callback, thisArg) { for (var i = 0; i < this.length; i++) { callback.call(thisArg, this[i], i, this); } }; } ``` - **However, it's important to include only the polyfills needed for the features you use and the browsers you support to avoid unnecessary bloat in your web application.** - 例如原文章舉的 class 例子,必須用這麼冗長的 pollyfill 才能實作 class 功能 ![Untitled](https://hackmd.io/_uploads/SJ7ujytlC.png) ### 程式碼長度對 bundle 的影響巨大 > 是否能不追求達到所有瀏覽器都能支援的程度? (不要理會過於舊的版本) > 原文章 instagram 例子:IG開發團隊 將 transpile target 設為 ES2017 → 比 ES5 少了 5.7% 的 bundle size → modern browser page speed 提升 3% > 那麼為了效能,要犧牲舊版? > 可以在打包時分成兩個版本,再根據用戶瀏覽器版本決定載入哪個 bundle ```jsx <script type="module" src="modern_module.js"></script> // The `type="module"` attribute ensures that this script is only executed in browsers that support ES6+ module syntax. <script nomodule src="fallback.js"></script> // The nomodule attribute prevents this script from being executed in browsers that do support modules ``` ##### 補充 使用 `type="module"` 是一個比較大的差異判斷,可以再結合以下方式達到更好的效果 - **Specific Feature Detection**: [Modernizr](https://modernizr.com/) - 可以測試特定的 CSS or JS 功能是否能作用在瀏覽器 - **Polyfills** - **User-Agent Detection** - **Dynamic Imports - 搭配第一個 Specific Feature Detection → 當功能被支持時,才 import** [Modernizr](https://modernizr.com/) 簡單介紹 - Modernizr 會在瀏覽器上跑測試 → 僅測試功能是否存在 → 不會做 polyfill - 在 HTML tag 上註明 classes 表示當前瀏覽器是否有該功能 - CSS 例子 ``` // detection for CSS gradients <html class="cssgradients"> or <html class="no-cssgradients"> // 使用上述的 classes 做處理 .no-cssgradients .header { background: url("images/glossybutton.png"); } .cssgradients .header { background-image: linear-gradient(cornflowerblue, rebeccapurple); } ``` - JS 例子(搭配 HTML class) ``` if (Modernizr.cssanimations) { // Run animation code } else { // Fallback or polyfill } ``` - 很大彈性的客製化內容載入,減少不必要的 size [https://modernizr.com/download?setclasses](https://modernizr.com/download?setclasses) ![Screen_Shot_2024-04-14_at_11.22.26_AM](https://hackmd.io/_uploads/BJ2nhJYxC.png) - 使用策略 假設要做一個 Photo Gallery 1. 基本:Gallery Layout 呈現圖片排版 2. 進階:Modernizr 確認是否有 Grid、Animation 等功能 → 動態效果、非同步載入圖片,改善 UX 3. 回調:若瀏覽器不支援功能,回到基本的排版,如 Flexbox,確保仍可使用 ### **Estimator.dev** 作者推薦的 **[Estimator](https://github.com/GoogleChromeLabs/estimator.dev?tab=readme-ov-file) 已不再維護,該官方建議使用 `legacy-javascript` 搭配 Lighthouse** --- ## **File Compression** ![Screen_Shot_2024-04-14_at_12.04.23_PM](https://hackmd.io/_uploads/Bk31T1teA.png) ### 壓縮的種類 每一種文件類型基本上可以透過壓縮來節省空間,尤以媒體類型(音樂、影片)效果更為明顯。 ##### 圖片 - 有損壓縮 - 對原本數據進行修改,EX: jpeg - 壓縮程度可以控制 → 可接受的品質下,盡可能提高壓縮比率 - 無損壓縮 - 不對數據進行修改,EX: gif、 png ### End To End Compression (= HTTP Request & Response with Compression) Web 應用中對效能提升最有幫助的方法 由 client 端發起壓縮請求 → 在 server side 完成壓縮 → server side 回傳壓縮檔 → client 解壓縮得到原始檔案 ![Untitled 1](https://hackmd.io/_uploads/rJzQTkFxC.png) 要做到 End To End Compression,需要靠瀏覽器與伺服器之間的協商。 #### client 發起請求時標頭帶上 Accept-Encoding 需要給指定的壓縮方式,因為解壓縮的行為是 client 端自己負責 gzip, deflate, br 代表優先順序 ![Screen_Shot_2024-04-14_at_12.23.11_PM](https://hackmd.io/_uploads/SJDE61Fx0.png) #### server 根據 client 的要求壓縮 response body 並在 response header 帶入 content-encoding,告知 client 用哪個方式解壓縮 ``` Content-Encoding: br Vary: Accept-Encoding ``` *** 除了已壓縮過的檔案(jpeg、PNG),其他都建議可經由這種方式做壓縮優化。 ### 如何實作壓縮? 主要是 Server 端或者 Proxy 端去處理 - Server Side ```jsx // express 與 koa middleware const compression = require('compression') const express = require('express') const app = express() app.use(compression({ filter: shouldCompress })) function shouldCompress (req, res) { if (req.headers['x-no-compression']) { // don't compress responses with this request header return false } // fallback to standard filter function return compression.filter(req, res) } ``` - Reverse Proxy 有些開發者認為在Production 流量大的情況下,還要在 Server 端處理壓縮,會太耗效能,因此拉到 reverse proxy 實作 ``` // Nginx gzip server { ... gzip on; // 啟用 gzip 功能 gzip_types text/plain application/xml application/json; // 哪些檔案該被壓縮 gzip_comp_level 9; // 壓縮等級,gzip 為 1~9 gzip_min_length 1000; // 觸發壓縮的最小檔案,因為壓縮本來就小的文件沒有幫助,甚至耗能 gzip_proxied no-cache no-store private expired auth; // 根據標頭決定何種情況下代理要進行壓縮 ... } // Nginx brotli http { ... brotli on; brotli_comp_level 4; brotli_types text/plain text/css application/javascript application/json image/svg+xml; brotli_static on; // 對靜態文件進行壓縮 brotli_min_length 1000; brotli_buffers 4 16k; // 緩衝區大小 -> 4個緩衝區、每個緩衝區 16kb,越多緩衝區可以處理更多流量,但消耗記憶體 brotli_window 4m; // 壓縮的窗口大小 -> 窗口大小 4MB,大窗口提高壓縮率,但增加記憶體、影響壓縮速度 ... } ``` ##### 補充 End To End Compression 的要點與權衡 - 實行壓縮的代價:做壓縮需要計算 - 數據類型:不是所有的 data type 都適合做壓縮,尤其是已經壓縮過的檔案,再壓縮也可能沒有效果 - 前後端:確保壓縮與解壓縮的指定類型一致 - Server vs. Proxy 壓縮: ![Screen Shot 2024-04-14 at 2.10.54 PM](https://hackmd.io/_uploads/rkWeEgFeA.png) 隨著微服務( Micro Services )和容器架構( Docker )的普及,使用 Proxy 壓縮會是更常見的做法。