添加 web 应用到桌面

Web App Manifest文件 web 应用的信息,可以将 web 应用添加到设备的主屏幕,让用户直接访问,而不是通过浏览器输入 url 访问。

Manifest 文件示例和说明如下:

{
    name: "App 名称",
    short_name: "App 名称",
    display: "standalone", // 启动画面类型
    start_url: "/?from=homescreen",
    icons: [...],
    background_color: "#3e4eb8",
    theme_color: "#2f3ba2"
}
  • dispaly:
    • fullscreen: 占满整个屏幕
    • standalone: 隐藏浏览器相关 UI
    • minimal-ui: 与 standalone 类型
    • browser: 与普通网页在浏览器中打开的显示一致
  • start_url: 如果为空则为默认使用用户打开的当前页面为首屏/添加参数用于来源统计
  • icons: 用于设置图标
  • background_color: 启动画面背景颜色
  • theme_color”: 启动画面上状态栏、地址栏的颜色

通过在 html 页面 head 中增加 link,引入 manifest 文件。

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

本网站的 manifest.json

Service Worker

Service Worker 可以拦截本地请求,根据条件判断是否请求服务器,还是从本地缓存中取数据,这使得应用在离线状态时,仍可以提供基本的功能。

Pages  <---------->  Service Worker  <------------> Server
                          ↑ |
                          | ↓
                       CacheStorage

Service Workder 是浏览器中独立的线程

  1. 不能直接访问/操作 Dom
    • Promise
    • Fetch API
    • Cache API
  2. 需要时直接唤醒,不需要自动休眠
  3. 离线缓存内容开发者可控
  4. 一旦被安装则永远存活,手动触发卸载
  5. 必须在 HTTPS 环境下工作(本地环境 localhost 除外)

生命周期:

                 Error                Fetch/Message
                /                          |
                |                          |
Register ---> Install ---> Activated ---> Idle
                 |            ↑            |
                  \          /             |
                     Waiting           Terminated

查看当前运行的 Service Worker 线程:<chrome://inspect/#service-workers>

查看所有 Service Worker 线程信息:<chrome://serviceworker-internals>

注册

通过 serviceWorker.register 注册 service worker,参数中的脚本地址通知浏览器去哪里获取 service worker 脚本,service worker 是驻留在 app 内的一个 JavaScript 文件,工作在 worker context 里,没有访问 DOM 的权限。

if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('sw.js', {scope: '/'})
        .then(reg => {
            if (reg.installing) {
                console.log('Service worker installing')
            } else if (reg.waiting) {
                console.log('Service worker installed')
            } else if (reg.active) {
                console.log('Service worker active')
            }
        })
        .catch(error => {
            console.warn('Registration failed with ', error)
        })
}

scope 选项指定让 service worker 控制内容的子目录,默认为 service worker 脚本所在的目录。要注意的是,如果你的 service worker 脚本在类似 /foo/../bar/scripts/ 的目录下,scope 不能为 /foo/../, /foo/../bar/ 这类 scripts 的父目录。如果想指定父目录,需要把脚本的位置提到父目录,或者可以保留脚本的位置不变,通过 Service-Worker-Allowed http 返回头字段指定 scope

安装 (install 事件)

install 在注册完成后触发,为 install 事件增加监听方法用于填充浏览器缓存。

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

cache 是一个 service worker 上的全局对象,可以利用它存储网络响应的资源。

当安装完成之后,service worker 就会激活。

fetch 事件

任何 service worker 控制 scope 内的请求,都会触发 fetch 事件,通过监听 fetch 事件,可以拦截并处理请求,通过事件的 respondWith 方法返回响应。

self.addEventListener('fetch', event => {
    let request = event.request;

    // Always fetch non-GET requests from the network.
    if (request.method !== 'GET') {
        event.respondWith(fetch(request));
        return;
    }

    // For HTML requests, try the network first else fall back to the offline page.
    if (request.headers.get('Accept').indexOf('text/html') !== -1) {
        event.respondWith(
            fetch(request).catch(() => caches.match('/offline/'))
        );
        return;
    }

    // Look to the cache first, then fall back to the network.
    event.respondWith(
        caches.match(request).then(response => {
        // return response || fetch(request)
        return response || fetch(request).then(resp => {
            // 将从服务器请求的资源保存到缓存中
            return caches.open('v1').then(cache => {
                cache.put(event.request, response.clone());
                return response;
            })
        });
        })
    );
});

激活(activate 事件)

之前提到过 service worker 会在 install 完成之后激活,激活事件对第一个版本的来说无关紧要,但如果想要更新 service worker 脚本,可以利用新版本 service worker 的激活事件获取控制权,清理旧版本缓存资源。

self.addEventListener('activate', function(event) {
  var cacheWhitelist = ['v2'];

  event.waitUntil(
    caches.keys().then(function(keyList) {
      return Promise.all(keyList.map(function(key) {
        if (cacheWhitelist.indexOf(key) === -1) {
          return caches.delete(key);
        }
      }));
    })
  );
});

这里假设新版本 service worker 用 v2 缓存,清理所有不是 v2 的缓存资源。


APPCache 已不推荐使用 ## APPCache index.html ``` html ``` example.appcache ``` appcache CACHE MANIFEST # 2018-04-09: v2 CACHE /favicon.ico index.html stylesheet.css images/logo.png scripts/main.js NETWORK: /api FALLBACK: /index.html /static.html ```