2017-02-04 18:16:00

service worker初探:超级拦截器与预缓存

前些日子发现这样一个问题:

请教 JS 如何修改 UserAgent

这个问题说白了就是如何拦截请求。拦截fetch以及拦截XHR已经是很常规的办法了,但是如何拦截浏览器自行发出的请求呢?

这就是service worker可以做的了,因为它的拦截功能实在是太强大,因此博主称之为超级拦截器。

service worker

本文介绍的实际上只是service worker的冰山一角,并不介绍它的所有功能,有兴趣地详细了解的可以自行谷歌。

博主这里推荐两篇较本文更为进阶的文章。

Service Worker 生命周期那些事儿
Service Worker 消息推送功能“全军覆没”

预缓存

使用各种存储介质来缓存JS等请求结果,在命中缓存并经过安全监测后获取缓存,来加快首屏访问速度并降低静态资源对CDN的压力,这就是强缓存

本文介绍的预缓存则是,通过service worker这个介质,直接在首屏中将需要缓存的所有资源缓存下来,并进行强缓存。这时甚至可以缓存首屏中没有用到的资源。

作为Chrome重推的功能点,Chrome开发了一个可以开箱即用的强缓存库

但是这个库仍然需要在业务代码中引入,我们可以使用这个webpack插件,将缓存代码从业务代码中分离出来,效果相当不错。

这都是基于service worker可以拦截所有请求这一特点,如果它不能拦截浏览器发出的普通请求的话,校验以及提供缓存就不可能从业务代码中解耦出来,因此更不用提使用webpack来分离了

Demo

Demo同样是本博客了,如果读者使用的是chrome浏览器,那么即使强刷本页面,可以看到几乎所有资源全部都显示From service-worker,而service-worker.js请求上会显示一个小齿轮,这表示这个请求是由service-worker发出的

除了不怕强制刷新之外,这还会大大减少大流量节日用户对CDN的压力,是一种很不错的针对性优化策略。

注:如果读者想看看首次访问有哪些请求,可以进入chrome调试中application,unregister掉本博客的service-worker即可

使用webpack插件

接下来以本博客为例,展示如何配合webpack插件,使用service-worker

安装与初始配置

npm install --save-dev sw-precache-webpack-plugin

一种初始的配置如下:

new SWPrecachePlugin({
  cacheId: 'blog',
  filename: 'service-worker.js',
  dontCacheBustUrlsMatching: false,
  staticFileGlobsIgnorePatterns: [
    /index\.html$/,
    /\.map$/,
    /\.css$/,
    /\.svg$/,
    /\.eot$/
  ]
]})

这会在dist目录下生成service-worker.js文件,供给service worker运行,因此不要忘记挂路由

app.use('/service-worker.js', serve('./dist/service-worker.js'))

它会将webpack打包出来的所有资源通过staticFileGlobsIgnorePatterns进行一次过滤,得到需要缓存的文件路径,并写入precache默认缓存模板。

文件hash的位置

这里有一个问题,[name].[ext]?[hash]形式的资源被打包后无法被sw-precache找到(因为文件名肯定不包括尾巴上那个hash),因此最好更改成[name].[hash].[ext]这种形式。

另外,有些非主流手机浏览器会缓存[name].[ext]?[hash],即使hash变更它还是不会发出新请求,因此统一成[name].[hash].[ext]是最好的。

缓存非webpack打包的资源

添加资源目录及设置合并标志

为了将未使用webpack打包的资源(例如图片)也缓存下来,修改配置如下

