【PWA】PWA入门到进阶

本文详细介绍了渐进式Web应用(PWA)的核心技术Service Worker,从生命周期到实际应用,包括缓存策略、离线支持、推送通知和构建离线应用的方法。通过实例解析了Service Worker的安装、缓存管理以及如何处理网络请求,帮助开发者深入理解PWA的实现原理和优化技巧。

高级工程师必备技能,今天我们打造可 PWA 渐进式Web 应用开发实战课程,点击获取,帮助你由浅入深更专业地学习。
在这里插入图片描述在这里插入图片描述

最近在使用某款运动APP,使用过程中我发现一个很便捷的功能,就是你在跑步页,App会提示你添加“便捷”功能至桌面,添加后桌面会有一个APP图标,点进去它其实是一个web,然后通过web调用APP方法能直接进入APP跑步。我想了想,这难不成是PWA。由此原因,我们来看看PWA是怎么操作的 ·>_·>

1、Service Worker 引入

PWA的核心就是Service Worker。所以,我们不得不先对它进行介绍!

谷歌Jeff曾经这么描述它:“如果将你的网络请求想象成飞机起飞,那么Service Worker就是路由请求的空中交通管制员,它可以通过网络加载,甚至通过缓存加载。”

作为空管员,Service Worker 能让你全权控制网站发起的每个请求,这为许多不同的使用场景开辟了可能性。例如,空管员可以将飞机重定向到另一个机场,甚至命令飞机延迟降落。而Service Worker也能如此。

Service Worker 有几个特点:

  • 运行在它自己的全局脚本上下文
  • 不绑定到具体的网页
  • 无法修改网页中的元素,因为它无法访问DOM
  • 只能使用HTTPS

我们用一个图解释Service Worker是如何工作的:

在这里插入图片描述
Service Worker 运行在Worker上下文中,这意味这它无法访问DOM,它运行在Worker线程中,是完全异步,因此不会被阻塞。但是,你也因此无法使用XHR、localStorage之类的功能。

Service Worker生命周期

我们从用户访问页面时,发生的解析过程阐述SW的生命周期。如下图:
在这里插入图片描述
首先,用户首次访问URL时,服务器会返回响应的网页。当调用register()函数时,SW开始下载。在注册过程中,浏览器会下载、解析并执行SW。如果在此步骤出现错误,register()返回的Promise会执行reject操作,并且SW会被废弃。

一旦SW成功执行,安装事件就会激活。SW是基于事件驱动的,这意味着你可以进入这些事件中的任意一个。你可以通过进入不同事件来监听任何网络请求。

一旦完成安装,SW就会激活,并控制在其范围内的一切。如果生命周期中的所有事件都成功,SW就随时可供使用。

  • 简单理解SW生命周期——交通信号灯

你可能会觉得SW的生命周期不好记?
这样,你可以把SW生命周期当作一组交通信号灯。在注册过程中,SW处于红灯状态,因为它还需要下载和解析。接下来,它处于黄灯状态,因为它正在执行,还没有完全准备好。如果红灯、黄灯都执行了,SW就进入到绿灯状态,随时可以使用

当第一次加载页面时,SW还没有激活,所以它不会处理任何请求。只有当它安装和激活后,才能控制其范围内的一切。所以,只有刷新页面或导航到另一个页面,SW内的逻辑才会启动。

SW示例

准备工作

为了安全考虑,SW可能用于恶意用途。例如,如果有人在你的网页上注册一个恶意的SW,它能劫持连接并将其重定向到恶意端点。为了避免这种情况,你只能通过HTTPS提供服务的网页上注册SW,确保网页在通过网络传输的过程中没有被纂改。

但是,作为开发者,你可以在本地localhost中测试SW并调试。

如果你要将PWA发布到网上,你也可以先使用一些免费的HTTPS服务,例如 https://letsencrypt.org ,或者使用GitHub Pages来测试你的PWA。

下面,我们通过一个简单的例子,带你了解SW。

//index.html
<!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>Document</title>
    <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Lato">
    //注意,在头部添加“清单文件”。这个文件提供Web应用信息,如名称、作者、图标和描述。它使得浏览器能将web应用安装到设备主屏幕上,以便为用户提供更快捷的访问。
    <link rel="manifest"  href="/manifest.json">
