Try   HackMD

前端效能優化 Day16 Polyfill-less Bundling Script & File Compression

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。

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 為例

    ​​​​// 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

程式碼長度對 bundle 的影響巨大

是否能不追求達到所有瀏覽器都能支援的程度? (不要理會過於舊的版本)

原文章 instagram 例子:IG開發團隊 將 transpile target 設為 ES2017 → 比 ES5 少了 5.7% 的 bundle size → modern browser page speed 提升 3%

那麼為了效能,要犧牲舊版?

可以在打包時分成兩個版本,再根據用戶瀏覽器版本決定載入哪個 bundle

<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 - 可以測試特定的 CSS or JS 功能是否能作用在瀏覽器
  • Polyfills
  • User-Agent Detection
  • Dynamic Imports - 搭配第一個 Specific Feature Detection → 當功能被支持時,才 import

Modernizr 簡單介紹
- 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

    Screen_Shot_2024-04-14_at_11.22.26_AM

    • 使用策略

      假設要做一個 Photo Gallery

      1. 基本:Gallery Layout 呈現圖片排版
      2. 進階:Modernizr 確認是否有 Grid、Animation 等功能 → 動態效果、非同步載入圖片,改善 UX
      3. 回調:若瀏覽器不支援功能,回到基本的排版,如 Flexbox,確保仍可使用

Estimator.dev

作者推薦的 Estimator 已不再維護,該官方建議使用 legacy-javascript 搭配 Lighthouse


File Compression

Screen_Shot_2024-04-14_at_12.04.23_PM

壓縮的種類

每一種文件類型基本上可以透過壓縮來節省空間,尤以媒體類型(音樂、影片)效果更為明顯。

圖片
  • 有損壓縮
    • 對原本數據進行修改,EX: jpeg
    • 壓縮程度可以控制 → 可接受的品質下,盡可能提高壓縮比率
  • 無損壓縮
    • 不對數據進行修改,EX: gif、 png

End To End Compression (= HTTP Request & Response with Compression)

Web 應用中對效能提升最有幫助的方法

由 client 端發起壓縮請求 → 在 server side 完成壓縮 → server side 回傳壓縮檔 → client 解壓縮得到原始檔案

Untitled 1

要做到 End To End Compression,需要靠瀏覽器與伺服器之間的協商。

client 發起請求時標頭帶上 Accept-Encoding

需要給指定的壓縮方式,因為解壓縮的行為是 client 端自己負責

gzip, deflate, br 代表優先順序

Screen_Shot_2024-04-14_at_12.23.11_PM

server 根據 client 的要求壓縮 response body

並在 response header 帶入 content-encoding,告知 client 用哪個方式解壓縮

Content-Encoding: br
Vary: Accept-Encoding

*** 除了已壓縮過的檔案(jpeg、PNG),其他都建議可經由這種方式做壓縮優化。

如何實作壓縮?

主要是 Server 端或者 Proxy 端去處理

  • Server Side

    ​​​​ // 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

    隨著微服務( Micro Services )和容器架構( Docker )的普及,使用 Proxy 壓縮會是更常見的做法。