Open monkeyandmiss opened 4 years ago
PWA(Progressive web apps,渐进式 Web 应用)运用现代的 Web API 以及传统的渐进式增强策略来创建跨平台 Web 应用程序。
如何理解渐进式?
PWA实现上述功能,依赖于service worker提供的能力。
service worker是web worker的一种,是运行在独立线程中的js代码。一个service worker的完整生命周期如下图所示。
通常遵循以下基本步骤来使用 service worker:
示例代码如下:
navigator.serviceWorker.register(opts.url).then(function(registration) { console.log("Service worker successfully registered."); })
为了处理业务,我们对service worker相关的基本方法进行了封装
主线程js封装如下:
/** * service worker sdk * * @param {string} opts.url [required] sw文件地址 * @param {function} opts.onReady [optional] sw注册成功 * @param {function} opts.onBeforeInstallPrompt [optional] 未安装pwa事件触发 * @param {function} opts.onClickInstallPrompt [optional] 点击安装确认弹窗 * @param {function} opts.onInstalled [optional] pwa安装成功时触发 * @param {function} opts.onNotificationPermission [optional] 点击通知授权确认弹窗 * */ export function SWSdk(opts) { /** * 初始化sw */ /** * sw注册成功 */ /** * 未安装pwa事件触发 */ /** * pwa安装成功时触发 */ /** * 弹出安装确认弹窗 */ /** * 监听sw事件 */ /** * 触发sw事件 */ /** * 弹出通知授权确认弹窗 */ /** * 发送一条通知 */ /** * 缓存资源 */ /** * 删除缓存资源 */ }
sw线程js封装如下:
/** * SW * * @param {string} opts.CACHE_NAME [optional] 缓存命名空间,建议每个应用独立命名 * @param {number} opts.tickTime [optional] 每个tick的时间间隔,单位ms,默认1000 * @param {function} opts.onTick [optional] 每个时间间隔调用一次 * @param {function} opts.onProxy [optional] 代理网络请求 * @param {function} opts.onInstall [optional] 安装事件的回调 * @param {function} opts.onActivate [optional] 激活事件的回调 * @param {function} opts.onPush [optional] 收到服务端事件的回调 * @param {function} opts.notificationOnClick [optional] 点击push通知的回调 */ var SW = function (opts) { /** * 初始化sw */ /** * 监听窗口事件 */ /** * 触发窗口事件 */ /** * 设置cache */ /** * 获取cache */ /** * 发送一条通知 */ };
对service worker api的封装,使我们可以更加集中精力处理业务。
封装的基本方法有:
a. 线程间通讯。主线程和service worker线程之间需要频繁的通信,因此需要封装比较友好的通信方法
主线程:
/** * 监听sw事件 * * @param {string} eventName [required] 事件名称 * @param {function} handler [required] 处理函数 */ this.on = function(eventName, handler) { this.eventListener.push({ eventName: eventName, handler: handler }) }; /** * 触发sw事件 * * @param {string} eventName [required] 事件名称 * @param {any} payload [optional] 传递的数据 */ this.emit = function(eventName, payload) { const data = { eventName: eventName, payload: payload }; try { if (navigator.serviceWorker.controller) { navigator.serviceWorker.controller.postMessage(data); } else { navigator.serviceWorker.addEventListener("controllerchange", () => { navigator.serviceWorker.controller.postMessage(data); }); } } catch(err) { console.error(err); } }
service worker线程:
/** * 监听窗口事件 * * @param {string} eventName [required] 事件名称 * @param {function} handler [required] 处理函数 */ this.on = function(eventName, handler) { this.eventListener.push({ eventName: eventName, handler: handler }) }; /** * 触发窗口事件 * * @param {string} eventName [required] 事件名称 * @param {any} payload [optional] 传递的数据 */ this.emit = function(eventName, payload) { clients.matchAll({ type: 'window', includeUncontrolled: true }).then(function(matchClient) { matchClient.forEach(function(client) { client.postMessage({ eventName: eventName, payload: payload }); }) }); };
b. 本地存储。在service worker 线程中,我们无法使用cookie,localStorage和sessionStorage,我们只能使用cache api或者indexDB作为存储key-value数据的载体。
/** * 设置cache * * @param {string} key cache的key * @param {any} value cache的值 */ this.setCache = function (key, value) { try { return caches.open(this.CACHE_NAME).then(function(cache) { return cache.put(key, new Response(value)); }) } catch (err) { const that = this; return new Promise(function(resolve) { if (!that.cacheStorage[that.CACHE_NAME]) { that.cacheStorage[that.CACHE_NAME] = {}; } that.cacheStorage[that.CACHE_NAME][key] = value; resolve(); }) } }; /** * 获取cache * * @param {string} key cache的key */ this.getCache = function(key) { try { return caches.open(this.CACHE_NAME).then(function(cache) { return cache.match(key); }).then(function(response) { return response ? response.text() : ''; }) } catch (err) { const that = this; return new Promise(function(resolve) { resolve(new String(that.cacheStorage[that.CACHE_NAME][key])); }) } };
cache api无法直接保存key-value键值对数据,只能保存url-response对数据,我们这里使用了一些小技巧,使它可以存储key-value型数据
c. 通知
主线程申请授权
/** * 弹出通知授权确认弹窗 */ this.requestNotificationPermission = function() { Notification.requestPermission().then((result) => { that.onNotificationPermission.bind(that)(result); }); };
service worker线程发送通知
/** * 发送一条通知 * * @param {object} params [required] * @param {string} params.title [required] 标题 * @param {string} params.desc [optional] 描述 * @param {string} params.icon [optional] 图标 * @param {any} params.data [optional] 传递参数 * @param {string} params.url [optional] 点击跳转地址 */ this.showNotification = function(params) { try { self.registration.showNotification(params.title, { body: params.desc, icon: params.icon, image: params.image, data: Object.assign({ url: params.url }, params.data) }) } catch (err) { console.log(err); } };
此小节内容太多,不详细展开,有兴趣可以私聊
a. 安装桌面快捷方式 未安装事件 弹出询问安装弹窗api b. 本地推送通知 询问授权通知api 发送通知 c. sw内埋点 Fetch api 请求数据构造 d. 拉活桌面pwa 需要安装google play服务 需要由不同域的页面发起重定向跳转 与pwa同域的链接均可拉活pwa,且pwa展示跳转链接,而非start_url中配置的链接 中转页策略 e. 视频预加载 使用cache api f. 识别用户访问的是web页面还是桌面pwa 桌面入口拉活 链接拉活
a. 安装桌面快捷方式
b. 本地推送通知
c. sw内埋点
d. 拉活桌面pwa
e. 视频预加载
f. 识别用户访问的是web页面还是桌面pwa
a. 兼容问题
pwa的兼容性是比较差的,几乎每个api都有兼容问题,需要对不同的设备做适配。这些兼容问题很多是查看线上统计数据后才发现的
b. 数据统计
为了统计pwa转化效果,我们需要识别用户访问的是web页面还是桌面的pwa,然而,我们只能统计到桌面图标打开的用户和链接拉活的pwa用户,对于push拉活,第三方app拉活的场景,我们是无法识别的。
1.什么是PWA?
PWA(Progressive web apps,渐进式 Web 应用)运用现代的 Web API 以及传统的渐进式增强策略来创建跨平台 Web 应用程序。
如何理解渐进式?
2.PWA能做什么?
3.PWA的工作原理
PWA实现上述功能,依赖于service worker提供的能力。
service worker是web worker的一种,是运行在独立线程中的js代码。一个service worker的完整生命周期如下图所示。
通常遵循以下基本步骤来使用 service worker:
示例代码如下:
4.处理业务需要封装的基本方法
为了处理业务,我们对service worker相关的基本方法进行了封装
主线程js封装如下:
sw线程js封装如下:
对service worker api的封装,使我们可以更加集中精力处理业务。
封装的基本方法有:
主线程:
service worker线程:
cache api无法直接保存key-value键值对数据,只能保存url-response对数据,我们这里使用了一些小技巧,使它可以存储key-value型数据
主线程申请授权
service worker线程发送通知
5. 业务需求及对策
此小节内容太多,不详细展开,有兴趣可以私聊
6. 遇到的问题
pwa的兼容性是比较差的,几乎每个api都有兼容问题,需要对不同的设备做适配。这些兼容问题很多是查看线上统计数据后才发现的
为了统计pwa转化效果,我们需要识别用户访问的是web页面还是桌面的pwa,然而,我们只能统计到桌面图标打开的用户和链接拉活的pwa用户,对于push拉活,第三方app拉活的场景,我们是无法识别的。