vite pwa项目使用

11,747 阅读9分钟

截止目前可能vite用户依旧不是很多,所以有关插件相关文章很少,比如今天介绍的主角vite-plugin-pwa。正好前段时间有相关使用经验,给仍在找寻文档的同学一个简易的介绍。

pwa介绍

一般说起PWA应用,很多人都会联想到这样一串概念:

PWA( 全称:Progressive Web App )渐进式的网页应用程序,这个Google在2015年提出,2016年6月才推广的项目有着非常显著的特点:

  • Reliable :当用户从手机主屏幕启动时,不用考虑网络的状态是如何,都可以立刻加载出 PWA。
  • Fast:这一点应该都很熟悉了吧,站在用户的角度来考虑,如果一个网页加载速度有点长的话,那么我们会放弃浏览该网站,所以 PWA 在这一点上做的很好,他的加载速度是很快的。
  • Engaging : PWA 可以添加在用户的主屏幕上,不用从应用商店进行下载,他们通过网络应用程序 Manifest file 提供类似于 APP 的使用体验( 在 Android 上可以设置全屏显示哦,由于 Safari 支持度的问题,所以在 IOS 上并不可以 ),并且还能进行 ”推送通知” 。

它可以理解为将传统web网页变成了一个app类型的网页,同理app需要的一些功能他也必须同样具备,其中最重要的功能之一便是加载离线数据

正常加载web网页,网页中的资源每次都需要网络请求成功才能显示,但是这显然在app上是不成立的,一个短时间内断网的应用也需要有能够加载渲染内容的能力让用户不至于不能使用,这其中需要用到的一个技术就是service worker

service worker

关于service worker的文章其实还是非常多的,我这里就只是简要介绍一下,你可以把它理解成服务器与浏览器之间的中间人角色,如果网站中注册了service worker那么它可以拦截当前网站所有的请求,进行判断(需要编写相应的判断程序),如果需要向服务器发起请求的就转给服务器,如果可以直接使用缓存的就直接返回缓存不再转给服务器。这样就可以很容易的解决离线数据拉取的问题,同样对于接口数据变更不频繁并且接口数据拉取慢的场景进行大幅优化。下面是一些细碎的介绍

  • 基于web worker(一个独立于JavaScript主线程的独立线程,在里面执行需要消耗大量资源的操作不会堵塞主线程)
  • 在web worker的基础上增加了离线缓存的能力
  • 本质上充当Web应用程序(服务器)与浏览器之间的代理服务器(可以拦截全站的请求,并作出相应的动作->由开发者指定的动作)
  • 创建有效的离线体验(将一些不常更新的内容缓存在浏览器,提高访问体验)
  • 由事件驱动的,具有生命周期
  • 可以访问cache和indexDB
  • 支持推送
  • 并且可以让开发者自己控制管理缓存的内容以及版本

关于service worker如何注册或者更详细的可以查看这篇文章 Service Worker ——这应该是一个挺全面的整理,个人觉得比较详细,基本上照着写就能实现相关功能。

vite-plugin-pwa

vite-plugin-pwa是vite的一个官方插件,它的功能就是通过简单的配置将你的vite项目变成pwa应用,其关于service worker的实现直接采用的谷歌开源库workbox,workbox内部帮你对缓存做了大量的逻辑代码处理,并且也支持非常多种不同的缓存策略,并且也封装好了sw.js文件的更新策略,另外它的配置也非常简单。有时候站在巨人的肩膀上是更好的一种选择。

workbox

workbox的使用这些都无太多可讲的,基本上照着文档使用即可,这里主要介绍一下它的目前封装好的4种缓存策略

  • Stale-While-Revalidate
  • Cache First
  • Network First
  • Network Only
  • Cache Only

注意以下源码都是进行精简过之后的,方便分析策略

Stale-While-Revalidate

这一缓存策略和先有的http的同名策略几乎相同,优先选择本地缓存,随后请求并更新缓存,换而言之,要到下一次请求才会正确更新。

image.png

我们再结合源码分析:

