PWA(Progressive Web App)的概念出来的时间已经不短了,由于忙于业务,一直没有时间好好去了解它。近日,花了点时间小研究了一下,我觉得很有必要做个学习记录来分享一下。

因为,PWA 的知识和概念,我们可以通过 Google 来深入了解,我不想在这里重复介绍。下面的总结和记录,是我认为比较重要的知识点。

Service Worker

首先,我们需要一个叫 Service Worker 的 HTML5 API 来帮你做各种事情,比如:离线资源缓存、消息推送、后台同步等。不过,有个前提条件,你的站点必须是 HTTPS 的环境,但在开发环境,非 HTTPS 的 localhost127.0.0.1 也是可以调试的。

注册

在使用前,我们必须要在 JS 主线程(通常是页面的 JS)中注册 Service Worker。

if ('serviceWorker' in navigator) {
  window.addEventListener('load', function () {
    navigator.serviceWorker.register('/sw.js', { scope: '/' })
      .then(function (registration) {
        // 注册成功
      })
      .catch(function (err) {
        // 注册失败:(
      });
  });
}

这段代码,有几个地方需要关注。sw.js 这个 Service Worker 文件,必须和当前站点同域。scope 参数将决定 Service Worker 在当前域下可控制的子目录范围。如果不传,默认是 sw.js 文件所在的路径。

安装

在注册 Service Worker 时,你可以把 sw.js 留空,但这样显然没有意义。当 Service Worker 注册成功后,就会触发 install 事件。在 install 事件中,你可以缓存静态资源,以填充浏览器的离线缓存能力。

this.addEventListener('install', function(event) {
  event.waitUntil(
    caches.open('cache-v1.0').then(function(cache) {
      return cache.addAll([
        '/',
        '/index.html',
        '/main.css',
        '/main.js',
        ...
      ]);
    })
  );
});

除了 install 事件,我们还可以通过 fetch 事件,动态的缓存资源的请求。

this.addEventListener('fetch', function(event) {
  event.respondWith(
    caches.match(event.request).then(function(response) {

      // 如果 Service Worker 有自己的返回,就直接返回,减少一次 http 请求
      if (response) {
        return response;
      }

      // 如果 service worker 没有返回,那就得直接请求真实远程服务

      // 把原始请求复制过来
      var request = event.request.clone(); 

      return fetch(request).then(function(httpRes) {

        // 请求失败了,直接返回失败的结果就好了。。
        if (!httpRes || httpRes.status !== 200) {
          return httpRes;
        }

        // 请求成功的话,将请求缓存起来。
        var responseClone = httpRes.clone();

        caches.open('cache-v1.0').then(function(cache) {
          cache.put(event.request, responseClone);
        });

        return httpRes;
      });
    })
  );
});

使用 fetch 还可以对 Ajax 数据加以适当缓存处理,以实现真正的离线可用。但 fetch 事件有个缺点,在注册/安装成功后,需要再多一次访问后才能离线使用。

更新

sw.js 有了新的缓存策略,Service Worker 会重新触发 install 事件,但在安装完成后,只会进入 waiting 状态,直到所有打开的页面关闭,新的 Service Worker 才会在重新打开的页面中生效。

所以,我们需要通过自动更新的方法来跳过 waiting 状态。

self.addEventListener('install', function (event) {
  event.waitUntil(self.skipWaiting());
});

self.addEventListener('activate', function(event) {
  event.waitUntil(
    Promise.all([

      // 更新客户端
      self.clients.claim(),

      // 清理旧版本
      caches.keys().then(function(cacheList) {
        return Promise.all(
          cacheList.map(function(cacheName) {
            if (cacheName !== 'cache-v2.0') {
              return caches.delete(cacheName);
            }
          })
        );
      }),
    ])
  );
});

Workbox

有没有觉得上面的安装和更新有点复杂和繁琐,而且,当静态资源更新后(比如用 webpack 构建),需要重新调整缓存策略。幸运的是,Google 工程师已经帮我们做好了封装,你可以直接使用 Workbox 框架来处理这些琐碎的事。

如果你的项目是用 webpack 构建的,可以直接安装 workbox-webpack-plugin 插件,具体配置可以参考 webpack 官方文档

manifest.json

注册/安装了 Service Worker,只实现了 Web App 的离线工作,有了 manifest.json 就可以把站点添加至主屏幕。

{
  "short_name": "短名称",
  "name": "这是一个完整名称",
  "icon": [
     {
       "src": "icon.png",
       "type": "image/png",
       "sizes": "48x48"
      }
  ],
  "start_url": "index.html"
}

manifest.json 文件中,除了上面示例属性外,还可以配置启动背景色、显示类型、主题颜色等等。具体可以自行 Google。

然后,我们需要使用 linkmanifest.json 添加到 HTML 页面的头部。

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

兼容性支持

由于 PWA 采用的技术比较新,目前,浏览器还没有达到完全支持的程度, W3C 也还处于草案阶段,还没有定稿。我们可以通过 Can I use 来查看以上技术的支持性。

参考链接