基本知识普及请参考
https://www.jianshu.com/p/623...
https://zhuanlan.zhihu.com/p/...css
下面简单介绍一下插件的使用
如下是我在项目中使用的配置
webpack.prod.conf.js中webpack
{ ... plugins: [ new OfflinePlugin({ responseStrategy: 'cache-first', // 缓存优先 AppCache: false, // 不启用appCache safeToUseOptionalCaches: true, // Removes warning for about `additional` section usage autoUpdate: true, // 自动更新 caches: { // webpack打包后须要换的文件正则匹配 main: [ '**/*.js', '**/*.css', /\.(png|jpe?g|gif|svg)(\?.*)?$/, /\.(woff2?|eot|ttf|otf)(\?.*)?$/ ], additional: [ ':externals:' ] }, externals: [], // 设置外部连接,例如配置http://hello.com/getuser,那么在请求这个接口的时候就会进行接口缓存 excludes: ['**/.*', '**/*.map', '**/*.gz', '**/manifest-last.json'], // 须要过滤的文件 ServiceWorker: { output: './static/sw.js', // 输出目录 publicPath: '/static/sw.js', // sw.js 加载路径 scope: '/', // 做用域(此处有坑) minify: true, // 开启压缩 events: true // 当sw状态改变时候发射对应事件 } }) ] }
在入口js文件中web
OfflinePluginRuntime.install({ // 监听sw事件,当更新ready的时候,调用applyUpdate以跳过等待,新的sw当即接替老的sw onUpdateReady: () => { console.log('SW Event:', 'onUpdateReady') OfflinePluginRuntime.applyUpdate() }, onUpdated: () => { console.log('SW Event:', 'onUpdated') window.swUpdate = true } })
首先介绍一下assets里面的三个属性:
main: [] 这里配置的是serviceWorker在install阶段须要缓存的文件清单,若是其中有一个失败了,那么整个serviceWorder就会安装失败,因此必须谨慎配置ajax
additional: [] 这里配置的文件清单会在serviceWorker activate的时候进行缓存,与main不同,若是这里的文件缓存失败,不会影响serviceWorker的正常安装。并且,在接下来页面的ajax异步请求中,还能进行缓存尝试json
optional: [] 这里配置的文件清单在serviceWorker安装激活阶段不会进行缓存,只有在监听到网络请求的时候才进行缓存。浏览器
刚才说到做用域的时候有坑,若是按照上面的文件配置,最后在网页中会提示,sw最大的做用域权限在/static下面,言外之意这么写是没法将sw的做用域设置在/根路径下面。
因此这边须要服务端在返回sw.js的时候手动设置Service-Worker-Allowed头字段,而且值设置为/,同时这个文件的缓存时间设为0,不然,当更新serviceWorker的时候,因为浏览器缓存了sw.js用户端这边的serviceWorker没法第一时间更新。缓存
最后来一张线上项目,在网速极慢的状况下也能实现秒开网络
-------------------追加--------------------扩展fetch事件
首先在配置文件里添加入口app
sw-entry.js异步
self.addEventListener('fetch', function (event) { function cachesMatch (request, cacheName) { return caches.match(request, { cacheName: cacheName }).then(function (response) { return response }) // Return void if error happened (cache not found) ['catch'](function () {}) } function cacheFirst(cacheUrl, CACHE_NAME) { var resource = cachesMatch(cacheUrl, CACHE_NAME).then(function (response) { if (response) { return response; } // Load and cache known assets var fetching = fetch(urlString).then(function (response) { if (!response.ok) { return response; } (function () { var responseClone = response.clone(); var storing = caches.open(CACHE_NAME).then(function (cache) { return cache.put(urlString, responseClone); }).then(function () { console.log('[SW]:', 'Cache asset: ' + urlString); }); event.waitUntil(storing); })(); return response; }); return fetching; }) return resource } function netWorkFirst(cacheUrl, CACHE_NAME) { var resource = fetch(cacheUrl).then(response => { if (response.ok) { var responseClone = response.clone() var storing = caches.open(CACHE_NAME).then(function (cache) { cache.put(cacheUrl, responseClone); }).then(function () { console.log('[SW]:', 'Cache asset: ' + cacheUrl); }); event.waitUntil(storing); return response; } // Throw to reach the code in the catch below throw new Error('Response is not ok'); }) ['catch'](function () { return cachesMatch(cacheUrl, CACHE_NAME); }); return resource } var url = new URL(event.request.url) url.hash = '' var pathname = url.pathname var urlString = url.toString() var cacheUrl = urlString var IS_KANO = /kano\.guahao\.cn/ var IS_STATIC = /\/static\// var IS_HOME = /^\/(e|u|n)\/(\d+)$/ var IS_EDITOR = /^\/editor(?!\.)/ var IS_PREVIEW = /^\/preview(?!\.)/ var CACHE_PREFIX = __wpo.name var CACHE_TAG = __wpo.version var CACHE_NAME = CACHE_PREFIX + ':' + CACHE_TAG var resource = undefined var isGET = event.request.method === 'GET' // 以缓存优先的形式缓存 kano 以及 static/* 静态资源 if ((cacheUrl.match(IS_KANO) || pathname.match(IS_STATIC)) && isGET) { resource = cacheFirst(cacheUrl, CACHE_NAME) event.respondWith(resource) } // 以网络优先的形式缓存 editor页面 preview页面和 production页面 if ((pathname.match(IS_HOME) || pathname.match(IS_EDITOR) || pathname.match(IS_PREVIEW)) && isGET) { resource = netWorkFirst(cacheUrl, CACHE_NAME) event.respondWith(resource) } })