• 0

  • 433

深入了解PWA工作原理

5天前

前言:

pwa 讲起来大家都有所耳闻, 在实际项目开发中会运用到,它最重要的一个作用就是可以实现客户端离线缓存,其次是服务端消息推送。我们在现有的一些webapp用户体验差(不能离线访问),用户粘性低(无法保存入口)而pwa就是为了解决这一系列问题(Progressive Web Apps),让webapp具有快速,可靠,安全等特点。

PWA一系列用到的技术

  • Web App Manifest (生成桌面图标)
  • Service Worker  (离线访问)
  • Push Api & Notification Api (服务端消息推送)
  • App Shell & App Skeleton   (骨架屏)

一.Web App Manifest

将网站添加到桌面、更类似native的体验。

针对安卓手机:

  <link rel="apple-touch-icon" href="/icon.png" />

{
    "name": "PWA效果展示",  // 应用名称 
    "short_name": "PWA", // 桌面应用的名称  ✓ (兼容性)
    "display": "standalone", // fullScreen (standalone) minimal-ui browser ✓
    "start_url": "/", // 打开时的网址  ✓
    "icons": [{ // 设置桌面图片 icon图标
        "src": "/icon.png",
        "sizes": "144x144",
        "type": "image/png"
    }],
    "background_color": "#aaa", // 启动画面颜色
    "theme_color": "#aaa" // 状态栏的颜色
}
复制代码

ios 不支持 manifest文件,可以通过 meta/link 私有属性进行设置

<!-- 图标icon -->
<link rel="apple-touch-icon" href="/icon.png"/>
<!-- 添加到主屏后的标题 和 short_name一致 -->
<meta name="apple-mobile-web-app-title" content="标题"> 
<!-- 隐藏safari地址栏 standalone模式下默认隐藏 -->
<meta name="apple-mobile-web-app-capable" content="yes" /> 
<!-- 设置状态栏颜色 -->
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" /> 
复制代码

二.Service Worker

Service Worker特点:

  • 不能访问/操作dom
  • 会自动休眠,不会随浏览器关闭所失效(必须手动卸载)
  • 离线缓存内容开发者可控
  • 必须在https或者localhost下使用
  • 所有的api都基于promise

生命周期:

  • 安装( installing ):这个状态发生在 Service Worker 注册之后,表示开始安装,触发 install 事件回调指定一些静态资源进行离线缓存。
  • 安装后( installed ):Service Worker 已经完成了安装,并且等待其他的 Service Worker 线程被关闭。
  • 激活( activating ):在这个状态下没有被其他的 Service Worker 控制的客户端,允许当前的 worker 完成安装,并且清除了其他的 worker 以及关联缓存的旧缓存资源,等待新的 Service Worker 线程被激活。
  • 激活后( activated ):在这个状态会处理 activate 事件回调 (提供了更新缓存策略的机会)。并可以处理功能性的事件 fetch (请求)、sync (后台同步)、push (推送)。
  • 废弃状态 ( redundant ):这个状态表示一个 Service Worker 的生命周期结束。

关键方法

  • self.skipWaiting():表示强制当前处在 waiting 状态的 Service Worker 进入 activate 状态
  • event.waitUntil():传入一个 Promise 为参数,等到该 Promise 为 resolve 状态为止。
  • self.clients.claim():在 activate 事件回调中执行该方法表示取得页面的控制权, 这样之后打开页面都会使用版本更新的缓存。旧的 Service Worker 脚本不再控制着页面,之后会被停止。

关键步骤:

1、注册serviceWorker

window.addEventListener('load', async () => {
    if ('serviceWorker' in navigator) {
        let registration = await navigator.serviceWorker.register('/sw.js');        
    }
});
复制代码

只要sw.js  中的内容发生改变,就会重新注册serviceWorker

2、注册监听函数

创建一个sw.js文件

self.addEventListener('fetch',(e)=>{
    console.log(e.request.url); // 拦截请求
});

self.addEventListener('install',(e)=>{   只有第一次安装才执行该回调
    e.waitUntil(skipWaiting()); // 跳过等待,直接激活
});

self.addEventListener('activate',(e)=>{    只有第一次激活才执行该回调
    e.waitUntil(self.clients.claim()); // 让serviceWorker拥有控制权
})

self指向的是当前浏览器进程
复制代码

以上三个步骤是关键

3、缓存静态资源

安装时将缓存列表进行缓存操作

