前言
-
頁面以最快的速度獲取到所有必須靜態資源,渲染飛快;
-
伺服器上靜態資源未更新時再次訪問不請求伺服器;
-
伺服器上靜態資源更新時請求伺服器最新資源,載入又飛快。
-
靜態資源載入速度
-
頁面渲染速度
先用一張圖來概括下本文中將會涉及到的內容。
常見快取型別
1、瀏覽器快取
可在w3c的官方文件中檢視所有HTTP Response Header欄位的定義,跟快取相關的主要有上圖中被圈出來的幾個:
- public:響應被快取,並且在多使用者間共享。
- private:預設值,響應只能夠作為私有的快取(e.g., 在一個瀏覽器中),不能再使用者間共享;
- no-cache:響應不會被快取,而是實時向伺服器端請求資源。
- max-age:數值,單位是秒,從請求時間開始到過期時間之間的秒數。基於請求時間(Date欄位)的相對時間間隔,而不是絕對過期時間;
-
Pragma: 只有一個用法Pragma: no-cache,它和Cache-Control:no-cache作用一模一樣。(Cache-Control: no-cache是http 1.1才提供的, 因此Pragma: no-cache可以使no-cache應用到http 1.0 和http 1.1。)
-
Expires:指定了在瀏覽器上緩衝儲存的頁距過期還有多少時間,等同Cache-control中的max-age的效果,如果同時存在,則被Cache-Control的max-age覆蓋。若把其值設定為0,則表示頁面立即過期。並且若此屬性在頁面當中被設定了多次,則取其最小值。
-
Date:生成訊息的具體時間和日期;
-
Last-Modified/If-Modified-Since:本地檔案在伺服器上的最後一次修改時間。快取過期時把瀏覽器端快取頁面的最後修改時間傳送到伺服器去,伺服器會把這個時間與伺服器上實際檔案的最後修改時間進行對比,如果時間一致,那麼返回304,客戶端就直接使用本地快取檔案。
-
Etag/If-None-Match:(EntityTags)是URL的tag,用來標示URL物件是否改變,一般為資源實體的雜湊值。和Last-Modified類似,如果伺服器驗證資源的ETag沒有改變(該資源沒有更新),將返回一個304狀態告訴客戶端使用本地快取檔案。Etag的優先順序高於Last-Modified,Etag主要為了解決 Last-Modified 無法解決的一些問題。
- 檔案也許會週期性的更改,但是他的內容並不改變,不希望客戶端重新get;
- If-Modified-Since能檢查到的粒度是s級;
- 某些伺服器不能精確的得到檔案的最後修改時間。
-
If-Modified-Since:值為之前response中Last-Modified;
-
If-None-Match:值為之前response中Etag(如果存在的話);
-
Cache-Control
-
Pragma
-
Expires
<meta http-equiv="Cache-Control" content="no-cache" /> <!-- HTTP 1.1 -->
<meta http-equiv="Pragma" content="no-cache" /> <!-- 相容HTTP1.0 -->
<meta http-equiv="Expires" content="0" /> <!-- 資源到期時間設為0 -->複製程式碼
Putting caching instructions into meta tags is not a good idea, because although browsers may read them, proxies won`t. For that reason, they are invalid and you should send caching instructions as real HTTP headers.
2、HTML5 Application Cache
a、增加manifest檔案
-
檔案的第一行必須是 CACHE MANIFEST
-
#開頭的行作為註釋語句
-
網站的快取不能超過5M
-
檔案資源路徑可以使用絕對路徑也可以使用相對路徑
-
檔案列表中任意一個快取失敗都會導致整個快取失效
-
既可以站點使用同一個minifest檔案,也可以每個頁面使用一個
-
CACHE:需要快取的資原始檔,瀏覽器會自動快取帶有manifest屬性的html頁面;
-
NETWORK:不需要快取的檔案,可以使用萬用字元;
-
FALLBACK:無法訪問快取檔案的備選檔案,可以使用萬用字元。
b、伺服器配置
AddType text/cache-manifest .appcache複製程式碼
c、html中引用
<html lang="zh" manifest="main.manifest">複製程式碼
1、事件
-
cached/checking/downloading/error/noupdate/obsolete/progress/updateready
2、執行過程
-
Creating Application Cache with manifest(訪問到帶manifest屬性的html檔案,將manifest檔案儲存,載入html檔案及其他資原始檔);
-
Application Cache Checking event(檢查要快取的檔案列表)
-
Application Cache Downloading event(開始下載快取檔案)
-
Application Cache Progress event (0 of 4)(依次下載快取檔案)
-
……
-
Application Cache Progress event (4 of 4)
-
Application Cache Cached event(檔案快取完畢)
-
Document was loaded from Application Cache with manifest(從快取中讀取html檔案和其他靜態資原始檔,供頁面展示)
-
Application Cache Checking event(獲取新的manifest檔案,檢查是否更新)
- 是:重新下載快取檔案,供下次訪問使用(不會影響當前瀏覽器展示內容)
- Application Cache Downloading event(開始下載快取檔案)
- Application Cache Progress event (0 of 4)(依次下載快取檔案)
- ……
- Application Cache Progress event (4 of 4)
- Application Cache UpdateReady event(快取檔案更新完畢)
- 否
- Application Cache NoUpdate event(啥也不做)
-
Document was loaded from Application Cache with manifest(從快取中讀取html檔案和其他靜態資原始檔,供頁面展示)
-
Application Cache Checking event(獲取新的manifest檔案,檢查是否更新)
-
Application Cache Obsolete event(刪除本地快取中的所有檔案,不再使用快取)
-
Application Cache會預設快取引用manifest檔案的HTML文件,對於動態更新的html頁面來說是個坑(可以使用tricky的iframe嵌入方式來避免);
-
只要快取列表中的一個資源載入失敗,所有檔案都將快取失敗;
-
如果資源沒有被快取,而又沒有設定NETWORK的情況下,將會無法載入,所以Network中必須使用萬用字元配置;
-
快取更新後第一次只能載入manifest檔案,其他靜態資源需要第二次載入才能看到最新效果;
-
快取檔案清單中的檔案本身更新瀏覽器是不會重新快取,那怎麼告訴瀏覽器快取需要更新了呢?
- 更新manifest檔案:修改註釋的版本號或者日期。
- 通過Application Cache提供的介面(window.applicationCache.swapCache)來檢查更新。
該特性已經從 Web 標準中刪除,雖然一些瀏覽器目前仍然支援它,但也許會在未來的某個時間停止支援,請儘量不要使用該特性。在此刻使用這裡描述的應用程式快取功能高度不鼓勵; 它正在處於從Web平臺中被刪除的過程。請改用Service Workers 代替。
3、PWA(Service Worker)
Service worker is a programmable network proxy, allowing you to control how network requests from your page are handled.
-
不能訪問 DOM
-
不能使用同步 API
-
需要HTTPS協議(http://localhost 或 http://127.0.0.1也可)
簡單使用
<script>
navigator.serviceWorker
.register(`./sw.js`)
.then(function (registration) {
// 註冊成功
});
</script>複製程式碼
self.addEventListener(`install`, function(event) {
/* 安裝後... */
// cache.addAll:把快取檔案加進來,如a.css,b.js
});
self.addEventListener(`activate`, function(event) {
/* 啟用後... */
// caches.delete :更新快取檔案
});
self.addEventListener(`fetch`, function(event) {
/* 請求資源後... */
// cache.put 攔截請求直接返回快取資料
});複製程式碼
index.html檔案中引用了static/js/main.js,main.js中註冊了service-worker.js。service-worker.js中我們可以看到有 precacheConfig(快取列表)和 cacheName(版本號)兩個變數。斷開網路,我們看到precacheConfig列表中的檔案仍能從本地載入。
更新機制
4、LocalStorage
5、CDN快取
更新機制
prebrowsing
預載入是瀏覽器對將來可能被使用資源的一種暗示,一些資源可以在當前頁面使用到,一些可能在將來的某些頁面中被使用。作為開發人員,我們比瀏覽器更加了解我們的應用,所以我們可以對我們的核心資源使用該技術。
-
dns-prefetch:DNS預解析,告訴瀏覽器未來我們可能從某個特定的 URL 獲取資源,當瀏覽器真正使用到該域中的某個資源時就可以儘快地完成 DNS 解析。多在使用第三方資源時使用。
-
preconnect:預連線,完成 DNS 預解析同時還將進行 TCP 握手和建立傳輸層協議。
-
prerender:預渲染,預先載入文件的所有資源,類似於在一個隱藏的 tab 頁中開啟了某個連結 – 將下載所有資源、建立 DOM 結構、完成頁面佈局、應用 CSS 樣式和執行 JavaScript 指令碼等。
-
prefetch:預獲取,使用 prefetch 宣告的資源是對瀏覽器的提示,暗示該資源可能『未來』會被用到,適用於對可能跳轉到的其他路由頁面進行資源快取。被 prefetch 的資源的載入時機由瀏覽器決定,一般來說優先順序較低,會在瀏覽器『空閒』時進行下載。
-
preload:預載入,主動通知瀏覽器獲取本頁的關鍵資源,只是預載入,載入資源後並不會執行;
prefetch & preload
相容性
優先順序
注:prebrowsing 好用但千萬不要亂用,除非你非常明確會載入要prebrowsing的檔案,不然會加重瀏覽器負擔適得其反。
應用
<Link prefetch href=`/`><a>Home</a></Link>
<Link prefetch href=`/features`> <a>Features</a></Link>
{ /* we imperatively prefetch on hover */ }
<Link href=`/about`>
<a onMouseEnter={() => { Router.prefetch(`/about`); console.log(`prefetching /about!`) }}>About</a>
</Link>
<Link href=`/contact`><a>Contact (<small>NO-PREFETCHING</small>)</a> </Link>複製程式碼
優化嘗試
1、HTML檔案
2、js/css/img檔案
現在一般都通過檔名進行版本控制。Webpack打包命名可根據檔案內容生成檔名的hash值,每次打包只有當內容改才重新生成hash值。此種情況之下,可以在HTTP Headers設定一個較大的快取時間,如max-age=2592000,儘量避免304請求和伺服器進行請求連線。
// js
output: {
path: config.build.assetsRoot,
filename: utils.assetsPath(`js/[name].[chunkhash].js`),
chunkFilename: utils.assetsPath(`js/[id].[chunkhash].js`),
}
// css
new ExtractTextPlugin({
filename: utils.assetsPath(`css/[name].[contenthash].css`),
}),複製程式碼
3、webfont
-
瀏覽器在DOMNode的CSS選擇器中發現@font-face時才會下載web fonts檔案,這個時候瀏覽器已經下載完成html/css/js檔案;
-
如果在瀏覽器發現需要載入font檔案之前就告訴瀏覽器下載font檔案,會加快檔案下載和頁面載入速度。
-
React + + Mobx + Webpack
-
React-Router 單頁 / bundle-loader動態載入 / 使用較大的webfont檔案
-
對靜態資原始檔進行如上的HTTP Headers快取配置;
-
所有的靜態資原始檔通過Service Worker進行快取控制和離線化載入,示範如上不再贅述;
動態載入的js
-
index.ef15ea073fbcadd2d690.js
-
static/js/0.1280b2229fe8e5582ec5.js
-
static/js/1.f3077ec7560cd38684db.js
-
static/js/2.39ecea8ad91ddda09dd0.js
-
static/js/3.d7ecc3abc72a136e8dc1.js
webpackConfig.plugins.push(new PreloadWebpackPlugin({
rel: `prefetch`,
}));複製程式碼
rel屬性還可以選擇preload / prefetch模式。打包出來是這樣:
訪問頁面可以看到,在不影響dom載入的情況下,瀏覽器預先載入了另外幾個後面將會用到的js,當切換到對應路由時,也會直接從快取取,不從伺服器請求資源。
css檔案
動態載入路由中css沒有單獨拆分而是在路由的js中,所以只能隨著js優化了。
webfont檔案
2、用外掛
- 安裝外掛
npm install fontpreload-webpack-plugin --save-dev複製程式碼
- 在webpack的config檔案的HtmlWebpackPlugin外掛之後增加:
const FontPreloadWebpackPlugin = require(`fontpreload-webpack-plugin`);複製程式碼
webpackConfig.plugins.push(new FontPreloadWebpackPlugin({
rel: `prefetch`,
fontNameList: [`fontawesome-webfont`],
crossorigin: true,
}));複製程式碼
3、打包效果