Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PWA入门之路 #4

Open
wanghanzhen opened this issue Jan 6, 2019 · 0 comments
Open

PWA入门之路 #4

wanghanzhen opened this issue Jan 6, 2019 · 0 comments

Comments

@wanghanzhen
Copy link
Owner

wanghanzhen commented Jan 6, 2019

PWA入门之路

由于项目中有个问题涉及到了Service Worker,所以找了时间去研究了一下PWA。也趁此写一篇文章总结一下。

前言:PWA作为今年最火热的技术概念之一,对提升Web应用的安全、性能和体验有着很大的意义,非常值得我们去了解与学习。

什么是PWA

PWA,全称Progressive Web App,即渐进式WEB应用, 是提升 Web App 的体验的一种新方法,能给用户原生应用的体验。它的优势主要体现在:

  • 可在离线网络较差的环境下正常打开页面。
  • 安全(HTTPS)。
  • 保持最新(及时更新)。
  • 支持安装(添加到主屏幕)和消息推送
  • 向下兼容,在不支持相关技术的浏览器中仍可正常访问。

PWA本身其实是一个概念集合,它不是指某一项技术,而是通过一系列的Web技术与Web标准来优化Web App的安全、性能和体验。其中涉及到的一些技术概念包括但不限于:

  • Web App Manifest
  • Service Worker
  • Cache API 缓存
  • Push&Notification 推送与通知
  • Background Sync 后台同步

本文主要讲一下Service Worker相关的东西。

Service Worker

1. 什么是Service Worker?

Service worker是一个注册在指定源和路径下的事件驱动worker。它采用JavaScript控制关联的页面或者网站,拦截并修改访问和资源请求,细粒度地缓存资源。你可以完全控制应用在特定情形(最常见的情形是网络不可用)下的表现。

Service worker运行在worker上下文,因此它不能访问DOM。相对于驱动应用的主JavaScript线程,它运行在其他线程中,所以不会造成阻塞。它设计为完全异步,同步API(如XHR和localStorage)不能在service worker中使用。

下图展示普通Web App与添加了Service Worker的Web App在网络请求上的差异:

img

2. 使用Service Worker

2.1. 注册Service Worker

在index.js文件里面注册Service Worker。

// index.js
// 注册service worker,service worker脚本文件为sw.js
if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('./sw.js').then(function () {
        console.log('Service Worker 注册成功');
    });
}

值得一提的是,Service Worker里的各类操作都被设计为异步,以避免一些长时间的阻塞操作。这些异步操作都是建立在Promise的基础上的,如果你对Promise不够了解,建议去熟悉一下Promise。传送门:Promise(ES6标准入门)

2.2. 使用Service Worker

Service Worker的生命周期

当我们注册了Service Worker后,它会经历生命周期的各个阶段,同时会触发相应的事件。整个生命周期包括了:installing --> installed --> activating --> activated --> redundant。当Service Worker安装(installed)完毕后,会触发install事件;而激活(activated)后,则会触发activate事件。

下面的例子监听了install事件

// 在sw.js里面
// 监听install事件
self.addEventListener('install', function (e) {
    console.log('Service Worker installed');
});

self是Service Worker中的一个特殊的全局变量,类似于windowself指向当前这个Service Worker。

缓存静态资源

一般情况下,我们会列出一个需要缓存的资源列表,当Service Worker install时,会将改列表的资源缓存下来。

// sw.js
var cacheName = 'v1';
var cacheFiles = [
    '/',
    './index.html',
    './index.js',
    './index.css'
];

// 监听install事件,安装完成后,进行文件缓存
self.addEventListener('install', e => {
    console.log(e);
    e.waitUntil(
        caches.open(cacheStorageKey)
        .then(cache => cache.addAll(cacheList))
        .then(_ => self.skipWaiting()) // 该函数可使新的sw.js马上生效。
    );
})

