PWA渐进式网络编程(Progressive Web App)

1 篇文章 0 订阅

PWA是什么?

PWA的全称是Progressive Web App, 意思是渐进式网络编程,这是Google提出的使用前沿的技术为网页提供原生app体验的技术解决方案.它其实并没有什么魔法,而是使用如Service Worker, Promise, fetch, cache API, Notification API这样一些相互解耦的技术组成的。今天我就将以PWA的最主要的技术Service Worker入手带大家了解一下PWA编程.


关于Service Worker

Service Worker 和 Web Worker类似,它可以常驻于内存运行,代理网络请求,依赖于https等等, 可以理解为Service Worker 就是PWA的大脑。所以开始前我们也先提一下,进行PWA编程首先需要是https的请求处理,当然,使用本地开发是在localhost环境下,使用 http 也是可以的,所以我们在这里给大家推荐两个为我们的项目开启一个服务的方式:

  1. serve: 使用 npm i serve -g 全局安装这个包后,在我们的项目目录下运行serve 命令就会依赖于该项目为我们开启一个服务,默认的服务端口是5000, 所以此时我们可以在浏览器上使用 localhost:5000 + 文件名打开我们的项目了。
  2. http-server: 它的安装和使用和serve是一样的,也是使用 npm i http-server -g安装后运行http-server命令即可,也可以指定服务的端口, 运行 http-server -p + 端口号

  • 好了废话不多说了,我们开始我们的PWA编程之旅吧: 首先我们简单的创建一个html页面,为了方便,我就直接在html上编写js代码了。
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>PWA</title>
    <link rel="stylesheet" href="./index.css" />
</head>
<body>
    <h1>Hello PWA</h1>
    <script>
        navigator.serviceWorker.register('./sw.js', {scope: '/'})
        .then((registration) => {
            console.log(register)
        }, (err) => {
            console.log(err)
        })
    </script>
</body>
</html>
  • 在navigator上存在一个serviceWorker对象,我们使用它的register方法注册一个Service Worker。注意,serviceWorker是一个单例对象,所以我们不可以使用new关键字进行实例化。register()方法的第一个参数是serviceWorker外链脚本的地址,第二个参数是一个配置对象,我们这里只使用了一个scope参数,不严格意义上的将,scope参数可以理解为这个serviceWorker脚本可以控制的页面的相对路径,默认就是脚本本生所在的路径。register返回的是一个Promise对象,如果注册成功,得到一个registration对象,如果失败,得到的是一个error对象.
    在这里插入图片描述
  • 使用ServiceWorker注册成功和可以查看到我们打印出的registration对象结构如上图所示,此时我们可以在开发者工具的Application选项中查看到我们当前注册的ServiceWorker的状态。
    在这里插入图片描述
    如此我们的ServiceWorker就注册好了,现在我们开始ServiceWorker编程,开始编写我们的sw.js脚本文件。
  • 注意:在ServiceWorker中我们不能够访问DOM, 不能访问诸如Window,Localstroge等这类的对象,这是一个全新的上下文,只有ServiceWorker的一些特有的功能可以访问。最典型的就是 self 对象,代表的是这个ServiceWorker的全局作用域对象,我们与ServiceWorker打交道,只能在self上监听它的事件,关于ServiceWoker的生命周期,一共有三种事件, 分别是 install, activate 和 fetch。现在我们就编写代码分别监听它的这三种事件:
self.addEventListener("install", (event) => {
    console.log("install" ,event)
})
self.addEventListener("activate", (event) => {
    console.log("activate", event)
    )
})
self.addEventListener("fetch", (event) => {
    console.log("fetch", event)
})

使用ServiceWorker编程,就是与ServiceWorker的生命周期打交道,install事件是在一个新的ServiceWoker脚本被安装后触发,注意,只要脚本有一丝丝的变化,serverWorker就会被从新安装,安装后激活serviceWorker,触发activate事件。我们演示一下这两个生命周期的流程.(首先需要保证没有ServiceWorker在运行)
在这里插入图片描述
fetch用于捕获资源请求,当页面进行资源请求的时候会触发到fetch事件,比如我们外链的css文件等等。

  • 在install事件中有一个重要的函数: event.waitUntil(), 这个函数传入一个Promise, 该函数会在Promise执行结束和才完成Install事件,所以它会推迟我们install事件的结束。waitUntil一般会配合特定的行为,比如self.skipWaiting(),它会强制停止旧的ServiceWorker的执行,开始新的ServiceWorker:
	self.addEventListener("install", (event) => {
    // 该函数传入一个Promise,需要在传入的Promise完成后install事件
    // 才真正的完成,意味着该函数会推迟activate的执行
    event.waitUntil(
         //skipWaiting()表示将强制停止旧的Service Worker,开始新的Service Worker
         self.skipWaiting()
     )

})
  • 在activate事件中的waitUntil()方法和install中的waitUntil是一样的,第二个方法时self.clients.claim(),这里的clients表示的是serviceWorker控制的所有页面,而claim()方法会在页面被首次加载后同样受到ServiceWorker的控制,默认情况下,页面的首次加载是不受控制的。
self.addEventListener("activate", (event) => {
    // 这里的waitUtil()和install中的一样,也是需要等到该函数执行完毕后activate才算是执行结束
    event.waitUntil(
    //     // 这里clients 表示的是service worker 控制的所有的页面
    //     // claim()这个方法能够让页面在首次的加载之后同样受到service worker的控制: 默认情况下页面的首次加载是不受控制的
        self.clients.claim()
    )
})
  • 当然,serviceWorker还有更多的其他的事件,比如push推送事件和Sync同步事件等等,但都没有以上的这几种事件重要,关于其他的这些这里就不做详细的解释了。

使用cache API在Service中编程: cache API使得我们的项目在离线环境下运行成为了可能。接下来我们使用cache API结合serviceWorker的三个生命周期实现页面的离线可用功能。

  • 我们前面说到了,serviceWorker的三个生命周期,install, activate, fetch, install事件和activate事件在整个serviceWorker中只会执行一次,而fetch事件可执行多次。所以我们需要分别在各个生命周期中处理响应的事情:
  1. 在install事件中我们需要去拉取相应的资源。
  2. activate事件中应该去清除上一个serviceWorker遗留下的无用的缓存。
  3. 在fetch事件中,当捕获到资源请求后去查询并返回serviceWorker中的资源。
  • 现在我们就围绕这三点进行操作: (一般在页面上线之前我们都能得到一个可能用到的静态资源的集合,我们就可以依据这个静态资源集合去拉去相应的静态资源存入到ServiceWorker中,这写资源我们一般可以在项目编译过程中得到,我们只需要操作cache将他们写入到缓存中即可。在serviceWorker上下文中,所有缓存空间的集合叫caches(), 这是一个全局对象,可以直接使用。使用caches.open()方法即可打开一个缓存空间,open()方法需要传入一个缓存的名称,open()方法得到的也是一个Promise, 在then()方法中我们可以得到缓存空间的句柄。使用这个句柄我们就可以直接写入缓存了,写入缓存的方法可以调用addAll()方法,该方法需要传入一个数组,每一项就是资源的路径。install事件中的代码如下:)
const CACHE_NAME = 'cache-1'
self.addEventListener("install", (event) => { 
    event.waitUntil(
        caches.open(CACHE_NAME).then(cache => {
            cache.addAll([
                '/',
                './index.css'
            ])
        })
    )
})
  • 写入资源后使用缓存: 在fetch中我们可以捕获到所有的网络资源请求,然后到ServiceWorker空间中查询,如果查询到资源就直接返回请求,如果ServiceWorker中不存在资源,就发起网络请求获取资源。在fetch事件中我们使用event.responseWith()进行资源请求的处理,参数仍然需要传入一个Promise, 我们将在这个方法中打开缓存(使用caches.open()方法,传入创建是的名称即可。在then()方法中得到缓存空间cache, 在缓存空间中判断缓存中是否有当前请求的资源,调用cache.match()方法,该方法需要传入一个request对象,我们可以在fetch事件的event中得到: event.request, 该方法会得到一个response对象,如果response对象存在,就说明命中了serviceWorker中的缓存,此时我们直接返回这个response即可,如果不存在,就只有调用fetch发起网络请求获取资源,在fetch中我们直接传入event.request即可,得到获取的资源response对象返回, 同时调用cache.put()将资源写入缓存,put方法的参数是key-value形式的. 由于response是流式的,所以我们这里需要调用clone()方法克隆一份出来。 fetch中的代码如下: )
self.addEventListener("fetch", (event) => {
    event.respondWith(caches.open(CACHE_NAME).then(cache => {
        return cache.match(event.request).then(res => {
            if (res) {
                return res
            }
            return fetch(event.request).then(res => {
                cache.put(event.request, res.clone())
                return res
            })
        })
    }))

经过如上的处理,我们运行serviceWorker后,关闭到我们的服务器,查看network网路请求,我们可以发现如下情况:
在这里插入图片描述
这里我们可以看到,此时我们的资源的来源路径就是从ServiceWorker中来的了。

  • 现在我们需要在activate中清除上一个serviceWorker的缓存: (使用caches.key()获取到serviceWorker中的所有的cache名字,返回所以的缓存名字,这里我们以一种粗暴的方式清除掉serviceWorker中的所有的缓存。使用cache.delete()方法清除掉缓存,传入caches.key()获取到的cache名字即可。 activate中的代码如下: )
self.addEventListener("activate", (event) => {
    // 在每次加载是清除浏览器的缓存
    event.waitUntil(
        // caches.keys()获取当前service worker中的所有缓存的名字
        caches.keys().then(cacheNames => {
            return Promise.all(cacheNames.map(cacheName => {
                if (cacheName != CACHE_NAME) {
                    return caches.delete(cacheName)
                }
            }))
        })
    )
})

如此我们基本的PWA实现一个简单的离线应用便实现了,当然,对于PWA中的另外的三个核心的内容想大家或多或少都是了解些的,fetch, Promise这里我们就都不说了,关于Notification API它可以实现浏览器的消息的推送,但消息的推送也需要用户的授权,它的一些具体的内容大家也可以到浏览器上输入Notification进行查看,大家看到的它应该是一个构在函数,而关于他的一些具体的方法今天就不在这里具体的叙述了,如果大家感兴趣,可以自行深入去学习。


好了,今天的PWA简介就到这里了,大家加油!!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值