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

前提:多頁面vite項(xiàng)目給native提供h5頁面,設(shè)置離線緩存優(yōu)化體驗(yàn)

實(shí)現(xiàn)service worker離線緩存以前需要自己編寫sw.js文件內(nèi)容,比較復(fù)雜。 谷歌提供了workbox-*庫來協(xié)助寫配置。
vite-plugin-pwa就是幫你使用workbox結(jié)合一些模板代碼自動(dòng)生成sw.js,實(shí)現(xiàn)0配置

第一: 我們通過類型文件了解值得關(guān)注的配置項(xiàng)

引入很簡單

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

export default {
  plugins: [
    VitePWA()
  ]
}

上面簡單的配置就可以用,默認(rèn)緩存了所有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;
}

這里關(guān)注以下幾個(gè)配置,其他的不作關(guān)注:
(1) . strategies: 默認(rèn)是generateSW然后去配置workbox; 如果想要更多自定義的設(shè)置,可以選擇injectManifest,那就對應(yīng)配置injectManifest
以下內(nèi)容都是針對generateSW策略進(jìn)行配置
(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提供這個(gè)注銷配置,注銷代碼給我們寫好了

// 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>;
}

這里關(guān)注以下幾個(gè)配置,其他的不作關(guān)注:
(1)cacheId: 定義預(yù)緩存的名稱
(2)ignoreURLParametersMatching:默認(rèn)無法緩存html后面帶參數(shù)的頁面,加上它忽略參數(shù)就可以緩存了
(3)navigateFallback: 未命中緩存導(dǎo)航到的頁面,比如可以可以設(shè)置為index.html或者404.html.
認(rèn)識它是因?yàn)槲业亩囗撁骓?xiàng)目首次加載報(bào)錯(cuò):WorkboxError non-precached-url index.html,issue中也有提到。說:默認(rèn)為null并未生效,需要手動(dòng)寫為null
(4)runtimeCaching: 運(yùn)行時(shí)緩存,可以自定義配置各種類型的緩存,下面我們會講到

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>;
}

這里關(guān)注兩個(gè)配置,其他的不作關(guān)注:
(1)globPatterns: 可以看到上面我們說最簡單的配置會默認(rèn)緩存所有的html,js,css,也就是通過它實(shí)現(xiàn)的。如果想增加圖片緩存可以在里面添加
(2)globIgnores: 很明顯是用來忽略不想緩存的資源

了解了上面 的配置后,我們增加一些基礎(chǔ)配置優(yōu)化效果

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

export default {
  plugins: [
    VitePWA({
    // 忽略html后面的參數(shù),緩存有參數(shù)的html 
       ignoreURLParametersMatching: [/.*/],
    // 自定義緩存名稱 
      cacheId: 'wisbayar-cache',
  // 增加圖片類緩存
      globPatterns: ["**\/*.{js,css,html,png,jpg,svg}"],
   // 不設(shè)置回退url,依照實(shí)際情況配置
      navigateFallback: null,
    })
  ]
}

第二 :配置自定義運(yùn)行時(shí)緩存

上面第二點(diǎn)最后配置在全部緩存資源的情況下是足夠了,但是隨著項(xiàng)目的增大,我期望

  • 緩存get請求
  • 不預(yù)緩存所有資源,訪問到哪個(gè)頁面就緩存哪個(gè)頁面資源
  • 分類單獨(dú)設(shè)置js或者h(yuǎn)tml的緩存?zhèn)€數(shù)和時(shí)間
  • 圖片優(yōu)先緩存,js優(yōu)先使用網(wǎng)絡(luò)請求

要實(shí)現(xiàn)上面三點(diǎn),就要用到runtimeCaching配置了,我們先看看它的類型文件

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

這里關(guān)注以下配置,其他的不作關(guān)注:
(1)urlPattern: 通過正則,字符或者函數(shù)形式匹配要緩存的資源類型
(2)cacheName: 自定義緩存的類型名稱
(3)cacheableResponse: 緩存狀態(tài)碼正確的資源,比如200的
(4)expiration: 設(shè)置緩存的時(shí)間,數(shù)量。超過數(shù)量就刪除之前的
(5)method: 默認(rèn)是緩存get請求的資源,想緩存post的可以配置
(6)handler: 取緩存的策略,有五種

