使用 service-worker 加强缓存及改进部署

缓存策略

workbox 是 google 出的关于 service worker 生成预缓存列表,缓存策略,Background API 的一个库,综合了自家之前 sw-toolbox 以及 sw-precache 的功能。javascript

workbox 介绍了几种缓存策略,workbox-strategieshtml

Service Worker Cookbook 也对这几种缓存策略作了介绍,caching-strategies前端

可是关于这些策略的原理以及如何使用,强烈推荐谷歌开发者文档中的 离线指南java

使用缓存,咱们都会关心浏览器会提供多达的存储空间,如下代码能够查看你的应用已使用了多少存储空间以及有多大的配额react

navigator.storage.estimate().then(info => console.log(info.quota, info.usage))

另外也能够在 chrome 的 devtool 中进行查看,Application -> clear storage -> usagewebpack

Service Worker in React

若是项目采用 create-react-app 脚手架搭建,内置了 sw-precache-webpack-plugin 这个离线化插件,因而就对它作了一些适配。它是基于 google 的 sw-precache 的一个插件。git

若是大家项目没有使用 create-react-app,建议使用 workbox 的 webpack Plugin,workbox 也是 google 新出的关于 service-worker 的工具。github

若是大家的静态资源不在 CDN 上,Create React APP 已帮你写好了 webpack 的配置。web

若是静态资源在 CDN 上,就要略微折腾一番了。chrome

静态资源在 CDN 上

/index.html/sw.js 须要在同域下,引用 /sw.js 时须要注意去掉 PUBLIC_PATH (webpackConfig.output.publicPath) 的前缀。

另外 sw-precache-webpack-plugin 生成 preCache 列表时,也会对 /index.html 添加上 PUBLIC_PATH 的前缀,须要替换掉,配置以下。其中 paths.appBuild 为 webpackConfig.output.path

{
  ...config,
  mergeStaticsConfig: true,
  stripPrefixMulti: {
    [`${paths.appBuild}/index.html`]: '/index.html'
  }
}

如下是对于为什么如此操做的源码分析

关于 stripPrefixMulti ,能够查看 sw-precache 的文档,sw-precache#stripprefixmulti-object。主要是处理 precache 文件的前缀的,如如下 源码

// https://github.com/GoogleChromeLabs/sw-precache/blob/5.2.1/lib/sw-precache.js#L170
var relativeUrl = fileAndSizeAndHash.file
  .replace(
    new RegExp('^(' + Object.keys(params.stripPrefixMulti)
        .map(escapeRegExp).join('|') + ')'),
    function(match) {
      return params.stripPrefixMulti[match];
    })
  .replace(path.sep, '/');

能够看出来它用来替换特定前缀。

sw-precache-webpack-plugin 中已经对它作了一些处理,查看 源码

// https://github.com/goldhand/sw-precache-webpack-plugin/blob/v0.11.5/src/index.js#L119
if (outputPath) {
  // strip the webpack config's output.path (replace for windows users)
  stripPrefixMulti[`${outputPath}${path.sep}`.replace(/\\/g, '/')] = publicPath;
}

它把 precache 文件列表的前缀所有替换为了 publicPath (即 webpackConfig.output.publicPath),可是 /index.html 不能在 cdn 的路径上,因此须要特殊配置一下。

stripPrefixMulti: {
  [`${paths.appBuild}/index.html`]: '/index.html'
}

根据正则的短路原则,恰好能够把 index.html 给替换回来。

'hello, world'.replace(/(hello, world)|(hello)/, 'shanyue')    // shanyue

动态缓存 API

对于静态资源,采起了全部静态资源添加hash,除部署后第一次外均不需再访问服务器。

若是这里采用 workbox 的术语的话,那么静态资源则是采用了 Cache-First 的策略,当缓存不可取时才回退到网络,而对于动态 API,则采用 Network-First 的策略,只有在离线状态下才使用缓存。

固然,若是你只想使用 service worker 作缓存控制的话,API 缓存就能够跳过了。

如下代码是 sw-precache-webpack-plugin 的配置,动态缓存利用了 google 的 sw-toolbox 工具,它提供了如 workbox 同样的缓存策略。

{
  runtimeCaching: [{
    urlPattern: /api/,
    handler: 'networkFirst'
  }]
}

缓存 GraphQL query

GraphQL 的 query 是使用 http 的 POST 请求进行发送的,而 service worker 不支持对 POST 请求进行缓存。

Replaying POST requests by w3c@ServiceWorker

其实一想很正常,POST 是非幂等的,连 http 也不对它进行缓存。

GraphQL 的 query 支持 GET 请求,修改成 GET 是可行的。另一种方案是使用 apollo-cache-persist 对访问过的数据进行持久化。

部署

关于前端项目在生产环境中部署的问题是一个比较工程化的问题,关于具体实现方案简单来讲是以下两点

  1. 先部署资源,每次部署对静态资源添加 hash 到文件名中,带有hash的资源添加超长时间缓存(Cache-Control: public, max-age=31536000),不带hash的资源配置 Etag,并配置 Cache-Control: no-cache
  2. 再部署页面,并配置 Cache-Control: no-cache

能够参考如下两篇文章

可是有了 Service Worker 以后有以下几个好处

  1. 部署资源与部署页面顺序能够不严格控制 (仍是严格控制比较好一些)
    假设先部署页面,后部署资源,用户进入了新页面,可是资源没有更新,这时候 Service Worker 会在 install 事件中,因为没法找到新的资源而致使 install 失败,资源进行回退。
  2. 节省带宽
    之前用户每次访问页面,须要向服务器请求 /index.html 与一切不带 hash 的资源,如今全部的资源都被 sw-toolbox 或者 workbox 加上了指纹,每次只须要请求 /sw.js。

注意要对 /sw.js 设置 Cache-Control: no-cache

对比 http 缓存

http 的缓存策略虽然是把静态资源缓存在浏览器中,可是缓存行为的控制倒是在服务端的 - 如 http response 中的 Cache-Control。而 service worker 对缓存资源的控制权彻底在浏览器手中,而且能够经过编程精度控制静态资源,动态请求的数据等。

可是这不表明 service worker 能够彻底控制 http 进行缓存控制,由于 http 不只仅缓存在浏览器中,还有代理缓存中。

在 http 的 Cache-Control 中有两个参数,private 和 public。private 表明不被 proxy 所缓存,区别详细以下

Private vs Public in Cache-Control

相关文章
相关标签/搜索