PWA详解

PWA 渐进式的web应用

介绍

我将带你快速熟悉一个pwa应用的基本使用css

本文中涉及到的内容:html

  • fetch
  • CacheStorage
  • service worker
  • SyncMannager
  • postManager 与 messageChannal
  • manifest.json
  • 消息推送

Fetch

Fetch 提供了许多与XMLHttpRequest相同的功能,为何要在这里说起这个,由于在咱们在service worker环境中是不能去使用XMLHttpRequest对象的,故而这是一个很是重要的api。web

Fetch 的核心在于对 HTTP 接口的抽象,包括 Request,Response,Headers,Body这些接口面试

这里只是简单介绍shell

基本用法

Promise<Response> fetch(input[, init]);
复制代码

参数介绍:json

  • input定义要获取的资源。可能值:
  1. string:资源的 URL。一些浏览器会接受 blob: 和 data: 做为 schemes.
  2. Request 对象。
  • init 可选
  1. method: 请求方法,如 GET、POST。
  2. headers: 请求的头,能够为 Headers 的对象,或者形如{'Content-Type': 'image/jpeg'}。
  3. body: 请求体。可能为 Blob、BufferSource、FormData、URLSearchParams 或者 USVString 对象。GET 或 HEAD 无请求体。
  4. mode: 请求的模式,如 cors、 no-cors 或者 same-origin。
  5. credentials: 请求的 credentials,如 omit、same-origin 或者 include。为了在当前域名内自动发送 cookie , 必须提供这个选项, 从 Chrome 50 开始, 这个属性也能够接受 FederatedCredential 实例或是一个 PasswordCredential 实例。
  6. ...

CacheStorage

这是一个能够缓存浏览器缓存的接口,离线应用的核心就靠他了api

CacheStorage经常使用方法介绍

name 描述
Cache.match(request, options) 该方法会检查是否存在该request的缓存,返回 Promise对象,resolve的结果是跟 Cache 对象匹配的第一个已经缓存的请求。
Cache.matchAll(request, options) 同上,不一样的是resolve的结果是跟Cache对象匹配的全部请求组成的数组。
Cache.add(request) 抓取这个URL, 检索并把返回的response对象添加到给定的Cache对象.这在功能上等同于调用 fetch(), 而后使用 Cache.put() 将response添加到cache中.
Cache.addAll(requests) 抓取一个URL数组,检索并把返回的response对象添加到给定的Cache对象。
Cache.put(request, response) 同时抓取一个请求及其响应,并将其添加到给定的cache。
Cache.delete(request, options) 搜索key值为request的Cache 条目。若是找到,则删除该Cache 条目,而且返回一个resolve为true的Promise对象;若是未找到,则返回一个resolve为false的Promise对象。
Cache.keys(request, options) 返回一个Promise对象,resolve的结果是Cache对象key值组成的数组。

options参数的解释:数组

  • ignoreSearch(Boolean): 忽略url中的query部分(?后面的参数)默认值:false
  • ignoreMethod(Boolean): 若是设置为 true在匹配时就不会验证 Request 对象的http 方法 (一般只容许是 GET 或 HEAD 。) 默认值:false
  • ignoreVary(Boolean): 该值若是为 true 则匹配时不进行 VARY 部分的匹配。默认值:false
  • cacheName(DOMString): 表明一个具体的要被搜索的缓存。注意该选项被 Cache.match()方法忽略。

service worker

这是咱们要花时间最多的地方!promise

生命周期

  1. installing(正在安装)

当你调用 navigator.serviceWorker.register 注册一个新的 service worker ,service worker 代码就会被下载、解析、进入installing阶段。若安装成功则进入installed,失败则进入redundant浏览器

  1. installed/waiting(已安装/等待中)

installed 状态下的service worker会判断本身是否已经被注册过,若是是第一次注册将进入activating状态,若是发现本身被注册且处于activated状态,那么将进入waiting状态

  1. activating(激活中)

此状态下的sw将进入activated(必将进入activated状态)

  1. activated(已激活)

