Skip to content

PWA 相关知识点

Web App Manifest

描述应用的 JSON 文件,通知支持 pwa 的浏览器将应用添加到主屏 web-app-manifest

  • 在静态资源目录下新建 manifest.json

    json
    {
      "name": "xx Web APP", // 启动过程中显示的文案
      "short_name": "xxx", // 显示在主屏 app 图标下的文案
      "description": "一个 xxx APP", // 描述应用的文案
      "icons": [
        {
          // 小的显示在主屏上,大的显示在安装过程中
          "src": "https://static.xxx.com/img/48.png",
          "sizes": "48x48",
          "type": "image/png"
        },
        {
          "src": "https://static.xxx.com/img/192.png",
          "sizes": "192x192",
          "type": "image/png"
        }
      ],
      "start_url": "/?utm_source=homescreen", // 主屏启动进入的页面路径 - utm_source 可用于统计流量来源
      "display": "standalone", // 定义浏览器 UI - standalone 全屏
      "orientation": "portrait", // 竖屏
      "background_color": "#f2f2f2", // 背景色
      "theme_color": "#000" // 主题色
    }
  • html 里引入

    html
    <link rel="manifest" href="/static/manifest.json" />

  • ga 埋点统计添加到主屏的用户

    js
    window.addEventListener('beforeinstallprompt', function (e) {
      window.ga('send', 'event', 'pwa', 'addToScreen', 'installprompt', 1)
      e.userChoice.then(function (choiceResult) {
        window.ga('send', 'event', 'pwa', 'addToScreen', choiceResult.outcome, 1)
      })
    })

service workers

缓存策略等 Workbox

  • 创建(构建)service-worker.js

    • node 脚本构建 npm install workbox-build -D

      • 先创建好 service-worker.js

        js
        // -> src/service-worker.js
        workbox.precaching.precacheAndRoute([]) // 后面脚本会在 [] 插入
        // ...
        // workbox.routing -> Reg 匹配静态 or 动态路由的缓存策略
      • 执行脚本

        js
        const workboxBuild = require('workbox-build')
        workboxBuild
          .injectManifest({
            swSrc: 'src/service-worker.js', // 上面建好的文件
            swDest: 'dist/service-worker.js', // 输出的文件
            globDirectory: '/dist/static/',
            globPatterns: ['**/*.{js,css}', '**/img/*.{jpg,png,ico}'], // 匹配要缓存的
            globIgnores: ['service-worker.js'], // 忽略的
            // 配置资源路径前缀(如果静态资源部署到单独服务器的话会有用)
            modifyUrlPrefix: { '': config.build.assetsPublicPath },
          })
          .then(({ count, size, warnings }) => {
            warnings.forEach(console.warn)
            console.log(`${count} files will be precached, totaling ${size} bytes.`)
          })
    • webpack 构建 npm install workbox-webpack-plugin -D

      • 先创建 service-worker.js

        js
        // ... 插件会在这里插入 self.__precacheManifest 变量和依赖
        // precaching -> webpack 打包后的资源缓存
        workbox.precaching.suppressWarnings()
        workbox.precaching.precacheAndRoute(self.__precacheManifest, {})
        // ...
        // workbox.routing -> Reg 匹配静态 or 动态路由的缓存策略
      • webpack 插件配置 webpack-chain

        require('workbox-webpack-plugin').InjectManifestworkboxBuild.injectManifest

        js
        const { InjectManifest } = require('workbox-webpack-plugin')
        // InjectManifest 参数同上
        config.plugin('pwa').use(InjectManifest, [
          {
            swSrc: 'src/service-worker.js',
            importsDirectory: './dist/js',
            exclude: [/\.html$/],
            // ...
          },
        ])
      • 缓存策略

        • 静态资源预缓存 precacheAndRoute

          • 根据资源文件内容生成 hash 版本号,文件变化后自动更新缓存
        • workbox.routing cacheFirst

          js
          workbox.routing.registerRoute(
            new RegExp(/123/), // 匹配路由
            workbox.strategies.cacheFirst({
              // 缓存优先
              cacheName: 'cross-cache', // 后台缓存的 key
              plugins: [
                new workbox.expiration.Plugin({
                  maxEntries: 100, // 缓存最大数
                  maxAgeSeconds: 30 * 24 * 60 * 60, // 过期
                }),
              ],
            })
          )
        • NetworkFirst 网络优先

        • StaleWhileRevalidate 缓存、网络竞速

        • NetworkOnly CacheOnly

  • 注册 service-worker.js (上面生成的)

    需尽可能早的执行

    js
    if (location.protocol === 'https:' && 'serviceWorker' in navigator) {
      navigator.serviceWorker
        .register('/service-worker.js')
        .then(function (registration) {
          console.log('Service Worker scope: ', registration.scope)
        })
        .catch(function (err) {
          console.log(err)
        })
    }