PWA之 Service worker

Posted tangttt

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PWA之 Service worker相关的知识,希望对你有一定的参考价值。

  渐进式 Web 应用(Progressive Web Apps,也被称为 PWAs)是 Web 技术方面一项令人兴奋的创新。PWA 混合了多项技术,能够让 Web 应用的功能类似于原生移动应用。它为开发人员和用户带来的收益能够突破纯 Web 解决方案和纯原生解决方案的限制:  

  1. 你只需要一个按照开放、标准 W3C Web 技术开发的应用,不需要开发单独的原生代码库;

  2. 用户在安装之前就能发现并尝试你的应用;

  3. 没有必要使用 AppStore,无需遵循复杂的规则或支付费用。应用程序会自动更新,无需用户交互;

  4. 用户会被提示“安装”,这样会添加一个图标到主屏幕上;

  5. 当启动的时候,PWA 会展现一个有吸引力的启动画面;

  6. 如果需要的话,浏览器的 chrome 选项可以进行修改,以便于提供全屏的体验;

  7. 基本文件会在本地缓存,所以 PWA 要比标准 Web 应用反应更快(它们甚至能够比原生应用更快);

  8. 安装是轻量级的,可能只需几 KB 的缓存数据;

  9. 所有的数据交换必须要通过安全的 HTTPS 连接来执行;

  10. PWA 支持离线功能,当网络恢复后,数据会进行同步。

 

  service worker 本意为渐进式网络应用(progressive web app, PWA)提供离线支持,使得网络应用可以离线运行。一旦启用 service worker ,则它可以根据开发者的缓存配置为用户缓存网站静态与动态资源,并截获用户的所有网络请求,并根据缓存配置来决定是从缓存还是网络获取相应资源,从而可以提高网页的加载速度。测试结果表明,一般能实现 4-5 倍的加速,最好的时候能够实现 10 倍的加速。使用 service worker 实现网站加速的优势有:

  • 可以很轻易地实现静态与动态资源缓存,决定缓存空间的大小与缓存时间期限,可定制性高;
  • 不需要服务端支持,只需要在本地生成 service worker 文件并上传就可以使用,特别适用于没有服务端的静态博客或网站;
  • 配合 sw-precache ,只需要做好缓存配置, sw-precache 可以自动生成 service woker 文件,不需要自己实现缓存逻辑;
  • 可以灵活为动态与静态资源,以及不同网址提供不同的缓存机制,并实现资源的动态更新,同样不需要自己编写代码
  Service workers 作为一个独立的线程,运行环境和普通环境不同,本质上充当Web应用程序与浏览器之间的代理服务器。service worker可以:
  • 后台消息传递
  • 网络代理,转发请求,伪造响应
  • 离线缓存
  • 消息推送
  •  … …
  在目前阶段,ServiceWorker的主要能力集中在网络代理和离线缓存上。具体的实现上,可以理解为ServiceWorker是一个能在网页关闭时仍然运行的WebWorker。
 
ServiceWorker的生命周期
 
  一个ServiceWorker会经历:安装、激活、等待、销毁的阶段。
 
技术分享图片
技术分享图片
 
     这张图把ServiceWorker的声明周期分为了两部分,主线程中的状态和ServiceWorker子线程中的状态。子线程中的代码处在一个单独的模块中,当我们需要使用ServiceWorker时,按照如下的方式来加载: 
 
 
  
 Install 
 
  这个时候ServiceWorker处于Parsed解析阶段,ServiceWorker处于Installing安装阶段,主线程的registration的installing属性代表正在安装的ServiceWorker实例。如果之前没有serviceWorker实例,或者之前有serviceWorker实例但serviceWorker文件有更新,这时子线程中才会触发install事件,可以在install事件中指定缓存资源。
技术分享图片
这里使用了Cache API来将资源缓存起来,同时使用e.waitUntil接手一个Promise来等待资源缓存成功,等到这个Promise状态成功后,ServiceWorker进入installed状态,意味着安装完毕。
 

const self = this;
const HOST_NAME = location.host;
const VERSION_NAME = ‘CACHE-v5‘;
const CACHE_NAME = HOST_NAME + ‘-‘ + VERSION_NAME;
const CACHE_HOST = [HOST_NAME, ‘0.plus‘, ‘icms.biyong.io‘];
var cacheList = [
    "./css/common.css",
    "./css/discover.css",
    "discover.html",
]

const onInstall = function (event) {
    console.log(‘触发install事件--------‘);
    // 打开一个缓存空间,将相关需要缓存的资源添加到缓存里面
    event.waitUntil(
        caches
            .open(CACHE_NAME)
            .then(function (cache) {
                self.skipWaiting();
                console.log(‘Install success---------------‘);
                return cache.addAll(cacheList)
            })
    );
};

// 当浏览器解析完sw文件时,serviceworker内部触发install事件
self.addEventListener(‘install‘, onInstall);

 