const fetchAndCacheResponse = this.requestWrapper.fetchAndCache({
  request: event.request,
  waitOnCache: this.waitOnCache,
  cacheResponsePlugin: this._cacheablePlugin,
}).catch(() => Response.error());

const cachedResponse = await this.requestWrapper.match({
  request: event.request,
});

return cachedResponse || await fetchAndCacheResponse;

这个缓存策略是首先通过service worker匹配缓存,并且每次仍然还是会fetch一次接口,并将请求的结果更新缓存。如果匹配不到缓存的情况下会直接返回fecth到的数据。

CACHE FIRST

缓存优先,顾名思义,优先访问缓存,如果在缓存不可用的情况下在使用网络进行请求

image.png

const cachedResponse = await this.requestWrapper.match({
  request: event.request,
});

return cachedResponse || await this.requestWrapper.fetchAndCache({
  request: event.request,
  waitOnCache: this.waitOnCache,
});

可以从代码发现每次都会先匹配缓存,缓存匹配不到才会重新fetch并缓存,所以这种方式对于数据更新频繁的情况并不适用。他比较适用于一些静态资源的缓存。

NETWORK FIRST

image.png

network-first 是一个比较复杂的策略,它接受networkTimeoutSeconds参数,如果没有传这个参数,请求将会发出,成功的话就返回结果添加到缓存中,如果失败则返回立即缓存。这种网络回退到缓存的方式虽然利于那些频繁更新的资源,但是在网络情况比较差的情况(无网会直接返回缓存)下,等待会比较久,这时networkTimeoutSeconds就提供了作用。如果设置了,会生成一个setTimeout后被resolve的缓存调用,再把它和请求放倒一个Promise.race中,那么请求超时后就会返回缓存。

NETWORK Only 、 Cache Only

NETWORK Only: NETWORK FIRST.png

Cache Only:

Cache FIRST.png 这两种就比较简单了,一种是只用fetch接口数据,一种是只用缓存,这两种一般都不适用。

vite-plugin-pwa使用

上面workbox的缓存策略介绍完成这边就主要介绍一下其使用方式了,配置非常简单

首先安装

yarn add vite-plugin-pwa --dev

接着在vite-config.js中配置好就行

import { VitePWA } from 'vite-plugin-pwa'
export const defineConfig({
  plugins: [
    VitePWA({})
  ]    
})

下面我列出常见的配置项,这边我直接把源码部分摘抄过来进行翻译解析

/**
 * Plugin options.
 */