type StrategyName = 'CacheFirst' | 'CacheOnly' | 'NetworkFirst' | 'NetworkOnly' | 'StaleWhileRevalidate';
  • NetworkFirst:網(wǎng)絡(luò)優(yōu)先
  • CacheFirst:緩存優(yōu)先
  • NetworkOnly:僅使用正常的網(wǎng)絡(luò)請求
  • CacheOnly:僅使用緩存中的資源
  • StaleWhileRevalidate:從緩存中讀取資源的同時(shí)發(fā)送網(wǎng)絡(luò)請求更新本地緩存

根據(jù)這幾個(gè)類型,我一般這么設(shè)置:都使用NetworkFirst,圖片變動(dòng)不大用CacheFirst
StaleWhileRevalidate 可以用在文章之類的,之前使用StaleWhileRevalidate出現(xiàn)了兩個(gè)問題。

  1. 保留在當(dāng)前頁面,發(fā)布版本后回退再進(jìn)來還是使用緩存的。
  2. 會出現(xiàn)第一次請求html出現(xiàn)500,回退再進(jìn)來又正常的情況
    先取消注冊selfDestroying發(fā)布一次后,在開啟就好了

了解了以上配置后,再看看最終的配置代碼

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個(gè)圖
              maxEntries: 30
            }
          }
        },
        {
          urlPattern: /.*\.js.*/,
          handler: 'StaleWhileRevalidate',
          options: {
            cacheName: 'wisbayar-js',
            expiration: {
              maxEntries: 30, // 最多緩存30個(gè),超過的按照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.*/正則單獨(dú)匹配緩存html后,已經(jīng)可以緩存帶參數(shù)的html了,ignoreURLParametersMatching用不上了
(2)globPatterns設(shè)置為空數(shù)組,就是不做預(yù)緩存,全部使用runtimeCaching的能力實(shí)現(xiàn)進(jìn)行時(shí)緩存,訪問到哪個(gè)頁面就緩存哪個(gè)頁面的資源,并通過expiration設(shè)置了數(shù)量限制
(3)為什么我在接口緩存那里沒有直接使用process.env判斷是否與url.origin相等呢? 發(fā)現(xiàn)urlPattern函數(shù)里面不能寫變量,生成的sw.js直接把變量copy過去了

第三 :最后看效果,從瀏覽器中的觀察service workers。

service workers.png
  1. 只有配置了service worker的網(wǎng)頁(也就是在項(xiàng)目根目錄擁有sw.js的網(wǎng)頁),才能看到以上內(nèi)容

  2. Cache storage 就是緩存到本地的文件,我這里進(jìn)行了區(qū)分命名,分類緩存wisbaya-jswisbaya-css,wisbaya-imageswisbaya-html,以及wisbaya-api(get請求), 精細(xì)的文件類別緩存可以控制緩存文件數(shù)量。
    以上緩存是運(yùn)行時(shí)緩存,也就是點(diǎn)擊進(jìn)入某個(gè)頁面請求成功才緩存當(dāng)前頁面(首次加載不會生成,切換頁面就有了)
    區(qū)別于第一個(gè)緩存wisbaya-cache-precache是預(yù)緩存列表,只要訪問,vite-plugin-pwa默認(rèn)全部將所有js,css,html緩存起來

  3. 很多頁面都應(yīng)用了service worker,可以點(diǎn)擊see all registrations會看到很多曾經(jīng)訪問過的應(yīng)用了service worker的頁面

現(xiàn)在只要斷開網(wǎng)絡(luò),刷新網(wǎng)頁還能看到原頁面和數(shù)據(jù)展示

以上就是通過vite-plugin-pwa插件按照generateSW策略配置離線緩存的全部。
更多關(guān)于generateSW/injectManifest兩種策略可以查看官網(wǎng)Service Worker 策略和行為

我在類型文件中還看到針對兩種策略的webpack類型,配置差不多,下次可以在webpack中試試

export declare type WebpackGenerateSWOptions = BasePartial & WebpackPartial & GeneratePartial & WebpackGenerateSWPartial;
export declare type WebpackInjectManifestOptions = BasePartial & WebpackPartial & InjectPartial & WebpackInjectManifestPartial;
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容