</head>
<body>
    <img src="./images/hello.jpeg" alt="" width="100%">
    <script src="/js/script.js"></script>
    <script>
        if('serviceWorker' in navigator) { //检查当前浏览器是否支持SW
            //注册
            navigator.serviceWorker.register('/sw.js') //支持,则注册一个名为sw.js的SW文件
            .then(function(registration){ //Promise语法,Promise表示一个操作还未完成,但是期待它将来会完成
                //注册成功
                console.log('sucess:',registration.scope);
            }).catch(err=>{
                //注册失败
                console.log('Error:',err);
            })
        }
    </script>
</body>
</html>

前面提到,SW是基于事件驱动的,所以我们可以通过监听事件,执行SW操作。例如fetch事件。当一个资源发起fetch事件时,你可以决定如何继续进行。可以将发出的HTTP请求或接收到的HTTP响应更改成任何内容。

//sw.js
self.addEventListener('fetch',function(event){ //为fetch事件添加事件监听器
	if(/\.jpg$/.test(event.request.url)){//检查传入的HTTP请求URL是否是以.jpg结尾的文件
		event.responseWith(fetch('/images/logo.jpg')); //尝试获取logo.jpg图片,并用它作为替代图片来响应请求
	}
})

2、PWA引入

PWA并不需要我们从头开始把项目再重新做一遍。例如,每当你发现某个新功能对用户有益并且能提升他们体验时,就可以试试添加这个功能。

这里有一个工具:Lighthouse,你也可以在Chrome扩展插件上安装它。
它能提供与Web应用相关的有用的性能分析和审核信息。

曾有一段时间,网络上讨论PWA与原生应用之间的矛盾,形成一种对立。我认为这应该根据用户的需要来选择。作为开发者,我们应该不断探索提升用户体验的方法,不应该把心思放在不休止的争论上。

PWA架构方式

PWA其实是一个外壳。想象一下,你第一次启动某个下载的App时,在内容加载前,你会看到一个空的UI外壳,包括了头部和导航。

依照这样的方式,PWA借助SW正可以实现这样的体验。例如使用SW缓存应用的UI外壳。

这里简单对UI外壳做个解释即用户界面所必需的最小化HTML、CSS、Javascript。它可能会是类似网站头部、底部和导航这样没有任何动态内容的部分。如果可以加载UI外壳并对其进行缓存,则可以稍后讲动态内容加载到页面中。

例如,当你第一次或重新刷新访问GMail时,首先会显示网站的UI外壳,这让用户及时获取到反馈,让用户认为网站的速度很快,即使是在感官上。一旦外壳加载完成,网站就会使用javascript来获取并加载动态内容。

即,当用户首次访问网站时,SW会开始下载并自行安装。在安装阶段可以进入这个事件并缓存UI外壳所需要的所有资源,即基础的HTML和CSS、javascript资源文件。当这些资源文件都已添加到SW缓存中,这些资源的HTTP请求再也不需要发送给服务器。一旦用户导航到另一个页面,用户将立即看到UI外壳。

在这里插入图片描述
由此,我们总结PWA一般具备下列几点特征:

  • 响应式——适应不同尺寸屏幕
  • 连接无关——SW缓存,可以离线工作
  • 应用式交互——使用UI外壳架构进行构建
  • 始终保持最新——由于SW的更新,它会不断更新
  • 安全——基于HTTPS
  • 可搜索——搜索引擎可爬取到它
  • 可安装——使用清单文件mainfest.json 可以进行安装
  • 可链接——通过URL轻松分享、访问

SW缓存技术

回想一下,当你做火车进入一个山洞隧道,是不是会出现手机信号衰减或无信号状态,导致你正在网上浏览时加载出现问题。而,SW缓存能处理这个问题。

我们知道,Web服务器可以使用Expires响应头来通知web客户端去使用未过期的资源副本,指定指定的“过期时间”。反过来,浏览器可以缓存此资源,并且只有在有效期满后才会再次检查新版本。如下图所示:当浏览器发起一个资源的HTTP请求时,服务器会发送一个包含该资源相关有用信息的HTTP响应头。

