你的網頁性能優化的再好,若是網絡很差那也會致使網頁的體驗差。 離線應用是指經過離線緩存技術,讓資源在第一次被加載後緩存在本地,下次訪問它時就直接返回本地的文件,就算沒有網絡鏈接。javascript
離線應用有如下優勢:css
離線應用的核心是離線緩存技術,歷史上曾前後出現2種離線離線緩存技術,它們分別是:html
Service Workers 相比於 AppCache 來講更加靈活,由於它能夠經過 JavaScript 代碼去控制緩存的邏輯。 因爲第1種技術已經廢棄,本節只專一於講解如何用 Webpack 構建使用了 Service Workers 的網頁。java
Service Workers 是一個在瀏覽器後臺運行的腳本,它生命週期徹底獨立於網頁。它沒法直接訪問 DOM,但能夠經過 postMessage 接口發送消息來和 UI 進程通訊。 攔截網絡請求是 Service Workers 的一個重要功能,經過它能完成離線緩存、編輯響應、過濾響應等功能。webpack
想更深刻的瞭解 Service Workers,推薦閱讀文章服務工做線程:簡介。git
目前 Chrome、Firefox、Opera 都已經全面支持 Service Workers,但對於移動端瀏覽器就不太樂觀了,只有高版本的 Android 支持。 因爲 Service Workers 沒法經過注入 polyfill 去實現兼容,因此在你打算使用它前請先調查清楚你的網頁的運行場景。github
判斷瀏覽器是否支持 Service Workers 的最簡單的方法是經過如下代碼:web
// 若是 navigator 對象上存在 serviceWorker 對象,就表示支持
if (navigator.serviceWorker) {
// 經過 navigator.serviceWorker 使用
}
複製代碼
要給網頁接入 Service Workers,須要在網頁加載後註冊一個描述 Service Workers 邏輯的腳本。 代碼以下:chrome
if (navigator.serviceWorker) {
window.addEventListener('DOMContentLoaded',function() {
// 調用 serviceWorker.register 註冊,參數 /sw.js 爲腳本文件所在的 URL 路徑
navigator.serviceWorker.register('/sw.js');
});
}
複製代碼
一旦這個腳本文件被加載,Service Workers 的安裝就開始了。這個腳本被安裝到瀏覽器中後,就算用戶關閉了當前網頁,它仍會存在。 也就是說第一次打開該網頁時 Service Workers 的邏輯不會生效,由於腳本尚未被加載和註冊,可是之後再次打開該網頁時腳本里的邏輯將會生效。npm
在 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,正常的效果如圖:
本實例提供項目完整代碼