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