一旦 service worker 激活,它就准备好接管页面并监听功能性事件了(例如 fetch 事件)

  1. redundant(废弃)

当sw注册失败或者被新的sw替换将进入此状态(只有这两种状况会进入redundant)

结合代码理解service worker生命周期

页面中的代码:

if("serviceWorker" in navigator){
    window.onload = function(){
        navigator.serviceWorker.register("./sw.js").then((registration)=>{
            var sw = null, state;
            if(registration.installing) {
                sw = registration.installing;
                state = 'installing';
            } else if(registration.waiting) {
                sw = registration.waiting;
                state = 'installed'
            } else if(registration.active) {
                sw = registration.active;
                state = 'activated'
            }
            state && console.log(`sw state is ${state}`);
            if(sw) {
                sw.onstatechange = function() {
                    console.log(`sw state is ${sw.state}`);
                }
            }
        }).catch(()=>{
            console.log('sw fail');
        })
    }

}
复制代码

sw.js:

self.addEventListener('install',function () {
    console.log('install callback');
})

self.addEventListener('activate',function () {
    console.log('activate callback');
})

self.addEventListener('fetch',function () {
    console.log('fetch callback');
})
复制代码

首次刷新页面控制台输出:

install callback
sw state is installing
sw state is installed
activate callback
sw state is activating
sw state is activated
复制代码

再次刷新页面控制台输出:

sw state is activated
fetch callback
复制代码

install,与active事件仅仅执行一次,fetch在首次刷新也面试时是不请求的

更新service worker

当更新sw时,刷新页面,此时:

  1. 新的sw的install回调触发,进入installing
  2. 新的sw发现上一个sw还处于actived状态则进入waiting状态
  3. 此时调用slkipwating方法或者关闭浏览器再次打开浏览器,会使旧的sw进入redundant,新的sw进入actived

代码此处能够本身尝试

waitUntil延长生命周期

waitUntil函数传入promise做为参数,当promise执行完成才会接着往下走。

在install事件中调用该方法

1.加入成功的回调:

self.addEventListener('install',function (event) {
    event.waitUntil(new Promise(function(resolve) {
        setTimeout(() => {
            console.log('install 2s')
            resolve()
        }, 2000)
    }))
})
复制代码

控制台输出:

sw state is installing
install 2s
sw state is installed
activate callback
sw state is activating
sw state is activated
复制代码
  1. 加入失败的回调:
self.addEventListener('install',function (event) {
    event.waitUntil(new Promise(function(resolve,reject) {
        setTimeout(() => {
            console.log('install 2s')
            reject()
        }, 2000)
    }))
})
复制代码

控制台输出:

sw state is installing
install 2s
sw state is redundant
Uncaught (in promise) undefined
复制代码

sw直接进入redundant阶段

在activate事件中调用该方法

与install大体都同样,不一样的是当你调用reject时,sw不会进入redundant阶段,而是最终仍是进入actived阶段

生命周期常见应用

  1. 通常咱们会在install中缓存请求,这是为了可以在下一次请求中使用到这些缓存。
  2. 在active中咱们应该清除旧的缓存。
  3. 在fetch中咱们即可以使用这些缓存,而且更新他们

service worker的做用域范围?

service worker只能捕获当前目录及其子目录下的请求!

