angular|angular service worker搭配workbox使用

原文链接
略过前言直接进入主题
什么是 Service Worker? Service Worker是一个相对较新的技术,用于在应用中运行执行Javascript代码,即使用户还没有打开页面。通过它我们可以在浏览器中注册代码片段、对某些事件进行处理,甚至在我们的例子中,可以对请求进行缓存生成离线应用。
service worker可以设想成浏览器域名下的一个代理,流量通过Service Worker分发,Worker决定数据是从缓存还是从网络获取。缓存是通过service worker API接口定义的,我们可以通过service worker定制自己的缓存策略。
今天我们不打算这样做,事实上,自己实现缓存非常麻烦。幸运的是我们不需要自己造轮子,已经有可用的库提供高级的接口来实现我们需求。本文中我们使用谷歌的Workbox
不是所有的浏览器都支持service worker,但是它们都宣称未来会支持
为什么选择 Workbox?
angular-cli集成了service worker,在angular-cli配置service worker非常容易
新建 angular 项目

ng new angular-service-worker

使用npm i加载依赖
下一步,下载Workbox build API
npm i workbox-build --save-dev

环境以及搭建完成。
为 angular 项目搭建一个 Service Worker 缓存
我们以及下载了Workbox build API,接下来我们就可以去生成angular service worker,为什么我们要生成一个,而不是通过代码配置?
要缓存静态文件,service worker需要知道其文件名,使用 angular-cli 构建项目的时候,目标文件名每次都会改变,angular-cli 在文件名上加了hash值,浏览器可以为新文件作缓存又避免就文件缓存影响
目标文件名每次都会改变,我们无法在 worker-script 去配置,那样每次构建完都要去更改配置,所以我们使用Workbox build API 接口实现。
Workbox build API 允许我们通过 node.js 脚本文件扫描目标文件目录生成 service worker 脚本文件,目标文件大概这样:
dist/sw.js
... const fileManifest = [ { "url": "index.html", "revision": "2a59e6e03216061ad8eebe5d302b63be" }, { "url": "inline.5f8f26e0d9e0b566019d.bundle.js", "revision": "590f7d098ab30195f9f4761bf79ec18c" }, { "url": "main.c0c528c8c7636a1ce8fc.bundle.js", "revision": "f1646bf6ca91be91e46d35a173846e5b" }, { "url": "polyfills.67d068662b88f84493d2.bundle.js", "revision": "ad4076ac41e8c08e5b5f872136081192" }, { "url": "styles.d41d8cd98f00b204e980.bundle.css", "revision": "d41d8cd98f00b204e9800998ecf8427e" }, { "url": "vendor.dd97be8fb65ee0109dba.bundle.js", "revision": "dad8873f5866215d28f6a9d07764de3b" } ]; workboxSW.precache(fileManifest); ...

缓存静态内容
现在我们开始写脚本文件,我们在项目根目录下新建一个 generate-service-worker.js 文件
const build = require("workbox-build"); const SRC_DIR = "./"; const BUILD_DIR = "dist"; const options = { swDest: `${BUILD_DIR}/sw.js`, // target directory globDirectory: BUILD_DIR, navigateFallback: "/index.html", // redirect all reqest to the fallback, if no explicit route matches clientsClaim: true }; build.generateSW(options).then(() => { console.log("Generated service worker with static cache"); });

这段脚本代码中,我们定义了一些变量并将其传递给 builder 的 generateSW 方法,然后 builder 在 BUILD_DIR 指定的目录下创建 service worker 文件
基本就是这样,依靠 angular 生成的 service worker 会缓存所有静态文件。然而,大多数应用程序依赖动态内容,动态内容此时还没被缓存
缓存动态内容
要缓存动态内容,我们需要告诉 Workbox 内容来源,比如说我们有一个接口地址,它提供与应用程序同域下的内容,缓存这类内容,我们需要使用 Workbox builder 的 runtimeCaching 方法,举例说明:
generate-service-worker.js
const options = { swDest: `${BUILD_DIR}/sw.js`, globDirectory: BUILD_DIR, navigateFallback: "/index.html", clientsClaim: true, runtimeCaching: [ { urlPattern: //api/(.*)/, // reg ex handler: "networkFirst" // caching strategy } ], handleFetch: true };

现在,所有/api/* 下的数据都会被缓存
缓存策略
你还可以定义不同的缓存策略,这是缓存策略
拓展 Service Worker
更多时候,builder 提供的选项不满足我们的应用场景, 比如推送通知缓存到 service worker. 好在service worker 提供用户自定义选项,builder 会通过swSource属性合并两份文件成一个 service worker.
generate-service-worker.js
const options = { swSource: `${SRC_DIR}/service-worker.js`, swDest: `${BUILD_DIR}/sw.js`, globDirectory: BUILD_DIR, navigateFallback: "/index.html", clientsClaim: true, runtimeCaching: [ { urlPattern: //api/(.*)/, // reg ex handler: "networkFirst" // caching strategy } ], handleFetch: true };

同时我们将方法名从generateSW() 改为 injectManifest()
generate-service-worker.js
build.injectManifest(options).then(() => {});

为 Service Worker 配置构建脚本
到这一步,使用 Workbox 不会比 使用 angular-cli service worker 复杂
要想在构建时自动生成 generate-service-worker.js ,我们需要手动在构建脚本关联 service worker。
有一个办法是定制angular-cli 使用 Workbox Webpack pluign,这个可以让 generate-service-worker.js 运行良好,但会破坏angular-cli结构
【angular|angular service worker搭配workbox使用】我们可以在运行 "npm start" 启动项目的时候,通过 express 运行 node.js 脚本,这两个都不算是最佳方案,如果你更好的方案,请联系我
这是 npm package.json
... "scripts": { "ng": "ng", "start": "ng build --prod && npm run gn-sw && node server/server.js", "build": "ng build --prod && npm run gn-sw", "test": "ng test", "lint": "ng lint", "e2e": "ng e2e", "gn-sw": "node generate-service-worker.js" }, ...const express = require("express"); const app = express(); app.use(express.static(__dirname + "/../dist/")); app.listen(process.env.PORT || 8080, () => {});

    推荐阅读