通过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<VitePWAOptions>): 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<GenerateSWOptions>;
    /**
     * The workbox object for `injectManifest`
     */
    injectManifest: Partial<CustomInjectManifestOptions>;
    /**
     * 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<GenerateSWOptions>
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<RegExp>;
    /**
     * 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<RuntimeCaching>;
}

这里关注以下几个配置,其他的不作关注:
(1)cacheId: 定义预缓存的名称
(2)ignoreURLParametersMatching:默认无法缓存html后面带参数的页面,加上它忽略参数就可以缓存了
(3)navigateFallback: 未命中缓存导航到的页面,比如可以可以设置为index.html或者404.html.
认识它是因为我的多页面项目首次加载报错:WorkboxError non-precached-url index.htmlissue中也有提到。说:默认为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<string>;
    /**
     * 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<string>;
}

这里关注两个配置,其他的不作关注:
(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<RuntimeCaching>;
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:从缓存中读取资源的同时发送网络请求更新本地缓存

根据这几个类型,我一般这么设置:都使用NetworkFirst,图片变动不大用CacheFirst
StaleWhileRevalidate 可以用在文章之类的,之前使用StaleWhileRevalidate出现了两个问题。

  1. 保留在当前页面,发布版本后回退再进来还是使用缓存的。
  2. 会出现第一次请求html出现500,回退再进来又正常的情况
    先取消注册selfDestroying发布一次后,在开启就好了

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

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;
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,063评论 6 510
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,805评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,403评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,110评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,130评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,877评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,533评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,429评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,947评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,078评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,204评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,894评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,546评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,086评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,195评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,519评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,198评论 2 357

推荐阅读更多精彩内容