首页 话题 小组 问答 好文 用户 我的社区 域名交易

[分享]PWA离线应用实践

发布于 2025-02-17 12:34:15
0
45

上篇文章整理了PWA离线应用的技术脉络和注意事项,本篇文章将记录使用Vue + Workbox + localforage 实现离线应用的实践过程。一、Vue工程改造Vue CLI 提供了一个官方的 ...

上篇文章整理了PWA离线应用的技术脉络和注意事项,本篇文章将记录使用Vue + Workbox + localforage 实现离线应用的实践过程。

一、Vue工程改造

Vue CLI 提供了一个官方的 PWA 插件,可以快速为 Vue 项目添加 PWA 支持。运行以下命令安装插件:

// 在vue工程目录下执行如下命令
vue add pwa

安装完成后,插件会自动生成以下文件:
src/registerServiceWorker.js:用于注册 Service Worker(已在入口文件引入)。
public/manifest.json:Web App Manifest 文件,PWA 的元数据(配置文件中使用)。

在项目根目录下修改 vue.config.js 文件,配置 PWA 插件的行为。以下是一个示例配置:

module.exports = {
    ...
    pwa: {
    name: '超体',
    themeColor: '#FDEAEC',
    msTileColor: '#000000',
    appleMobileWebAppCapable: 'yes',
    appleMobileWebAppStatusBarStyle: 'black',
    manifestPath: 'https://storage.360buyimg.com/xxx/manifest.json', // 由于部署的缘故,将上面的manifest.json文件路径替换为了绝对地址
    // 配置 workbox 插件
    workboxPluginMode: 'InjectManifest',
    workboxOptions: {
      importScripts: ['https://storage.googleapis.com/workbox-cdn/releases/5.1.4/workbox-sw.js'], // 引入workbox依赖文件
      exclude: [/\.html$/], // 不缓存的文件,按需配置
      swSrc: 'src/offline/service-worker.js', // 按实际路径引入
      // ...其它 Workbox 选项...
    }
  }
}

编写 service-worker.js 文件

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

if (workbox) {
  console.log(`Yay! Workbox is loaded`)
} else {
  console.log(`Boo! Workbox didn't load`)
}

workbox.core.setCacheNameDetails({
  prefix: 'jd-ct', // 缓存前缀
  suffix: 'v0.0.1'
})

workbox.core.skipWaiting() // 强制等待中的 Service Worker 被激活
workbox.core.clientsClaim() // Service Worker 被激活后使其立即获得页面控制权
workbox.precaching.precacheAndRoute(self.__precacheManifest || []) // 设置预加载,构建后会生成预加载资源文件 precache-manifest.js

//html采用networkFirst策略,支持离线也能大体访问
workbox.routing.registerRoute(
    /(.*\.html|.*pf.jd.com(\/?)|.*localhost(\/?)$)/,
    new workbox.strategies.NetworkFirst({
      cacheName: 'jd:ct:html',
      plugins: [
        new workbox.expiration.ExpirationPlugin({
          maxEntries: 10,
        })
      ]
    })
);

// 缓存web的css资源
workbox.routing.registerRoute(
    // Cache CSS files
    /.*\.css/,
    // 使用缓存,但尽快在后台更新
    new workbox.strategies.StaleWhileRevalidate({
      // 使用自定义缓存名称
      cacheName: 'jd:ct:css',
    })
)

// 缓存web的js资源
workbox.routing.registerRoute(
    // 缓存JS文件
    /(.*\.js|\/gljs)/,
    // 使用缓存,但尽快在后台更新
    new workbox.strategies.StaleWhileRevalidate({
      // 使用自定义缓存名称
      cacheName: 'jd:ct:js'
    })
)

// 缓存web的json资源
workbox.routing.registerRoute(
    // 缓存JS文件
    /(.*\.json)/,
    // 使用缓存,但尽快在后台更新
    new workbox.strategies.StaleWhileRevalidate({
      // 使用自定义缓存名称
      cacheName: 'jd:ct:json'
    })
)

// 缓存web的图片资源
workbox.routing.registerRoute(
    function ({ url }) {
      const reg = /\.(?:png|gif|jpg|jpeg|svg|webp|avif)$/
      const isCache = reg.test(url.href)
      return isCache
    },
    new workbox.strategies.CacheFirst({
      cacheName: 'jd:ct:img',
      plugins: [
        new workbox.expiration.ExpirationPlugin({ // 此处的配置指在优化清除缓存资源
          maxEntries: 60,
          maxAgeSeconds: 30 * 24 * 60 * 60, // 设置缓存有效期为30天
          purgeOnQuotaError: true,
        })
      ],
    })
)

