使用 workbox 协助构建 PWA 应用
1. 说明
workbox
是 GoogleChrome 团队推出的一套解决方案,提供站点离线访问能力,可以更方便、更简单的解决Service Workers
绝大多数问题
重要文件版本
workbox-build
‘3.0.0’
2. workbox 简单使用
workbox
是协助Service worker
使用的,与一般的Service worker
一样,首先需要在文件中注册Service worker
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker
.register('./sw.js')
.then(registration => {
console.log('SW registered: ', registration);
})
.catch(registrationError => {
console.log('SW registration failed: ', registrationError);
});
});
}
</script>
- 在注册的
Service worker
文件sw.js
中使用workbox
处理资源的缓存
// sw.js
// 引入核心文件
importScripts(
'https://storage.googleapis.com/workbox-cdn/releases/3.0.0/workbox-sw.js'
);
// 设置缓存名称前缀
workbox.core.setCacheNameDetails({ prefix: 'gulp-pwa-demo' });
/*
一下两个接口对应 service worker 中的
self.skipWaiting() 强制等待中的 Service Worker 被激活
self.clients.claim() 新的 Service Worker 被激活后使其立即获得页面控制权
*/
workbox.skipWaiting();
workbox.clientsClaim();
// 要 precache 预先缓存的文件
self.__precacheManifest = [
{
url: 'index.html',
revision: 'dbd40fec5ea49691d7c6c775192f66f5'
},
{
url: 'scripts/index.js',
revision: 'd7968e072cb4b90efcbaf72e94ba00c9'
},
{
url: 'styles/index.css',
revision: '9707c6dcc441f4704f5ede55bf2f7d55'
}
].concat(self.__precacheManifest || []);
workbox.precaching.suppressWarnings();
// precacheAndRoute 预缓存静态文件
workbox.precaching.precacheAndRoute(self.__precacheManifest, {});
/*
registerRoute 路由请求缓存
第一个参数可以是正则、完整路径字符串、或者是返回布尔值的函数
第二个参数 workbox.strategies.networkFirst() 是一个路由缓存策略,用来确定资源缓存方式
*/
workbox.routing.registerRoute(
new RegExp('.*.js'),
workbox.strategies.networkFirst()
);
路由缓存策略
staleWhileRevalidate
请求的路由有对应的 cache 缓存就直接返回,同时在后台再次发起请求并更新 CachenetworkFirst
请求后,首先尝试拿到网路请求的返回结果,请求到就直接返回并且更新 cache,否则返回缓存中的内容cacheFirst
请求后,直接从 Cache 中取得结果,没有的话在发起网络请求networkOnly
强制使用网络请求cacheOnly
强制使用 Cache 资源
*
3. 结合 gulp 使用 workbox
gulp 中使用 workbox 需要引入
workbox-build
sudo yarn add --dev workbox-build
在 gulpfile.js 中配置,全部的 workbox 配置都要写在
workbox.generateSW(options)
中,workbox-build
工具会根据这个配置生成对应的Service worker
文件,但是主文件中注册Service worker
还是需要手动写的,如果要实现‘添加到主屏幕功能’ 同样需要自己配置manifest.json
workbox.generateSW(options)
与workbox.InjectManifest(options)
方法都是用来生成Service worker
文件的,区别在于前者不需要模板文件,后者必须配置swSrc: 'sw.js'
指明模板来源,相当于在 模板Service worker
中插入了 workbox 的代码
const gulp = require('gulp');
const workbox = require('workbox-build');
/*
配置 PWA,API 与直接在 sw.js 中写是有区别的
*/
gulp.task('generate-service-worker', () => {
return workbox
.generateSW({
cacheId: 'gulp-pwa-demo', // 设置前缀
globDirectory: './build', //匹配根目录
globPatterns: ['**/*.{html,js,css,png.jpg}'], // 匹配的文件
globIgnores: ['sw.js'], // 忽略的文件
swDest: `./build/sw.js`, // 输出 Service worker 文件
clientsClaim: true, // Service Worker 被激活后使其立即获得页面控制权
skipWaiting: true, // 强制等待中的 Service Worker 被激活
runtimeCaching: [
// 配置路由请求缓存 对应 workbox.routing.registerRoute
{
urlPattern: /.*\.js/, // 匹配文件
handler: 'networkFirst' // 网络优先
},
{
urlPattern: /.*\.css/,
handler: 'staleWhileRevalidate', // 缓存优先同时后台更新
options: {
// 这里可以设置 cacheName 和添加插件
plugins: [
{
cacheableResponse: {
statuses: [0, 200]
}
}
]
}
},
{
urlPattern: /.*\.(?:png|jpg|jpeg|svg|gif)/,
handler: 'cacheFirst', // 缓存优先
options: {
plugins: [
{
expiration: {
maxAgeSeconds: 24 * 60 * 60, // 最长缓存时间,
maxEntries: 50 // 最大缓存图片数量
}
}
]
}
},
{
urlPattern: /.*\.html/,
handler: 'networkFirst'
}
]
})
.then(() => {
console.info('Service worker generation completed.');
})
.catch(error => {
console.warn('Service worker generation failed: ' + error);
});
});
// 压缩输出, 将 generate-service-worker 任务添加到末尾
gulp.task('build', cb => {
runSequence(
'clean-build',
'minify-html',
['minify-css', 'minify-js', 'move-libs-build'],
'minify-img',
'build-json',
'generate-service-worker',
cb
);
});
执行后生成的 service-worker 文件如下,与直接写 service-worker 是一样的
importScripts(
'https://storage.googleapis.com/workbox-cdn/releases/3.0.0/workbox-sw.js'
);
workbox.core.setCacheNameDetails({ prefix: 'gulp-pwa-demo' });
workbox.skipWaiting();
workbox.clientsClaim();
/**
* The workboxSW.precacheAndRoute() method efficiently caches and responds to
* requests for URLs in the manifest.
* See https://goo.gl/S9QRab
*/
self.__precacheManifest = [
{
url: 'index.html',
revision: 'ffed06b43693a980d15df9eb95171465'
},
{
url: 'libs/jquery-3.2.1.min.js',
revision: 'c9f5aeeca3ad37bf2aa006139b935f0a'
},
{
url: 'pages/aboutus.html',
revision: '1bd0eef5e0e1a30063fe89ba68860f24'
},
{
url: 'scripts/index.js',
revision: 'd7968e072cb4b90efcbaf72e94ba00c9'
},
{
url: 'scripts/registerServiceWorker.js',
revision: '998c0ec9841a4e1f12872b37e4223d61'
},
{
url: 'styles/aboutus.css',
revision: '96895554b989e2f194169fe7e4d2a8b5'
},
{
url: 'styles/index.css',
revision: '9707c6dcc441f4704f5ede55bf2f7d55'
}
].concat(self.__precacheManifest || []);
workbox.precaching.suppressWarnings();
workbox.precaching.precacheAndRoute(self.__precacheManifest, {});
workbox.routing.registerRoute(
/.*\.js/,
workbox.strategies.networkFirst(),
'GET'
);
workbox.routing.registerRoute(
/.*\.css/,
workbox.strategies.staleWhileRevalidate({
plugins: [{ cacheableResponse: { statuses: [0, 200] } }]
}),
'GET'
);
workbox.routing.registerRoute(
/.*\.(?:png|jpg|jpeg|svg|gif)/,
workbox.strategies.cacheFirst({
plugins: [{ expiration: { maxAgeSeconds: 86400, maxEntries: 50 } }]
}),
'GET'
);
workbox.routing.registerRoute(
/.*\.html/,
workbox.strategies.networkFirst(),
'GET'
);