通过vite-plugin-pwa配置了解pwa

前提:多页面vite项目给native提供h5页面,设置离线缓存优化体验

实现service worker离线缓存以前需要自己编写sw.js文件内容,比较复杂。 谷歌提供了workbox-*库来协助写配置。
vite-plugin-pwa就是帮你使用workbox结合一些模板代码自动生成sw.js,实现0配置

第一: 我们通过类型文件了解值得关注的配置项

引入很简单

// vite.config.js / vite.config.ts
import { VitePWA } from 'vite-plugin-pwa'

export default {
  plugins: [
    VitePWA()
  ]
}

上面简单的配置就可以用,默认缓存了所有js,css,html
如果想做更多配置,推荐看代码里面插件的类型文件,里面写的很清楚

1. 先看VitePWAOptions
VitePWA(userOptions?: Partial): Plugin[]
interface VitePWAOptions {
    /**
     * Build mode
     *
     * @default process.env.NODE_ENV or "production"
     */
    mode?: 'development' | 'production';
    /**
     * @default 'generateSW'
     */
    strategies?: 'generateSW' | 'injectManifest';
    /**
     * The workbox object for `generateSW`
     */
    workbox: Partial;
    /**
     * The workbox object for `injectManifest`
     */
    injectManifest: Partial;
    /**
     * Unregister the service worker?
     *
     * @default false
     */
    selfDestroying?: boolean;
}

这里关注以下几个配置,其他的不作关注:
(1) . strategies: 默认是generateSW然后去配置workbox; 如果想要更多自定义的设置,可以选择injectManifest,那就对应配置injectManifest
以下内容都是针对generateSW策略进行配置
(2). workbox: 给generateSW的配置,配置的所有workbox,将交给workbox-build插件中的generateSW处理,生成最终sw.js中的配置代码

// vite-plugin-pwa/index.js
function loadWorkboxBuild() {
  return require("workbox-build");
}
const { generateSW } = loadWorkboxBuild();
  const buildResult = await generateSW(options2.workbox);

(3). selfDestroyingvite-plugin-pwa提供这个注销配置,注销代码给我们写好了

