你的網頁性能優化的再好,若是網絡很差那也會致使網頁的體驗差。
離線應用是指經過離線緩存技術,讓資源在第一次被加載後緩存在本地,下次訪問它時就直接返回本地的文件,就算沒有網絡鏈接。javascript
離線應用有如下優勢:css
離線應用的核心是離線緩存技術,歷史上曾前後出現2種離線離線緩存技術,它們分別是:html
它經過攔截網絡請求實現離線緩存,比 AppCache 更加靈活。它也是構建 PWA 應用的關鍵技術之一。java
Service Workers 相比於 AppCache 來講更加靈活,由於它能夠經過 JavaScript 代碼去控制緩存的邏輯。
因爲第1種技術已經廢棄,本節只專一於講解如何用 Webpack 構建使用了 Service Workers 的網頁。webpack
Service Workers 是一個在瀏覽器後臺運行的腳本,它生命週期徹底獨立於網頁。它沒法直接訪問 DOM,但能夠經過 postMessage 接口發送消息來和 UI 進程通訊。
攔截網絡請求是 Service Workers 的一個重要功能,經過它能完成離線緩存、編輯響應、過濾響應等功能。git
想更深刻的瞭解 Service Workers,推薦閱讀文章服務工做線程:簡介。github
目前 Chrome、Firefox、Opera 都已經全面支持 Service Workers,但對於移動端瀏覽器就不太樂觀了,只有高版本的 Android 支持。
因爲 Service Workers 沒法經過注入 polyfill 去實現兼容,因此在你打算使用它前請先調查清楚你的網頁的運行場景。web
判斷瀏覽器是否支持 Service Workers 的最簡單的方法是經過如下代碼:chrome
// 若是 navigator 對象上存在 serviceWorker 對象,就表示支持 if (navigator.serviceWorker) { // 經過 navigator.serviceWorker 使用 }
要給網頁接入 Service Workers,須要在網頁加載後註冊一個描述 Service Workers 邏輯的腳本。
代碼以下:npm
if (navigator.serviceWorker) { window.addEventListener('DOMContentLoaded',function() { // 調用 serviceWorker.register 註冊,參數 /sw.js 爲腳本文件所在的 URL 路徑 navigator.serviceWorker.register('/sw.js'); }); }
一旦這個腳本文件被加載,Service Workers 的安裝就開始了。這個腳本被安裝到瀏覽器中後,就算用戶關閉了當前網頁,它仍會存在。
也就是說第一次打開該網頁時 Service Workers 的邏輯不會生效,由於腳本尚未被加載和註冊,可是之後再次打開該網頁時腳本里的邏輯將會生效。
在 Chrome 中能夠經過打開網址 chrome://inspect/#service-workers
來查看當前瀏覽器中全部註冊了的 Service Workers。
Service Workers 在註冊成功後會在其生命週期中派發出一些事件,經過監聽對應的事件在特色的時間節點上作一些事情。
在 Service Workers 腳本中,引入了新的關鍵字 self
表明當前的 Service Workers 實例。
在 Service Workers 安裝成功後會派發出 install
事件,須要在這個事件中執行緩存資源的邏輯,實現代碼以下:
// 當前緩存版本的惟一標識符,用當前時間代替 var cacheKey = new Date().toISOString(); // 須要被緩存的文件的 URL 列表 var cacheFileList = [ '/index.html', '/app.js', '/app.css' ]; // 監聽 install 事件 self.addEventListener('install', function (event) { // 等待全部資源緩存完成時,才能夠進行下一步 event.waitUntil( caches.open(cacheKey).then(function (cache) { // 要緩存的文件 URL 列表 return cache.addAll(cacheFileList); }) ); });
接下來須要監聽網絡請求事件去攔截請求,複用緩存,代碼以下:
self.addEventListener('fetch', function(event) { event.respondWith( // 去緩存中查詢對應的請求 caches.match(event.request).then(function(response) { // 若是命中本地緩存,就直接返回本地的資源 if (response) { return response; } // 不然就去用 fetch 下載資源 return fetch(event.request); } ) ); });
以上就實現了離線緩存。
線上的代碼有時須要更新和從新發布,若是這個文件被離線緩存了,那就須要 Service Workers 腳本中有對應的邏輯去更新緩存。
這能夠經過更新 Service Workers 腳本文件作到。
瀏覽器針對 Service Workers 有以下機制:
新 Service Workers 線程中的 activate 事件就是最佳的清理舊緩存的時間點,代碼以下:
// 當前緩存白名單,在新腳本的 install 事件裏將使用白名單裏的 key var cacheWhitelist = [cacheKey]; self.addEventListener('activate', function(event) { event.waitUntil( caches.keys().then(function(cacheNames) { return Promise.all( cacheNames.map(function(cacheName) { // 不在白名單的緩存所有清理掉 if (cacheWhitelist.indexOf(cacheName) === -1) { // 刪除緩存 return caches.delete(cacheName); } }) ); }) ); });
最終完整的代碼 Service Workers 腳本代碼以下:
// 當前緩存版本的惟一標識符,用當前時間代替 var cacheKey = new Date().toISOString(); // 當前緩存白名單,在新腳本的 install 事件裏將使用白名單裏的 key var cacheWhitelist = [cacheKey]; // 須要被緩存的文件的 URL 列表 var cacheFileList = [ '/index.html', 'app.js', 'app.css' ]; // 監聽 install 事件 self.addEventListener('install', function (event) { // 等待全部資源緩存完成時,才能夠進行下一步 event.waitUntil( caches.open(cacheKey).then(function (cache) { // 要緩存的文件 URL 列表 return cache.addAll(cacheFileList); }) ); }); // 攔截網絡請求 self.addEventListener('fetch', function (event) { event.respondWith( // 去緩存中查詢對應的請求 caches.match(event.request).then(function (response) { // 若是命中本地緩存,就直接返回本地的資源 if (response) { return response; } // 不然就去用 fetch 下載資源 return fetch(event.request); } ) ); }); // 新 Service Workers 線程取得控制權後,將會觸發其 activate 事件 self.addEventListener('activate', function (event) { event.waitUntil( caches.keys().then(function (cacheNames) { return Promise.all( cacheNames.map(function (cacheName) { // 不在白名單的緩存所有清理掉 if (cacheWhitelist.indexOf(cacheName) === -1) { // 刪除緩存 return caches.delete(cacheName); } }) ); }) ); });
用 Webpack 構建接入 Service Workers 的離線應用要解決的關鍵問題在於如何生成上面提到的 sw.js
文件,
而且sw.js
文件中的 cacheFileList
變量,表明須要被緩存文件的 URL 列表,須要根據輸出文件列表所對應的 URL 來決定,而不是像上面那樣寫成靜態值。
假如構建輸出的文件目錄結構爲:
├── app_4c3e186f.js ├── app_7cc98ad0.css └── index.html
那麼 sw.js
文件中 cacheFileList
的值應該是:
var cacheFileList = [ '/index.html', 'app_4c3e186f.js', 'app_7cc98ad0.css' ];
Webpack 沒有原生功能能完成以上要求,幸虧龐大的社區中已經有人爲咱們作好了一個插件 serviceworker-webpack-plugin 能夠方便的解決以上問題。
使用該插件後的 Webpack 配置以下:
const path = require('path'); const ExtractTextPlugin = require('extract-text-webpack-plugin'); const { WebPlugin } = require('web-webpack-plugin'); const ServiceWorkerWebpackPlugin = require('serviceworker-webpack-plugin'); module.exports = { entry: { app: './main.js'// Chunk app 的 JS 執行入口文件 }, output: { filename: '[name].js', publicPath: '', }, module: { rules: [ { test: /\.css/,// 增長對 CSS 文件的支持 // 提取出 Chunk 中的 CSS 代碼到單獨的文件中 use: ExtractTextPlugin.extract({ use: ['css-loader'] // 壓縮 CSS 代碼 }), }, ] }, plugins: [ // 一個 WebPlugin 對應一個 HTML 文件 new WebPlugin({ template: './template.html', // HTML 模版文件所在的文件路徑 filename: 'index.html' // 輸出的 HTML 的文件名稱 }), new ExtractTextPlugin({ filename: `[name].css`,// 給輸出的 CSS 文件名稱加上 Hash 值 }), new ServiceWorkerWebpackPlugin({ // 自定義的 sw.js 文件所在路徑 // ServiceWorkerWebpackPlugin 會把文件列表注入到生成的 sw.js 中 entry: path.join(__dirname, 'sw.js'), }), ], devServer: { // Service Workers 依賴 HTTPS,使用 DevServer 提供的 HTTPS 功能。 https: true, } };
以上配置有2點須要注意:
sw.js
,構建輸出的 sw.js
文件中會在頭部注入一個變量 serviceWorkerOption.assets
到全局,裏面存放着全部須要被緩存的文件的 URL 列表。須要修改上面的 sw.js
文件中寫成了靜態值的 cacheFileList
爲以下:
// 須要被緩存的文件的 URL 列表 var cacheFileList = global.serviceWorkerOption.assets;
以上已經完成全部文件的修改,在從新構建前,先安裝新引入的依賴:
npm i -D serviceworker-webpack-plugin webpack-dev-server
安裝成功後,在項目根目錄下執行 webpack-dev-server
命令後,DevServer 將以 HTTPS 模式啓動,並輸出以下日誌:
> webpack-dev-server Project is running at https://localhost:8080/ webpack output is served from / Hash: 402ee6ce5bffb16dffe2 Version: webpack 3.5.5 Time: 619ms Asset Size Chunks Chunk Names app.js 325 kB 0 [emitted] [big] app app.css 21 bytes 0 [emitted] app index.html 235 bytes [emitted] sw.js 4.86 kB [emitted]
用 Chrome 瀏覽器打開網址 https://localhost:8080/index.html
後,就能訪問接入了 Service Workers 離線緩存的頁面了。
爲了驗證 Service Workers 和緩存生效了,須要經過 Chrome 的開發者工具來查看。
經過打開開發者工具的 Application-Service Workers 一欄,就能看到當前頁面註冊的 Service Workers,正常的效果如圖:
經過打開開發者工具的 Application-Cache-Cache Storage 一欄,能看到當前頁面緩存的資源列表,正常的效果如圖:
爲了驗證網頁在離線時能訪問的能力,須要在開發者工具中的 Network 一欄中經過 Offline 選項禁用掉網絡,再刷新頁面能正常訪問,而且網絡請求的響應都來自 Service Workers,正常的效果如圖:
本實例 提供項目完整代碼