你的网页性能优化的再好,若是网络很差那也会致使网页的体验差。
离线应用是指经过离线缓存技术,让资源在第一次被加载后缓存在本地,下次访问它时就直接返回本地的文件,就算没有网络链接。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,正常的效果如图:
本实例 提供项目完整代码