const CACHE_NAME = 'cache_v' + 1;
const CACHE_LIST = [
    '/',
    '/index.css',
    '/main.js',
    '/api/list',
    '/index.html',
];
async function preCache(){
    let cache = await caches.open(CACHE_NAME);
    await cache.addAll(CACHE_LIST);
    await self.skipWaiting();
}
self.addEventListener('install',(e)=>{
    e.waitUntil(preCache()); // 跳过等待,直接激活
});
复制代码

激活后删除无用的缓存

async function clearCache(){
    self.clients.claim();
    let keys = await caches.keys();
    await Promise.all(keys.map(key=>{
        if(key !== CACHE_NAME){
            return caches.delete(key);
        }
    }));
}

self.addEventListener('activate',(e)=>{
    e.waitUntil(clearCache()); // 让serviceWorker拥有控制权
});
复制代码

4、离线使用缓存

self.addEventListener('fetch',(e)=>{
    let url = new URL(e.request.url);
    if(url.origin !== self.origin){ // 静态资源走浏览器缓存
        return;
    }
    e.respondWith(
        fetch(e.request).catch(err=>{
            return caches.match(e.request);
        })
    )
});
复制代码

5、缓存策略

  • cachefirst 缓存优先
  • cacheonly 仅缓存
  • networkfirst 网络优先
  • networkonly 仅网络
  • StaleWhileRevalidate 从缓存取,用网络数据更新缓存

完整的sw.js如下:

// 对资源进行离线缓存  seriveWorker 可以自定义缓存的内容
const CACHE_NAME = 'cache_v' + 2;
const CAHCE_LIST = [ // 列表越长 越容易出问题
    '/',
    '/index.html',
    '/main.js',
    '/index.css',
    '/api/list',
    '/manifest.json',
    '/icon.png'
];
// 当断网时 我需要拦截请求, 使用缓存的结果 

// 核心就是拦截请求
async function fetchAndSave(request){
    let res = await fetch(request); // 数据流
    let cloneRes = res.clone(); // 为了保证不破坏原有的响应结果
    let cache = await caches.open(CACHE_NAME);
    await cache.put(request,cloneRes); // 用响应结果更新缓存
    return res;
}
self.addEventListener('fetch', (e) => {
    // 如果是静态资源 不做拦截
    let url = new URL(e.request.url);
    if(url.origin !== self.origin){
        return
    }

    // 缓存策略, 如果接口是不停的变化的 我们希望将数据更新到缓存中
    if(e.request.url.includes('/api')){
        return e.respondWith(
            fetchAndSave(e.request).catch(res => {
                 return caches.match(e.request);
            })
        )
    }

    // serviceWorker中不支持ajax, 但是支持fetch

    // 如果断网了, 抛出异常
    e.respondWith(
        fetch(e.request).catch(res => {
             return caches.match(e.request);
        })
    )
});


// 当serviceWorker 安装时 需要跳过等待

async function preCache() {
    let cache = await caches.open(CACHE_NAME); // 创建一个缓存空间
    await cache.addAll(CAHCE_LIST);
    await self.skipWaiting()
}
self.addEventListener('install', (e) => {
    // e.waitUtil表示等待promise执行完成
    // 预先将缓存列表的数据缓存起来
    e.waitUntil(preCache())
})
async function clearCache() {
    let keys = await caches.keys();
    return Promise.all(keys.map(key => {
        if (key !== CACHE_NAME) {
            return caches.delete(key)
        }
    }))
}
self.addEventListener('activate', (e) => {
    e.waitUntil(Promise.all([clearCache(), clients.claim()])); 
    // 激活后立刻让serviceWorker拥有控制权
})
复制代码

 以上就是pwa的离线缓存的一个完整的过程,

总结

1、注册serviceWorker;2、注册监听函数,分为: 请求拦截、安装、激活。 拦截请求,将对应的数据,缓存起来,调用cache.put,**更新视图数据; **在安装的时候需要将缓存列表里对应的数据缓存起来,并且执行self.skipWaiting()跳过等待;激活的时候 执行self.clients.claim(), 让当前的serviceWorker获得控制权并删除无用的缓存;

在实际项目项目开发中,我们只需要在webpack中配置workbox-webpack-plugin  这个插件。

new WorkboxPlugin.GenerateSW({ clientsClaim: true, skipWaiting: true })
复制代码

关于pwa服务端消息推送的原理,未完待续, 先不在这里阐述了。

免责声明:文章版权归原作者所有,其内容与观点不代表Unitimes立场,亦不构成任何投资意见或建议。

ios

433

相关文章推荐

未登录头像

暂无评论