[关闭]
@xiaoqq 2018-03-14T07:41:08.000000Z 字数 4614 阅读 1480

PWA使用汇总

PWA


一、 国内外使用情况

PWA正如火如荼地发展起来了,我对国内使用到的大厂网站做了一个汇总:

  1. 天猫超市: https://chaoshi.m.tmall.com/?_ig=shoumao&scm=1003.64.201704071.OTHER_1511581550776_1699747&pos=2&acm=201704071.1003.64.1699747&spm=a1z60.8521859.icon.2&ttid=201200%40tmall_iphone_7.2.1&deviceId=50E1DC42-9319-4D3B-868E-691B170FB06C
    天猫超市生鲜店的比较赞,https://chaoshi.m.tmall.com/sw.htm

  2. 微博beta版:https://m.weibo.cn/beta

  3. 花呗网页版:https://render.alipay.com/p/h5/huabei/www/index.html
    使用场景较少,仅仅只限做测试用,玩具级别引用。
    例如:https://render.alipay.com/p/h5/huabei/www/swHtmlTest.html

  4. 饿了么:https://h5.ele.me/msite/
    饿了么是国内比较早一批尝试PWA的厂家,但是给人感觉却技术不是特别好,网站也不稳定,
    https://h5.ele.me/sw.js

  5. 百度Lavas框架:https://lavas.baidu.com/mip/guide/vue/doc/vue/webpack/sw-register-webpack-plugin

  6. https://zhuanlan.zhihu.com/p/28113197

二、 PWA代码片段

1. 埋点发送

思路有两种,a) 在SW通过fetch方法发送,这种方法限制太多

  1. const sendMars = function(seed, data) {
  2. function fixHref(href) {
  3. if (href.indexOf("?") < 0) {
  4. href += "?"
  5. }
  6. if (href.indexOf("vipruid") < 0) {
  7. href += "&vipruid="
  8. }
  9. return href
  10. }
  11. var url = "https://mar.vip.com/b?" + "one=" + encodeURIComponent(seed) + "&data_mars=" + (!!data ? encodeURIComponent(JSON.stringify(data)) : "") + "&url=" + escape(fixHref(self.location.href)) + "&bv=" + escape(navigator.userAgent.toLowerCase()) + "&r=" + Math.random();
  12. if (url.length <= 6144) {
  13. var req = new Request(url, {
  14. mode:'no-cors',
  15. method: "GET",
  16. headers: {
  17. "Content-Type": "image/png",
  18. },
  19. credentials: "include"
  20. });
  21. fetch(req);
  22. }
  23. }

b) 通过client.postMessage发送消息给JS,发送埋点

  1. function sendMars(seed, data) {
  2. self.clients.matchAll()
  3. .then(function (clients) {
  4. if(clients && clients.length) {
  5. clients.forEach(function (client) {
  6. client.postMessage({
  7. name: 'sw.mars',
  8. value: {
  9. seed: seed,
  10. data: data
  11. }
  12. });
  13. })
  14. }
  15. });
  16. }
  1. navigator.serviceWorker.addEventListener('message', e => {
  2. // service-worker.js 如果更新成功会 postMessage 给页面,内容为 'sw.update'
  3. if(e.data.name == 'sw.mars') {
  4. if(window.Mar) {
  5. window.Mar.Seed.request("mars_sead", "click", e.data.value.seed.toString(), e.data.value.data);
  6. }else {
  7. sendMars(e.data.value.seed.toString(), e.data.value.data);
  8. }
  9. }
  10. })

2. PWA容错降级方案

1) 后端提供接口,每次请求sw.js之前都必须要请求接口;

  1. //调用后端降级接口
  2. fetch(`//m.vip.com/api/collection/pwa/downgrade`).then(function(response) {
  3. return response.json();
  4. }).then(result => {
  5. if(result.success && !result.results._defaultResult.downgrade) {
  6. log('注册SW');
  7. registerSw()
  8. }else {
  9. log('降级SW');
  10. unRegisterSw();
  11. }
  12. });