看完这段代码,你可能会有所疑惑。caches是个什么鬼东西?

caches是暴露在window作用域的一个变量,我们通过caches属性访问CacheStorage

CacheStorage是一种新的本地存储,它的存储结构是这样的:

每个域有若干个存储模块,每个模块内可以存储若干个键值对。 它的键是网络请求(Request),值是请求对应的响应(Response)。 CacheStorage的接口集中在全局变量caches中,且仅在HTTPS协议(或localhost:*域)下可用。

我们在chrome上的devtool-application中可以看到CacheStorage的相关信息。

image-20190106172427647.png

介绍变量caches常用方法

  • open(cacheName)

    返回一个 Promise,resolve为匹配 cacheName (如果不存在则创建一个新的cache)的 Cache对象。

  • keys()

    返回一个 Promise ,它将使用一个包含与 CacheStorage 追踪的所有命名 Cache对象对应字符串的数组来resolve。 使用该方法迭代所有 Cache对象的列表。

Cache对象常用方法

  • match(request, options)

    返回一个 Promise对象,resolve的结果是跟 Cache对象匹配的第一个已经缓存的请求。

  • add(request)

    抓取这个URL,检索并把返回的response对象添加到给定的Cache对象。这在功能上等同于调用 fetch(),然后使用 Cache.put() 将response添加到cache中。

  • addAll(requests)

    抓取一个URL数组,检索并把返回的response对象添加到给定的Cache对象。

  • put(request, response)

    同时抓取一个请求及其响应,并将其添加到给定的cache。

  • keys(request, options)

    返回一个Promise对象,resolve的结果是Cache对象key值组成的数组。

更多详细介绍和方法请查阅MDN-CacheStorageMDN-Cache

看到这里你可能又会问,Request???Response???

这里跟Fetch API有着密切的关系。

Request对象,用来表示资源的请求。

Response对象,用来表示一次请求的响应数据。

详细资料传送门在这里:RequestResponse

我们打印一下这两个东西,就非常明了了。

image-20190106173445713.png

好了,接下来继续我们的Service Worker。

我们可以给 service worker 添加一个 fetch 的事件监听器,接着调用 event 上的 respondWith() 方法来劫持我们的 HTTP 响应,然后我们就可以进行一波操作了。

// sw.js
self.addEventListener('fetch', e => {
    e.respondWith(
        caches.match(e.request).then(res => {
            return res || fetch(e.request);
        })
    )
})

这里的逻辑是这样的:

  1. 浏览器发起请求,请求各类静态资源(html/js/css/img);
  2. Service Worker拦截浏览器请求,并查询当前cache;
  3. 若存在cache则直接返回,结束;
  4. 若不存在cache,则通过fetch方法向服务端发起请求,并返回请求结果给浏览器。

最终这里就简单实现了缓存静态资源文件的目的了。

更新静态缓存资源

我们通过修改cacheName来达到更新缓存资源的目的。由于浏览器判断sw.js是否更新是通过字节方式,因此修改cacheName会重新触发install并缓存资源。此外,在activate事件中,我们需要检查cacheName是否变化,如果变化则表示有了新的缓存资源,原有缓存需要删除。

self.addEventListener('activate', e => {
    e.waitUntil(
        caches.keys().then(cacheNames => {
            return Promise.all(
                cacheNames.map(cache => {
                    if (cache !== cacheStorageKey) {
                        return caches.delete(cache);
                    }
                })
            )
        })
    )
    return self.clients.claim();
})

最后我们可以在network看到请求资源的信息。

image-20190106175113184.png

资源来自于Service Worker,时间也是在10ms左右,可以说是非常快的加载速度了,这样的体验对用户非常友好。

2.4. Service Worker其他功能

除了缓存静态资源文件以外,Service Worker还有缓存API数据,进行消息提醒,后台同步的功能。东西很多,目前还在慢慢探索当中。

参考资料

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant