我是怎樣讓網站用上HTML5 Manifest

人人網FED發表於2019-03-02

Manifest是用來做離線頁面的,即使斷網也能正常開啟頁面,用起來簡單,但是在實際使用中存在以下問題:

(1)如何自動快取所有的頁面的資源?因為manifest不能使用*萬用字元進行cache

(2)如果網站資源更新,怎麼讓manifest檔案自動更新?不然如果使用者不清快取即使聯網也會載入老頁面

我覺得很多網站沒有使用Manifest是因為上面提到的兩個原因,有些人有嘗試過,但使用起來比較麻煩,離線應用價值好像不太大。但是使用Manifest還是有很多好處的,特別是像部落格等之類的偏向於展示的網站,或者是線上APP,這種網站的資料動態變化頻率比較低,不需要頻繁地向服務請求資料。這樣當使用者需要頻繁退回首頁或者頻繁地在幾個頁面來回切換的時候,由於幾乎所有資源都在本地,所以載入起來是瞬時的。

1. 使用Manifest

使用Manifest很簡單,就是在html標籤上加一個manifest屬性:

<html manifest="/static/manifest/home.appcache">複製程式碼

這個屬性指向一個manifest的檔案,這個檔案指明瞭當前頁面哪些資源需要進行離線快取,如下home.appcache:

CACHE MANIFEST
#9/27/2017, 3:04:25 PM
#html
https://github.com/
#img
https://assets-cdn.github.com/images/modules/site/universe-octoshop.png
https://assets-cdn.github.com/images/modules/site/universe-wordmark.png
#css
https://assets-cdn.github.com/assets/frameworks-bedfc518345231565091.css
#js
https://assets-cdn.github.com/assets/compat-94eba6e3cd1fa18902d9.js
NETWORK:
*

FALLBACK
https://github.com/ /html/manifest/html/home.html複製程式碼

這個檔案第一行必須以CACHE MANIFEST開頭,否則瀏覽器解析會報錯,註釋使用#開頭,在這一行下面跟著需要快取的資源,接著的NETWORK表示哪些資源需要聯網載入,一般需要寫成NETWORK *,表示除了CACHE外的其它所有資源都需要聯網,包括一些動態請求,如果你不是寫的*,而是寫了具體路徑,那些既沒有在CAHCE的,也沒有在NETWORK的就會報載入失敗的錯誤,如下所示:

即使聯網也會這樣,所以一般寫成*。

FALLBACK表示替代資源,這些資源載入不到就替代載入哪些資源,如上面的檔案https://github.com訪問不了就使用一個靜態的html訪問:https://github.com/html/manifest/html/home.html。

開啟支援Manifest的網站,例如fed.renren.com,可以觀察到Chrome控制檯cache的過程:

然後再重新整理頁面,你會發現頁面幾乎所有資源都是在本地快取取的,如下圖所示:

並且你把網斷了,重新整理頁面,頁面依舊能夠正常載入出來。這個在Chrome/Firefox/Safari等瀏覽器均支援。

除了Manifest之外,還有另外一個快取的手段,就是設定HTTP報文頭的Cache-Control欄位進行快取,這個可以快取JS/CSS/圖片資源,但是如果你把HTML也快取了就會有一個問題,如果使用者不清除快取,即使你的頁面更新了,使用者仍然會載入老的頁面,直到快取設定Max-Age時間到了。所以用Manifest可以解決這個問題。

Manifest怎麼知道當前頁面資料更新了呢?只要把你把manifest檔案如上面的home.appcache更改一下就可以了,瀏覽器開啟頁面時都會去載入這個檔案,一旦發現這個檔案發生了變化下次重新整理的時候就會重新載入所有Cache的檔案,最簡單的可以把註釋裡的時間改成當前的時間就可以了:

#9/29/2017, 9:08:49 AM複製程式碼

所以當網站的資源發生更改就可以改變這個manifest的內容,進而聯網的瀏覽器就能進行更新。

使用Manifest需要注意以下問題:

(1)Manifest有大小限制,它其實也算本地儲存,本地儲存一般每個域有限制使用的空間,PC Chrome是5Mb,參考如下表格

Browser Application Cache (AppCache) Storage Limit
Safari Desktop (Mac & Win) Unlimited
Safari Mobile (iOS) 10 MB
Chrome Desktop (Mac & Win) 5 MB *
Chrome Mobile (Android) Unlimited **
Firefox 4 Beta Unlimited (with user prompt)
IE No idea. It sucks. ***

(2)Manifest檔案如home.appcahce不能跨域,如果跨域需要支援CORS

(3)Manifest Cache的資源不能跨域,同樣如果跨域該資源需要支援CORS,一般瀏覽器會自動處理