好比:
当service worker在/pwa/sw.js下时那么只能捕获/pwa/*的请求,因此通常咱们都应该将sw.js放置于/根目录下。

代码示例

若是你复制如下代码将在页面显示css代码

var CACHE_NAME = "gih-cache";
var CACHED_URLS = [
    "/pwa/test.css"
];

self.addEventListener('install',function (event) {
    event.waitUntil(
        caches.open(CACHE_NAME).then(function(cache) {
            return cache.addAll(CACHED_URLS);
        })
    );
})

self.addEventListener('activate',function (event) {
    // event.waitUntil(
    // caches.keys().then(function(cacheNames) {
    // return Promise.all(
    // cacheNames.map(function(cacheName) {
    // if (CACHE_NAME !== cacheName && cacheName.startsWith("gih-cache")) {
    // return caches.delete(cacheName);
    // }
    // })
    // );
    // })
    // );
})

self.addEventListener('fetch',function (event) {
    event.respondWith(
        caches.open(CACHE_NAME).then(function(cache) {
            return cache.match(event.request).then(function(response) {
                let fetchPromise = fetch(event.request).then(function(networkResponse) {
                    cache.put(event.request, networkResponse.clone());
                    return networkResponse;
                })
                return response || fetchPromise;
            })
        })
    );
})

复制代码

离线开发的策略

  1. 仅网络
  2. 先网络后缓存:当咱们但愿用户看见内容一直是最新的,那么可使用这种模式,在fetch中更新缓存数据
  3. 缓存后网络:当不须要用户看见最新的内容,咱们能够先将缓存呈现给用户,在fetch中更新缓存,下一次用户刷新能够看见最新的缓存。(以上代码采起的就是这种)

syncManager 后台同步

在用户使用web app时,网页可能会被关闭,用户链接可能会断开,甚至服务器有时候也会故障。可是,只要用户设备上安装了浏览器,后台同步中的操做就不会消失,直到它成功完成为止。

注册后台同步事件

在页面中:

navigator.serviceWorker.ready.then(function(registration) {     
    registration.sync.register('send-messages');
});
复制代码

监听sync事件

在sw.js中

self.addEventListener("sync", function(event) { 
    if (event.tag === "send-messages") {
        event.waitUntil(function() { 
            var sent = sendMessages(); 
            if (sent) {
                return Promise.resolve(); 
            }else{
                return Promise.reject(); 
                
            }
        }); 
    }
});
复制代码

sync事件什么时候结束?

当后台屡次尝试不成功时,那么sync事件也会提供结束时的标识符event.lastChance

self.addEventListener("sync", event => { 
    if (event.tag == "add-reservation") {
        event.waitUntil( 
            addReservation()
            .then(function() {
                return Promise.resolve();
            }).catch(function(error) {
                if (event.lastChance) { 
                    return removeReservation();
                } else {
                    return Promise.reject();
                }
            })
        ); 
    }
});
复制代码

postMessage

当咱们将逻辑代码放入service worker中时,咱们就必定会有页面与service worker通讯的需求,此时postMessage即是这么一个担任通讯的角色。

1. 窗口向service worker通讯

页面代码:

navigator.serviceWorker.controller.postMessage( {
    arrival: "05/11/2022", 
    nights: 3, 
    guests: 2
})
复制代码

service worker代码:

self.addEventListener("message", function (event) { 
    console.log(event.data);
});
复制代码

2. service worker向全部打开的窗口通讯

页面代码:

navigator.serviceWorker.addEventListener("message", function (event) {
    console.log(event.data);
});

复制代码

service worker代码:

self.clients.matchAll().then(function(clients) {
    clients.forEach(function(client) {
        if (client.url.includes("/my-account")) { 
            client.postMessage("Hi client: "+client.id);
        } 
    });
});
复制代码

3. service worker向特定窗口通讯

service worker代码:

//当你能够得到某个客户端id时即可以向特定的客户端发送消息
self.clients.get("d2069ced-8f96-4d28").then(function(client) {
    client.postMessage("Hi window, you are currently " +
                        client.visibilityState);
});
复制代码

得到特定的客户端id

//经过clients对象获取客户端id
self.clients.matchAll().then(function(clients) {
    clients.forEach(function(client) {
        self.clients.get(client.id).then(function(client) {  
            client.postMessage("Messaging using clients.matchAll()");
        });
    });
});

//经过event.sourse.id 得到·客户端id
self.addEventListener("message", function(event) {
    self.clients.get(event.source.id).then(function(client) {
        client.postMessage("Messaging using clients.get(event.source.id)");
    });
});

//简化写法
self.clients.matchAll().then(function(clients) {
    clients.forEach(function(client) {
        client.postMessage("Messaging using clients.matchAll()"); 
    });
});

复制代码

MessageChannel

在介绍第四种通讯方式(窗口间通讯)时,我想先插入介绍一下MessageChannel这个对象,他是实现咱们service worker与窗口间相互通讯的一种有效的技术手段。

演示代码

// 窗口代码
var msgChan = new MessageChannel(); 
msgChan.port1.onmessage = function(event) {
    console.log("Message received in page:", event.data); 
};
var msg = {action: "triple", value: 2}; 
//这里能够是postMessage的第二个参数
navigator.serviceWorker.controller.postMessage(msg, [msgChan.port2]);


// service worker代码 
self.addEventListener("message", function (event) {
    var data = event.data;
    var openPort = event.ports[0]; 
    if (data.action === "triple") {
        openPort.postMessage(data.value*3); 
    }
});
复制代码

4. 窗口间的通讯

窗口间通讯方式有多种,你如localStorage这些均可以实现,这里仍是应该思考如何使用service worker来进行通讯,这里先再也不赘述。

manifest.json

当咱们的web应用已经使用以上技术作到了一系列的离线优化后,咱们能够考虑将咱们的应用安装在本地。

页面引入:

<link rel="manifest" href="/manifest.json">
复制代码

manifest.json:

{
    "short_name": "Gotham Imperial",
    "name": "Gotham Imperial Hotel",
    "description": "Book your next stay, manage reservations, and explore Gotham", "start_url": "/my-account?utm_source=pwa",
    "scope": "/",
    "display": "fullscreen",
    "icons": [
        {
        "src": "/img/app-icon-192.png", "type": "image/png",
        "sizes": "192x192"
        }, {
        } 
    ],
    "theme_color": "#242424",
    "background_color": "#242424" 
}
复制代码

属性介绍

  • name与/或short_name:
    name 是应用的全名。当空间足够长时,就会使用这个字段做为显示名称,short_name 能够做为短 名的备选方案

  • start_url:
    当用户点击图标时,打开的 URL。能够是根域名,也能够是内部页面。

  • icon:
    包含了一个或多个对象的数组,对象属性:src(图标的绝对路径或者相对路径)、type(文件类型)和 sizes(图片的像素尺寸)。要触发Web应用安装横条,清单中至少要包含一个图标, 尺寸至少是 144像素×144像素。 因为每一个设备都会根据设备分辨率,从这个数组中选择最佳的图标尺寸,所以建议至少 包含 192×192 的图标和 512×512 的图标,以覆盖大多数的设备和用途。

  • display:

  1. browser——在浏览器中打开应用。
  2. standalone——打开应用时不显示浏览器栏(不显示浏览器界面,例如地址栏)。
  3. fullscreen——打开应用时不显示浏览器栏和设备栏(例如在安卓设备上,这意味着 同时隐藏浏览器界面和屏幕顶部的状态栏)。
  • description:
    应用的描述。

  • orientation:
    容许你强制指定某个屏幕方向。

  • theme_color
    主题颜色可让浏览器和设备调整 UI 以匹配你的网站(见图 9-5)。这个颜色的选择会 影响浏览器地址栏颜色、任务切换器中的应用颜色,甚至是设备状态栏的颜色。主题颜色也能够经过页面的meta标签进行设置(例如:)。若是页面带有 theme-color 的 meta 标签,则该设置会覆盖清单 中的 theme_color 设置。请注意,虽然 meta 标签可让你设置或者覆盖单个页面的主 题颜色,可是清单文件中的 theme_color 设置是会影响整个应用的。

  • background_color
    设置应用启动画面的颜色以及应用加载时的背景色。一旦加载后,页面中定义的任何背 景色(经过样式表或者内联 HTML 标签设置)都会覆盖这一设置;可是,经过将其设 置为与页面背景色相同的颜色,就能够实现从页面启动的瞬间到彻底渲染之间的平滑过 渡。若是不设置这一颜色,页面就会从白色背景启动,随后被页面的背景色替换。

更新中

相关文章
相关标签/搜索