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

阿迪 (Addy Osmani)
Addy Osmani
Matt Gaunt

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

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

應用程式殼層是穩定可靠效能的秘密。請將應用程式的殼層想像中,當建構原生應用程式時,您會將此套件發布到應用程式商店的程式碼組合。這是奠定基礎所需的負載,但或許不是整個故事。它會將您的 UI 保存在本機,並透過 API 動態提取內容。

將 HTML、JS 和 CSS 殼層與 HTML 內容的應用程式命令分離

背景

Alex Russell 的漸進式網頁應用程式文章,說明如何逐步調整網頁應用程式的使用者同意聲明和使用者同意聲明,藉此提供更符合原生應用程式需求的體驗,同時支援離線支援功能、推播通知,以及將網站加入主畫面的功能。依賴於服務工作處理程序及其快取能力的功能和效能優勢,如此一來,您可以專注速度,讓網頁應用程式擁有相同的即時載入和定期更新,如同您在原生應用程式中看到的樣子。

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

讓我們深入瞭解如何使用服務工作處理程序應用程式殼層架構來建構應用程式。我們將探討用戶端和伺服器端的轉譯功能,並分享您可以立即試用的端對端範例。

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

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

Service Worker 呢?

Service Worker 是指在背景中執行的指令碼,與網頁分開執行。可回應事件,包括伺服器發出的網路要求,以及來自您伺服器的推播通知。Service Worker 的生命週期較短。它會在收到事件時喚醒,並且僅在需要處理期間執行。

與在一般瀏覽環境中的 JavaScript 相比,Service 工作處理程序的 API 也有限。這是網路上的工作站標準。Service Worker 無法存取 DOM,但可以存取 Cache API 之類的內容,也可以使用 Fetch API 發出網路要求。IndexedDB APIpostMessage() 也可用於資料持續性,以及服務工作處理程序和其控制的頁面之間的訊息。從伺服器傳送的推送事件可叫用 Notification API,藉此提高使用者參與度。

Service Worker 可攔截從網頁發出的網路要求 (這會觸發 Service Worker 上的擷取事件),並傳回從網路擷取的回應,或從本機快取擷取,甚至利用程式建構的回應。這實際上是瀏覽器中的可程式 Proxy。簡單來說,無論回應來自何處,網頁都會視為沒有任何 Service Worker,

如要進一步瞭解 Service Worker,請參閱服務工作站簡介

效能優點

服務工作處理程序對於離線快取功能強大,不過也能以即時載入形式提供卓越的效能,讓您的網站或網頁應用程式重複造訪。您可以快取應用程式殼層以離線運作,並使用 JavaScript 填入內容。

當使用者回流時,這個做法可讓你在沒有網路的情況下在螢幕上取得實用的像素,即使你的內容最終來自該網路也一樣。這可視為「立即」顯示工具列和資訊卡,然後逐步載入其他內容。

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

實驗組 1: 使用 Chrome 開發人員版搭配 Nexus 5 傳輸線進行測試

應用程式的第一個檢視畫面必須從網路擷取所有資源,而且在 1.2 秒前都不能做出有意義的繪製。拜 Service Worker 快取所賜,我們下次造訪時會進行有意義的繪製,並在 0.5 秒內完全載入完畢。

連接線連線的網頁測試繪製圖表

Test 2: 使用 Chrome 開發人員版,在 Nexus 5 上進行 3G 測試

我們可以利用較慢的 3G 連線來測試範例。也就是首次造訪第一個有意義的畫作後,需要 2.5 秒的時間。網頁需要 7.1 秒才能完全載入。透過 Service Worker 快取功能,我們重複造訪時會進行有意義的繪製,並在 0.8 秒內完成載入。

3G 連線的網頁測試顯示圖

其他觀點述說的則是類似的故事。比較在應用程式殼層中完成首次有效繪製所需的時間 (3 秒):

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

服務工作站快取載入相同網頁所需的時間設為 0.9 秒。使用者需要儲存超過 2 秒的時間。

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

