workbox构建pwa应用配置

2,452 阅读1分钟

使用workbox构建pwa应用配置

1.webpack项目引入workbox插件:workbox-webpack-plugin

const WorkBoxPlugin = require('workbox-webpack-plugin')
module.exports = {
  // ...
  plugins: [
    // ...
    // swSrc: 自定义部分sw文件
    // swDest: 生成sw目标文件,将会用swSrc的sw文件作为模板注入
    // 静态资源预缓存列表匹配,配置视情况而定
    new WorkBoxPlugin.InjectManifest({
      swSrc: path.resolve(__dirname, 'src/service-worker.js'),
      swDest: path.resolve(BUILD_PATH, 'service-worker.js'),
      include: [/\.(?:js|css)$/],
    }),
  ]
}

2.设置缓存策略: src/service-worker.js

// offline ga init
workbox.googleAnalytics.initialize();

// 引入预缓存列表(precacheManifest 是workbox自动生成的)
workbox.precaching.precacheAndRoute(
  self.__precacheManifest || [], {
    ignoreUrlParametersMatching: [/./],
    cleanUrls: false,
  }
);

// 本地图片
workbox.routing.registerRoute(
  new RegExp('/static/img/'),
  workbox.strategies.cacheFirst({
    // Use a custom cache name
    cacheName: 'static-img-cache',
    plugins: [
      new workbox.cacheableResponse.Plugin({
        statuses: [0, 200]
      }),
      new workbox.expiration.Plugin({
        // Cache only 50 images
        maxEntries: 50,
      })
    ],
  })
)

// CDN图片缓存
workbox.routing.registerRoute(
  new RegExp('^https://d1vs5f.XXXX.net/'),
  workbox.strategies.cacheFirst({
    cacheName: 'cdn-images-cache',
    plugins: [
      new workbox.cacheableResponse.Plugin({
        statuses: [0, 200]
      }),
      new workbox.expiration.Plugin({
        maxEntries: 300,
        // Cache for a maximum of a week
        maxAgeSeconds: 7 * 24 * 60 * 60,
      })
    ]
  }),
)

// workbox-cdn
workbox.routing.registerRoute(
  new RegExp('^https://storage.googleapis.com/workbox-cdn/releases/'),
  workbox.strategies.cacheFirst({
    cacheName: 'workbox-cdn',
    plugins: [
      new workbox.cacheableResponse.Plugin({
        statuses: [0, 200]
      }),
      new workbox.expiration.Plugin({
        maxEntries: 10
      })
    ]
  })
)

// iconfont
workbox.routing.registerRoute(
  new RegExp('^https://m.cfcdn.club/static/icons'),
  workbox.strategies.cacheFirst({
    cacheName: 'icons-cache',
    plugins: [
      new workbox.cacheableResponse.Plugin({
        statuses: [0, 200]
      }),
      new workbox.expiration.Plugin({
        maxEntries: 50
      })
    ],
  })
)

// cache google analytics js
workbox.routing.registerRoute(
  new RegExp('^https://www.google-analytics.com/'),
  workbox.strategies.cacheFirst({
    cacheName: 'google-analytics',
    plugins: [
      new workbox.cacheableResponse.Plugin({
        statuses: [0, 200]
      }),
      new workbox.expiration.Plugin({
        maxEntries: 2
      })
    ]
  })
)

// cache facebook pixel js
workbox.routing.registerRoute(
  new RegExp('^https://connect.facebook.net/'),
  workbox.strategies.cacheFirst({
    cacheName: 'facebook-pixel',
    plugins: [
      new workbox.cacheableResponse.Plugin({
        statuses: [0, 200]
      }),
      new workbox.expiration.Plugin({
        maxEntries: 2
      })
    ]
  })
)

// 其他静态资源:cacheFirst
workbox.routing.registerRoute(
  new RegExp('/static/'),
  workbox.strategies.cacheFirst({
    cacheName: 'server-static',
    plugins: [
      new workbox.cacheableResponse.Plugin({
        statuses: [0, 200]
      }),
      new workbox.expiration.Plugin({
        maxEntries: 50
      })
    ]
  })
)