在这里插入图片描述
但是,使用HTTP缓存存在一个缺陷,即客户端要依赖于服务器来告知何时缓存资源以及资源何时过期。如果内容具有相关性,任何更新都可能导致服务器发送的到期日期变得很容易不同步,以至于影响网站。

SW缓存

SW缓存,不同于HTTP缓存,SW无须由服务器告知浏览器资源要缓存多久。而是,SW能自己控制资源如何缓存。所以,SW缓存是对HTTP缓存的增强,并可以与之配合使用。

现在我们先看个例子:

<body>
    <img src="./images/hello.jpeg" alt="" width="100%">
    <script src="/js/script.js"></script>
    <script>
        if('serviceWorker' in navigator) {
            //注册
            navigator.serviceWorker.register('/sw.js')
            .then(function(registration){
                //注册成功
                console.log('sucess:',registration.scope);
            }).catch(err=>{
                //注册失败
                console.log('Error:',err);
            })
        }
    </script>
</body>
  • 重点:
//创建缓存资源
var cacheName = 'hello'; //缓存名称
self.addEventListener('install',event=>{//进入SW的安装事件
    event.waitUntil(
        caches.open(cacheName)//使用指定的缓存名称来打开缓存
        .then(cache=>cache.addAll([//把JS和hello.png添加到缓存中
            '/js/script.js'
            '/images/hello.jpeg'
        ]))
    );
});

  • 解析:

  • install事件,它发生在浏览器安装并注册SW时,它是把后面阶段可能会用到的资源添加到缓存中的绝佳时间。例如,我们缓存了script.js这个文件,那么,另一个引用了此文件的网页,在后面的阶段就可以轻松地从缓存中获取它。

  • cacheName:字符串,用于设置缓存的名称。可以为每个缓存取不同的名称,甚至可以拥有一个缓存的多个不同的副本,每个新的字符串对应唯一的缓存。

  • event.waitUntil() 使用Promise来知晓安装所需的时间以及是否安装成功。

  • 需要知道的一点,如果所有的文件都成功缓存,那么SW便是安装成功。但是,如果其中之一的文件缓存失败,那么安装过程也会随之失败。所以,一个很长的缓存列表,会增加缓存失败的概率,多一个文件便多一份风险,从而导致SW无法安装。

OK,前面我们把缓存准备好了。现在我们要读取缓存。

//利用fetch事件,读取缓存。fetch事件会监听URL请求,
//如果在SW缓存中,就从SW中取;如果不在,就通过网络从服务器中取。
self.addEventListener('fetch',function(event){
	event.respondWith(
		caches.match(event.request)//检查传入的请求URL是否匹配当前缓存中存在的任何内容
			.then(function(response){
				if(response){return response;}//SW有,则返回
				return fetch(event.request);//SW没有,通过网络从服务器中取
		});
	);
});

现在,交给你一个任务:打开devTool->Network,刷新页面,看看资源的获取方式是否真的发生变化。同时,打开Applicaion-》Cache Storage看看SW缓存了哪些文件。

拦截并缓存

前面介绍到的SW缓存,是在install阶段进行的,通常称作“预缓存”。

But,当你的资源是动态的时,该怎么进行缓存呢?

Don’t Worry. SW能够拦截HTTP请求,所以这是发起请求然后将响应存储在缓存中的绝佳机会。那么,这样以后将首先请求资源,然后立即将其缓存起来。这对于同样资源发起下一次HTTP请求时,就可以立即将其从Service Worker缓存中取出。

现在我们再通过示例说明:

<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>Document</title>
    <!--字体引用-->
    <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Lato">
</head>
<body>
    <script src="/js/script.js"></script>
    <script>
        if('serviceWorker' in navigator) {
            //注册
            navigator.serviceWorker.register('/sw.js')
            .then(function(registration){
                //注册成功
                console.log('sucess:',registration.scope);
            }).catch(err=>{
                //注册失败
                console.log('Error:',err);
            })
        }
    </script>
</body>

//注意到,我们添加了字体引用,即我们需要对该字体资源在请求时进行SW缓存

var cacheName = 'hello';
self.addEventListener('fetch',funct
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蟹蟹蟹风流

期望和你分享一杯咖啡

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值