Service Worker 和應用程式殼層模型

單一網頁網頁應用程式 (SPA) 的常見架構功能是提供應用程式全球功能所需的最低 HTML、CSS 和 JavaScript 組合。實際上,這通常是固定在所有網頁中的標題、導覽元素和其他常見的使用者介面元素。當 Service Worker 預先快取這個極少 UI 的 HTML 和相依資產時,這就是所謂的「應用程式殼層」

應用程式殼層的圖表。這就是一個網頁螢幕截圖,其中包含一個標題在頂端,底部則是內容區域。標題標示為「應用程式殼層」,底部則標示為「內容」。

應用程式殼層在可感知網頁應用程式的效能中扮演重要角色。這是最先載入的項目,因此也會是使用者在等待內容填入使用者介面時,最先看到的內容。

雖然應用程式殼層能快速載入 (前提是網路可以使用且至少較快過),此時服務 Worker 會預先快取應用程式殼層及其關聯資產,進而提供應用程式殼層模型,但有以下好處:

  • 回訪者值得信賴且一致。第一次造訪未安裝 Service Worker 的應用程式時,必須先從網路載入應用程式的標記及其相關資產,服務工作處理程序才能將這些項目放入快取中。不過,如果重複造訪,系統會從快取提取應用程式殼層,這表示系統可以立即載入與轉譯。
  • 在離線情況下提供可靠的功能。有時網際網路連線不穩定或完全無法使用,且「我們找不到該網站」畫面反而引發不良。應用程式殼層模型會使用快取中的應用程式殼層標記回應任何導覽要求,藉此解決這個問題。即使有人從從未造訪的網頁應用程式中造訪網址,應用程式殼層仍會從快取提供,並且可以填入實用的內容。

何時應使用應用程式殼層模型

如果您有常見的使用者介面元素,該元素不會隨路徑變更,但內容確實如此,應用程式殼層就能成為最合理的。大多數 SPA 都會使用最有效率的應用程式殼層模型。

如果這些說明就是您的專案,而您想新增 Service Worker 來提升可靠性和效能,應用程式殼層應執行以下動作:

  • 快速載入
  • 使用 Cache 執行個體的靜態資產。
  • 在網頁內容之外,加入標頭和側欄等常見介面元素。
  • 擷取及顯示網頁特定內容。
  • 你可以視情況快取動態內容供離線瀏覽。

應用程式殼層會透過 API 或 JavaScript 中封裝的內容,動態載入網頁特定內容。此外,如果應用程式殼層的標記有所變更,Service Worker 更新應該也要接收新的應用程式殼層並自動快取,因此也必須自行更新。

建構應用程式殼層

應用程式殼層應與內容獨立存在,但會提供將內容填入的基礎。理想情況下,應盡量精簡,但必須在初始下載時提供足夠的有意義的內容,讓使用者知道體驗速度很快。

適當的平衡取決於您的應用程式。Jake Archibald 訓練至 Thrill 應用程式的應用程式殼層包含具有重新整理按鈕的標頭,可從 Flickr 提取新內容。

Trained to Thrill 網頁應用程式在兩個不同狀態下的螢幕截圖。左側只有快取的應用程式殼層會顯示,其中未填入內容。右側的內容 (一些火車的圖片) 會以動態方式載入至應用程式殼層的內容區域。

每個專案都有不同的應用程式殼層標記,但以下是提供應用程式樣板的 index.html 檔案範例:

​​<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>
      Application Shell Example
    </title>
    <link rel="manifest" href="/manifest.json">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="stylesheet" type="text/css" href="styles/global.css">
  </head>
  <body>
    <header class="header">
      <!-- Application header -->
      <h1 class="header__title">Application Shell Example</h1>
    </header>

    <nav class="nav">
      <!-- Navigation items -->
    </nav>

    <main id="app">
      <!-- Where the application content populates -->
    </main>

    <div class="loader">
      <!-- Spinner/content placeholders -->
    </div>

    <!-- Critical application shell logic -->
    <script src="app.js"></script>

    <!-- Service worker registration script -->
    <script>
      if ('serviceWorker' in navigator) {
        // Register a service worker after the load event
        window.addEventListener('load', () => {
          navigator.serviceWorker.register('/sw.js');
        });
      }
    </script>
  </body>
</html>

不過,您為專案建構應用程式殼層時必須具備下列特性:

  • HTML 必須為個別使用者介面元素提供清楚獨立的區域。在上述範例中,這包括應用程式的標頭、導覽、主要內容區域,以及僅在載入內容時顯示的載入「旋轉圖示」空間。
  • 針對應用程式殼層載入的初始 JavaScript 和 CSS 應盡可能精簡,且只與應用程式殼層本身的功能相關,而非內容。這樣可確保應用程式盡快轉譯其殼層,並盡量減少主要執行緒的工作,直到內容顯示為止。
  • 用於註冊 Service Worker 的內嵌指令碼。

應用程式殼層建立完成後,您就可以建構 Service Worker 來快取該程式及其資產。

快取應用程式殼層

應用程式殼層和必要的資產是服務 Worker 在安裝時應立即預先快取的內容。假設應用程式殼層與上述範例類似,讓我們看看如何使用 workbox-build 以基本 Workbox 範例來完成此操作:

// build-sw.js
import {generateSW} from 'workbox-build';

// Where the generated service worker will be written to:
const swDest = './dist/sw.js';

generateSW({
  swDest,
  globDirectory: './dist',
  globPatterns: [
    // The necessary CSS and JS for the app shell
    '**/*.js',
    '**/*.css',
    // The app shell itself
    'shell.html'
  ],
  // All navigations for URLs not precached will use this HTML
  navigateFallback: 'shell.html'
}).then(({count, size}) => {
  console.log(`Generated ${swDest}, which precaches ${count} assets totaling ${size} bytes.`);
});

儲存在 build-sw.js 中的設定會匯入應用程式的 CSS 和 JavaScript,包括 shell.html 中的應用程式殼層標記檔案。該指令碼會透過節點執行,如下所示:

node build-sw.js

產生的 Service Worker 會寫入 ./dist/sw.js,並在完成後記錄以下訊息:

Generated ./dist/sw.js, which precaches 5 assets totaling 44375 bytes.

網頁載入時,Service Worker 會預先快取應用程式殼層標記及其依附元件:

Chrome 開發人員工具的「網路」面板螢幕截圖,顯示從網路下載的資產清單。Service Worker 預先快取的資產與資料列左側有齒輪的其他資產有所區別。Service Worker 會在安裝時預先快取多個 JavaScript 和 CSS 檔案。
Service Worker 會在安裝時預先快取應用程式殼層的依附元件。預先快取要求是最後兩列,要求旁邊的齒輪圖示則代表服務供應商處理了要求。

在幾乎任何工作流程中,都能預先快取應用程式殼層的 HTML、CSS 和 JavaScript,包括使用 Bundler 的專案。閱讀說明文件時,您將瞭解如何直接使用 Workbox 設定工具鍊,建構最適合專案的 Service Worker,無論專案是否為 SPA 都適用。

結語

結合應用程式殼層模型與 Service Worker 非常適合在離線快取中使用,特別是在將預先快取功能與網路優先,改回使用快取策略來處理標記或 API 回應時更是如此。這樣一來,使用者就能快速穩定地在回訪時算繪應用程式殼層,即使在離線狀態下也沒問題。