Progressive Web App,简称 PWA,是提升 Web App 的体验的一种新方法,能给用户原生应用的体验。
目录:
1、PWA 基本介绍
PWA 全称 Progressive Web App,即渐进式 WEB 应用
2、PWA 核心技术揭秘
- Web app manifest
- Service worker
- Promise / async / await
- Fetch api
- Cache storage
- 常见的缓存策略
- notification
- test
3、PWA 项目实战
内容:
1.PWA 基本介绍
- PWA:渐进式 Web 应用,MDN 地址:https://developer.mozilla.org/zh-CN/docs/Web/Progressive_web_apps
- Progressive Web App,简称 PWA,是提升 Web App 的体验的一种新方法,能给用户原生应用的体验。
- PWA 运用现代的 Web APP 以及传统的渐进式增强策略来创建跨平台 Web 应用程序。
- PWA 能做到原生应用的体验不是特指某一项技术,而是经过应用一些新技术进行改造
- 只要你拥有一个 Web App,那么 PWA 的旅程就开始了
- 现在 Vue 和 React 的脚手架中都已经集成了 PWA 功能
2.PWA 的优势
- 渐进式
适用于所有浏览器,因为它是以渐进式增强作为宗旨开发的。 - 流畅
能够借助 Service Worker 在离线或者网络较差的情况下进行正常访问。 - 可安装
用户可以添加常用的 webApp 到桌面,免去去应用商店下载的麻烦 - 原生体验
可以和 app 一样,拥有首屏加载动画,可以隐藏地址栏等沉浸式体验。 - 黏性
通过推送离线通知等,可以让用户回流。
3.PWA 核心技术揭秘
1.Web App manifest:应用程序清单
-
基本介绍
Web App manifest 可以让网站安全安装到设备的主屏幕,而不需要用户
通过应用商店下载。
Web App manifest:在一个 JSON 文本文件中提供有关应用程序的信息
(如名称、作者、图标和描述)
传统的 Web App 入口:1、网址 2、书签、收藏夹 3、直接搜索Web App manifest:
- 可以添加到桌面,有唯一的图标和名单
- 有启动时界面,避免生硬的过渡
- 隐藏浏览器相关的 UI,比如地址栏等
-
使用步骤
- 在项目根目录下创建一个 manifest.json 文件。
- 在 index.html 中引入 manifest.json
- 在 manifest.json 文件中提供常见的配置。
- 需要在 https 协议或者http://localhost 下访问项目
-
常见配置:
- name:用于指定应用的名称,用户安装横幅提示的名称,和启动动画里的文字。
- short_name:应用的端名称,用于主屏幕显示
- start_url:指定用户从设备启动应用程序时加载的 URL,可以是绝对路径和相对路径
- icons:用于指定可在各种环境中用做应用程序图标的图像对象数组,144*144
- background_color:用户指定启动动画的背景色
- theme_color:用于指定应用程序的主题颜色
- display:用于指定 app 的显示模式
- fullscreen:全屏模式,所有可用的显示区域都被使用,并且不现实状态栏
- standalone 让这个应用看起来像一个独立的应用程序,包括具有不同的窗口,在应用程序启动器中拥有自己的图标等。
- minimal-ui 该应用程序将看起来像一个独立的应用程序,但会有浏览器地址栏。
2. Service Worker
-
基本介绍
-
一个标准的 PWA 程序,必须包含 3 个部分
manifest.json
service worker
https 服务器或者http://localhost -
W3C 组织早在 2014 年 5 月就提出过 Service Worker 这样一个 html5 API,主要用来做持久的离线缓存。
-
前端有很多性能优化的手段:CDN、CSS Sprite、文件的合并压缩、异步加载、资源缓存等等,这
些手段都是用来做性能优化的,但是如果断网了,会发生什么? -
service worker 允许 web 应用在网络环境比较差或者是离线环境下依然可以使用
-
service worker 可以极大的提升 web app 的用户体验
-
service worker 是一个独立的 worker 线程,独立于当前网页链接,是一种特殊的 web worker
-
Web Worker 是临时的,每次做的事情的结果还不能被持久存下来,如果下次有同样的复杂操作,还得
费时间的重来一遍 -
一旦被 install,就永远存在,除非手动 unregister
-
用到的时候可以直接唤醒,不用的时候自动休眠
-
可编程拦截代理请求和返回,缓存文件,缓存的文件可以被网页进程取到(包括网络离线状态)
-
离线内容开发者可控
-
必须在 Https 环境下才能工作
-
异步实现,内部大都是通过 Promise 实现
-
-
使用步骤
- 在 window.onload 中注册 service worker,防止与其他资源竞争
- 在 navigator 对象中内置了 serviceWorker 属性
- serviceWorker 在老版本的浏览器中不支持,需要进行浏览器兼容 if(‘serviceWordker’ in navigator){}
- 注册 service worker navigator.serviceWorker.register(‘./sw.js’),返回一个 promise 对象
3. Promise
-
基本使用
-
Promise 是异步编程的一种解决方案,比传统的解决方案—回调函数和事件—更合理更强大
-
Promise 可以链式的进行异步请求,解决了回调地狱的问题
-
Promise 常用的静态方法
1. Promise.resolve()返回一个解析过带着给定值的 Promise 对象,如果返回值 2. 是一个 Promise 对象,则直接返回这个 Promise 对象 3. Promise.reject()静态函数 Promise.reject 返回一个被拒绝的 Promise 对象 4. Promise.all()返回一个 Promise 实例,等所有 promise 对象都成功了,才会成功 5. Promise.race()竞速,只要有一个 promise 对象成功或者失败了,结果就是成功或者失败
-
4. async/await
- ES2017 标准引入 async 函数,使得异步操作变得更加方便
- async 用于修饰一个函数,async function fn(){},await 函数会返回一个 promise 对象
- await 只能出现在 async 函数中,await 后面跟一个 promise 对象,用于获取 promise 对象成功的结果,
如果不是 promise 对象,直接返回值 - await 后面的 promise 如果没有成功,那么会抛出异常,需要使用 try.catch 语法
5. fetch api
- Fetch API 提供了一个 Javascript 接口,用于访问和操纵 Http 管道的部分,例如请求和响应
- 在 service worker,如果想要发送请求,无法使用 XMLHttpRequest,必须使用 fetch api
- Fetch.api 是基于 promise 实现的
- request 是一个二进制数据流,需要调用 json()方法可以转换 json
- config 常见参数
- body:用于设置请求体
- headers:用于设置请求头
- method:用于设置请求方式
6. cache storage
- cacheStorage 接口表示 Cache 对象的存储,配合 service worker 来实现资源的缓存
- caches api 类似数据库的操作
- caches.open(cacheName).then(function(catch){}):用于打开缓存,返回一个匹配 cacheName 的 cache 对象的 promise,类似于链接数据库
- caches.keys()返回一个 promise 对象,包括所有的缓存 key
- caches.delete(key)根据 key 删除对应的缓存(数据库)
- cache 对象常用方法(单挑数据的操作)
- cache 接口为缓存的 Request/response 提供存储机制
- cache.put(req,res)把请求当成可以,并且把对应的响应存储起来
- cache.add(url)根据 url 发起请求,并且把响应结果存储起来
- cache.addAll(urls)抓去一个 url 数组,并且把结果都存储起来
- cache.match(req)获取 req 对应的 response
7. 常见的缓存策略
Cache only
Network only
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ThkrMxuj-1579502717128)(https://m.360buyimg.com/img/jfs/t1/94162/24/7057/446668/5df89f4eE4447cca5/dfbb6edcaa343ffd.pngg)]
Cache,falling back to Network
Network,falling back to Cache
Cache & Network race
8. notification api
- Notification API 的通知接口用于向用户配置和显示桌面通知
- Notification.permission 可以获取当前用户的授权情况
- default:默认的,未授权
- Denied:拒绝的,如果拒绝了,无法再次请求授权,也无法弹窗提醒
- Granted:授权的,可以弹窗提醒
- 通过 Notification.requestPermission()可以请求用户的授权
- 通过 new Notification(‘title’,{body:’’,icon:’’})可以显示通知
- 在授权通过的情况下,可以在 service worker 中显示通知 self.registration.showNotification(‘您好’,{body:’msg’})
3.PWA 项目实战
代码展示:
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" />
<link rel="manifest" href="./manifest.json" />
<title>PWA</title>
<script>
window.onload = function() {
//serviceWorker
// if ("serviceWorker" in navigator) {
// navigator.serviceWorker
// .register("./sw.js")
// .then(registration => {
// console.log("registration", registration);
// })
// .catch(err => {
// console.log(err);
// });
// }
//添加通知功能
if (Notification.permission === "default") {
Notification.requestPermission().then(result => {
console.log(result);
});
}
if (!window.navigator.onLine) {
new Notification("提示", { body: "当前没有网络" });
}
if (window.navigator.onLine) {
new Notification("提示", { body: "当前有网络" });
}
};
</script>
</head>
<body>
Hello PWA
</body>
</html>
mainfest.json
{
"name": "PWA例子PWA例子",
"short_name": "PWA",
"start_url": ".",
"display": "minimal-ui",
"background_color": "#f8f8f8",
"theme_color": "#ff0000",
"description": "PWA例子",
"icons": [
{
"src": "https://storage.jd.com/tenant-image/1/bigLogo.png",
"sizes": "144x144",
"type": "image/png"
},
{
"src": "https://storage.jd.com/tenant-image/1/bigLogo.png",
"sizes": "168x168",
"type": "image/png"
},
{
"src": "https://storage.jd.com/tenant-image/1/bigLogo.png",
"sizes": "192x192",
"type": "image/png"
}
]
}
sw.js
// //缓存内容
// self.addEventListener('install', event => {
// console.log('install', event);
// //跳过等待,直接进入activate
// //self.skipWaiting()等待结果,才进入activate
// event.waitUntil(self.skipWaiting());
// });
// //主要清除旧的缓存
// self.addEventListener('activate', event => {
// //跳过等待,直接进入active
// console.log('activate', event);
// //表示service worker激活后,立即获取控制权
// event.waitUntil(self.clients.claim());
// });
// //fetch事件会在请求发送的时候触发
// self.addEventListener('fetch', event => {
// //跳过等待,直接进入active
// self.skipWaiting('fetch', event);
// });
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
const CACHE_NAME = "cache";
//缓存内容
self.addEventListener("install", async event => {
// //开启一个cache
// const cache = await caches.open(CACHE_NAME);
// //cache对象就可以存储的资源
// //等待cache把所有资源存储起来
// cache.addAll(['./index.html', './manifest.json', '/']);
// await self.skipWaiting();
event.waitUntil(
caches.open(CACHE_NAME).then(cache => {
return cache.addAll(["/", "/manifest.json", "/index.html"]);
})
);
});
//主要清除旧的缓存
self.addEventListener("activate", async () => {
const keys = await caches.keys();
keys.forEach(key => {
if (key !== CACHE_NAME) {
caches.delete(key);
}
});
await self.clients.claim();
});
//fetch事件会在请求发送的时候触发
//判断资源是否能够请求成功,如果请求成功,就响应成功的结果,如果断网,就读取caches缓存
self.addEventListener("fetch", event => {
const req = event.request;
const url = new URL(req.url);
// if(url.origin !== self.origin){
// return
// }
event.respondWith(cacheFirst(req));
// if(req.url.includes('./api')){
// event.respondWith(networkFirst(req))
// }else{
// event.respondWith(cacheFirst(req))
// }
});
//缓存优先
async function cacheFirst(req) {
const cache = await caches.open(CACHE_NAME);
const cached = await cache.match(req);
//如果从缓存中得到
if (cached) {
return cached;
} else {
const fresh = await fetch(req);
return fresh;
}
}
//网络请求优先,如果我们获取到了数据,应该往缓存中存一份
async function networkFirst(req) {
const cache = await caches.open(CACHE_NAME);
try {
const fresh = await fetch(req);
cache.put(req, fresh.clone());
return fresh;
} catch (e) {
const cached = await cache.match(req);
return cached;
}
}