運用應用程式命令介面架構立即載入網頁應用程式

Addy Osmani
Addy Osmani
Matt Gaunt

應用程式殼層是運用最精簡的 HTML、CSS 及 JavaScript 來提供使用者介面。應用程式殼層應:

  • 快速載入
  • 可快取
  • 動態顯示內容

應用程式殼層是達成穩定良好效能的秘訣。就像是打包程式碼的組合一樣,就像是打包代碼的組合一樣,如果建構的是原生應用程式,您就會將其發布到應用程式商店。這麼做需要載入應用程式所需的負載,但可能不會有整個故事。它將 UI 留存於本機中,並透過 API 動態提取內容。

App Shell 的 HTML、JS 和 CSS 殼層以及 HTML 內容

背景

Alex Russell 的「漸進式網頁應用程式」一文說明瞭網頁應用程式該如何藉由使用和使用者同意聲明來「漸進式」變更,提供更類似原生應用程式的體驗,並支援離線支援、推播通知,以及新增至主畫面的功能。這主要取決於服務工作處理程序的功能與效能優勢,以及其快取能力。這樣一來,您就能專心速度,為網頁應用程式提供與原生應用程式相同的立即載入功能和定期更新。

為充分發揮這些功能的效益,我們需要採用新的網站思維模式:應用程式殼層架構

我們會深入瞭解如何使用服務工作站擴充的應用程式殼層架構建構應用程式。我們會探討用戶端和伺服器端的算繪作業,並分享今天可以試用的端對端範例。

為強調此點,以下範例顯示使用此架構的應用程式第一次載入。請注意,畫面底部會顯示「應用程式已可用於離線使用」浮動式訊息。如果日後殼層有更新,我們可以通知使用者更新新版本。

在開發人員工具中執行應用程式殼層的 Service Worker 圖片

Service Worker 又是什麼?

Service Worker 是會在背景執行的指令碼,與網頁有所不同。回應事件,包括從服務網頁發出的網路要求,以及從伺服器推送通知。某個 Service Worker 的生命週期太短。它會在收到事件時喚醒,且只在需要處理該事件時執行。

與一般瀏覽環境中的 JavaScript 相比,Service Worker 也具備一組有限的 API。這是同事適用的標準做法。Service Worker 無法存取 DOM,但可存取 Cache API 等內容,也能使用 Fetch API 發出網路要求。IndexedDB APIpostMessage() 也可以用來在 Service Worker 和其控制的頁面之間保留資料,以及傳送訊息。從伺服器傳送的推送事件可以叫用 Notification API,提高使用者參與度。

Service Worker 可以攔截來自網頁的網路要求 (在 Service Worker 上觸發擷取事件),並傳回從網路擷取的回應、從本機快取擷取,甚至透過程式輔助方式建構的回應。實際上,這是瀏覽器中程式化的 Proxy。關鍵在於無論回應來自何處,都會看起來與網頁類似,只是沒有任何服務工作人員參與。

如要進一步瞭解 Service Worker,請參閱 Service Worker 簡介

效能優點

Service Worker 非常適合用於離線快取,但可在重複造訪網站或應用程式時立即載入,大幅提升效能。您可以快取應用程式殼層,以便離線運作並透過 JavaScript 填入內容。

對重複造訪而言,即使內容最終來自該網路,也能在沒有網路的情況下在畫面上看到有意義的像素。可視為「立即」顯示工具列和資訊卡,然後「逐步」載入其他內容。

為了在實際裝置上測試這個架構,我們在 WebPageTest.org 上執行應用程式殼層範例,並於下方顯示結果。

測試 1: 使用 Chrome 開發人員版透過 Nexus 5 使用傳輸線測試

應用程式的第一次檢視畫面必須從網路擷取所有資源,且直到 1.2 秒才執行有意義的繪製作業。拜 Service Worker 快取技術所賜,我們的重複瀏覽體驗能在 0.5 秒內完整完成載入作業,讓系統呈現有意義的油漆效果。

傳輸線連接網頁測試繪畫圖

Test 2: 以 Nexus 5 搭配 Chrome 開發人員版執行 3G 測試

我們也可以透過速度稍慢的 3G 連線來測試範例。這次第一次造訪有意義的畫作需要 2.5 秒的時間。網頁需要 7.1 秒才能完全載入。透過服務工作處理程序快取,我們的重複巡訪便可達成有意義的繪製,而且在 0.8 秒內完全完成載入。

3G 連線的網頁測試繪畫圖

其他觀點則是類似的報導。在應用程式殼層中比較完成首次有意義的繪製所需的 3 秒

網頁測試中第一次檢視畫面的繪製時間軸

也就是從 Service Worker 快取載入相同網頁所需要的 0.9 秒。能為使用者省下超過 2 秒的時間。