// ** 构建时自动生成,无需编写 **
// 该文件会记录构建文件和工程中的一些icon,资源内容需按需引用,毕竟预加载同样占用网络资源
self.__precacheManifest = (self.__precacheManifest || []).concat([
  {
    "revision": "f4421206102fa2971be968f4576b3753",
    "url": "favicon.ico"
  },
  {
    "revision": "d60ab2076b97d8a76e06",
    "url": "js/chunk-vendors.f45b0b1a.js"
  },
  {
    "revision": "d60ab2076b97d8a76e06",
    "url": "js/chunk-vendors.f45b0b1a.js.map"
  },
  {
    "revision": "148cadf1d0b64e5aa7d7",
    "url": "js/index.7058d434.js"
  },
  {
    "revision": "a26142391a19d57bd5745cf1cc159eca",
    "url": "js/three-140.min.js"
  },
  {
    "revision": "f147ae01feb6e9dbc161ae9e1befe720",
    "url": "js/three.js"
  }
]);

至此,我们的PWA应用已经改造完成了,构建后将会看到以下输出文件 index.html、precache-manifest.js、service-worker.js等

<!DOCTYPE html>
<html lang="">
<head>
  <meta charset="utf-8">
  <link rel="icon" type="image/png" sizes="32x32" href="img/icons/favicon-32x32.png">
  <link rel="icon" type="image/png" sizes="16x16" href="img/icons/favicon-16x16.png">
  <link rel="manifest" href="https://storage.360buyimg.com/arvideo/chaoti/manifest.json">
  <meta name="theme-color" content="#FDEAEC">
  <meta name="apple-mobile-web-app-capable" content="yes">
  <meta name="apple-mobile-web-app-status-bar-style" content="black">
  <meta name="apple-mobile-web-app-title" content="xxx">
  <link rel="apple-touch-icon" href="img/icons/apple-touch-icon-152x152.png">
  <link rel="mask-icon" href="img/icons/safari-pinned-tab.svg" color="#FDEAEC">
  <meta name="msapplication-TileImage" content="img/icons/msapplication-icon-144x144.png">
  <meta name="msapplication-TileColor" content="#000000">
</head>
<body>
</body>
</html>

本地要访问不能使用file://方式,需要起个本地服务(推荐:http-server)

二、接口离线访问

通过上面的改造实现了静态资源的离线访问,但部分场景可能依赖接口数据才能看到页面内容,针对此场景采用了indexdb的缓存方案,将部分接口数据存储到本地,提升应用的离线可用性。

import localforage from 'localforage'

localforage.config({
  driver: [localforage.INDEXEDDB, localforage.WEBSQL, localforage.LOCALSTORAGE],
  name: 'jd-ct',
  description: 'xxx',
})

export const atlasListStore = localforage.createInstance({
  name: 'atlasList',
})

// 判断网络连接状态,按需调用本地数据or接口请求
export const getLocalForageData = (onlineCallback, offlineCallback) => {
  if (navigator.onLine) {
    onlineCallback && onlineCallback()
  } else {
    offlineCallback && offlineCallback()
  }
}
// 业务代码示例
// 获取接口数据,并存储到本地
getAtlasList: function (isRefresh = false) {
      getLocalForageData(() => {
        // 获取远程数据
        services.designTool
            .GetDigitalPhotoList(this.searchParams)
            .then((res) => {
              if (res.code === 0) {
                // 更新本地数据
                (res.data.data || []).forEach((item) => {
                    atlasListStore.setItem(String(item.id), item)
                  })
              } else {
                return Promise.reject()
              }
            })
            .catch(() => {
              
            })
      }, () => {
        // 获取本地数据
        const result = []
        atlasListStore.iterate((value) => {})
            .then(() => {})
            .catch((err) => {
              console.log('catch',err)
            })
      })
    },

三、调试

图片alt

目前实用chrome对PWA应用调试也是非常方便的:
1)可以在 chrome://serviceworker-internals/ 页面中查看到全部PWA应用
2)也可以在控制台中查看

图片alt

图片alt

图片alt

图片alt

另外也可以查看indexdb缓存数据

四、注意事项

1、项目迭代过程中需要维护server worker缓存版本,可通过suffix: 'v0.0.1'更新
2、虽然部分旧版浏览器不支持service worker,但在要求不是很高或者用户不是很广泛的场景下可使用
3、Service Worker 运行在独立线程中,调试相对复杂,workbox也有调试模式,缓存和命中会输出在控制台,可以减轻调试压力

评论
站长交流