使用 gzip 壓縮網路酬載並壓縮

本程式碼研究室將說明如何壓縮及壓縮下列應用程式的 JavaScript 套件,如何藉由縮減應用程式的要求大小來改善網頁效能。

應用程式螢幕截圖

測量

在開始新增最佳化作業之前,建議您先分析應用程式的目前狀態。

  • 如要預覽網站,請按下「查看應用程式」,然後按下「全螢幕」圖示 全螢幕

移除未使用的程式碼」程式碼研究室也介紹了這個應用程式,可讓您投票支持喜愛的小貓。🐈

現在,看看這個應用程式的大小:

  1. 按下 `Control+Shift+J 鍵 (在 Mac 上為 Command+Option+J 鍵) 開啟開發人員工具。
  2. 按一下 [網路] 分頁標籤。
  3. 勾選「停用快取」核取方塊。
  4. 重新載入應用程式。

「網路」面板中的原始組合大小

雖然「移除未使用的程式碼」程式碼研究室已有很多進度來縮減此套件大小,但 225 KB 仍相當龐大。

壓縮

請參考以下程式碼區塊。

function soNice() {
  let counter = 0;

  while (counter < 100) {
    console.log('nice');
    counter++;
  }
}

如果這個函式儲存在自己的檔案中,檔案大小約為 112 B (位元組)。

如果移除所有空白字元,產生的程式碼如下所示:

function soNice(){let counter=0;while(counter<100){console.log("nice");counter++;}}

檔案大小現在為 83 B 左右。如果減少變數名稱長度並修改某些運算式而遭到進一步破壞,最終的程式碼可能會如下所示:

function soNice(){for(let i=0;i<100;)console.log("nice"),i++}

檔案大小現在達到 62 B

每執行一個步驟,程式碼就會變得越來越難以閱讀。不過,瀏覽器的 JavaScript 引擎解讀這些方法時,都會以相同方式解讀。以這種方式模糊處理程式碼的好處,有助於縮減檔案大小。112 B 一開始還不算什麼,但仍然有 50% 的縮減!

在這個應用程式中,webpack 第 4 版可做為模組封裝器。您可以在 package.json 中查看特定版本。

"devDependencies": {
  //...
  "webpack": "^4.16.4",
  //...
}

在正式操作模式下,套件 4 已預設壓縮套件。這會使用 TerserTerserWebpackPlugin 外掛程式。Terser 是用於壓縮 JavaScript 程式碼的熱門工具。

如要大致瞭解壓縮程式碼的樣貌,請在開發人員工具的「Network」面板中點選 main.bundle.js。接著點選「Response」分頁標籤。

精簡回覆

最終形式的程式碼會顯示在回應內文中,形成經過壓縮且遭到破壞。如要確認套件在未壓縮的情況下可能的大小,請開啟 webpack.config.js 並更新 mode 設定。

module.exports = {
  mode: 'production',
  mode: 'none',
  //...

重新載入應用程式,然後透過開發人員工具的網路面板再次查看套件大小

套件大小為 767 KB

差異很大!😅

繼續操作前,請務必先還原這裡的變更。

module.exports = {
  mode: 'production',
  mode: 'none',
  //...

實際包含會壓縮應用程式程式碼的程序,具體取決於您使用的工具:

  • 如果使用 Webpack v4 或以上版本,則無須進行其他作業,因為在實際工作環境模式中會預設壓縮程式碼。👍
  • 如果您使用舊版的 Webpack,請安裝 TerserWebpackPlugin 並納入 Webpack 建構程序中。如需詳細說明,請參閱說明文件
  • 您也可以使用其他壓縮外掛程式,例如 BabelMinifyWebpackPluginClosureCompilerPlugin
  • 如果完全未使用模組套件,請將 Terser 做為 CLI 工具使用,或直接將其納入為依附元件。

壓縮

雖然「壓縮」一詞有時會用於說明壓縮過程中程式碼的縮減方式,但實際上並不會將這類程式碼壓縮。

「壓縮」通常是指使用資料壓縮演算法修改的程式碼。與最終會提供完全有效程式碼的壓縮不同,壓縮過的程式碼在使用之前需要「解壓縮」

透過每一次 HTTP 要求與回應,瀏覽器和網路伺服器都可新增「標頭」headers,以納入已擷取或接收資產的其他資訊。您可以在開發人員工具「Network」面板的「Headers」分頁中查看這項資訊,其中會顯示以下三種類型:

  • 「General」是指與整個要求/回應互動相關的一般標頭。
  • 回應標頭會顯示伺服器實際回應專用的標頭清單。
  • 「Request Headers」會顯示用戶端附加至要求的標頭清單。

請查看 Request Headers 中的 accept-encoding 標頭。

接受編碼標頭

瀏覽器會使用 accept-encoding 來指定支援的內容編碼格式或壓縮演算法。文字壓縮演算法有很多種,但這裡只有三種支援 HTTP 網路要求的壓縮 (和解壓縮) 支援:

  • Gzip (gzip):伺服器和用戶端互動最常用的壓縮格式。此 API 以 Deflate 演算法為基礎,且所有現有瀏覽器都支援此功能。
  • 延遲 (deflate):不常使用。
  • Brotli (br):一種新型的壓縮演算法,旨在進一步改善壓縮率,因此可加快頁面載入速度。大多數瀏覽器的最新版本支援這項功能。

本教學課程中的範例應用程式與「移除未使用的程式碼」程式碼研究室中完成的應用程式相同,差別只在於 Express 現已做為伺服器架構使用。在接下來幾個章節中,我們會介紹靜態和動態壓縮。

動態壓縮

「動態」壓縮是指在瀏覽器要求時即時壓縮素材資源。

優點

  • 您無需建立及更新已儲存的資產壓縮版本。
  • 即時壓縮功能特別適合用於動態產生的網頁。

缺點

  • 壓縮較高層級的檔案才能達到更好的壓縮率,所需時間較長。使用者會先等待素材資源壓縮,然後再由伺服器傳送,這可能會導致效能命中。

使用 Node/Express 動態壓縮

server.js 檔案負責設定代管應用程式的節點伺服器。

const express = require('express');

const app = express();

app.use(express.static('public'));

const listener = app.listen(process.env.PORT, function() {
  console.log('Your app is listening on port ' + listener.address().port);
});

目前,這只是匯入 express,並使用 express.static 中介軟體載入 public/ 目錄中所有靜態 HTML、JS 和 CSS 檔案 (這些檔案是由每個版本的 webpack 建立)。

為確保所有資產在收到要求時都會經過壓縮,可以使用壓縮中介軟體程式庫。請先在 package.json 中將其新增為 devDependency

"devDependencies": {
  //...
  "compression": "^1.7.3"
},

然後匯入伺服器檔案 server.js

const express = require('express');
const compression = require('compression');

然後將其新增為中介軟體,然後「再」安裝 express.static

//...

const app = express();

app.use(compression());

app.use(express.static('public'));

//...

現在,請重新載入應用程式,然後在「Network」面板中查看套件大小。

使用動態壓縮功能組合大小

從 225 KB 到 61.6 KB!在 Response Headers 中,content-encoding 標頭顯示伺服器正在傳送以 gzip 編碼的檔案。

內容編碼標頭

靜態壓縮

靜態壓縮的概念是預先壓縮並儲存資產。

優點

  • 我們不再關注高壓縮等級造成的延遲問題,您可以直接擷取檔案,不需要即時進行壓縮。

缺點

  • 資產需要在每次建構時進行壓縮。如果使用高壓縮程度,建構時間可能會大幅增加。

使用 Node/Express 和 webpack 進行靜態壓縮

靜態壓縮需要預先壓縮檔案,因此您可以在建構步驟中修改 webpack 設定,將素材資源壓縮。您可以使用 CompressionPlugin 達到此效果。

請先在 package.json 中將其新增為 devDependency

"devDependencies": {
  //...
  "compression-webpack-plugin": "^1.1.11"
},

如同任何其他 Webpack 外掛程式,將其匯入設定檔:webpack.config.js:

const path = require("path");

//...

const CompressionPlugin = require("compression-webpack-plugin");

並加入 plugins 陣列中:

module.exports = {
  //...
  plugins: [
    //...
    new CompressionPlugin()
  ]
}

根據預設,外掛程式會使用 gzip 壓縮建構檔案。請參閱說明文件,瞭解如何新增使用其他演算法的選項,或納入/排除特定檔案。

應用程式重新載入及重新建構時,系統會建立主要套件的壓縮版本。開啟 Glitch 主控台,查看節點伺服器提供的最終 public/ 目錄中的內容。

  • 按一下「工具」按鈕。
  • 按一下「Console」按鈕。
  • 在主控台中執行下列指令,將帳戶變更為 public 目錄,並查看當中的所有檔案:
cd public
ls

公開目錄中的最終輸出檔案

Bundle main.bundle.js.gz 的 gzip 壓縮版本現已儲存在此處。根據預設,CompressionPlugin 也會壓縮 index.html

接下來,您需要指示伺服器在系統要求原始 JS 版本時傳送這些 gzip 壓縮的檔案。您可以先在 server.js 中定義新路徑,再透過 express.static 提供檔案。

const express = require('express');
const app = express();

app.get('*.js', (req, res, next) => {
  req.url = req.url + '.gz';
  res.set('Content-Encoding', 'gzip');
  next();
});

app.use(express.static('public'));

//...

app.get 是用於指示伺服器如何回應特定端點的 GET 要求。然後,使用回呼函式來定義處理這項要求的方式。路線的運作方式如下:

  • '*.js' 指定為第一個引數,表示此方式適用於觸發擷取 JS 檔案的每個端點。
  • 在回呼中,.gz 會附加至要求的網址,Content-Encoding 回應標頭則設為 gzip
  • 最後,next() 可確保序列繼續進行接下來的任何回呼。

應用程式重新載入後,請再次查看 Network 面板。

透過靜態壓縮功能縮減套件大小

如同以往,大幅縮減套件大小!

結語

本程式碼研究室涵蓋壓縮及壓縮原始碼的程序。這兩種技術都成為目前許多可用工具的預設功能,因此請務必瞭解您的工具鍊是否是否支援這些工具,或者是否應該自行開始應用這兩項程序。