網頁測試中重複檢視畫面的繪製時間軸

您的應用程式可使用應用程式殼層架構,以同樣可靠的效能為前提。

需要重新思考應用程式的結構嗎?

服務工作處理程序表示應用程式架構的一些細微變動。與其將所有的應用程式壓縮成 HTML 字串,不如採用 AJAX 樣式,將有助於提高效能。這就是殼層 (一律都會快取,且一律可在沒有網路的情況下啟動) 以及會定期重新整理並個別管理的內容。

這個分割畫面帶來的影響很大。首次造訪時,您可在伺服器上轉譯內容,並在用戶端安裝 Service Worker。之後造訪時,只需要要求資料即可。

漸進式增強呢?

雖然目前並非所有瀏覽器都支援 Service Worker,但應用程式內容殼層架構採用漸進式強化功能,確保所有人都能存取內容。以範例專案為例,

以下是在 Chrome、Firefox 夜間模式和 Safari 顯示的完整版本。左側是 Safari 版本,「沒有」Service Worker 會在伺服器上顯示內容。右側顯示的是由 Service Worker 技術提供的 Chrome 和 Firefox 夜間版本。

Safari、Chrome 和 Firefox 中載入的應用程式 Shell 圖片

這個架構何時適合使用?

對於動態應用程式和網站來說,應用程式殼層架構最為合適。如果你的網站規模較小,可能就不需要應用程式殼層,可以直接在 Service Worker oninstall 步驟中快取整個網站。請使用最適合您的專案的方法。許多 JavaScript 架構都鼓勵分割您的應用程式邏輯與內容,使此模式更直接地套用。

是否有任何正式版應用程式正在使用這個模式?

只要稍微調整整體應用程式的使用者介面,應用程式命令介面架構就能生效,且適用於大型網站,例如 Google 的 I/O 2015 漸進式網頁應用程式和 Google 收件匣。

正在載入 Google 收件匣的圖片。顯示使用 Service Worker 的收件匣畫面。

離線應用程式殼層是效能的主要優勢,在 Jake Archibald 的離線維基百科應用程式Flipkart Lite 的漸進式網頁應用程式中也表現優異。

Jake Archibald 的 Wikipedia Demo 螢幕截圖。

架構說明

最初載入體驗的目標,是要在使用者螢幕上盡快取得有意義的內容。

先載入並載入其他網頁

使用 App Shell 進行首次載入的圖表

一般而言,應用程式殼層架構會:

  • 優先執行初始載入,但允許 Service Worker 快取應用程式殼層,這樣重複造訪時便不需從網路重新擷取殼層。

  • 延遲載入或背景載入其他所有項目。建議您針對動態內容使用讀寫快取

  • 使用 Service Worker 工具,例如 sw-precache,例如確保快取和更新管理靜態內容的 Service Worker。(進一步瞭解 sw 友善快取)。

如要達成這個目標:

  • 伺服器會傳送可供用戶端轉譯的 HTML 內容,並使用遠程的 HTTP 快取到期標頭,將不支援 Service Worker 的瀏覽器納入考量。它會使用雜湊提供檔案名稱,以便在之後的應用程式生命週期中同時啟用「版本管理」和簡便的更新功能。

  • 頁面會在 <head> 文件的 <style> 標記中加入內嵌 CSS 樣式,協助您快速為應用程式殼層快速繪製內容。每個網頁都會以非同步方式載入目前檢視所需的 JavaScript。由於 CSS 無法非同步載入,因此我們可以使用 JavaScript 來請求樣式,因為其是非同步,而非剖析器驅動和同步。我們也可以利用 requestAnimationFrame(),避免在快取快速命中的情況下,導致樣式意外成為重要轉譯路徑的一部分。requestAnimationFrame() 會強制先繪製第一個影格,再載入樣式。另一種做法是使用 Filament Group 的 loadCSS 等專案,以非同步方式使用 JavaScript 要求 CSS。

  • Service Worker 會儲存應用程式殼層的快取項目,這樣一來,在重複造訪時,除非網路有可用的更新,否則殼層可以從 Service Worker 快取中完全載入。

內容應用程式命令介面

實用的實作方式

我們使用以下方式編寫完整的範例:應用程式殼層架構、基本 ES2015 JavaScript (用戶端),以及伺服器的 Express.js。當然,您可以自行選擇要停止針對用戶端或伺服器部分 (例如 PHP、Ruby、Python) 使用堆疊。

Service Worker 生命週期

針對應用程式殼層專案,我們使用 sw-precache 提供的下列服務工作站生命週期:

活動 動作
安裝 快取應用程式殼層和其他單一頁面應用程式資源。
啟用 清除舊的快取。
擷取 為網址建立單一網頁網頁應用程式,並運用快取收集資產和預先定義部分的部分。將網路用於其他要求。

伺服器位元

在這個架構中,伺服器端元件 (如本範例為 Express 語言) 必須能分別處理內容和呈現作業。內容可以新增到 HTML 版面配置,進而產生網頁的靜態轉譯,也可以單獨提供或動態載入。

具體來說,您的伺服器端設定可能會與我們用於試用版應用程式的不同。大部分的伺服器設定都能採用這種網頁應用程式模式,但確實需要重新建構應用程式。我們發現以下模型的功效良好:

應用程式命令介面架構圖表
  • 端點的定義是應用程式的三個部分:面向使用者的網址 (索引/萬用字元)、應用程式殼層 (服務工作處理程序) 和 HTML 部分。

  • 每個端點都有一個提取「控點」版面配置的控制器,接著可以提取處理常式部分和檢視畫面。簡單來說,部分畫面是複製到最終頁面的 HTML 區塊。 注意:執行進階資料同步處理作業的 JavaScript 架構,往往是輕鬆移植到應用程式命令架構的 JavaScript 架構。他們傾向使用資料繫結和同步功能,而非部分資料。

  • 使用者一開始會看到含有內容的靜態網頁。這個頁面會註冊 Service Worker (如果支援的話),此頁面會快取應用程式殼層及其所有必要內容 (CSS、JS 等)。

  • 應用程式命令介面將作為單頁網頁應用程式,在內容中使用 JavaScript 到 XHR。XHR 呼叫會對 /partials* 端點發出,然後傳回顯示該內容所需的一小段 HTML、CSS 和 JS。 注意:方法有很多,XHR 只是其中一種。某些應用程式會以內嵌方式 (可能會使用 JSON) 內嵌資料,以進行初始轉譯,因此在簡化 HTML 的情況下並不表示「靜態」。

  • 如果瀏覽器「不支援」Service Worker 支援功能,瀏覽器一律會提供備用體驗。在示範中,我們會改回使用基本的靜態伺服器端轉譯,但這只是眾多選項之一。Service Worker 部分提供新的機會,可以使用快取應用程式殼層提升單頁應用程式樣式應用程式的效能。

檔案版本管理

這時有個常見疑問,就是如何處理檔案版本管理與更新。僅適用於特定應用程式,可用選項如下:

  • 網路,否則請使用快取版本。

  • 僅限網路連線,且離線時會失敗。

  • 請快取舊版本並稍後再更新。

如果是應用程式殼層本身,則應採用快取優先的方法來設定 Service Worker。如未在快取應用程式殼層,代表未妥善採用架構。

工具

我們維護了許多不同的服務工作處理程序輔助程式庫,使得預先快取應用程式殼層或處理常見快取模式的程序變得更簡單。

Service Worker Library 網站的螢幕截圖

為應用程式殼層使用 sw-precache

使用 sw-precache 快取應用程式殼層應能處理檔案修訂版本、安裝/啟用問題,以及應用程式殼層擷取的情境。將 sw-precache 放入應用程式的建構程序中,並透過可設定的萬用字元來擷取靜態資源。您可以讓 sw-precache 運用快取優先的擷取處理常式,以安全且有效率的方式產生可管理快取的指令碼,而不需手動編寫您的 Service Worker 指令碼。

應用程式初次造訪觸發條件時,系統會預先快取完整的必要資源。這類似於從應用程式商店安裝原生應用程式。使用者返回應用程式時,系統只會下載更新過的資源。在我們的示範中,當新殼層可使用時,我們會通知使用者:「應用程式更新。請重新整理以使用新版本。」這個模式可有效讓使用者知道他們可以更新到最新版本。

使用 sw-toolbox 進行執行階段快取

根據資源,使用 sw-toolbox 進行執行階段快取,採用不同的策略:

  • cacheFirst 適用於映像檔,另外也包含已命名的快取,其自訂到期日政策為 N maxEntry。

  • networkFirst 或 API 要求最快 (視所需的內容更新間隔而定)。最快可能沒關係,但是如果有特定 API 動態饋給經常更新,請使用 networkFirst。

結論

應用程式殼層架構有許多優點,但只適用於部分應用程式類別。這個模型才剛起步,我們會評估這個架構的成果和整體成效效益。

在實驗中,我們利用用戶端與伺服器之間的範本共用,降低建立兩個應用程式層的作業。這可確保漸進式強化仍為核心功能。

如果您正在考慮在應用程式中使用 Service Worker,請參考相關架構,並評估這個架構是否適合您自己的專案。

得益於我們的審查人員:Jeff Posnick、Paul Lewis、Alex Russell、Seth Thompson、Rob Dodson、Tayor Savage 和 Joe Medley。