export interface VitePWAOptions {
  /**
   * Build mode
   *
   * @default process.env.NODE_ENV or "production" 开发环境选择 一般不需要设置
   */
  mode?: 'development' | 'production'
  /**
   * @default 'public' 
   */
  srcDir?: string 
  /**
   * @default 'dist' sw.js输出目录 默认dist
   */
  outDir?: string 
  /**
   * @default 'sw.js' service worker最后生成的文件名 默认 sw.js
   */
  filename?: string
  /**
   * @default 'generateSW'  workbox生成service worker的模式,默认是generateSW
   * generateWS方法将在构建服务工作者时从使用服务工作者api中抽象出来。可以使用插件配置此方法,而不是编写您自己的服务工作者代码(`generateWS`将为您生成代码)。一般不需要高度定制service worker的情况直接选择generateSW就行。
   * injectManifest方法将获取您的自定义服务工作者并构建/编译它,这也就意味着你需要写更具体的service worker策略代码,一般injectManifest适用于复杂一点的缓存策略
   */
  strategies?: 'generateSW' | 'injectManifest'
  /**
   * The scope to register the Service Worker
   *
   * @default same as `base` of Vite's config 
   * service worker的scope, 一般不需要变动,相当于可以用来指定你想让service worker控制的内容的子目录
   * 不明白的可以直接访问官方文档 https://developer.mozilla.org/zh-CN/docs/Web/API/Service_Worker_API/Using_Service_Workers
   */
  scope?: string
  /**
   * Inject the service worker register inlined in the index.html 
   *
   * With `auto` set, depends on whether you used the `import { registerSW } from 'virtual:pwa-register'`
   * it will do nothing or use the `script` mode
   *
   * `inline` - inject a simple register, inlined with the generated html
   *
   * `script` - inject <script/> in <head>, with the `sr` to a generated simple register
   *
   * `null` - do nothing, you will need to register the sw you self, or imports from `virtual:pwa-register`
   *
   * @default 'auto'
   * service worker的注入方式,这个没特殊需求可以不用管
   */
  injectRegister: 'inline' | 'script' | 'auto' | null | false
  /**
   * Mode for the virtual register.
   * Does NOT available for `injectRegister` set to `inline` or `script`
   *
   * `prompt` - you will need to show a popup/dialog to the user to confirm the reload.
   *
   * `autoUpdate` - when new content is available, the new service worker will update caches and reload all browser
   * windows/tabs with the application open automatically, it must take the control for the application to work
   * properly.
   *
   * @default 'prompt'
   * 这个比较重要 sw.js的更新策略,autoUpdate会在sw.js有变动的情况下自动更新
   * prompt会触发相关钩子函数,让你手动选择更新 这个可以直接看官网例子 https://github1s.com/antfu/vite-plugin-pwa/blob/HEAD/examples/vue-router/src/ReloadPrompt.vue
   */
  registerType?: 'prompt' | 'autoUpdate'
  /**
   * Minify the generated manifest
   *
   * @default true 是否压缩manifest文件
   */
  minify: boolean
  /**
   * The manifest object
   * pwa应用相关配置,一般包括图标,应用名等,具体的可以直接看 https://github1s.com/antfu/vite-plugin-pwa/blob/HEAD/src/types.ts#L117
   */
  manifest: Partial<ManifestOptions> | false 
  /**
   * Whether to add the `crossorigin="use-credentials"` attribute to `<link rel="manifest">`
   * @default false
   */
  useCredentials?: boolean
  /**
   * The workbox object for `generateSW`
   * 在generateSW模式下配置workbox 后面会给出一份workbox配置的参考
   */
  workbox: Partial<GenerateSWOptions>
  /**
   * The workbox object for `injectManifest`
   */
  injectManifest: Partial<InjectManifestOptions>
  /**
   * Override Vite's base options only for PWA
   *
   * @default "base" options from Vite
   */
  base?: string
  /**
   * `public` resources to be added to the PWA manifest.
   *
   * You don't need to add `manifest` icons here, it will be auto included.
   *
   * The `public` directory will be resolved from Vite's `publicDir` option directory.
   */
  includeAssets: string | string[] | undefined
  /**
   * By default the icons listed on `manifest` option will be included
   * on the service worker *precache* if present under Vite's `publicDir`
   * option directory.
   */
  includeManifestIcons: true
}

然后在main.js文件中注册就可以享受service worker带来的极速load体验

import { createApp } from 'vue';
import App from '@/App.vue';
import { useRegisterSW } from 'virtual:pwa-register/vue';

useRegisterSW();

const app = createApp(App);
app.mount('#app', true);

下面我给出了一些常用的workbox配置

VitePWA({
      includeAssets: ['favicon.svg'],
      manifest: false,
      registerType: 'autoUpdate',
      workbox: {
        runtimeCaching: [
          {
            urlPattern: /someInterface/i, // 接口缓存 此处填你想缓存的接口正则匹配
            handler: 'CacheFirst',
            options: {
              cacheName: 'interface-cache',
            },
          },
          {
            urlPattern: /(.*?)\.(js|css|ts)/, // js /css /ts静态资源缓存
            handler: 'CacheFirst',
            options: {
              cacheName: 'js-css-cache',
            },
          },
          {
            urlPattern: /(.*?)\.(png|jpe?g|svg|gif|bmp|psd|tiff|tga|eps)/, // 图片缓存
            handler: 'CacheFirst',
            options: {
              cacheName: 'image-cache',
            },
          },
        ],
      },
    }),

以上就是我总结的一些简要的vite-plugin-pwa的使用文档、一些service worker以及workbox相关的知识。希望能够帮助更多同学少加班。