Progressive Web App

逐渐建立用户和应用的连接,响应式设计,适应低质量网络,像原生应用一样使用,便于更新,只使用 HTTPS,搜索引擎能搜到,推送,桌面快捷方式,通过 URL 连接...

# 特点

  • Progressive - 逐渐建立用户和应用的连接
  • Responsive - 响应式设计
  • Connectivity independent - 适应低质量网络
  • App-like - 像原生应用一样使用
  • Fresh - 便于更新
  • Safe - 只使用 HTTPS
  • Discoverable - 搜索引擎能搜到
  • Re-engageable - 推送
  • Installable - 桌面快捷方式
  • Linkable - 通过 URL 连接

# 目的

  1. 使用 app shell 方式创建应用
  2. 使你的应用能离线工作
  3. 为你的离线应用存储数据

# App Shell

# 什么是 app shell

app shell 包含最基础的 HTML, CSS 和 JS,用户在第一次访问 PWA 的时候加载一次就缓存在本地,以后在打开 PWA 就能从本地直接加载 app shell 然后再从网络加载数据渲染包含内容的 UI。

# app shell 包含什么

  • 打开 PWA 时需要马上显示的东西
  • 关键的 UI 组件
  • app shell 需要的图片、样式、脚本资源

# Service worker

# 什么是 service worker

service worker 是一段跑在浏览器后台线程的脚本,可用于拦截作用域下的请求,从而直接返回缓存的数据。实现离线使用的目的!

# 什么是 fetch

fetch 是新一代的 AJAX API,用于替代 XmlHttpRequest

# 安装

目前 Chrome 和 Firefox 可直接使用。service worker 是有作用域的,一般都把 service-worker.js 文件放在根目录下,这样可拦截所有请求。

if ('serviceWorker' in navigator) {
  navigator.serviceWorker
    .register('./service-worker.js')
    .then(function () {
      console.log('Service Worker Registered');
    });
}

# 缓存策略

  1. 有缓存直接返回缓存,没缓存再从网络获取再返回数据
  2. 不管有没有,先返回缓存,然后从网络获取数据,根据更新时间决定是否更新 UI

# 缓存 app shell

# 首次打开时缓存静态文件

⚠️ cache.addAll() 是链式的,也就是说前面有请求出错,后面的请求都会中断。

const cacheName = 'app-shell-v1';
const filesToCache = ['files to cache'];
self.addEventListener('install', function (e) {
  e.waitUntil(
    caches.open(cacheName).then(function (cache) {
      return cache.addAll(filesToCache);
    });
  );
});

# 拦截请求

这段拦截代码目标是静态文件,使用缓存策略2。

self.addEventListener('fetch', function (e) {
  e.respondWith(
    caches.match(e.request).then(function (response) {
      return response || fetch(e.request);
    })
  );
});

# 删除过时缓存

每次打开 PWA 都会触发这个事件,根据 cacheName 来判断是否过时。

self.addEventListener('activate', function (e) {
  e.waitUntil(
    caches.keys().then(function (keyList) {
      return Promise.all(keyList.map(function (key) {
        if (key !== cacheName && key !== dataCacheName) {
          console.log('[ServiceWorker] Removing old cache', key);
          return caches.delete(key);
        }
      }));
    })
  );
  return self.clients.claim();
});

# 缓存数据

self.addEventListener('fetch', function (e) {
  var dataUrl = '/api/v1';
  if (e.request.url.indexOf(dataUrl) > -1) {
    e.respondWith(
      caches.open(dataCacheName).then(function (cache) {
        return fetch(e.request).then(function (response) {
          cache.put(e.request.url, response.clone());
          return response;
        });
      });
    );
  }
});

# 获取缓存的数据

if ('caches' in window) {
  caches.match(url).then(function (response) {
    if (response) {
      response.json().then(function (json) {
        console.log(json);
      });
    }
  });
}

# 原生应用集成

只需要添加一个 manifest.json 文件来宣告应用的详情,Chrome 会自动根据是否使用了 service worker, 是否开启了 SSL 加密以及访问频率来决定是否显示添加到桌面的快捷方式条。

{
  "name": "Weather",
  "short_name": "Weather",
  "icons": [{
      "src": "images/icons/icon-256x256.png",
      "sizes": "256x256",
      "type": "image/png"
    }],
  "start_url": "/index.html",
  "display": "standalone",
  "background_color": "#3E4EB8",
  "theme_color": "#2F3BA2"
}
<link rel="manifest" href="/manifest.json">

原文