精华内容
下载资源
问答
  • 2020-02-19 10:50:37

    将应用中的静态资源缓存是目前最主流的性能优化方法,甚至能让应用秒开!目前常见的缓存方式有http缓存、memory cache、disk cahce、localstorage、Service worker缓存等方式,本文介绍的Workbox就是实现Service worker离线缓存的一个工具。
    那么问题来了,Service worker离线缓存和传统的缓存方式对比,有什么优势和劣势呢,service worker之所以越来越流行,是因为它让前端缓存脱离了服务端,不需要服务端再额外做些什么,前端工程师自己就可以实现缓存,而且缓存内容完全可控,下面是我搜索的几条主流缓存方式的介绍和对比。

    上图从深入理解浏览器缓存处参考
    http缓存依赖于服务端配置,memory cache和disk cache缓存内容不可控,而且只缓存一些静态资源,push cache是临时缓存,localstorage适用于缓存一些全局的数据,对于静态资源很少用它。
    service worker缓存的优缺点:
    优点:

    非侵入式缓存
    缓存内容开发者完全可控
    持续性缓存
    独立于主线程之外,不堵塞进程

    缺点:

    权限太大,能拦截所有fetch请求,需要控制一下
    发版更新处理比较麻烦

    Workbox简介
    Workbox 是 Google Chrome 团队推出的一套 PWA 的解决方案,这套解决方案当中包含了核心库和构建工具,因此我们可以利用 Workbox 实现 Service Worker 的快速开发。
    引入方式
    有两种方式可以引入workbox:
    第一种最为方便,就是通过importScripts()方法从谷歌官方CDN中引入。
    importScripts(‘https://storage.googleapis.com/workbox-cdn/releases/5.0.0/workbox-sw.js’);
    if (workbox) {
    console.log(Yay! Workbox is loaded 🎉);
    } else {
    console.log(Boo! Workbox didn't load 😬);
    }
    复制代码第二种方式就是从本地引入,本地需要从npm库中下载相应的workbox包,然后通过import按需导入,本文的例子就是这种方式。
    import {precaching} from ‘workbox-precaching’;
    import {registerRoute} from ‘workbox-routing’;
    import {BackgroundSyncPlugin} from ‘workbox-background-sync’;

    // 按需引入,然后使用对应模块…
    复制代码详细介绍请查阅官方文档
    配置
    Workbox可以修改缓存名称,可以用setCacheNameDetails设置预缓存和运行时缓存的名称,还可以通过workbox.core.cacheNames.precache 和 workbox.core.cacheNames.runtime 获取当前定义的预缓存和动态缓存名称。
    // 修改默认配置
    workbox.core.setCacheNameDetails({
    prefix: ‘app’,
    suffix: ‘v1’,
    precache: ‘precache’,
    runtime: ‘runtime’
    })

    // 打印修改结果

    // 将打印 ‘app-precache-v1’
    console.log(worbox.core.cacheNames.precache)
    // 将打印 ‘app-runtime-v1’
    console.log(workbox.core.cacheNames.runtime)
    复制代码更多配置下信息请参考官方文配置文档
    预缓存功能
    预缓存功能可以在service worker安装前将一些静态文件提前缓存下来,这样就能保证service worker安装后可以直接存缓存中获取这些静态资源,可以通过以下代码实现。
    import {precacheAndRoute} from ‘workbox-precaching’;

    precacheAndRoute([
    {url: ‘/index.html’, revision: ‘383676’ },
    {url: ‘/styles/app.0c9a31.css’, revision: null},
    {url: ‘/scripts/app.0d5770.js’, revision: null},
    ]);
    复制代码更多预缓存请参考官方预缓存功能文档
    路由功能
    路由功能是workbx的核心功能,主要是匹配资源路径后,采用相应的缓存策略,或者自定义缓存处理,使用方法如下所示:
    import {registerRoute} from ‘workbox-routing’;
    import {CacheFirst} from ‘workbox-strategies’;

    registerRoute( /.(?:png|jpg|jpeg|svg|gif)$/, new CacheFirst({
    cacheName: ‘my-image-cache’,
    }));
    复制代码registerRoute有两个参数,第一个参数是一个正则表达式,用来匹配路径,第二个参数是对匹配路径进行的处理函数,可以用workbox封装的缓存策略处理函数,也可以自定义,上述示例就是使用的workbox内部封装的CacheFirst缓存策略。
    如果第二个参数使用自定义函数,那么这个函数有三个默认参数,示例如下:
    import {registerRoute} from ‘workbox-routing’;

    const handler = async ({url, event, params}) => {
    // Response will be “A guide to Workbox”
    return new Response(A ${params.type} to ${params.name} );
    };
    registerRoute(/.css$/, handler);
    复制代码缓存策略
    Workbox内部封装了以下五种缓存策略:

    NetworkFirst:网络优先
    CacheFirst:缓存优先
    NetworkOnly:仅使用正常的网络请求
    CacheOnly:仅使用缓存中的资源
    StaleWhileRevalidate:从缓存中读取资源的同时发送网络请求更新本地缓存

    五种缓存策略使用方法一致,各适用于不同的场景,具体适用场景可在离线指南中查看。
    Webpack+Workbox构建离线应用
    目前大部分前端项目都离不开webpack,为了方便我们使用workbox,谷歌官方给我们提供了workbox的webpack插件,通过这个插件,我们能在项目中快速引入workbox,通过配置来定制化我们的缓存。
    通过以下四个步骤,我们能将webpack引入到一个由webpack构建的应用中并实现缓存。
    第一步:使用workbox-webpack-plugin

    安装

    npm install workbox-webpack-plugin
    复制代码
    在webpack 配置文件中引入并配置

    workbox-webpack-plugin有两种配置方式:
    第一种:GenerateSW
    通过配置自动在项目中引入service-worker.js,代码如下:
    const WorkboxPlugin = require(‘workbox-webpack-plugin’);

    module.exports = {
    // Other webpack config…
    plugins: [
    // Other plugins…
    new WorkboxPlugin.GenerateSW({
    // Do not precache images
    exclude: [/.(?:png|jpg|jpeg|svg)KaTeX parse error: Can't use function '\.' in math mode at position 205: … urlPattern: /\̲.̲(?:png|jpg|jpeg…/,
    // Apply a cache-first strategy.
    handler: ‘CacheFirst’,
    options: {
    // Use a custom cache name.
    cacheName: ‘images’,
    // Only cache 10 images.
    expiration: {
    maxEntries: 10,
    },
    },
    }],
    })
    ]
    };
    复制代码适用于:

    预缓存静态文件
    只简单的应用运行时的缓存功能

    不适用:

    需要使用service worker 其他功能的场景,如push等
    需要在service worker中导入其他脚本或添加其他逻辑
    具体的配置文件可查阅官方文档,本文示例使用的是第二种方式

    第二种:InjectManifest
    通过已有的service-worker.js文件生成新的service-worker.js,示例如下:
    new workboxPlugin.InjectManifest({
    // 目前的service worker 文件
    swSrc: ‘./src/sw.js’,
    // 打包后生成的service worker文件,一般存到disk目录
    swDest: ‘sw.js’
    })
    复制代码适用于:

    预缓存文件
    更多的定制化缓存需求
    使用service worker其他特性

    如果你只想简单的引入service worker,建议使用第一种方式
    第二步:注册Service Worker
    配置好插件之后,我们需要在项目中注册service worker。
    注册比较简单,只需要在项目入口文件中进行注册即可,代码如下:
    // Check that service workers are supported
    if (‘serviceWorker’ in navigator) {
    // Use the window load event to keep the page load performant
    window.addEventListener(‘load’, () => {
    navigator.serviceWorker.register(’/sw.js’);
    });
    }
    复制代码上述代码是最简单的注册方式,在我们的项目中我们使用register-service-workernpm包注册service worker并添加一下自定义事件,方便后期进行更新和离线事件的处理。
    代码如下:
    import { register } from ‘register-service-worker’;

    export default function(swDest) {
    console.log(“注册sw”)
    register(/${swDest}, {
    ready(registration) {
    // 此方法是我们项目中自己封装的创建自定义事件的公共方法
    dispatchServiceWorkerEvent(‘sw.ready’, registration);
    },
    registered(registration) {
    dispatchServiceWorkerEvent(‘sw.registered’, registration);
    },
    cached(registration) {
    dispatchServiceWorkerEvent(‘sw.cached’, registration);
    },
    updatefound(registration) {
    dispatchServiceWorkerEvent(‘sw.updatefound’, registration);
    },
    updated(registration) {
    dispatchServiceWorkerEvent(‘sw.updated’, registration);
    },
    offline() {
    dispatchServiceWorkerEvent(‘sw.offline’, {});
    },
    error(error) {
    dispatchServiceWorkerEvent(‘sw.error’, error);
    },
    });
    }
    复制代码在入口文件中引入注册文件:
    import registerSW from ‘./sw-register’;
    registerSW(‘sw.js’);
    复制代码第三步:自定义Service Worker缓存配置
    如果我们使用injectMainfest的方式引入servicce worker,需要在src目录下创建一个sw.js(命名自定义,但需要和webpack配置中一致),在这个文件中我们可以进行预缓存等操作。代码示例如下(sw.js):
    import { setCacheNameDetails, clientsClaim } from ‘workbox-core’;
    import { precacheAndRoute } from ‘workbox-precaching/precacheAndRoute’;
    import {createHandlerBoundToURL} from ‘workbox-precaching’;
    import {NavigationRoute, registerRoute} from ‘workbox-routing’;
    import {StaleWhileRevalidate,NetworkOnly} from ‘workbox-strategies’;

    // 设置缓存名称
    setCacheNameDetails({
    prefix: ‘app’,
    suffix: ‘v1.0.2’,
    });

    // 更新时自动生效
    clientsClaim();

    // 预缓存文件,self.__WB_MANIFEST是workbox生成的文件地址数组,项目中打包生成的所有静态文件都会自动添加到里面
    precacheAndRoute(self.__WB_MANIFEST || []);

    // 单页应用需要应用NavigationRoute进行缓存,此处可自定义白名单和黑名单
    // 跳过登录和退出页面的拦截
    const handler = createHandlerBoundToURL(’/index.html’);
    const navigationRoute = new NavigationRoute(handler, {
    denylist: [
    /login/,
    /logout/,
    ],
    });
    registerRoute(navigationRoute);
    // 运行时缓存配置
    // 接口数据使用服务端数据
    registerRoute(/^api/,new NetworkOnly());

    //图片cdn地址,属于跨域资源,我们使用StaleWhileRevalidate缓存策略
    registerRoute(/^https://img.xxx.com//,new StaleWhileRevalidate());
    复制代码上述的代码有一段针对单页应用处理的逻辑,应为单页应用依靠路由变化来加载不同的内容,使用navigationRoute可以匹配导航请求,从而从换从中加载index.html,但默认情况会拦截所有导航请求,如果需要控制,可以在方法中添加白名单和黑名单加以控制。
    第四步:处理Service Worker的更新和离线状态
    更新状态
    配置完成后,我们需要注意service worker的更新和离线状态,service worker的更新较为复杂,如果处理不当回引发各种问题,目前主流的方式就是每次发版,提醒用户更新,如果用户点击确定更新,新发版的service worker会替换掉旧的service worker,此代码我们项目中放在了入口文件中(webpack配置的入口文件),示例代码如下:
    // sw.updated是在注册文件中添加的自定义事件
    window.addEventListener(‘sw.updated’, event => {
    const e = event;

    const reloadSW = async () => {
      // Check if there is sw whose state is waiting in ServiceWorkerRegistration
      // https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration
      const worker = e.detail && e.detail.waiting;
    
      if (!worker) {
        return true;
      } // Send skip-waiting event to waiting SW with MessageChannel
    
      await new Promise((resolve, reject) => {
        const channel = new MessageChannel();
    
        channel.port1.onmessage = msgEvent => {
          if (msgEvent.data.error) {
            reject(msgEvent.data.error);
          } else {
            resolve(msgEvent.data);
          }
        };
    
        worker.postMessage(
          {
            type: 'skip-waiting',
          },
          [channel.port2],
        );
      }); 
      // Refresh current page to use the updated HTML and other assets after SW has skiped waiting
    
      window.location.reload(true);
      return true;
    };
    
    const key = `open${Date.now()}`;
    const btn = (
      <Button
        type="primary"
        onClick={() => {
          notification.close(key);
          reloadSW();
        }}
      >
        确定
      </Button>
    );
    notification.open({
      message: "新版本发布",
      description: "Boss发布新版本了,请点击页面更新",
      btn,
      key,
      onClose: async () => {},
    });
    

    });
    复制代码对应sw.js文件里面要监听主线程传递过来的更新事件,代码如下:
    // service worker通过message和主线程通讯
    addEventListener(‘message’, event => {
    const replyPort = event.ports[0];
    const message = event.data;
    console.log(message)
    if (replyPort && message && message.type === ‘skip-waiting’) {
    event.waitUntil(
    self.skipWaiting().then(
    () =>
    replyPort.postMessage({
    error: null,
    }),
    error =>
    replyPort.postMessage({
    error,
    }),
    ),
    );
    }
    });
    复制代码离线状态
    对于离线状态的监听比较简单,在入口文件中添加以下代码即可:
    window.addEventListener(‘sw.offline’, () => {
    message.warning(“当前处于离线状态”,0);
    });
    复制代码检查效果
    经过上述四个步骤,我们就能将service worker引入到我们已有的用webpack构建的项目上。
    如果正常引入,我们可以在控制台中看到下图:

    更多相关内容
  • 工作箱示例 Workbox@3.0.0的示例 开始使用 | 交叉起源 | 使用CLI | 使用Webpack | 阅读更多
  • yarn add parcel-plugin-workbox --dev 或使用npm npm install parcel-plugin-workbox --save-dev 用法 当您使用Parcel构建资源时,该插件将生成服务工作程序sw.js并将其插入到项目的index.html条目文件中。 您...
  • yarn add next-with-workbox 基本用法 使用以下命令更新或创建next.config.js const withWorkbox = require ( "next-with-workbox" ) ; module . exports = withWorkbox ( { workbox : { swSrc : "worker.js" , ...
  • 此扩展为您的Mix(v4.0及更高版本)版本提供即时的Workbox支持。 用法 首先,安装扩展程序。 npm install laravel-mix-workbox --save-dev 然后,在您的webpack.mix.js文件中要求它,如下所示: // webpack.mix....
  • 使用Webpack + Workbox构建React PWA的样板 我已经按照Webpack文档构建了这个样板,它很棒。 为什么要另辟boiler径? 答案很简单。 我想完成整个环境设置过程,以构建出色的Web应用程序,并能够理解样板配置的每个...
  • yarn add react-workbox NPM : npm install react-workbox 在create-react-app项目中使用 从src/index.js删除serviceWorker.unregister()或serviceWorker.register() 删除src/serverWorker.js 将...
  • react-app-rewire-workbox 通过 添加到您的create-react-app应用而无需退出OR fork 。 默认情况下,create react应用程序在后台使用SWPrecacheWebpackPlugin生成服务工作程序,该服务程序预缓存您的应用程序外壳...
  • Workbox-Window v4.x 中文版

    千次阅读 2019-03-14 01:48:21
    Workbox 目前发了一个大版本,从 v3.x 到了 v4.x,变化有挺大的,下面是在 window 环境下的模块。 什么是 workbox-window? workbox-window 包是一组模块,用于在 window 上下文中运行,也就是说,在你的网页内部...

    Workbox 目前发了一个大版本,从 v3.x 到了 v4.x,变化有挺大的,下面是在 window 环境下的模块。


    什么是 workbox-window?

    workbox-window 包是一组模块,用于在 window 上下文中运行,也就是说,在你的网页内部运行。 它们是 servicewoker 中运行的其他 workbox 的补充。

    workbox-window的主要功能/目标是:

    • 通过帮助开发人员确定 serviceWorker 生命周期中最关键的时刻,并简化对这些时刻的响应,简化 serviceWoker 注册和更新的过程。
    • 帮助防止开发人员犯下最常见的错误。
    • 使 serviceWorker 程序中运行的代码与 window 中运行的代码之间的通信更加轻松。

    导入和使用 workbox-window

    workbox-window 包的主要入口点是 Workbox 类,你可以从我们的CDN或使用任何流行的 JavaScript 打包工具将其导入代码中。

    使用我们的 CDN

    在您的网站上导入 Workbox 类的最简单方法是从我们的 CDN:

    <script type="module">
    import {Workbox} from 'https://storage.googleapis.com/workbox-cdn/releases/4.0.0/workbox-window.prod.mjs';
    
    if ('serviceWorker' in navigator) {
      const wb = new Workbox('/sw.js');
    
      wb.register();
    }
    </script>
    

    注意,此示例使用 <script type ="module">import 语句来加载 Workbox 类。 虽然您可能认为需要转换此代码以使其在旧版浏览器中运行,但实际上并不是必需的。

    支持 serviceWorker 的所有主要浏览器也支持 JavaScript 模块,因此将此代码提供给任何浏览器都是完美的(旧版浏览器将忽略它)。

    通过 JavScript 打包加载 Workbox

    虽然使用 Workbox 绝对不需要工具,但如果您的开发基础架构已经包含了与 npm 依赖项一起使用的 webpackRollup 等打包工具,则可以使用它们来加载 Workbox

    第一步就是安装 Workbox 做为你应用的依赖:

    npm install workbox-window
    

    然后,在您的某个应用程序的 JavaScript 文件中,通过引用 workbox-window 包名称导入 Workbox

    import {Workbox} from 'workbox-window';
    
    if ('serviceWorker' in navigator) {
      const wb = new Workbox('/sw.js');
    
      wb.register();
    }
    

    如果您的打包工具支持通过动态 import 语句进行代码拆分,你还可以有条件地加载workbox-window,这有助于减少页面主包的大小。

    尽管 Workbox 非常小(1kb gzip压缩),但是没有理由需要加载站点的核心应用程序逻辑,因为 serviceWorker 本质上是渐进式增强。

    if ('serviceWorker' in navigator) {
      const {Workbox} = await import('workbox-window');
    
      const wb = new Workbox('/sw.js');
      wb.register();
    }
    

    高级打包概念

    与在 Service worker 中运行的 Workbox 包不同,workbox-windowpackage.json 中的 mainmodule 字段引用的构建文件被转换为 ES5。 这使它们与当今的构建工具兼容 - 其中一些不允许开发人员转换其 node_module 依赖项的任何内容。

    如果你的构建系统允许您转换依赖项(或者如果您不需要转换任何代码),那么最好导入特定的源文件而不是包本身。

    以下是你可以导入 Workbox 的各种方法,以及每个方法将返回的内容的说明:

    // 使用ES5语法导入UMD版本
    // (pkg.main: "build/workbox-window.prod.umd.js")
    const {Workbox} = require('workbox-window');
    
    // 使用ES5语法导入模块版本
    // (pkg.module: "build/workbox-window.prod.es5.mjs")
    import {Workbox} from 'workbox-window';
    
    // 使用ES2015 +语法导入模块源文件
    import {Workbox} from 'workbox-window/Workbox.mjs';
    

    重要! 如果您直接导入源文件,则还需要配置构建过程以缩小文件,并在将其部署到生产时删除仅开发代码。 有关详细信息,请参阅使用打包(webpack / Rollup)和Workbox的指南

    示例

    导入 Workbox 类后,可以使用它来注册 serviceWorker 并与之交互。 以下是您可以在应用程序中使用 Workbox 的一些示例:

    注册 serviceWorker 并在 serviceWorker 第一次处于 active 状态时通知用户:

    许多 Web 应用程序用户 serviceWorker 预缓存资源,以便其应用程序在后续页面加载时离线工作。在某些情况下,通知用户该应用程序现在可以离线使用是有意义的。

    const wb = new Workbox('/sw.js');
    
    wb.addEventListener('activated', (event) => {
      // 如果另一个版本的 serviceWorker,`event.isUpdate`将为true
      // 当这个版本注册时,worker 正在控制页面。
      if (!event.isUpdate) {
        console.log('Service worker 第一次激活!');
    
        // 如果您的 serviceWorker 配置为预缓存资源,那么
        // 资源现在都应该可用。
      }
    });
    
    // 添加事件侦听器后注册 serviceWorker 。
    wb.register();
    

    如果 serviceWorker 已安装但等待激活,则通知用户

    当由现有 serviceWorker 控制的页面注册新的 serviceWorker 时,默认情况下,在初始 serviceWorker 控制的所有客户端完全卸载之前,serviceWorker 将不会激活。

    这是开发人员常见的混淆源,特别是在重新加载当前页面不会导致新 serviceWorker 程序激活的情况下。

    为了帮助减少混淆并在发生这种情况时明确说明,Workbox 类提供了一个可以监听的等待事件:

    const wb = new Workbox('/sw.js');
    
    wb.addEventListener('waiting', (event) => {
      console.log(`已安装新的 serviceWorker,但无法激活` +
          `直到运行当前版本的所有选项卡都已完全卸载。`);
    });
    
    // 添加事件侦听器后注册 service worker 。
    wb.register();
    

    从 workbox-broadcast-update 包通知用户缓存更新

    workbox-broadcast-update 包非常棒

    能够从缓存中提供内容(快速交付)的方式,同时还能够通知用户该内容的更新(使用stale-while-revalidate 策略)。

    要从 window 接收这些更新,您可以侦听 CACHE_UPDATE 类型的消息事件:

    const wb = new Workbox('/sw.js');
    
    wb.addEventListener('message', (event) => {
      if (event.data.type === 'CACHE_UPDATE') {
        const {updatedURL} = event.data.payload;
    
        console.log(`${updatedURL} 的更新版本可用`);
      }
    });
    
    // 添加事件侦听器后注册 service worker。
    wb.register();
    

    向 serviceWorker 发送要缓存的URL列表

    对于某些应用程序,可以知道在构建时需要预先缓存的所有资源,但某些应用程序根据用户首先登陆的 URL 提供完全不同的页面。

    对于后一类别的应用程序,仅缓存用户所访问的特定页面所需的资源可能是有意义的。 使用 workbox-routing 软件包时,您可以向路由器发送一个 URL 列表进行缓存,它将根据路由器本身定义的规则缓存这些 URL。

    每当激活新的 serviceWorker 时,此示例都会将页面加载的 URL 列表发送到路由器。 请注意,发送所有 URL 是可以的,因为只会缓存与 serviceWorker 中定义的路由匹配的 URL:

    const wb = new Workbox('/sw.js');
    
    wb.addEventListener('activated', (event) => {
      // 获取当前页面URL +页面加载的所有资源。
      const urlsToCache = [
        location.href,
        ...performance.getEntriesByType('resource').map((r) => r.name),
      ];
      // 将该URL列表发送到 serviceWorker 的路由器。
      wb.messageSW({
        type: 'CACHE_URLS',
        payload: {urlsToCache},
      });
    });
    
    // 添加事件侦听器后注册 serviceWorker。
    wb.register();
    

    注意:上述技术适用于通过默认路由器上的 workbox.routing.registerRoute() 方法定义的任何路由。 如果您要创建自己的路由器实例,则需要手动调用 addCacheListener() 。

    重要的 serviceWorker 生命周期

    serviceWorker 的生命周期很复杂,完全可以理解。 它之所以如此复杂,部分原因在于它必须处理 serviceWorker 所有可能使用的所有边缘情况(例如,注册多个 serviceWorker,在不同的框架中注册不同的 serviceWorker,注册具有不同名称的 serviceWorker 等)。

    但是大多数实现 serviceWorker 的开发人员不应该担心所有这些边缘情况,因为它们的使用非常简单。 大多数开发人员每页加载只注册一个 serviceWorker,并且他们不会更改他们部署到服务器的 serviceWorker 文件的名称。

    Workbox 类通过将所有 serviceWorker 注册分为两类来包含 serviceWorker 生命周期的这个更简单的视图:实例自己的注册 serviceWorker 和外部 serviceWorker:

    • 注册 serviceWorker:由于 Workbox 实例调用 register() 而已开始安装的 serviceWorker,或者如果调用 register() 未在注册时触发 updatefound 事件,则已启用安装 serviceWorker。
    • 外部 serviceWorker:一个 serviceWorker,开始独立于 Workbox 实例调用 register() 安装。 当用户在另一个标签页中打开新版本的网站时,通常会发生这种情况。

    我们的想法是,来自 serviceWorker 的所有生命周期事件都是你的代码应该期待的事件,而来自外部 serviceWorker 的所有生命周期事件都应该被视为具有潜在危险,并且应该相应地警告用户。

    考虑到这两类 serviceWorker,下面是所有重要serviceWorker 生命周期时刻的细分,以及开发人员如何处理它们的建议:

    第一次安装 serviceWorker

    你可能希望在 serviceWorker 第一次安装时不同于处理所有未来更新的方式。

    在 Workbox 中,你可以通过检查以下任何事件的 isUpdate 属性来区分版本首次安装和未来更新。 对于第一次安装,isUpdate 将为 false。

    const wb = new Workbox('/sw.js');
    
    wb.addEventListener('installed', (event) => {
      if (!event.isUpdate) {
        // 在这里编写第一次安装需要的代码
      }
    });
    
    wb.register();
    
    时刻事件建议操作
    新的 serviceWorker 已安装(第一次)installedserviceWorker 第一次安装时,通常会预先缓存网站离线工作所需的所有资源。 你可以考虑通知用户他们的站点现在可以离线运行。

    此外,由于 serviceWorker 第一次安装它时不会截获该页面加载的获取事件,你也可以考虑缓存已加载的资源(尽管如果这些资源已经被预先缓存,则不需要这样做)。 向上面的缓存示例发送 serviceWorker 的URL列表显示了如何执行此操作。
    serviceWorker 已经控制页面controlling安装新 serviceWorker 程序并开始控制页面后,所有后续获取事件都将通过该 serviceWorker 程序。 如果你的 serviceWorker 添加了任何特殊逻辑来处理特定的 fetch 事件,那么当你知道逻辑将运行时就是这一点。

    请注意,第一次安装 serviceWorker 时,它不会开始控制当前页面,除非该 serviceWorker 在其 activate 事件中调用 clients.claim()。 默认行为是等到下一页加载开始控制。

    workbox-window 的角度来看,这意味着仅在 serviceWorker 调用 clients.claim() 的情况下才调度 controlling 事件。 如果在注册之前已经控制了页面,则不会调度此事件。
    serviceWorker 已经完成激活activated如上所述,serviceWorker 第一次完成激活它可能(或可能不)已经开始控制页面。

    因此,你不应该将 activate 事件视为了解 serviceWorker 何时控制页面的方式。 但是,如果你在活动事件中(在 serviceWorker )运行逻辑,并且你需要知道该逻辑何时完成,则激活的事件将让你知道。

    发现 serviceWorker 的更新版本时

    当新 serviceWorker 开始安装但现有版本当前正在控制该页面时,以下所有事件的 isUpdate 属性都将为 true。

    在这种情况下,你的反应通常与第一次安装不同,因为你必须管理用户何时以及如何获得此更新。

    时刻事件建议操作
    已安装新 serviceWorker(更新前一个)installed如果这不是第一个 serviceWorker 安装(event.isUpdate === true),则表示已找到并安装了较新版本的 serviceWorker(即,与当前控制页面的版本不同)。

    这通常意味着已将更新版本的站点部署到你的服务器,并且新资源可能刚刚完成预先缓存。

    注意:某些开发人员使用已安装的事件来通知用户其新版本的站点可用。 但是,根据我是否在安装 serviceWorker 程序中调用 skipWaiting(),安装的 serviceWorker 可能会立即生效,也可能不会立即生效。 如果你确实调用 skipWaiting(),那么最好在新 serviceWorker 激活后通知用户更新,如果你没有调用 skipWaiting,最好通知他们等待事件中的挂起更新(见下文了解更多信息) 细节)。
    serviceWorker 已安装,但它仍处于等待阶段waiting如果 serviceWorker 的更新版本在安装时未调用skipWaiting(),则在当前活动 serviceWorker 控制的所有页面都已卸载之前,它不会激活。 你可能希望通知用户更新可用,并将在下次访问时应用。

    警告! 开发人员通常会提示用户重新加载以获取更新,但在许多情况下刷新页面不会激活已安装的工作程序。 如果用户刷新页面并且serviceWorker 仍在等待,则等待事件将再次触发,并且 event.wasWaitingBeforeRegister 属性将为 true。 请注意,我们计划在将来的版本中改进此体验。 关注问题#1848以获取更新。

    另一种选择是提示用户并询问他们是否想要获得更新或继续等待。 如果选择获取更新,则可以使用 postMessage() 告诉 serviceWorker 运行 skipWaiting()。 有关示例,请参阅高级配方为用户提供页面重新加载。
    serviceWorker 已开始控制页面controlling当更新的 serviceWorker 开始控制页面时,这意味着当前控制的 serviceWorker 的版本与加载页面时控制的版本不同。 在某些情况下可能没问题,但也可能意味着当前页面引用的某些资源不再位于缓存中(也可能不在服务器上)。 你可能需要考虑通知用户页面的某些部分可能无法正常工作。

    注意:如果不在serviceWorker 中调用 skipWaiting(),则不会触发控制事件。
    serviceWorker 已完成激活activated当更新的 serviceWorker 完成激活时,这意味着你在 serviceWorker 的激活中运行的任何逻辑都已完成。 如果有什么需要延迟,直到逻辑完成,这是运行它的时间。

    找到意外版本的 serviceWorker

    有时用户会在很长一段时间内在后台标签中打开你的网站。 他们甚至可能会打开一个新标签并导航到你的网站,却没有意识到他们已经在后台标签中打开了您的网站。 在这种情况下,您的网站可能同时运行两个版本,这可能会为开发人员带来一些有趣的问题。

    考虑这样一种情况,即您的网站的标签 A 正在运行 v1,标签 B 正在运行 v2。 加载选项卡 B 时,它将由 v1 附带的 serviceWorker 版本控制,但服务器返回的页面(如果使用网络优先缓存策略用于导航请求)将包含所有 v2 资源。

    这对于选项卡 B 来说通常不是问题,因为当你编写 v2 代码时,你知道你的 v1 代码是如何工作的。但是,它可能是标签A的问题,因为你的 v1 代码无法预测你的 v2 代码可能会引入哪些更改。

    为了帮助处理这些情况,workbox-window 还会在检测到来自“外部” serviceWorker 的更新时调度生命周期事件,其中 external 表示任何不是当前 Workbox 实例注册的版本。

    时刻事件建议操作
    已安装外部 serviceWorkerexternalinstalled如果已安装外部 serviceWorker,则可能意味着用户在不同的选项卡中运行你网站的较新版本。

    如何响应可能取决于已安装的服务是进入等待还是活动阶段。
    通过等待激活来安装外部 serviceWorkerexternalwaiting如果外部 serviceWorker 正在等待激活,则可能意味着用户试图在另一个选项卡中获取你网站的新版本,但是由于此选项卡仍处于打开状态,因此他们已被阻止。

    如果发生这种情况,你可以考虑向用户显示通知,要求他们关闭此标签。 在极端情况下,你甚至可以考虑调用 window.reload(),如果这样做不会导致用户丢失任何已保存的状态。
    serviceWorker 外部 serviceWorker 已激活externalactivated如果外部 serviceWorker 程序已激活,则当前页面很可能无法继续正常运行。 你可能需要考虑向用户显示他们正在运行旧版本页面的通知,并且可能会出现问题。

    避免常见错误

    Workbox 提供的最有用的功能之一是它的开发人员日志记录。 对于 worbox-window 也是这样。

    我们知道与 serviceWorker 一起开发往往会让人感到困惑,当事情发生与你期望的相反时,很难知道原因。

    例如,当你对 serviceWorker 进行更改并重新加载页面时,你可能无法在浏览器中看到该更改。 最可能的原因是,你的 serviceWorker 仍在等待激活。

    但是当使用 Workbox 类注册 serviceWorker 时,你将被告知开发人员控制台中的所有生命周期状态更改,这应该有助于调试为什么事情不像你期望的那样。

    此外,开发人员在首次使用 serviceWorker 时常犯的错误是在错误的范围内注册 serviceWorker。

    为了防止这种情况发生,Workbox类将警告您注册服务工作者的页面是否不在该服务工作者的范围内。 如果您的服务工作者处于活动状态但尚未控制该页面,它还会警告你:

    window 到 serviceWorker 的沟通

    大多数高级 serviceWorker 使用涉及 serviceWorker 和 window 之间的消息传递丢失。 Workbox 类通过提供 messageSW() 方法来帮助解决这个问题,该方法将postMessage() 实例的注册 serviceWorker 并等待响应。

    虽然你可以以任何格式向 serviceWorker 发送数据,但所有 Workbox 包共享的格式是具有三个属性的对象(后两个是可选的):

    属性必须类型描述
    typestring标识此消息的唯一字符串。

    按照惯例,类型都是大写的,下划线分隔单词。 如果类型表示要采取的动作,则它应该是现在时的命令(例如 CACHE_URLS ),如果类型表示报告的信息,则它应该是过去时(例如 URLS_CACHED )。
    metastring在 Workbox 中,这始终是发送消息的 Workbox 包的名称。 自己发送邮件时,可以省略此属性或将其设置为你喜欢的任何内容。
    payload*正在发送的数据。 通常这是一个对象,但它不一定是。

    通过 messageSW() 方法发送的消息使用 MessageChannel,因此接收方可以响应它们。 要响应消息,你可以在消息事件侦听器中调用 event.ports[0].postMessage(response)。 messageSW() 方法返回一个 promise,该 promise 将解析为你返回的任何响应。

    这是一个从 window 到 serviceWorker 发送消息并获得响应的示例。 第一个代码块是 serviceWorker 中的消息侦听器,第二个块使用 Workbox 类发送消息并等待响应:

    sw.js 中的代码:

    const SW_VERSION = '1.0.0';
    
    addEventListener('message', (event) => {
      if (event.data.type === 'GET_VERSION') {
        event.ports[0].postMessage(SW_VERSION);
      }
    });
    

    main.js 中的代码(运行在 window 环境):

    const wb = new Workbox('/sw.js');
    wb.register();
    
    const swVersion = await wb.messageSW({type: 'GET_VERSION'});
    console.log('Service Worker version:', swVersion);
    

    管理版本不兼容性

    上面的示例显示了如何从 window 中实现检查 serviceWorker 版本。 使用此示例是因为当你在 window 和 serviceWorker 之间来回发送消息时,请务必注意你的 serviceWorker 可能没有运行与你的页面代码运行相同的站点版本,以及 处理此问题的解决方案会有所不同,具体取决于你是以网络优先服务还是缓存优先服务。

    网络优先

    首先为你的网页提供服务时,你的用户将始终从你的服务器获取最新版本的 HTML。 但是,当用户第一次重新访问你的站点时(在部署更新之后),他们获得的 HTML 将是最新版本,但在其浏览器中运行的 serviceWorker 将是先前安装的版本(可能是许多旧版本)。

    理解这种可能性非常重要,因为如果当前版本的页面加载的 JavaScript 向旧版本的 serviceWorker 发送消息,则该版本可能不知道如何响应(或者它可能以不兼容的格式响应)。

    因此,在进行任何关键工作之前,始终对 serviceWorker 进行版本控制并检查兼容版本是个好主意。

    例如,在上面的代码中,如果该 messageSW() 调用返回的 serviceWorker 版本早于预期版本,则最好等到找到更新(这应该在调用 register() 时发生)。 此时,你可以通知用户或更新,也可以手动跳过等待阶段以立即激活新的 serviceWorker。

    缓存优先

    与在网络服务页面时相比,首先,当你首先提供页面缓存时,你知道你的页面最初将始终与 serviceWorker 的版本相同(因为这是服务它的原因)。 因此,立即使用messageSW() 是安全的。

    但是,如果找到 serviceWorker 的更新版本并在页面调用 register() 时激活(即你有意跳过等待阶段),则向其发送消息可能不再安全。

    管理这种可能性的一种策略是使用版本控制方案,允许你区分中断更新和非中断更新,并且在更新中断的情况下,你知道向 serviceWorker 发送消息是不安全的。 相反,你需要警告用户他们正在运行旧版本的页面,并建议他们重新加载以获取更新。


    博客名称:王乐平博客

    CSDN博客地址:http://blog.csdn.net/lecepin

    知识共享许可协议
    本作品采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。
    展开全文
  • Workbox用于Progressive Web Apps的JavaScript库集合
  • Jekyll Workbox插件 适用于Jekyll的Google Workbox集成 该插件为静态网站生成器提供了与集成。 它生成一个服务工作者,并提供与Jekyll管理的工件的预缓存集成。 该插件为使用Jekyll的项目提供类似于功能。 它基于...
  • 使用Workbox 2.0的Angular渐进式Web应用程序
  • Statoscope-for-Workbox 使用Statoscope for Workbox Webpack插件 解决报告生成问题的演示项目。
  • Jekyll PWA Workbox插件 关于将来版本的说明; 从现在开始,我们将使用与Workbox相同的版本号。 一个Jekyll插件,可让您的PWA /网站脱机使用,并允许您在台式机和移动设备上安装*。 它生成预缓存列表并将其注入到...
  • workbox学习笔记

    2020-04-28 15:45:08
    workbox学习笔记 一 PWA介绍 1.1 学习workbox之前先了解一下PWA(如果了解请跳过) PWA( 全称:Progressive Web App )也就是说这是个渐进式的网页应用程序。一个 PWA 应用首先是一个网页, 可以通过 Web 技术编写...

    workbox学习笔记

    一 PWA介绍

    1.1 学习workbox之前先了解一下PWA(如果了解请跳过)

    PWA( 全称:Progressive Web App )也就是说这是个渐进式的网页应用程序。一个 PWA 应用首先是一个网页, 可以通过
    Web 技术编写出一个网页应用. 随后添加上Manifest 和 Service Worker 来实现 PWA 的安装和离线等功能。
    而ServiceWorker是PWA中最重要的一部分,它就像是一个网站安插在用户浏览器中的大脑。Service Worker是这样被注册
    在页面上的。

        对比原生应用
        原生应用:
        •	使用原生SDK和开发工具开发
        •	需要考虑跨平台,不同系统往往需要独立开发
        •	需要发布到应用商店才能下载使用
        •	可以安装到手机主屏,生成应用图标
        •	直接运行于操作系统上,访问系统资源方便
        •	可以离线使用
        •	可以获取消息通知
        PWA应用:
        •	使用HTML,CSS,JS开发
        •	无需考虑跨平台,只需要考虑浏览器兼容性
        •	通过url访问,无需发布到应用商店
        •	可以安装到手机主屏,生成应用图标
        •	运行于浏览器中,可访问系统资源
        •	可以离线使用
        •	可以获取消息通知
    

    基于https

    HTTPS 不仅仅可以保证你网页的安全性,还可以让一些比较敏感的 API 完美的使用。值得一提的是,SW 是基于 HTTPS 的,
    所以,如果你的网站不是 HTTPS,那么基本上你也别想了 SW。但是在因为方便开发者在本地测试,在本地调试时支持
    localhost和127.0.0.1本地域名

       if(navigator.serviceWorker!=null){//判断是否支持serviceWorker
             //加载相关serviceWorker脚本
             navigator.serviceWorker.register('/sw.js', {scope: '/'}).then(function(registartion){
                    console.log('support sw',registartion.scope)
             })
       }
    

    为什么说SW(下文将ServiceWorker简称为SW)是网站的大脑?举个例子,如果在www.example.com的根路径下注册了一个SW,
    那么这个SW将可以控制所有该浏览器向www.example.com站点发起的请求。只需要监听fetch事件,你就可以任意的操纵请求,
    可以返回从cacheStorage中读的数据,也可以通过fetch API发起新的请求,你可以操作他返回的请求,将其存到浏览器的缓
    存中,你甚至可以new一个Response,返回给页面。例如下面是一个比较完整的sw文件

        var cacheStorageKey='check-demo-1.0' //版本号,当想更新缓存资源(文件、数据等)
        var cacheList=[
            //需要缓存的文件路径
        ] 
        //当脚本加载完毕执行
        self.addEventListener('install',function(e){
            e.waitUntil(
                //创建缓存并缓存cacheList的所以文件
                    caches.open(cacheStorageKey)
                    .then(function(cache){
                        return cache.addAll(cacheList)
                    })
                    .then(function(){
                        //使用了一个方法那就是 self.skipWaiting( ) ,为了在页面更新的过程当中,新的 SW 脚本能够立刻激活和生效
                        return self.skipWaiting()
                    })
                )
        })
        //监听所有请求
        self.addEventListener('fetch',function(e){
            var reqUrl = e.request.url
            // 过滤条件
            if (someRules) {
                e.respondWith(
                    caches.match(e.request).then(function(response){
                        if(response!=null){
                            return response
                        }
                        // 因为 event.request 流已经在 caches.match 中使用过一次,
                        // 那么该流是不能再次使用的。我们只能得到它的副本,拿去使用。
                        var fetchRequest = e.request.clone();
                        return fetch(fetchRequest)
                            .then(function(res){
                                    // 检查是否成功
                                    //失败了
                                    if(!res || res.status !==200){
                                        return res
                                    }
                                    // 如果成功,该 response 一是要拿给浏览器渲染,二是要进行缓存。
                                    // 不过需要记住,由于 caches.put 使用的是文件的响应流,一旦使用,
                                    // 那么返回的 response 就无法访问造成失败,所以,这里需要复制一份。
                                    var responseToCache = res.clone()
                                    caches
                                        .open(cacheStorageKey)
                                        .then(function(cache){
                                            cache.put(e.request,responseToCache)
                                        });
                                    return res
                                }
                            )
                    })
                )
            }
        })
        //当被激活时,检查版本资源,移除旧版本的资源
        self.addEventListener('activate',function(e){
            e.waitUntil(
                //获取所有cache名称
                caches.keys().then(function(cacheNames){
                    return Promise.all(
                        //移除不是该版本的所有资源
                        cacheNames.filter(function(cacheName){
                            return cacheName !==cacheStorageKey
                        }).map(function(cacheName){
                            return caches.delete(cacheName)
                        })
                    )
        
                }).then(function(){
                    return self.clients.claim() //在新安装的 SW 中通过调用 self.clients.claim( ) 取得页面的控制权,这样之后打开页面都会使用版本更新的缓存。
                })
            )
        })
        
    

    其实所有站点SW的install和active都差不多,无非是做预缓存资源列表,更新后缓存清理的工作,
    逻辑不太复杂,而重点在于fetch事件。上面的代码,我把fetch事件的逻辑省略了,因为如果认真
    写的话,太多了,而且也不利于讲明白缓存策略这件事。想象一下,你需要根据不同文件的扩展名
    把不同的资源通过不同的策略缓存在caches中,各种css,js,html,图片,都需要单独搞一套缓存

    保存到桌面

    Pwa有一个很方便的功能能就是将页面以快捷图标的方式保存到桌面,使得一个它更加接近原生app,实现这种方式需要满足三个条件:

    •	包含一个 manifest.json 文件,其中包含 short_name 以及 icons 字段
    •	包含 service workers
    •	使用 HTTPS(这个好像是废话,既然使用了 service workers,那肯定已经基于 https了)
    

    从上面 3 点可以看到,如果你的应用已经是个 PWA 应用的话,那么,第二,第三点就已经满足了,添加至桌面的功能其实只需为项目添加一个描述性的配置文件 manifest.json 就可以了。

    那 manifest.json 这东西到底是啥?
    它是 PWA 的一部分,是一个 JSON 格式的文件用来描述应用相关的信息,目的是提供将应用添加至桌面的功能,从而使用户可以无需下载就可以如应用一般从桌面打开 web 应用,大大增强用户体验和粘性。

    以下是mainfest的一些参数解析:
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P7jhWV7v-1588059855159)(https://github.com/gla-TBG/image-storage/blob/master/workbox-study/3.4-2.png?raw=true)]

    1.2 什么是workbox?

    workBox由许多node模块和插件库组成的一个库,可以轻松缓存资产并充分利Service Worker的
    特性用于构建Progressive Web Apps的功能。workbox的出现就是为了解决上述sw的问题的,它
    被定义为PWA相关的工具集合其实可以把workbox理解为Google官方PWA框架,因此workbox也是
    基于https的,它解决的就是用底层API写PWA太过复杂的问题。这里说的底层API,指的就是去监听
    SW的install, active, fetch事件做相应逻辑处理等。

    二 使用workbox

    2.1安装

    方法一:使用workbox sw 通过cdn开始使用此模块的最简单方法是通过CDN。 开发者只需将以下内容添加到sw.js:

        // workbox-sw.js
        
        importScripts('https://storage.googleapis.com/workbox-cdn/releases/3.0.0/workbox-sw.js')
    

    方法二:使用本地Workbox Files
    通过workbox-cli的copyLibraries命令或GitHub Release获取文件,然后通过modulePathPrefix配置选项告诉
    workbox-sw在哪里找到这些文件。如果你把文件放在/ third_party / workbox /下,你会像这样使用它们:

        importScripts('/third_party/workbox/workbox-sw.js');
        workbox.setConfig({
          modulePathPrefix: '/third_party/workbox/'
        });
    

    2.2 基础实例

    要开始使用Workbox,只需要在service worker中导入工作workbox-sw.js文件。
    更改你的service worker,使其具有以下importScripts()调用。

        // workbox-sw.js
        importScripts('https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js');
        
        if (workbox) {
          console.log(`Yay! Workbox is loaded 🎉`);
        } else {
          console.log(`Boo! Workbox didn't load 😬`);
        }
    

    运行效果
    Image text

    workbox的主要功能之一是路由和缓存策略模块。它允许你监听来之网页的请求,并提供多种策略来处理你的请求

        // skipWaiting() ,为了在页面更新的过程当中,新的 SW 脚本能够立刻激活和生效
        workbox.skipWaiting();
    
        // clientsClaim() 在新安装的 SW 后,打开页面都会使用版本更新的缓存。
        workbox.clientsClaim();
    

    一个简单的例子,缓存所有js文件

        workbox.routing.registerRoute(
            new RegExp('.*\.js'),
            new workbox.strategies.NetworkFirst()
        );
    

    Image text
    文件已存入缓存中
    Image text

        需要注意的是: 
        
        1、请求的类型很重要。默认情况下,路由已注册了“GET”请求的事件监听。 如果开发者希望拦截其他类型的请求,则需要指定额外的方法。
                
        2、路由注册的顺序很重要。如果注册了多个可以处理请求的路由,则首先注册的路由将优先响应请求
    

    三 模块介绍

    3.1 workbox.routing

    Service worker可以拦截网页的网络请求,它可以把从网络请求中缓存的或者在service worker中
    请求获取的内容返回给浏览器。也就是说浏览器不会再直接与服务器交互,而是由service worker代理。
    Workerbox.routing是一个模块,可以容易地将这些请求“路由”映射到不同的响应,并提供设置相应策略的方法。

    3.2 路由的匹配和处理

    路由器将请求与路由匹配,然后处理该请求(即提供响应)的过程。

    workbox.routing.registerRoute()接收两个变量:

    1. matchFunction(请求的规则)
    2. handler (处理符合规则的请求)
    

    可通过三种方式将请求与workbox路由相匹配 :

    1、 字符串

    workbox.routing.registerRoute(
      '/https://some-other-origin.com/logo.png',
      handler
    );
    

    2、正则表达式
    正则表达式是根据网站的完整的URL匹配的,如果有匹配,handler会被触发
    例如:

       workbox.routing.registerRoute(
         new RegExp('\\.js$'),
         jsHandler
       );
       
       workbox.routing.registerRoute(
         new RegExp('\\.css$'),
         cssHandler
       );
       
       workbox.routing.registerRoute(
         new RegExp('/blog/\\d{4}/\\d{2}/.+'),
         handler
       );
    

    3、回调函数

        const matchFunction = ({url, event}) => {
        // Return true if the route should match
          return false;
        };
        
        workbox.routing.registerRoute(
          matchFunction,
          handler
        );
    
    

    3.3 使用缓存策略处理请求

    所谓缓存策略,就是当workbox捕获到一条已存在于缓存中的请求时,以何种方式获取他的返回(网络请求/缓存)

    1、StaleWhileRevalidate

    StaleWhileRevalidate模式允许你使用缓存的内容尽快响应请求(如果可用),如果未缓存,则回退到网络请求。
    然后,网络请求用于更新缓存。这是一种相当常见的策略,适合更新频率很高但重要性要求不高(至少允许一次缓存
    读取)的内容。在有缓存的情况下,该模式保证了客户端第一时间拿到数据的同时,也会去请求网络资源更新缓存,
    保证下次客户端从缓存中读取的数据是最新的。因此该模式不能减轻后台服务器的访问压力,但却能给前端用户提供
    快速响应的体验。

       // workbox-sw.js
    
       workbox.routing.registerRoute(
           new RegExp('.*\.js'),
           workbox.strategies.staleWhileRevalidate()
       );
    

    Image text

    2、Cache First (缓存优先,缓存回落到网络)

    如果缓存中有响应,则将使用缓存的响应来完成请求,根本不会使用网络。 如果没有缓存的响应,则将通过网络请求
    来满足请求,并且将缓存响应,以便直接从缓存提供下一个请求。该模式可以在为前端提供快速响应的同时,减轻后
    台服务器的访问压力。但是数据的时效性就需要开发者通过设置缓存过期时间或更改sw.js里面的修订标识来完成缓
    存文件的更新。一般需要结合实际的业务场景来判断数据的时效性。

        // workbox-sw.js
    
        workbox.routing.registerRoute(
            new RegExp('.*\.js'),
            workbox.strategies.cacheFirst()
        );
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1PmxtxWy-1588059855164)(https://github.com/gla-TBG/image-storage/blob/master/workbox-study/3.1-2.png?raw=true)]

    3、Network First (网络回落到缓存)

    对于经常更新且关键(由业务判断出来)的请求,网络优先策略是理想的解决方案。 默认情况下,它将尝试从网络获
    取最新请求。 如果请求成功,它会将响应放入缓存中。 如果网络无法返回响应,则将使用缓存响应。这意味着只要当
    第一次请求成功时,service worker就会缓存一份请求结果。当后续重复请求时,缓存也会每次更新。若网络请求失
    败时,只要缓存里有内容,就能让前端获取一个响应(从service worker的缓存里)。因此,此种模式提高了前端页
    面的稳固性,即使在网络请求失败的情况下,页面也能从上次的缓存中读取到数据展示,而不是粗鲁的告诉用户网络请
    求失败的一个响应。

        // workbox-sw.js
        
        workbox.routing.registerRoute(
            new RegExp('.*\.js'),
            workbox.strategies.networkFirst()
        );
    

    Image text

    4、Network Only

    如果你需要从网络中完成特定请求,则只使用网络策略。

        // workbox-sw.js
    
        workbox.routing.registerRoute(
          new RegExp('.*\.js'),
          workbox.strategies.networkOnly()
        );
    

    Image text

    只使用网络策略,此时缓存中并无任何数据
    Image text

    5、Cache Only

    仅缓存策略确保从缓存获取请求。 这在workbox中不太常见,但如果你有自己的预先缓存步骤则可能很有用。

        // workbox-sw.js
    
        workbox.routing.registerRoute(
          new RegExp('.*\.js'),
          workbox.strategies.cacheOnly()
        );
    
    

    Image text

    3.4 配置策略

    一些常用的配置:

    · cacheName (存入缓存的名字)

    · Workbox附带了一组可以与这些策略一起使用的插件, 可以设置缓存在策略中使用的过期限制,最大条目数,哪些特定返回能加入缓存等

    1、策略中使用的缓存名

        // workbox-sw.js
    
        workbox.routing.registerRoute(
            new RegExp('.*\.js'),
            workbox.strategies.staleWhileRevalidate({
                cacheName: 'js-cache', // 缓存名字
            })
        );
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LECRNgAt-1588059855168)(https://github.com/gla-TBG/image-storage/blob/master/workbox-study/3.4-1.png?raw=true)]

    2、 workbox.expiration.Plugin —— 根据缓存中的项目数或缓存请求的期限删除缓存的请求

        workbox.routing.registerRoute(
            new RegExp('.*\.js'),
            workbox.strategies.cacheFirst({
                cacheName: 'js-cache', // 缓存名字
                plugins: [
                    new workbox.expiration.Plugin({
                        maxEntries: 60, // 最大条目限制为60条
                        maxAgeSeconds: 30 * 24 * 60 * 60, // 过期期限30天
                    }),
                ]
            })
        );
    

    插件将检查配置的缓存,并确保缓存数量不超过限制。如果超过,最早的条目将被删除。

    3、workbox.cacheableResponse.Plugin —— 根据响应的状态代码或标头限制缓存哪些请求

        workbox.routing.registerRoute(
            new RegExp('.*\.js'),
            workbox.strategies.cacheFirst({
                cacheName: 'js-cache', // 缓存名字
                plugins: [
                    new workbox.cacheableResponse.Plugin({
                        // 缓存状态代码为“200”或“404”的所有请求。
                        statuses: [200, 404],
                        // 处理符合正则的请求URL的响应时,请查看名为X-Is-Cacheable的标头(将由服务器添加到响应中)。
                        // 如果该标头存在,并且如果它设置为值'true',则可以缓存响应
                        headers: {
                            'X-Is-Cacheable': 'true',
                        }
                        /**
                         * 注意: 如同时设置了上述条件statuses和headers,则需要两者都满足条件才视为可加入缓存
                         */
                    })
                ]
        
            })
        );
    
    展开全文
  • Workbox---高级建议

    2021-01-29 21:04:17
    给所有用户提供一个可以reload的页面 渐进式Web应用程序的常见UX模式是在service...import {Workbox, messageSW} from 'https://storage.googleapis.com/workbox-cdn/releases/6.0.2/workbox-window.prod.mjs'; if ('

    给所有用户提供一个可以reload的页面

    渐进式Web应用程序的常见UX模式是在service worker更新并等待安装时显示标语。
    为此,您需要在页面和service worker中添加一些代码。

    <script type="module">
    import {Workbox, messageSW} from 'https://storage.googleapis.com/workbox-cdn/releases/6.0.2/workbox-window.prod.mjs';
    
    if ('serviceWorker' in navigator) {
      const wb = new Workbox('/sw.js');
      let registration;
    
      const showSkipWaitingPrompt = (event) => {
        // `event.wasWaitingBeforeRegister` will be false if this is
        // the first time the updated service worker is waiting.
        // When `event.wasWaitingBeforeRegister` is true, a previously
        // updated service worker is still waiting.
        // You may want to customize the UI prompt accordingly.
    
        // Assumes your app has some sort of prompt UI element
        // that a user can either accept or reject.
        const prompt = createUIPrompt({
          onAccept: async () => {
            // Assuming the user accepted the update, set up a listener
            // that will reload the page as soon as the previously waiting
            // service worker has taken control.
            wb.addEventListener('controlling', (event) => {
              window.location.reload();
            });
    
            if (registration && registration.waiting) {
              // Send a message to the waiting service worker,
              // instructing it to activate.  
              // Note: for this to work, you have to add a message
              // listener in your service worker. See below.
              messageSW(registration.waiting, {type: 'SKIP_WAITING'});
            }
          },
    
          onReject: () => {
            prompt.dismiss();
          }
        });
      };
    
      // Add an event listener to detect when the registered
      // service worker has installed but is waiting to activate.
      wb.addEventListener('waiting', showSkipWaitingPrompt);
      wb.addEventListener('externalwaiting', showSkipWaitingPrompt);
    
      wb.register().then((r) => registration = r);
    }
    </script>
    

    此代码使用workbox-window程序包注册service worker,并在陷入等待阶段时作出反应。
    当发现service worker处于等待状态时,我们通知用户该站点的有更新版本可用,并提示他们重新加载。如果他们接受提示,我们通过postMessage()告诉新的service worker运行skipWaiting(),这意味着它将跳过等待,直接激活。新的service worker激活并控制后,我们将重新加载当前页面,从而显示所有预缓存资产的最新版本。

    service worker中的code

    如果您在GenerateSW模式下使用其中一个Workbox构建工具,并且将skipWaiting设置为false(默认值),则此代码将自动包含在您生成的service worker文件中,而无需添加它
    如果您要编写自己的service worker代码,或者与InjectManifest模式下的Workbox构建工具结合使用,则需要自己将下面的代码添加到service worker文件中:

    addEventListener('message', (event) => {
      if (event.data && event.data.type === 'SKIP_WAITING') {
        skipWaiting();
      }
    });
    

    这将侦听类型为“ SKIP_WAITING”的消息并运行skipWaiting()方法,从而迫使service worker立即激活。

    预热运行时缓存

    当你配置了一些路由来管理静态资源缓存是,你可能希望在service worker安装时添加一下文件到缓存中
    为了实现这个需求,你可能需要把你急需的静态资源添加到运行时缓存中

    import {cacheNames} from 'workbox-core';
    
    self.addEventListener('install', (event) => {
      const urls = [/* ... */];
      const cacheName = cacheNames.runtime;
      event.waitUntil(caches.open(cacheName).then((cache) => cache.addAll(urls)));
    });
    

    如果你使用了自定义cache name,你也可以做到同样的事情,仅仅需要指定你的自定义cache name 为 cacheName

    给一个路由提供一个后备的响应

    在某些情况下,返回后备响应比根本不返回响应要好。一个示例是在无法检索原始图像时返回占位符图像,或者返回脱机HTML体验而不是浏览器的标准脱机页面。

    只有离线页面

    如果您的目标是提供一个自定义的脱机HTML页面,但不缓存其他内容,那么这是可以遵循的基本方法。它利用导航预加载(在受支持的浏览器中)的优势来帮助减轻service worker的启动成本。

    import * as navigationPreload from 'workbox-navigation-preload';
    import {registerRoute, NavigationRoute} from 'workbox-routing';
    import {NetworkOnly} from 'workbox-strategies';
    
    const CACHE_NAME = 'offline-html';
    // This assumes /offline.html is a URL for your self-contained
    // (no external images or styles) offline page.
    const FALLBACK_HTML_URL = '/offline.html';
    // Populate the cache with the offline HTML page when the
    // service worker is installed.
    self.addEventListener('install', async (event) => {
      event.waitUntil(
        caches.open(CACHE_NAME)
          .then((cache) => cache.add(FALLBACK_HTML_URL))
      );
    });
    
    navigationPreload.enable();
    
    const networkOnly = new NetworkOnly();
    const navigationHandler = async (params) => {
      try {
        // Attempt a network request.
        return await networkOnly.handle(params);
      } catch (error) {
        // If it fails, return the cached HTML.
        return caches.match(FALLBACK_HTML_URL, {
          cacheName: CACHE_NAME,
        });
      }
    };
    
    // Register this strategy to handle all navigations.
    registerRoute(
      new NavigationRoute(navigationHandler)
    );
    

    全面的后备响应

    当出现网络故障和/或缓存未命中时,所有内置的缓存策略均以一致的方式拒绝。这就提倡通过设置全局“捕获”的处理程序来处理单个处理程序功能中的出现的任何故障。

    import {matchPrecache, precacheAndRoute} from 'workbox-precaching';
    import {registerRoute, setDefaultHandler, setCatchHandler} from 'workbox-routing';
    import {CacheFirst, StaleWhileRevalidate} from 'workbox-strategies';
    
    // Optional: use the injectManifest mode of one of the Workbox
    // build tools to precache a list of URLs, including fallbacks.
    precacheAndRoute(self.__WB_MANIFEST);
    
    // Use an explicit cache-first strategy and a dedicated cache for images.
    registerRoute(
      ({request}) => request.destination === 'image',
      new CacheFirst({
        cacheName: 'images',
        plugins: [...],
      })
    );
    
    // Use a stale-while-revalidate strategy for all other requests.
    setDefaultHandler(new StaleWhileRevalidate());
    
    // This "catch" handler is triggered when any of the other routes fail to
    // generate a response.
    setCatchHandler(({event}) => {
      // The FALLBACK_URL entries must be added to the cache ahead of time, either
      // via runtime or precaching. If they are precached, then call
      // `matchPrecache(FALLBACK_URL)` (from the `workbox-precaching` package)
      // to get the response from the correct cache.
      //
      // Use event, request, and url to figure out how to respond.
      // One approach would be to use request.destination, see
      // https://medium.com/dev-channel/service-worker-caching-strategies-based-on-request-types-57411dd7652c
      switch (event.request.destination) {
        case 'document':
          // If using precached URLs:
          // return matchPrecache(FALLBACK_HTML_URL);
          return caches.match(FALLBACK_HTML_URL);
        break;
    
        case 'image':
          // If using precached URLs:
          // return matchPrecache(FALLBACK_IMAGE_URL);
          return caches.match(FALLBACK_IMAGE_URL);
        break;
    
        case 'font':
          // If using precached URLs:
          // return matchPrecache(FALLBACK_FONT_URL);
          return caches.match(FALLBACK_FONT_URL);
        break;
    
        default:
          // If we don't have a fallback, just return an error response.
          return Response.error();
      }
    });
    

    大多数开发人员将使用Workbox的策略之一作为路由器配置的一部分。通过此设置,可以轻松地使用从策略获得的响应来自动响应特定的fetch事件。
    在某些情况下,您可能想在自己的路由器设置中使用策略,或者代替简单的fetch()请求。
    为了帮助解决这类用例,您可以通过handle()方法以“独立”方式使用任何Workbox策略。

    import {NetworkFirst} from 'workbox-strategies';
    
    // Inside your service worker code:
    const strategy = new NetworkFirst({networkTimeoutSeconds: 10});
    
    const response = await strategy.handle({
      request: new Request('https://example.com/path/to/file'),
    });
    // Do something with response.
    

    request参数是必需的,并且必须是Request类型。event参数是一个可选的ExtendableEvent。如果提供,它将用于使service worker保持活动状态(通过event.waitUntil())足够长的时间以完成任何“后台”缓存更新和缓存清理.
    handle()返回对Response对象的promise。您可以在像下面这样更复杂的示例中使用:

    import {StaleWhileRevalidate} from 'workbox-strategies';
    
    self.addEventListener('fetch', (event) => {
      if (event.request.url.endsWith('/complexRequest')) {
        event.respondWith((async () => {
          // Configure the strategy in advance.
          const strategy = new StaleWhileRevalidate({cacheName: 'api-cache'});
    
          // Make two requests using the strategy.
          // Because we're passing in event, event.waitUntil() will be called automatically.
          const firstPromise = strategy.handle({event, request: 'https://example.com/api1'});
          const secondPromise = strategy.handle({event, request: 'https://example.com/api2'});
    
          const [firstResponse, secondResponse] = await Promise.all(firstPromise, secondPromise);
          const [firstBody, secondBody] = await Promise.all(firstResponse.text(), secondResponse.text());
    
          // Assume that we just want to concatenate the first API response with the second to create the
          // final response HTML.
          const compositeResponse = new Response(firstBody + secondBody, {
            headers: {'content-type': 'text/html'},
          });
    
          return compositeResponse;
        })());
      }
    });
    

    提供缓存的音频和视频

    除非您在配置Workbox时采取特定步骤,否则某些浏览器如何请求媒体资源(例如或元素的src)会出现一些抖动,这可能导致错误服务返回。
    详细的细节可以在GitHub问题讨论找到;要点摘要如下:

    • 必须通过将workbox-range-requests插件添加到用作处理程序的策略中,告知Workbox遵守Range请求标头。
    • 音频或视频元素需要使用来选择加入CORS模式crossOrigin属性,例如通过<video src =“ movie.mp4” crossOrigin =“ anonymous”> </ video>。
    • 如果要从缓存中提供媒体,则应提前将其明确添加到缓存中。这可以通过precaching或直接调用cache.add()来实现。使用运行时缓存策略将媒体文件隐式添加到缓存中不太可能起作用,因为在运行时通过Range请求仅从网络中获取部分内容.

    综合上面的内容,这里提供一个可用的示例使用Workbox缓存的媒体内容:

    <!-- In your page: -->
    <!-- You currently need to set crossOrigin even for same-origin URLs! -->
    <video src="movie.mp4" crossOrigin="anonymous"></video>
    
    import {registerRoute} from 'workbox-routing';
    import {CacheFirst} from 'workbox-strategies';
    import {CacheableResponsePlugin} from 'workbox-cacheable-response';
    import {RangeRequestsPlugin} from 'workbox-range-requests';
    
    // In your service worker:
    // It's up to you to either precache or explicitly call cache.add('movie.mp4')
    // to populate the cache.
    //
    // This route will go against the network if there isn't a cache match,
    // but it won't populate the cache at runtime.
    // If there is a cache match, then it will properly serve partial responses.
    registerRoute(
      ({url}) => url.pathname.endsWith('.mp4'),
      new CacheFirst({
        cacheName: 'your-cache-name-here',
        plugins: [
          new CacheableResponsePlugin({statuses: [200]}),
          new RangeRequestsPlugin(),
        ],
      }),
    );
    
    展开全文
  • 神奇的 Workbox 3

    2022-04-29 14:51:40
    有 三种 方法可以通过 workbox-route 来匹配一个请求 URL 字符串方式 1 2 3 4 5 6 7 8 9 10 11 // 可以直接是当前项目下的绝对路径 workbox.routing.registerRoute( ‘/logo.png’, handler // handler 是做缓存...
  • 关于 workbox 缓存策略

    2021-06-28 17:10:22
    workbox 是 google 推出的一套 service-worker 实现工具库; 本文只是试图用最简洁的语言说明缓存策略,如有偏差,欢迎指正交流。 关于 workbox 的缓存策略: Stale-While-Revalidate 先查看是否有缓存, 如果有...
  • workbox 路由请求配置

    2019-12-25 12:31:27
    Workbox中的路由就是一个匹配请求的路由器,然后路由处理该请求(提供响应)的一个过程。 workbox-routing 匹配请求有三种方式: 字符串. 正则表达式. 回调函数. 我们将首先研究如何使用这三种方法进行匹配,...
  • 在这个指南中,我们会探索一个跨域的请求是如何不同的,并且我们应该在Workbox中如何支持这些跨域的请求 跨域请求和不透明响应 浏览器中的一种安全机制是,当一段JavaScript请求来自其他来源的URL时,它就无法访问...
  • vanillajs-workbox-pwa

    2021-04-29 04:24:23
    超级简单香草JS PWA项目 这是基于以下教程的项目: 更多精彩内容 帮我个忙,看看这些很棒的链接。 我在美国东部时间周一至周四上午10:30发布了有关全堆栈开发的新视频教程! 订阅 查看相关的网站 ...
  • 4 技术实践(Service Worker) 4.1 使用 CLI 安装 Workbox: npm install workbox-cli -D npx workbox --help 按照引导配置 workbox-config.js: npx workbox wizard 根据配置生成 Service Worker 程序: npx ...
  • Workbox 是一组库,可以为您的 Progressive Web App 提供生产就绪的 Service Worker。 停止等待网络! 您可以通过缓存和提供由 Service Worker 提供支持的文件来提高 Web 应用程序的性能。 即使在不可靠的连接上,您...
  • 安装克隆repo git克隆[受电子邮件保护]:webmaxru / pwatter.git确保您位于“ workbox”分支上:git checkout workbox安装依赖项:npm install安装“ serve”开发Web服务器:npm install serve -g在内部运行服务器...
  • Workbox CLI v3.x 中文版

    千次阅读 2019-01-02 21:57:50
    What’s the Workbox CLI? The Workbox command line interface (contained in the workbox-cli package) consists of a Node.js program called workbox that can be run from a Windows, macOS, of UNIX-...
  • 原文链接: workbox 4.3.1 手动注入pwa 并替换谷歌链接 ...
  • 并且结合webpack创建serviceWorker文件 安装依赖 npm install --save-dev workbox-webpack-plugin npm install --save workbox-core workbox-routing workbox-strategies workbox-precaching workbox-...
  • Workbox 缓存

    2018-04-03 22:05:00
    介绍 https://developers.google.cn/web/tools/workbox/guides/get-started 先注册一个service worker <script> // Check that service workers are registered if ('serviceWorker' in navigato...
  • 使用 workbox 协助构建 PWA 应用

    千次阅读 2018-03-15 17:00:37
    使用 workbox 协助构建 PWA 应用 1. 说明 workbox 是 GoogleChrome 团队推出的一套解决方案,提供站点离线访问能力,可以更方便、更简单的解决 Service Workers 绝大多数问题 重要文件版本 workbox-build ...
  • 我在Atlas kief/infra-workbox盒子的版本发布为kief/infra-workbox 。 请随时使用此功能,但请记住,我不保证它的可用性或在一个版本与下一个版本之间的一致性。 我主要使用此框在AWS上处理Terraf

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,244
精华内容 497
关键字:

workbox4