// vite-plugin-pwa/index.js
if (options2.selfDestroying) {
    const selfDestroyingSW = `
self.addEventListener('install', function(e) {
  self.skipWaiting();
});
self.addEventListener('activate', function(e) {
  self.registration.unregister()
    .then(function() {
      return self.clients.matchAll();
    })
    .then(function(clients) {
      clients.forEach(client => client.navigate(client.url))
    });
});
    `;
    await import_fs.promises.writeFile(options2.swDest.replace(/\\/g, "/"), selfDestroyingSW, { encoding: "utf8" });
....
2. 再看workbox的类型:GenerateSWOptions 中的 GeneratePartial 和GlobPartial
workbox: Partial
type GenerateSWOptions = BasePartial & GlobPartial & GeneratePartial & RequiredSWDestPartial & OptionalGlobDirectoryPartial;

GeneratePartial

export interface GeneratePartial {
    /**
     * An optional ID to be prepended to cache names. This is primarily useful for
     * local development where multiple sites may be served from the same
     * `http://localhost:port` origin.
     */
    cacheId?: string | null;
    /**
     * Any search parameter names that match against one of the RegExp in this
     * array will be removed before looking for a precache match. This is useful
     * if your users might request URLs that contain, for example, URL parameters
     * used to track the source of the traffic. If not provided, the default value
     * is `[/^utm_/, /^fbclid$/]`.
     *
     */
    ignoreURLParametersMatching?: Array;
    /**
     * If specified, all
     * [navigation requests](https://developers.google.com/web/fundamentals/primers/service-workers/high-performance-loading#first_what_are_navigation_requests)
     * for URLs that aren't precached will be fulfilled with the HTML at the URL
     * provided. You must pass in the URL of an HTML document that is listed in
     * your precache manifest. This is meant to be used in a Single Page App
     * scenario, in which you want all navigations to use common
     * [App Shell HTML](https://developers.google.com/web/fundamentals/architecture/app-shell).
     * @default null
     */
    navigateFallback?: string | null;
    runtimeCaching?: Array;
}

这里关注以下几个配置,其他的不作关注:
(1)cacheId: 定义预缓存的名称
(2)ignoreURLParametersMatching:默认无法缓存html后面带参数的页面,加上它忽略参数就可以缓存了
(3)navigateFallback: 未命中缓存导航到的页面,比如可以可以设置为index.html或者404.html.
认识它是因为我的多页面项目首次加载报错:WorkboxError non-precached-url index.html,issue中也有提到。说:默认为null并未生效,需要手动写为null
(4)runtimeCaching: 运行时缓存,可以自定义配置各种类型的缓存,下面我们会讲到

GlobPartial

export interface GlobPartial {
/**
     * A set of patterns matching files to always exclude when generating the
     * precache manifest. For more information, see the definition of `ignore` in
     * the `glob` [documentation](https://github.com/isaacs/node-glob#options).
     * @default ["**\/node_modules\/**\/*"]
     */
    globIgnores?: Array;
    /**
     * Files matching any of these patterns will be included in the precache
     * manifest. For more information, see the
     * [`glob` primer](https://github.com/isaacs/node-glob#glob-primer).
     * @default ["**\/*.{js,css,html}"]
     */
    globPatterns?: Array;
}

这里关注两个配置,其他的不作关注:
(1)globPatterns: 可以看到上面我们说最简单的配置会默认缓存所有的html,js,css,也就是通过它实现的。如果想增加图片缓存可以在里面添加
(2)globIgnores: 很明显是用来忽略不想缓存的资源

了解了上面 的配置后,我们增加一些基础配置优化效果

// vite.config.js / vite.config.ts
import { VitePWA } from 'vite-plugin-pwa'

export default {
  plugins: [
    VitePWA({
    // 忽略html后面的参数,缓存有参数的html 
       ignoreURLParametersMatching: [/.*/],
    // 自定义缓存名称 
      cacheId: 'wisbayar-cache',
  // 增加图片类缓存
      globPatterns: ["**\/*.{js,css,html,png,jpg,svg}"],
   // 不设置回退url,依照实际情况配置
      navigateFallback: null,
    })
  ]
}

第二 :配置自定义运行时缓存

上面第二点最后配置在全部缓存资源的情况下是足够了,但是随着项目的增大,我期望

  • 缓存get请求
  • 不预缓存所有资源,访问到哪个页面就缓存哪个页面资源
  • 分类单独设置js或者html的缓存个数和时间
  • 图片优先缓存,js优先使用网络请求

要实现上面三点,就要用到runtimeCaching配置了,我们先看看它的类型文件

runtimeCaching?: Array;
export interface RuntimeCaching {
    handler: RouteHandler | StrategyName;
    method?: HTTPMethod;
    options?: {
        cacheableResponse?: CacheableResponseOptions;
        cacheName?: string | null;
        expiration?: ExpirationPluginOptions;
    };
    urlPattern: RegExp | string | RouteMatchCallback;
}

这里关注以下配置,其他的不作关注:
(1)urlPattern: 通过正则,字符或者函数形式匹配要缓存的资源类型
(2)cacheName: 自定义缓存的类型名称
(3)cacheableResponse: 缓存状态码正确的资源,比如200的
(4)expiration: 设置缓存的时间,数量。超过数量就删除之前的
(5)method: 默认是缓存get请求的资源,想缓存post的可以配置
(6)handler: 取缓存的策略,有五种

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

根据这几个类型,我一般这么设置:get请求缓存用NetworkFirst,图片变动不大用CacheFirst,其他js,html用StaleWhileRevalidate

了解了以上配置后,再看看最终的配置代码

VitePWA({
    workbox: {
      // ignoreURLParametersMatching: [/.*/],
      cacheId: 'wisbayar-cache',
      globPatterns: [],
      // globIgnores: ['static/js/**'],
      navigateFallback: null,
      runtimeCaching: [
        mode !== 'production'
          ? {
              urlPattern: ({ url }) => url.origin === 'https://app-api-0.com',
              handler: 'NetworkFirst',
              options: {
                cacheName: 'wisbayar-api',
                cacheableResponse: {
                  statuses: [200]
                }
              }
            }
          : {
              urlPattern: ({ url }) => url.origin === 'https://app-api.id',
              handler: 'NetworkFirst',
              options: {
                cacheName: 'wisbayar-api',
                cacheableResponse: {
                  statuses: [200]
                }
              }
            },
        {
          urlPattern: /\.(?:png|jpg|jpeg|svg)$/,
          handler: 'CacheFirst',
          options: {
            cacheName: 'wisbayar-images',
            expiration: {
              // 最多30个图
              maxEntries: 30
            }
          }
        },
        {
          urlPattern: /.*\.js.*/,
          handler: 'StaleWhileRevalidate',
          options: {
            cacheName: 'wisbayar-js',
            expiration: {
              maxEntries: 30, // 最多缓存30个,超过的按照LRU原则删除
              maxAgeSeconds: 30 * 24 * 60 * 60
            },
            cacheableResponse: {
              statuses: [200]
            }
          }
        },
        {
          urlPattern: /.*\.css.*/,
          handler: 'StaleWhileRevalidate',
          options: {
            cacheName: 'wisbayar-css',
            expiration: {
              maxEntries: 20,
              maxAgeSeconds: 30 * 24 * 60 * 60
            },
            cacheableResponse: {
              statuses: [200]
            }
          }
        },
        {
          urlPattern: /.*\.html.*/,
          handler: 'StaleWhileRevalidate',
          options: {
            cacheName: 'wisbayar-html',
            expiration: {
              maxEntries: 20,
              maxAgeSeconds: 30 * 24 * 60 * 60
            },
            cacheableResponse: {
              statuses: [200]
            }
          }
        }
      ]
    },
    // 取消注册
    selfDestroying: false
  })

说明一下:
(1)上面使用了urlPattern: /.*\.html.*/正则单独匹配缓存html后,已经可以缓存带参数的html了,ignoreURLParametersMatching用不上了
(2)globPatterns设置为空数组,就是不做预缓存,全部使用runtimeCaching的能力实现进行时缓存,访问到哪个页面就缓存哪个页面的资源,并通过expiration设置了数量限制
(3)为什么我在接口缓存那里没有直接使用process.env判断是否与url.origin相等呢? 发现urlPattern函数里面不能写变量,生成的sw.js直接把变量copy过去了

第三 :最后看效果,从浏览器中的观察service workers。

service workers.png
  1. 只有配置了service worker的网页(也就是在项目根目录拥有sw.js的网页),才能看到以上内容

  2. Cache storage 就是缓存到本地的文件,我这里进行了区分命名,分类缓存wisbaya-jswisbaya-csswisbaya-imageswisbaya-html,以及wisbaya-api(get请求), 精细的文件类别缓存可以控制缓存文件数量。
    以上缓存是运行时缓存,也就是点击进入某个页面请求成功才缓存当前页面(首次加载不会生成,切换页面就有了)
    区别于第一个缓存wisbaya-cache-precache是预缓存列表,只要访问,vite-plugin-pwa默认全部将所有js,css,html缓存起来

  3. 很多页面都应用了service worker,可以点击see all registrations会看到很多曾经访问过的应用了service worker的页面

现在只要断开网络,刷新网页还能看到原页面和数据展示

以上就是通过vite-plugin-pwa插件按照generateSW策略配置离线缓存的全部。
更多关于generateSW/injectManifest两种策略可以查看官网Service Worker 策略和行为

我在类型文件中还看到针对两种策略的webpack类型,配置差不多,下次可以在webpack中试试

export declare type WebpackGenerateSWOptions = BasePartial & WebpackPartial & GeneratePartial & WebpackGenerateSWPartial;
export declare type WebpackInjectManifestOptions = BasePartial & WebpackPartial & InjectPartial & WebpackInjectManifestPartial;

你可能感兴趣的:(通过vite-plugin-pwa配置了解pwa)