// get接口请求: networkFirst
workbox.routing.registerRoute(
  new RegExp('/api/'),
  workbox.strategies.networkFirst({
    cacheName: 'api-cache',
  })
)

// html: networkFirst
const routerName = ["home","product","payment","account","service","orders","order","categories","cart","size","search","reviews","cod","trackinginfo","forgot","resetpassword"];
workbox.routing.registerRoute(
  // new RegExp('/')
  new RegExp('/('+routerName.join("|")+')(?!(.html|.html?.*))'),
  workbox.strategies.networkFirst({
    cacheName: 'url-cache',
  })
)

3.注册:serviceWorker

// 这个文件写到index.js

(function () {
  'use strict';

  // Check to make sure service workers are supported in the current browser,
  // and that the current page is accessed from a secure origin. Using a
  // service worker from an insecure origin will trigger JS console errors.
  var isLocalhost = Boolean(window.location.hostname === 'localhost' ||
    // [::1] is the IPv6 localhost address.
    window.location.hostname === '[::1]' ||
    // 127.0.0.1/8 is considered localhost for IPv4.
    window.location.hostname.match(
      /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
    )
  );

  window.addEventListener('load', function () {
    if ('serviceWorker' in navigator &&
      (window.location.protocol === 'https:' || isLocalhost)) {
      navigator.serviceWorker.register('/service-worker.js')
        .then(function (registration) {
          // updatefound is fired if service-worker.js changes.
          registration.onupdatefound = function () {
            // updatefound is also fired the very first time the SW is installed,
            // and there's no need to prompt for a reload at that point.
            // So check here to see if the page is already controlled,
            // i.e. whether there's an existing service worker.
            if (navigator.serviceWorker.controller) {
              // The updatefound event implies that registration.installing is set
              var installingWorker = registration.installing;

              installingWorker.onstatechange = function () {
                switch (installingWorker.state) {
                  case 'installed':
                    // At this point, the old content will have been purged and the
                    // fresh content will have been added to the cache.
                    // It's the perfect time to display a "New content is
                    // available; please refresh." message in the page's interface.
                    break;

                  case 'redundant':
                    throw new Error('The installing ' +
                      'service worker became redundant.');

                  default:
                    // Ignore
                }
              };
            }
          };
        }).catch(function (e) {
          console.error('Error during service worker registration:', e);
        });
    }
  });

  window.addEventListener('beforeinstallprompt', function (event) {
    event.userChoice.then(choiceResult => {
      // add to homescreen report ga
      window.ga('send', 'event', 'add2home', choiceResult.outcome)
    })
  })
})();
 

--------------------------------------
// webpack

function loadMinified = (filePath) {
  const code = fs.readFileSync(filePath, 'utf-8')
  const result = UglifyJS.minify(code)
  if (result.error) return ''
  return result.code
}

...
new HtmlWebpackPlugin({
      filename: entryIndex,
      template: 'index.html',
      serviceWorkerLoader: `<script>${loadMinified(path.join(__dirname,
        './service-worker-prod.js'))}</script>`,
      ...
    }),
....

4. 增加一些离线提示

componentDidMount () {
      // 判断是否离线
      if (window.navigator && window.navigator.onLine === false) {
        this.$toast('兄弟,你当前是离线状态')
      }
      window.addEventListener('online', () => {
        this.$toast('嘿,上线了,请刷新页面')
      }, false)

      window.addEventListener('offline', () => {
        this.$toast('兄弟,你当前是离线状态')
      }, false)
}

5. 添加 manifest文件

// webpack
const AppManifestWebpackPlugin = require('app-manifest-webpack-plugin')

...
 plugins: [
  ...
  new AppManifestWebpackPlugin({
      logo: path.resolve(__dirname, '../static/img/logo.png'),
      persistentCache: false,
      prefix: config.cdn,
      output: '/static/icons-' + config.iconVersion + '-[hash:8]/',
      config: {
        appName: 'My PWA Site',
        shortName: 'MY Site',
        scope: config.host,
        start_url: url.resolve(config.host, "/?utm_source=homescreen"),
        display: "standalone",
        orientation: "portrait",
        background_color: "#FFFFFF",
        theme_color: "#FFFFFF",
        version: '1.0',
      }
    }),
]

...