new SWPrecachePlugin({
  cacheId: 'blog',
  filename: 'service-worker.js',
  minify: true,
  // 添加下面两个属性
  mergeStaticsConfig: true,
  staticFileGlobs: [
    path.join(__dirname, '../dist/static/*.*')
  ],
  dontCacheBustUrlsMatching: false,
  staticFileGlobsIgnorePatterns: [
    /index\.html$/,
    /\.map$/,
    /\.css$/,
    /\.svg$/,
    /\.eot$/
  ]

SPA图片请求URL的问题

但是,此时缓存的图片地址为/dist/static/XXX.png,而真实访问的地址为/static/XXX.png

之所以地址不同,这是因为,SSR形式的SPA,图片资源一般是这样加载的:

  • 开发时,将图片放入根目录中的static目录中
    • 通过webpack-dev-middleware将这个static目录弄成publicPath,可以直接通过/static/XXX.png访问到图片
  • 正式环境时,将static目录直接拷贝到dist目录下
    • 再通过nginx将/static/XXX.png重写为/dist/static/XXX.png,此时/static/XXX.png同样可以访问到图片

因为开发环境下根本没有dist目录这个概念,为了保证开发与正式环境的图片访问路由一致,所以才需要在正式环境下重写路由。

重写缓存文件的文件地址

为了将缓存图片的地址中的/dist/static重写为/static,添加stripPrefixMulti字段

new SWPrecachePlugin({
  cacheId: 'blog',
  filename: 'service-worker.js',
  minify: true,
  mergeStaticsConfig: true,
  staticFileGlobs: [
    path.join(__dirname, '../dist/static/*.*')
  ],
  stripPrefixMulti: {
    [path.join(__dirname, '../dist/static')]: '/static'
  },
  dontCacheBustUrlsMatching: false,
  staticFileGlobsIgnorePatterns: [
    /index\.html$/,
    /\.map$/,
    /\.css$/,
    /\.svg$/,
    /\.eot$/
  ]

现在,缓存的图片资源地址,也是正确的了。

注意点

dontCacheBustUrlsMatching属性一定要为false,这样sw生成的缓存key是http://domain/path/to/filename?hash这种形式的,否则会没有尾巴上那个hash,导致更换同名文件时key仍然相同,会让用户浏览器上的sw无法更新缓存

结语

博主只在本文中介绍了如何使用service-worker来做强缓存。实际上,360前端团队已经在几年前就开始用LocalStorage来缓存JS以及CSS了,博主所在的前端团队也有Bowl.js这种已经到开源程度的强缓存库,因此强缓存实际上是一种已经非常流行的策略了。

service-worker来做强缓存,相比LocalStorage来说,有如下优点:

  • 缓存逻辑不需要侵入到App代码中
    • 使用LocalStorage的强缓存库,需要在App代码中自行检测是否需要发请求还是直接使用旧缓存,耦合性相当高。
    • 通过webpack插件,service-worker的检测与缓存被完全独立到App代码之外,我们只需要关注缓存哪些文件,解耦十分成功。
  • 不需要检测缓存的文件是否安全
    • LocalStorage十分容易被XSS攻击,因此需要配合服务器端content-security-policy中对应的hash来判断是否安全
    • 使用service worker,缓存文件的安全全部由浏览器自行控制,博主甚至找不到任何修改这种缓存的办法

对应的,也有一些缺点:

  • 从service-worker中命中以及取缓存这些动作,会花费十几毫秒的时间,这些时间会显示在Network面板请求中
    • LocalStorage的强缓存库也有这些动作,但是属于JS层面,不会被计入Network请求,虽然真实时间应当没有差别,但首屏请求的毫秒数要少一些
  • service worker依赖浏览器实现
    • 根本没法铺垫片,只能等浏览器自行内部实现。
    • 但是chrome目前份额已经很高了,即使只有chrome的用户可以享受到这种缓存优化,CDN的压力也会减小不少

对于公司来说,一般都会为本文这种service-worker的强缓存策略做降级到LocalStorage的策略。访问本站的用户的浏览器,大多数都支持service-worker(样本为304个用户,其中支持sw的有294个),因此博主就偷懒没做降级了

本文链接:https://smallpath.me/post/service-worker-precache

-- EOF --