引子
我們現在使用的web技術更多是傾向于兼容老瀏覽器,沒有考慮新的API的使用。例如fetch,IndexedDB等,只能等到瀏覽器份額超出一大半,才會考慮到使用。換句話說,瀏覽器還有很多潛力有待我們開發。webapp和nativeapp之間的差距除了頁面交互效果外,最大的不同點就是首屏加載的等待中白屏。白屏的時間差一直是我們需要面對的一個問題,可以用以下方法解決:
- skeleton
- SSR
- service worker
skeleton是骨架屏幕下文有介紹。SSR不在這里贅述。
PWA全稱為“Progressive Web Apps”,漸進式網頁應用。它的核心技術包括:
- Web App Manifest – 在主屏幕添加app圖標,定義手機標題欄顏色之類
- Service Worker – 緩存,離線開發,以及地理位置信息處理等
- App Shell – 先顯示APP的主結構,再填充主數據,更快顯示更好體驗
- Push Notification – 消息推送,之前有寫過“簡單了解HTML5中的Web Notification桌面通知”
成功案例
國外有非常多pwa提高轉換率的案例。同樣,國內大廠也紛紛試水。它們無一例外都采用了App Shell模型。
App Shell模型
PWA的原理就是改變HTTP緩存的機制,優先取本地的資源,在下一次加載才會采用新的內容。這時候谷歌提出一個新的概念----App Shell。不是所有內容都要進行離線緩存,App Shell更像是app的空殼。殼將會進行離線緩存,這些“條條框框”是不需要每次都修改的,這個“空殼”僅包含頁面框架所需的最基本的 HTML 片段,CSS 和 javaScript,而對實時性要求比較高的內容將不會進行進行額外的緩存,例如列表。
這樣部分離線渲染的情況被稱之為App Shell模型,以便它可以在離線時正常展現,達到類似 Native App的體驗。stale-while-revalidate
會成為一個非常重要的概念,SWR是優先于本地緩存數據,再發送請求,最后在替換為新數據。
SWR first returns the data from cache (stale), then sends the fetch request (revalidate), and finally comes with the up-to-date data again.
開發一個 App Shell, 我們通常要注意以下幾點:
- 將動態內容與 Shell 分離
- 盡可能使用接口緩存數據,實現快速首屏加載
- 使用本地緩存中的靜態資源
明確以上內容之后,我們就可以著手開發、定制自己的 App Shell 。就下圖這個例子而言,我們可以將頭部組件以及標簽欄組件寫入頁面,對html進行stale-while-revalidate
的緩存策略,并且對標簽欄的接口數據進行離線緩存,那么頭部組件就成為一個離線加載的App Shell。下面的內容由于沒有進行離線處理,則為實時接口,保證了新聞內容的實時性。
這樣說來,兩個點成為App Shell的關鍵。
- 動態部分(空白部分)使用Skeleton進行填充
- 靜態部分需要接口緩存數據填充
Skeleton
Skeleton也被稱為骨架頁面,在頁面的空白處插入html的圖像,減緩視覺差距。頁面在數據尚未加載前先給用戶展示出頁面的大致結構,直到請求數據返回后再渲染頁面,補充進需要顯示的數據內容,常用在單頁面應用的列表頁。
骨架圖的制作也有很多中方法。
- UI提供樣式生成圖片
- UI提供樣式繪制對應的樣式
- 餓了么團隊page-skeleton-webpack-plugin
- 百度lavasvue-skeleton-webpack-plugin
我個人建議移動端使用圖片,PC端寫樣式,比較簡單處理。由于圖片只有兩種分色,所以圖片大小不大。
接口緩存
后續的文章會介紹怎么使用workbox進行接口緩存。
接口緩存同樣可以做到優先讀本地數據再請求遠端數據。sw-toolbox是針對動態/運行時請求的離線緩存的工具,它已經Deprecated。后續,workbox已經把sw-precache和sw-toolbox進行了整合為一個平臺,提供大量插件。precache
是默認讀取本地文件,runtimeCaching
則是提供動態緩存的功能,動態緩存分為五種情況:
- networkFirst 網絡優先
- cacheFirst 緩存優先
- fastest 緩存優先和網絡同時執行,取最快
- cacheOnly 只取緩存
- networkOnly 只取網絡
配置文件設置runtimeCaching,可以攔截所有/api/
的接口,按照設置的情況進行緩存。cacheFirst
可以大大提高首屏App shell數據加載的效率。
runtimeCaching: [{
urlPattern: /api/,
handler: 'fastest',
options: {
cache: {
name: 'my-api-cache',
maxEntries: 5,
maxAgeSeconds: 60,
},
},
}],