Service Worker 和應用程式殼層模型

單頁網頁應用程式 (SPA) 的常見架構功能是一組 HTML、CSS 和 JavaScript 來強化應用程式的全域功能。實際上,這通常是常駐所有頁面的標頭、導覽和其他常見的使用者介面元素。當服務工作處理程序預先快取這個最小 UI 的 HTML 和相依素材資源時,我們將此稱為應用程式殼層

應用程式殼層的圖表。這是一個網頁的螢幕截圖,頂端有標頭,底部有一個內容區域。標頭標示為「Application Shell」,底部標示為「Content」。

對 Web 應用程式的效能來說,應用程式殼層扮演著重要的角色。這是載入的第一個項目,因此使用者在等待內容填入使用者介面時,也是第一眼看到的資訊。

應用程式殼層雖然很快就能載入 (前提是網路可用,且至少速度快),也就是預先載入應用程式殼層及其相關聯資產的服務工作站,為應用程式殼層模型帶來下列附加好處:

  • 回訪者提供穩定一致的成效。第一次造訪未安裝 Service Worker 的應用程式時,應用程式的標記及相關資產必須先從網路載入,服務工作處理程序才能將其放入快取。不過,重複造訪會從快取提取應用程式殼層,這表示可立即執行載入和轉譯作業。
  • 在離線情境中穩定使用功能。有時候,上網不穩或完全找不到,而且就會有心頭「我們找不到該網站」及螢幕前。應用程式殼層模型會使用快取的應用程式殼層標記回應任何導覽要求,以解決此問題。即使有人造訪網頁應用程式中的某個網址,而他們從未使用過,應用程式殼層仍將透過快取提供,而且可以填入實用的內容。

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

當您的常見使用者介面元素沒有從路徑改為路徑,但內容確實有變化時,應用程式殼層最合理。大多數 SPA 都可能使用實際上有效的應用程式 shell 模型。

如果您的專案符合上述情況,而您想新增 Service Worker 以提高穩定性和效能,應用程式殼層應該:

  • 快速載入
  • 使用 Cache 執行個體的靜態資產。
  • 在頁面內容中加入標頭和側欄等常見的介面元素。
  • 擷取並顯示網頁專屬內容。
  • 如有需要,您也可以快取動態內容以供離線檢視。

應用程式命令介面會透過 API 或 JavaScript 封裝的內容,以動態方式載入頁面專屬內容。應用程式 shell 的標記變更時,也應該自行更新:當應用程式 shell 的標記有所變更時,服務工作處理程序更新應能取得新的應用程式 shell 並自動快取。

建構應用程式殼層

應用程式殼層應與內容分開,同時提供填入內容的基礎。最好盡可能精簡,但在初始下載時加入足夠的有意義內容,讓使用者能瞭解體驗很快就能載入。

至於能否取得適當平衡,則因您的應用程式而異。Jake Archibald 的「Trained To 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 來快取應用程式及其資產。

快取應用程式殼層

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

// 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 執行,如下所示:

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 回應。這個做法可帶來快速可靠的體驗,還能在重複造訪時立即轉譯您的應用程式殼層,即使在離線狀態下也沒問題。