2. 解決Manifefst的自動生成和更新問題

由於Manifest不能使用萬用字元匹配資源,所以需要把要進行cache的資源一個個列出來,而網站的內容經常是動態更新的,所以這個就比較麻煩。為此筆者寫了一個自動生成manifest的NPM包generate-manifest,用起來非常簡單:

npm install -g generate-manifest
generate-manifest --url=https://github.com複製程式碼

它就會生成一個home.appchache的Manifest檔案,這個檔案包括頁面上的img/js/css的資源連結:

CACHE MANIFEST
#9/27/2017, 3:04:25 PM
#html
https://github.com/
#img
https://assets-cdn.github.com/images/modules/site/universe-octoshop.png
https://assets-cdn.github.com/images/modules/site/universe-wordmark.png
#css
https://assets-cdn.github.com/assets/frameworks-bedfc518345498ab3204d330c1727cde7e733526a09cd7df6867f6a231565091.css
#js
https://assets-cdn.github.com/assets/compat-91f98c37fc84eac24836eec2567e9912742094369a04c4eba6e3cd1fa18902d9.js
NETWORK:
*

FALLBACK
https://github.com/ /html/manifest/html/home.html複製程式碼

還可以支援其它引數定製,詳見:generate-manifest

這樣就解決了自動生成的問題,自動更新應該怎麼辦呢?

由於我是一個部落格網站,網站內容發生變化的地方主要有:1. 發表/更改部落格; 2. 使用者發表評論; 3. 網站的瀏覽量發生變化,第一個解決的方法寫了一個介面,只要發表部落格就調一下這個介面去生成一個新的manifest檔案:

https://fed.renren.com/refresh-manifest.php?link=https://fed.renren.com/2017/09/26/manifest/

然後就會調上面的generate-manifest的包,生成一個manifest.appcache的檔案,在html裡面是根據路徑的最後一個部分決定manifest的名字:

<?php
    $uri = "$_SERVER[REQUEST_URI]";
    $uriArray = explode("/", $uri);
    $uriName = count($uriArray) > 2 ? $uriArray[count($uriArray) - 2] : "home";
?>
<!DOCTYPE html>
<html <?php language_attributes(); ?> manifest=<?php echo "/html/manifest/appcache/$uriName.appcache"?>>複製程式碼

這個和生成的檔名一一對應。

第二個問題:使用者發表評論——在調發表介面那裡自動地調一下這個介面,需要注意的是這個介面需要防指令碼注入,不然比較危險,

第三個問題:閱讀量資料變化的——寫一個Linux定時任務,使用crontab新增一個定時任務,執行crontab -e新增:

0 3 * * * /home/fed/manifest/update-all.sh複製程式碼

上面的意思是每天3:00的時候跑一下update-all.sh這個指令碼,這個指令碼把所有頁面的更新命令都寫進去:

generate-manifest --url=https://fed.renren.com
generate-manifest --url=https://fed.renren.com/page/2/
generate-manifest --url=https://fed.renren.com/page/3/
#..其它...複製程式碼

第一點提到的發表文章,也會新增一行命令到這個指令碼里面。

由於閱讀量這個資料不是很重要,所以一天更新一次就好了。這樣可以讓使用者在同一天的操作有快取。如果第二天再來看的話就更新一下。

因此基本上就解決了自動更新的問題。

還有一個問題是,Manifest改了之後的第一次重新整理還是老的頁面,只有第二次重新整理的時候才是對的,所以我們希望改了manifest之後能夠一重新整理就是新的,而不是之前快取的那個,也不需要刷兩次。

那麼怎麼辦呢?Manifest有一個更新的事件,一旦manifest檔案有更新就會觸發這個事件,所以我們可以監聽這個事件,然後自動重新整理頁面讓頁面重新載入就可以了,如下程式碼:

function onUpdateReady() {
    window.location.reload(true);
}
window.applicationCache.addEventListener(`updateready`, onUpdateReady);
if(window.applicationCache.status === window.applicationCache.UPDATEREADY) {
    onUpdateReady();
}複製程式碼

綜上,我們很好地利用Manifest做了一個離線頁面應用,解決了自動生成和自動更新的問題。即使使用者沒有離線,第二次載入的資源都是在本地快取的,所以當使用者在幾個頁面來回切換的時候這個速度是很快的,如很多人可能會在主頁的列表和內容頁之間來回切換。

雖然Manifest已經被deprecated了,被Service Worker取代了,但是由於它的簡單易用以及相容性好,我們還是可以用一用。

相關閱讀:

  1. 為什麼要把網站升級到HTTPS
  2. 怎樣把網站升級到http/2

相關文章