然而这个时候并不意味着这个ServiceWorker会立马进入Activate阶段,除非之前没有新的ServiceWorker实例,如果之前已有ServiceWorker,新的ServiceWorker安装完成后会延时激活并进入了waiting状态,浏览器仍在运行之前的ServiceWorker,那么需要满足如下任意一个条件,新的ServiceWorker才会进入Activate阶段:
  • 在新的ServiceWorker线程代码里,使用了self.skipWaiting() 
  • 或者当用户导航到别的网页,因此释放了旧的ServiceWorker时候
  • 或者指定的时间过去后,释放了之前的ServiceWorker
 
 
 Activate 
 
这个时候ServiceWorker的生命周期进入Activating阶段,ServiceWorker子线程接收到activate事件,这个时候通常做一些缓存清理工作,当e.waitUntil接收的Promise进入成功状态后,ServiceWorker的生命周期则进入activated状态。这个时候主线程中的registration的active属性代表进入activated状态的ServiceWorker实例。
 
const onActive = function (event) {
    event.waitUntil(
        caches
            .keys()
            .then(function (cacheNames) {
                return Promise.all(
                    cacheNames.map(function (cacheName) {
                        // active事件中通常做一些过期资源释放的工作
                        if (CACHE_NAME.indexOf(cacheName) === -1) {
                            return caches.delete(cacheName);
                        }
                    })
                );
            })
            .then(function () {
                console.log(‘active claim success 2-------------‘);
                self.clients.claim();
            })
    );
};

// 如果当前浏览器没有激活的service worker或者已经激活的worker被解雇,
// 新的service worker进入active事件
self.addEventListener(‘activate‘, onActive);
技术分享图片
 
  到此一个ServiceWorker正式进入激活状态,可以拦截网络请求了。如果主线程有fetch方式请求资源,那么就可以在ServiceWorker代码中触发fetch事件。
 
const onFetch = function (event) {
    event.respondWith(handleFetchRequest(event.request));
};

const handleFetchRequest = function (req) {
    const request = req;
    if (isCORSRequest(req.url, HOST_NAME)) {
        const request = new Request(req.url, {mode: ‘no-cors‘});
    }
    return caches.match(request)
        .then(function (response) {
            if (response) {
                return response;
            } else {
                console.log(‘缓存没找到‘ + request.url);
            }
        })

    const request = req;
    if (isCORSRequest(req.url, HOST_NAME)) {
        const request = new Request(req.url, {mode: ‘no-cors‘});
    }
    return fetch(request)
        .then(function (response) {
            console.log(request.url + ‘请求回来了‘);
            // online: Return the response if need
            if (isNeedCache(req) && isValidResponse(response)) {
                const clonedResponse = response.clone();
                caches
                    .open(CACHE_NAME)
                    .then(function (cache) {
                        cache.put(request, clonedResponse);
                        console.log(‘添加缓存: ‘ + request.url);
                    })
            }
            return response;
        })
        .catch(error => {
            // offline: Return the caches
            console.log(‘fetch ‘ + request.url + ‘ fail: ‘ + error.message);
            return caches.match(request)
                .then(function (response) {
                    if (response) {
                        return response;
                    } else {
                        console.log(‘缓存没找到‘ + request.url);
                    }
                })
        })

};

self.addEventListener(‘fetch‘, onFetch);

 

 
 Asw-precache模块 
 
 
  sw-precache也是NODE中的一个模块,可以通过npm install sw-precache来进行安装。sw-precache可以配合多个工具使用,这里我主要介绍一下如何配合gulp来使用。
我们通过利用sw-precache来生成service-worker.js文件。主要的方式是检测你在staticFileGlobs里面的文件有没有发生变化,如果发生变化就会重新生成hash码,自动更新service-worker.js文件。从而能够使得APP在代码更新之后,浏览器通过字节逐一解析到服务器的service-worker.js不一样了,缓存Cache中的数据才会重新向远程请求新的代码。
技术分享图片
 技术分享图片 
 
缓存资源更新
 
(缓存问题)如果当前已有serviceWorker实例, 并将某些数据缓存在了cache中 当服务器上这些缓存的数据更新时,浏览器请求到的数据始终是serviceWorker的缓存cache中的旧内容。 只有服务器更新了service-worker.js文件,浏览器通过字节逐一解析到新的service-worker.js不一样了,缓存Cache中的数据才会重新更新。
 
 
 
 





以上是关于PWA之 Service worker的主要内容,如果未能解决你的问题,请参考以下文章

[翻译]Service workers:PWA背后的英雄

Service Worker 在 PWA 中的应用

@vue/cli-plugin-pwa 没有创建 service-worker.js

使用 Service Worker 的 PWA 无法与 Django 一起正常工作

带有 PWA 和 Service Worker 的 NGRX 离线缓存

单击 service-worker ng7 + android 处理的推送通知时打开 PWA