透過應用程式殼層架構,您的應用程式有機會以同樣可靠的方式提升效能。

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

使用服務工作處理程序就代表應用程式架構的些微改變。與其在 HTML 字串中壓縮應用程式的所有元素,是採用 AJAX 樣式的好方法。您可以在這裡儲存殼層 (一律經過快取,且一律可在沒有網路的情況下啟動),以及定期重新整理及管理的內容。

這種分割方式的影響相當大,使用者第一次造訪時,您可在伺服器上轉譯內容,並在用戶端安裝 Service Worker。後續造訪時只需要求資料。

先進的增強功能呢?

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

以下是在 Chrome、Firefox Nightly 和 Safari 中呈現的完整版本。畫面最左邊可看到內容在伺服器上轉譯的 Safari 版本,「沒有」Service Worker。右側則是採用 Service Worker 的 Chrome 和 Firefox 夜間版本。

在 Safari、Chrome 和 Firefox 中載入 Application Shell 的圖片

何時適合使用這個架構?

應用程式殼層架構最適合以動態方式運作的應用程式和網站。如果網站規模較小,且並非靜態,您可能不需要應用程式殼層,只需在 Service Worker 的 oninstall 步驟中快取整個網站。請使用最適合您的專案的方法。有些 JavaScript 架構已鼓勵將應用程式邏輯與內容分割,讓模式更直接地套用。

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

您只要對整體應用程式 UI 進行些許變更,就能使用應用程式殼層架構,而且在大規模的網站 (例如 Google 的 I/O 2015 漸進式網頁應用程式和 Google 的收件匣) 方面都十分出色。

正在載入 Google 收件匣的圖片。說明收件匣:使用 Service Worker。

Jake Archibald 的離線 Wikipedia 應用程式Flipkart Lite 漸進式網頁應用程式具有優異效能,成效卓越。

Jake Archibald 的維基百科示範螢幕截圖。

說明架構

而在首次載入體驗中,目標是盡快在使用者螢幕上呈現有意義的內容。

先載入及載入其他頁面

第一次使用應用程式命令載入的圖表

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

  • 優先執行初始載入,但讓服務工作處理程序快取應用程式殼層,因此重複造訪時,就不需要從網路重新擷取殼層。

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

  • 使用 Service Worker 工具 (例如 sw-precache),例如穩定快取及更新管理靜態內容的 Service Worker。(稍後進一步瞭解 sw-precache。)

為實現目標:

  • 伺服器會傳送 HTML 內容,用戶端可轉譯並使用未來的 HTTP 快取到期標頭,以涵蓋不支援 Service Worker 的瀏覽器。它會提供使用雜湊的檔案名稱,藉此啟用「版本管理」功能,並在日後的應用程式生命週期中易於更新。

  • Page(s) 會在文件 <head><style> 標記中加入內嵌 CSS 樣式,提供應用程式殼層的快速初次繪製程序。每個網頁會以非同步方式載入目前檢視區塊所需的 JavaScript。由於 CSS 無法非同步載入,因此我們可以使用 JavaScript 要求樣式,因為 JavaScript 是非同步的,而不是剖析器導向或同步。此外,我們也會善用 requestAnimationFrame(),避免發生在快速快取命中的情況下,導致樣式意外成為重要轉譯路徑的一部分。requestAnimationFrame() 會強制在載入樣式之前繪製第一個影格。另一種做法是使用 Filament Group 的 loadCSS 這類專案,使用 JavaScript 以非同步的方式要求 CSS。

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

內容應用程式命令介面

實際實作

我們根據應用程式殼層架構編寫了可正常運作的範例、適用於用戶端的 vanilla ES2015 JavaScript,以及伺服器專用的 Express.js。當然,您無法阻止在用戶端或伺服器部分 (例如 PHP、Ruby、Python) 使用自己的堆疊。

Service Worker 生命週期

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

活動 動作
安裝 快取應用程式殼層和其他單頁應用程式資源。
啟用 清除舊的快取。
擷取 為網址提供單一網頁應用程式,並使用快取處理資產和預先定義的部分項目。使用網路處理其他要求。

伺服器位元

在此架構中,伺服器端元件 (在本範例中為 Express 撰寫) 應可分別處理內容和呈現方式。你可以將內容新增至產生網頁靜態轉譯功能的 HTML 版面配置,或是單獨提供內容及動態載入。

值得一提的是,您的伺服器端設定可能與試用版應用程式所用的設定極大不同。雖然大多數伺服器設定確實需要重新建構架構,還是能滿足這類網頁應用程式的模式。我們發現下列模型的效果相當不錯:

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

  • 每個端點都有可以提取把手版面配置的控制器,反過來就能提取部分和檢視畫面。簡單來說,部分檢視畫面是複製至最終頁面的 HTML 片段。 注意:執行更進階資料同步處理的 JavaScript 架構,通常較易於轉移至應用程式殼層架構。這類 API 通常使用資料繫結和同步處理,而非部分資料。

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

  • 然後,應用程式命令介面將成為單一網頁網頁應用程式,使用 javascript 在特定網址的內容中使用 XHR。XHR 呼叫會傳送至 /partials* 端點,此端點會傳回顯示該內容所需的小型 HTML、CSS 和 JS 小區塊。注意:方法有很多種,XHR 只是其中一種。部分應用程式會內嵌資料 (可能是使用 JSON) 以進行初始轉譯,因此並非以「靜態」方式呈現的 HTML 感覺。

  • 如果瀏覽器「不支援」服務工作處理程序,則應一律使用備用服務。在示範中,我們會改回使用基本的靜態伺服器端轉譯,但這只是眾多選項之一。Service Worker 面向可讓您使用快取應用程式殼層,提升單頁應用程式樣式應用程式的效能。

檔案版本管理

這時有個疑問,就是如何處理檔案版本管理和更新方式。此功能適用於特定應用程式,選項如下:

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

  • 僅限網路並失敗。

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

如果是應用程式殼層本身,則應採用快取優先方法設定服務工作處理程序。如果您未快取應用程式殼層,表示您尚未正確採用架構。

工具

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

Service Worker Library Site on Web Fundamentals 的螢幕截圖

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

使用 sw-precache 快取應用程式殼層,應能處理檔案修訂版本、安裝/啟用問題,以及應用程式殼層擷取的情況。將 sw-precache 插入應用程式的建構程序,並使用可設定的萬用字元來選擇靜態資源。使用快取優先擷取處理常式,您不用手動編寫 Service Worker 指令碼,還能使用快取優先擷取處理常式,在安全且有效率的情況下管理快取。

初次造訪應用程式時,會預先快取完整的必要資源。做法與從應用程式商店安裝原生應用程式的做法類似。當使用者返回應用程式時,系統只會下載更新的資源。在示範中,如果有新的殼層會顯示「應用程式更新」,重新整理即可查看新版本。」這個模式旨在讓使用者瞭解可重新整理以取得最新版本,是相當不方便的做法。

使用 sw-toolbox 執行階段快取

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

  • cacheFirst,適用於映像檔,以及專屬的已命名快取,自訂到期政策為 N max 項目。

  • networkFirst 或以最快的 API 要求為依據,視所需內容更新間隔而定。最快可能沒有問題,但如果特定 API 動態饋給會經常更新,請使用 networkFirst。

結語

應用程式殼層架構有許多優點,但只適用於某些應用程式類別。這個模型仍處於年輕階段,因此值得評估這個架構的努力和整體成效優勢。

我們的實驗利用在用戶端和伺服器之間共用範本的功能,將建構兩個應用程式層的工作降到最低。以確保漸進式強化功能仍是核心功能。

如果您已經考慮在自家應用程式中使用 Service Worker,請查閱相關架構,並評估自己的專案是否適合使用服務工作處理程序。

感謝我們的審查人員:Jeff Posnick、Paul Lewis、Alex Russell、Seth Thompson、Rob Dodson、Taylor Savage 和 Joe Medley。