2)利用html不会被缓存的特性,使用Html + iframe标签,在html中装载JSON数据,通过运营后台更改

  1. var entryNode = document.querySelector('#entryChunks');
  2. entryNode.innerHTML = '<iframe id="entryIframe" style="display:none;" src="/pages/json/entrychunks.html?t=' + (new Date).getTime() + '"></iframe>';
  3. var iframe = document.querySelector('#entryIframe');
  4. iframe.onload = function() {
  5. var doc = iframe.contentDocument;
  6. var html = doc.body.innerHTML;
  7. var result = JSON.parse(html);
  8. if(result.success && !result.results._defaultResult.downgrade) {
  9. log('注册SW');
  10. registerSw()
  11. }else {
  12. log('降级SW');
  13. unRegisterSw();
  14. }
  15. }

3) 监听SW的push事件

  1. this.addEventListener('push', function(e){
  2. console.log("receive push message::", e.data.text());
  3. // u4 1.0不支持json()
  4. var data;
  5. try{
  6. data = e.data.json ? e.data.json() : JSON.parse(e.data.text());
  7. // alipay下需要多一层message,并且message里的东西是一个字符串
  8. if(data.message){
  9. data = JSON.parse(data.message);
  10. }
  11. }catch(ex){}
  12. if(!data || !data.action){
  13. return;
  14. }
  15. if(data.action === 'update'){
  16. // 当下发的版本大于当前版本时才更新
  17. // U4 1.0不支持update,降级为清楚本地缓存
  18. if(data.version && compareVer(data.version, VERSION)){
  19. if(this.registration.update){
  20. this.registration.update().then(function(){
  21. log('19');
  22. }, function(){
  23. log('20');
  24. });
  25. }else{
  26. caches.open(CACHE_NAME)
  27. .then(function (cache) {
  28. return cache.delete(FRESH_URL).then(function(){
  29. return cache.add(FRESH_URL);
  30. }).then(function(){
  31. log('17');
  32. }, function(){
  33. log('18');
  34. });
  35. });
  36. }
  37. }else{
  38. console.warn('push: current version is lastest')
  39. }
  40. log('25');
  41. }else if(data.action === 'unregister'){
  42. caches.delete(VERSION).then(function(){
  43. self.registration.unregister().then(function(){
  44. log('21');
  45. }, function(){
  46. log('22');
  47. });
  48. });
  49. }else{
  50. console.warn('push: this push message is not supported, ' + e.data.text())
  51. }
  52. //heartBeat();
  53. })

3. 防止sw.js被缓存

service worker有一个非常重要的点就是sw.js每次必须请求最新的数据。如果html被service worker缓存,sw.js被浏览器缓存,那么页面的静态资源就永远不会请求服务器!所以一定要保证sw.js处于最新。sw.js但凡有一个字节的改变都会触发重新安装。

防止sw.js被缓存有三种做法
1) 在nginx设置sw.js的缓存头文件

  1. location ~ \/sw\.js$ {
  2. add_header Cache-Control no-store;
  3. add_header Pragma no-cache;
  4. }

2) 设置sw.js后缀为sw.html,并且将content-type改为application/x-javascript; charset=utf-8
由于html不会被缓存,所以每次都可以请求最新的数据。

  1. location ~ sw\.html$ {
  2. add_header Content-Type application/javascript;
  3. }

3) 增加一个sw-register.js来加载sw.js,每次上线,保证sw.js的版本号也同时被刷新

  1. //sw-register.js
  2. window.onload = function () {
  3. var script = document.createElement('script');
  4. var firstScript = document.getElementsByTagName('script')[0];
  5. script.type = 'text/javascript';
  6. script.async = true;
  7. script.src = '/sw-register.js?v=' + Date.now();
  8. firstScript.parentNode.insertBefore(script, firstScript);
  9. };
  1. if (navigator.serviceWorker) {
  2. navigator.serviceWorker.register('/service-worker.js?v=build的版本号').then(function () {
  3. // balabala
  4. });
  5. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注