普通网站如何快速接入 PWA

2,762 阅读3分钟

前言

PWA的好处 我就不提了,介绍类文章已经非常多了。产品需要这个作为流量入口,不考虑离线网络访问,缓存维护等等,只想多一个用户访问链接的前提下,如何快速接入PWA成为了我的需求。

必备前提

  • manifest.json 物料配置清单
  • service-worker.js 静态swjs,这里可以定义较多的 缓存维护策略,请参考其他文章

以上物料建议放在网站根目录
https 环境

A2HS 介绍

A2HS(Add To Home Screen)从chrome版本68开始,开发人员将必须通过捕获 beforeinstallprompt 手动触发提示。

官方声明如下:
    1. The web app is not already installed
    1. Meets a user engagement heuristic (currently, the user has interacted with the domain for at least 30 seconds)
    1. Meets the Progressive Web App criteria:\
    • (a) Includes a web app manifest that includes:
      • (i) short_name or name
      • (ii) icons mustinclude a 192px and a 512px sized icons
      • (iii) start_url
      • (iiii) display must be one of: fullscreen, standalone, or minimal-ui
    • (b) Served over HTTPS (required for service workers)
    • (c) Has registered a service worker with a fetch event handler

业务接入

1. 是否可以启用 serviceWorker

/* Only register a service worker if it's supported */
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/service-worker.js');
}

2. 监听 beforeinstallprompt 事件

把 beforeinstallprompt 的事件处理绑定到 一个window对象上去,建议是全局变量。因为后面会用到,请确保后续操作的作用域内可访问该变量

window.addEventListener('beforeinstallprompt', (event) => {
  event.preventDefault();
  console.log('👍', 'beforeinstallprompt', event);
  // 这个变量后面要用
  window.deferredPrompt = event;
  // 你用来显示 添加到PWA 的 按钮可以显示出来了
  // your code here
});

3. 处理添加到pwa的 您的DOM事件点击事件

主要是为了 调用上面绑定的变量 deferredPrompt.prompt(), 请确保您的事件是通过用户点击事件来触发调用的,否则将不会生效。

// butInstall替换为 你的DOM
const butInstall = document.getElementById('butInstall');

butInstall.addEventListener('click', () => {
  console.log('👍', 'butInstall-clicked');
  const promptEvent = window.deferredPrompt;
  if (!promptEvent) {
    // deferred prompt 不可用
    return;
  }
  promptEvent.prompt();
  promptEvent.userChoice.then((choiceResult) => {
  console.log('👍', 'userChoice', choiceResult);
    if (choiceResult.outcome === 'accepted') {
      console.log('用户 同意了');
      // 成功之后的业务回掉操作
      // your code here
    } else {
      console.log('用户 没同意');
    }
    // 回收 deferredPrompt 变量, prompt() 只能被调用一次
    window.deferredPrompt = null;
  });
});

4. 监听处理安装事件

PWA被安装后 可以通过 浏览器的菜单栏 / 当前网站地址栏的快捷链接 / 桌面快捷方式 等方式打开,请监听处理 appinstalled 事件

window.addEventListener('appinstalled', (event) => {
  console.log('👍', 'appinstalled', event);
  // 回收 deferredPrompt 变量
  window.deferredPrompt = null;
});

附注(示例配置如下):

示例 manifest.json 参考如下

icons 配置你有的尺寸即可

{
  "short_name": "【替换成您的应用短的名称】",
  "name": "【替换成您的应用名称】",
  "description": "【替换成您的应用描述】",
  "icons": [
    {
      "src": "https://via.placeholder.com/48",
      "sizes": "48x48",
      "type": "image/png"
    },
    {
      "src": "https://via.placeholder.com/72",
      "sizes": "72x72",
      "type": "image/png"
    },
    {
      "src": "https://via.placeholder.com/96",
      "sizes": "96x96",
      "type": "image/png"
    },
    {
      "src": "https://via.placeholder.com/144",
      "sizes": "144x144",
      "type": "image/png"
    },
    {
      "src": "https://via.placeholder.com/192",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "https://via.placeholder.com/512",
      "sizes": "512x512",
      "type": "image/png"
    }
  ],
  "start_url": "/",
  "background_color": "【替换成您的背景色】",
  "display": "standalone",
  "scope": "/",
  "theme_color": "【替换成您的主题色】"
}

示例 service-worker.js 参考如下

const CACHE_NAME = 'offline';
const OFFLINE_URL = 'offline.html';

self.addEventListener('install', function(event) {
  console.log('[ServiceWorker] Install');
  
  event.waitUntil((async () => {
    const cache = await caches.open(CACHE_NAME);
    // Setting {cache: 'reload'} in the new request will ensure that the response
    // isn't fulfilled from the HTTP cache; i.e., it will be from the network.
    await cache.add(new Request(OFFLINE_URL, {cache: 'reload'}));
  })());
  
  self.skipWaiting();
});

self.addEventListener('activate', (event) => {
  console.log('[ServiceWorker] Activate');
  event.waitUntil((async () => {
    // Enable navigation preload if it's supported.
    // See https://developers.google.com/web/updates/2017/02/navigation-preload
    if ('navigationPreload' in self.registration) {
      await self.registration.navigationPreload.enable();
    }
  })());

  // Tell the active service worker to take control of the page immediately.
  self.clients.claim();
});

self.addEventListener('fetch', function(event) {
  // console.log('[Service Worker] Fetch', event.request.url);
  if (event.request.mode === 'navigate') {
    event.respondWith((async () => {
      try {
        const preloadResponse = await event.preloadResponse;
        if (preloadResponse) {
          return preloadResponse;
        }

        const networkResponse = await fetch(event.request);
        return networkResponse;
      } catch (error) {
        console.log('[Service Worker] Fetch failed; returning offline page instead.', error);

        const cache = await caches.open(CACHE_NAME);
        const cachedResponse = await cache.match(OFFLINE_URL);
        return cachedResponse;
      }
    })());
  }
});

更多细节:

请参考: web.dev/learn/pwa/