菜单

澳门金沙国际React 同构应用 PWA 升级指南

2019年2月20日 - 金沙前端

缓存策略

精通了怎么样财富要求被缓存后,接下去就要商量缓存策略了。

开发者工具

Chrome浏览器提供了一连串的工具来协理您来调节ServiceWorker,日志也会直接体以往控制台上。

您最好使用匿超级模特式来展开付出工作,那样可以去掉缓存对开发的困扰。

最后,Chrome的Lighthouse扩展也足以为您的渐进式Web应用提供部分革新音讯。

(2)瑟维斯 Worker安装和激活

挂号完事后,瑟维斯Worker就会开展安装,这么些时候会触发install事件,在install事件之中可以缓存一些财富,如下sw-3.js:

JavaScript

const CACHE_NAME = “fed-cache”; this.add伊芙ntListener(“install”,
function(event) { this.skipWaiting(); console.log(“install service
worker”); // 创设和开拓一个缓存库 caches.open(CACHE_NAME); // 首页 let
cacheResources = [“];
event.waitUntil( // 请求能源并添加到缓存里面去
caches.open(CACHE_NAME).then(cache => {
cache.addAll(cacheResources); }) ); });

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const CACHE_NAME = "fed-cache";
this.addEventListener("install", function(event) {
    this.skipWaiting();
    console.log("install service worker");
    // 创建和打开一个缓存库
    caches.open(CACHE_NAME);
    // 首页
    let cacheResources = ["https://fed.renren.com/?launcher=true"];
    event.waitUntil(
        // 请求资源并添加到缓存里面去
        caches.open(CACHE_NAME).then(cache => {
            cache.addAll(cacheResources);
        })
    );
});

透过上面的操作,创设和添加了三个缓存库叫fed-cache,如下Chrome控制台所示:

澳门金沙国际 1

ServiceWorker的API基本上都以回来Promise对象防止堵塞,所以要用Promise的写法。上边在装置ServiceWorker的时候就把首页的请求给缓存起来了。在ServiceWorker的运行条件之中它有一个caches的大局对象,这几个是缓存的进口,还有1个常用的clients的大局对象,3个client对应三个标签页。

在ServiceWorker里面可以接纳fetch等API,它和DOM是与世隔膜的,没有windows/document对象,不能直接操作DOM,不能直接和页面交互,在ServiceWorker里面不能得知当前页面打开了、当前页面的url是哪些,因为3个瑟维斯Worker管理当前打开的多少个标签页,可以透过clients知道全部页面的url。还有可以因而postMessage的法门和主页面相互传递新闻和数码,进而做些控制。

install完之后,就会触发Service Worker的active事件:

JavaScript

this.addEventListener(“active”, function(event) { console.log(“service
worker is active”); });

1
2
3
this.addEventListener("active", function(event) {
    console.log("service worker is active");
});

瑟维斯Worker激活之后就可以监听fetch事件了,大家期待每拿到三个财富就把它缓存起来,就绝不像上一篇涉嫌的Manifest要求先生成3个列表。

您可能会问,当自身刷新页面的时候不是又重新挂号安装和激活了三个ServiceWorker?尽管又调了两次注册,但并不会再一次挂号,它发现”sw-3.js”这几个早已注册了,就不会再登记了,进而不会触发install和active事件,因为脚下ServiceWorker已经是active状态了。当须要更新ServiceWorker时,如变成”sw-4.js”,或许变更sw-3.js的文书内容,就会重新挂号,新的ServiceWorker会先install然后进入waiting状态,等到重启浏览器时,老的ServiceWorker就会被替换掉,新的ServiceWorker进入active状态,假设不想等到再一次开动浏览器可以像上边一样在install里面调skipWaiting:

JavaScript

this.skipWaiting();

1
this.skipWaiting();

面前的话

  渐进式网络应用 ( Progressive Web Apps ),即我们所纯熟的 PWA,是
谷歌 指出的用前沿的 Web 技术为网页提供 App 般使用体验的一三种方案。PWA
本质上是 Web App,借助一些新技巧也有着了 Native App
的有的风味。本文将详细介绍针对现有网站的PWA升级

 

接口缓存策略

谈完页面缓存,再来讲讲接口缓存,接口缓存就跟页面缓存很类似了,唯一的差距在于:页面第1次加载的时候不必然有缓存,然则会有接口缓存的存在(因为伪造了
cache 中的数据),所以缓存策略跟页面缓存类似:

  1. 互联网优先的方法,即优先拿到互连网上接口数据。当互连网请求战败的时候,再去赢得
    service worker 里以前缓存的接口数据
  2. 当互联网加载成功以往,就更新 cache
    中对应的缓存接口数据,有限扶助下次每趟加载页面,都以上次拜会的风靡接口数据

因此代码就像那样(代码类似,不再赘述):

self.add伊芙ntListener(‘fetch’, (e) => { console.log(‘将来正在呼吁:’

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
self.addEventListener(‘fetch’, (e) => {
  console.log(‘现在正在请求:’ + e.request.url);
  const currentUrl = e.request.url;
  if (matchHtml(currentUrl)) {
    // …
  } else if (matchApi(currentUrl)) {
    const requestToCache = e.request.clone();
    e.respondWith(
      fetch(requestToCache).then((response) => {
        if (!response || response.status !== 200) {
          return response;
        }
        const responseToCache = response.clone();
        caches.open(apiCacheName).then((cache) => {
          cache.put(requestToCache, responseToCache);
        });
        return response;
      }).catch(function() {
        return caches.match(e.request);
      })
    );
  }
});

那里其实可以再拓展优化的,比如在获取数据接口的时候,可以先读取缓存中的接口数据开展渲染,当真正的互联网接口数据再次回到之后再展开轮换,那样也能立见成效压缩用户的首屏渲染时间。当然那或者会发出页面闪烁的效果,可以加上一些卡通来展开过渡。

缓存刷新

演示代码中在倡议呼吁在此以前会先查询缓存。当用户处于离线状态时,那很好,可是如若用户处于在线状态,那她只会浏览到比较老旧的页面。

各个财富比如图片和视频不会转移,所以一般都把这几个静态能源设置为漫漫缓存。那几个能源可以直接缓存一年(31,536,000秒)。在HTTP
Header中,就是:

Cache-Control: max-age=31536000

1
Cache-Control: max-age=31536000

页面,CSS和本子文件或然变动的更频仍一些,所以你可以安装壹个相比小的缓存超时时间(24小时),并确保在用户互连网连接复苏时再次从服务器请求:

Cache-Control: must-revalidate, max-age=86400

1
Cache-Control: must-revalidate, max-age=86400

你也得以在历次网站公布时,通过更名的不二法门强制浏览爱戴新请求财富。

3. 使用Service Worker

ServiceWorker的利用套路是先登记二个Worker,然后后台就会运维一条线程,能够在那条线程运转的时候去加载一些能源缓存起来,然后监听fetch事件,在那一个事件里拦截页面的呼吁,先看下缓存里有没有,如若有平素回到,否则不奇怪加载。或许是一初步不缓存,各个财富请求后再拷贝一份缓存起来,然后下五次呼吁的时候缓存里就有了。

加上到显示器

  没人愿意无独有偶地在活动设备键盘上输入长长的网址。通过丰裕到显示屏的成效,用户可以像从使用集团安装本机应用这样,选用为其设备增进三个飞快链接,并且经过要顺遂得多

【配置项表明】

  使用manifest.json文件来贯彻拉长到显示器的机能,上面是该文件内的计划项

short_name: 应用展示的名字
icons: 定义不同尺寸的应用图标
start_url: 定义桌面启动的 URL
description: 应用描述
display: 定义应用的显示方式,有 4 种显示方式,分别为:
  fullscreen: 全屏
  standalone: 应用
  minimal-ui: 类似于应用模式,但比应用模式多一些系统导航控制元素,但又不同于浏览器模式
  browser: 浏览器模式,默认值
name: 应用名称
orientation: 定义默认应用显示方向,竖屏、横屏
prefer_related_applications: 是否设置对应移动应用,默认为 false
related_applications: 获取移动应用的方式
background_color: 应用加载之前的背景色,用于应用启动时的过渡
theme_color: 定义应用默认的主题色
dir: 文字方向,3 个值可选 ltr(left-to-right), rtl(right-to-left) 和 auto(浏览器判断),默认为 auto
lang: 语言
scope: 定义应用模式下的路径范围,超出范围会以浏览器方式显示

  上面是一份健康的manifest.json文件的布署

{
  "name": "小火柴的前端小站",
  "short_name": "前端小站",
  "start_url": "/",
  "display": "standalone",
  "description": "",
  "theme_color": "#fff",
  "background_color": "#d8d8d8",
  "icons": [{
      "src": "./logo_32.png",
      "sizes": "32x32",
      "type": "image/png"
    },
    {
      "src": "./logo_48.png",
      "sizes": "48x48",
      "type": "image/png"
    },
    {
      "src": "./logo_96.png",
      "sizes": "96x96",
      "type": "image/png"
    },
    {
      "src": "./logo_144.png",
      "sizes": "144x144",
      "type": "image/png"
    },
    {
      "src": "./logo_192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "./logo_256.png",
      "sizes": "256x256",
      "type": "image/png"
    }
  ]
}

【注意事项】

  壹 、在 Chrome 上首选使用 short_name,借使存在,则先行于 name
字段使用

  二 、图标的项目最好是png,,且存在144px的尺寸,否则会收获如下指示

Site cannot be installed: a 144px square PNG icon is required, but no supplied icon meets this requirement

  3、start_url表示项目运转路径

  借使是’/’,则运行路径为

localhost:3000/

  倘诺是’/index.html’,则运行路径为

localhost:3000/index.html

  所以,最好填写’/’

【HTML引用】

   在HTML文档中经过link标签来引用manifest.json文件

<link rel="manifest" href="/manifest.json">

  要特别注意manifest文件路径难点,要将该公文放到静态财富目录下,否则,会找不到该公文,控制台突显如下指示

Manifest is not valid JSON. Line: 1, column: 1, Unexpected token

  如果index.html也位于静态能源目录,则设置如下

<link rel="manifest" href="/manifest.json">

  借使index.html位于根目录,而静态财富目录为static,则设置如下

<link rel="manifest" href="/static/manifest.json" />

【meta标签】

  为了更好地SEO,需求通过meta标签设置theme-color

<meta name="theme-color" content="#fff"/>

【SSR】

  假若是劳务器端配置,需求在server.js文件中安插manifest.json、logo、icon等公事的静态路径

app.use(express.static(path.join(__dirname, 'dist')))
app.use('/manifest.json', express.static(path.join(__dirname, 'manifest.json')))
app.use('/logo', express.static(path.join(__dirname, 'logo')))
app.use('/service-worker.js', express.static(path.join(__dirname, 'dist/service-worker.js')))

 

缓存后端接口数据

缓存接口数据是亟需的,但也不是必须通过 service worker
来落到实处,前端存放数据的地点有无数,比如通过 localstorage,indexeddb
来举办仓储。那里我也是透过 service worker
来兑现缓存接口数据的,倘使想经过任何格局来落到实处,只需求留意好 url
路径与数码对应的炫耀关系即可。

其三步:创制三个 Service Worker

瑟维斯 Worker
是二个可编程的服务器代理,它能够阻碍大概响应互连网请求。瑟维斯 Worker
是坐落应用程序根目录的几个个的JavaScript文件。

你须要在页面对应的JavaScript文件中登记该ServiceWorker:

if (‘serviceWorker’ in navigator) { // register service worker
navigator.serviceWorker.register(‘/service-worker.js’); }

1
2
3
4
if (‘serviceWorker’ in navigator) {
  // register service worker
  navigator.serviceWorker.register(‘/service-worker.js’);
}

一旦你不必要离线的连锁职能,您能够只开创三个 /service-worker.js文件,那样用户就足以直接设置您的Web应用了!

ServiceWorker这几个定义恐怕相比较难懂,它实在是七个办事在其余线程中的标准的Worker,它不能够访问页面上的DOM成分,没有页面上的API,可是可以阻止全部页面上的网络请求,蕴含页面导航,请求能源,Ajax请求。

地点就是拔取全站HTTPS的第叁原因了。即使你没有在您的网站中运用HTTPS,贰个第叁方的台本就足以从其余的域名注入他协调的ServiceWorker,然后篡改全体的伸手——那活脱脱是可怜危险的。

Service Worker 会响应多个事件:install,activate和fetch。

(3)fetch资源后cache起来

如下代码,监听fetch事件做些处理:

JavaScript

this.addEventListener(“fetch”, function(event) { event.respondWith(
caches.match(event.request).then(response => { // cache hit if
(response) { return response; } return
util.fetchPut(event.request.clone()); }) ); });

1
2
3
4
5
6
7
8
9
10
11
12
this.addEventListener("fetch", function(event) {
    event.respondWith(
        caches.match(event.request).then(response => {
            // cache hit
            if (response) {
                return response;
            }
            return util.fetchPut(event.request.clone());
        })
    );
});

先调caches.match看一下缓存里面是还是不是有了,假如有一贯回到缓存里的response,否则的话不奇怪请求财富并把它放到cache里面。放在缓存里能源的key值是Request对象,在match的时候,须要请求的url和header都一模一样才是一致的能源,可以设定第四个参数ignoreVary:

JavaScript

caches.match(event.request, {ignoreVary: true})

1
caches.match(event.request, {ignoreVary: true})

表示一旦请求url相同就觉得是同贰个财富。

上边代码的util.fetchPut是这么完成的:

JavaScript

let util = { fetchPut: function (request, callback) { return
fetch(request).then(response => { // 跨域的财富直接return if
(!response || response.status !== 200 || response.type !== “basic”) {
return response; } util.putCache(request, response.clone()); typeof
callback === “function” && callback(); return response; }); }, putCache:
function (request, resource) { // 后台不要缓存,preview链接也不用缓存 if
(request.method === “GET” && request.url.indexOf(“wp-admin”) < 0 &&
request.url.indexOf(“preview_id”) < 0) {
caches.open(CACHE_NAME).then(cache => { cache.put(request,
resource); }); } } };

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
let util = {
    fetchPut: function (request, callback) {
        return fetch(request).then(response => {
            // 跨域的资源直接return
            if (!response || response.status !== 200 || response.type !== "basic") {
                return response;
            }
            util.putCache(request, response.clone());
            typeof callback === "function" && callback();
            return response;
        });
    },
    putCache: function (request, resource) {
        // 后台不要缓存,preview链接也不要缓存
        if (request.method === "GET" && request.url.indexOf("wp-admin") < 0
              && request.url.indexOf("preview_id") < 0) {
            caches.open(CACHE_NAME).then(cache => {
                cache.put(request, resource);
            });
        }
    }
};

内需留意的是跨域的资源不可以缓存,response.status会重返0,借使跨域的能源支撑COCRUISERS,那么能够把request的mod改成cors。假诺请求失利了,如404要么是晚点等等的,那么也直接回到response让主页面处理,否则的话表达加载成功,把那个response克隆3个停放cache里面,然后再回来response给主页面线程。注意能减缓存里的财富一般只可以是GET,通过POST获取的是不只怕缓存的,所以要做个判断(当然你也得以手动把request对象的method改成get),还有把一部分民用不期待缓存的能源也做个判断。

诸如此类一旦用户打开过一次页面,ServiceWorker就安装好了,他刷新页面可能打开第③个页面的时候就可以把请求的能源一一做缓存,包涵图片、CSS、JS等,只要缓存里有了随便用户在线或然离线都能够常常访问。那样我们自然会有1个标题,那一个缓存空间到底有多大?上一篇我们提到Manifest也总算地点存储,PC端的Chrome是5Mb,其实那些说法在新本子的Chrome已经不确切了,在Chrome
61本子可以看到地点存储的上空和运用状态:

澳门金沙国际 2

里头Cache Storage是指ServiceWorker和Manifest占用的半空中大小和,上图能够看看总的空间大小是20GB,大致是unlimited,所以基本上不用操心缓存会不够用。

功用演示

  在此从前端小站xiaohuochai.cc的PWA效果做示范,github移步至此

【添加到桌面】

澳门金沙国际 3

【离线缓存】

   由于手机录屏采用不可以进展离线录像,改由模拟器模拟离线效果

澳门金沙国际 4

 

公开场馆如何财富须求被缓存?

那么在上马运用 service worker 之前,首先需求通晓什么能源须要被缓存?

第④步:成立可用的离线页面

离线页面能够是静态的HTML,一般用于指示用户日前恳请的页面目前不能利用。然则,大家可以提供部分方可翻阅的页面链接。

Cache
API可以在main.js中运用。然则,该API使用Promise,在不援救Promise的浏览器中会战败,全体的JavaScript执行会就此遭到震慑。为了幸免那种意况,在做客/js/offlinepage.js的时候我们添加了一段代码来检查当前是或不是在离线环境中:

/js/offlinepage.js 中以版本号为名称保存了近日的缓存,获取具有UPAJEROL,删除不是页面的U景逸SUVL,将那几个U奥迪Q7L排序然后将具有缓存的USportageL体以后页面上:

// cache name const CACHE = ‘::PWAsite’, offlineURL = ‘/offline/’, list
= document.getElementById(‘cachedpagelist’); // fetch all caches
window.caches.keys() .then(cacheList => { // find caches by and order
by most recent cacheList = cacheList .filter(cName =>
cName.includes(CACHE)) .sort((a, b) => a – b); // open first cache
caches.open(cacheList[0]) .then(cache => { // fetch cached pages
cache.keys() .then(reqList => { let frag =
document.createDocumentFragment(); reqList .map(req => req.url)
.filter(req => (req.endsWith(‘/’) || req.endsWith(‘.html’)) &&
!req.endsWith(offlineURL)) .sort() .forEach(req => { let li =
document.createElement(‘li’), a =
li.appendChild(document.createElement(‘a’)); a.setAttribute(‘href’,
req); a.textContent = a.pathname; frag.appendChild(li); }); if (list)
list.appendChild(frag); }); }) });

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// cache name
const
  CACHE = ‘::PWAsite’,
  offlineURL = ‘/offline/’,
  list = document.getElementById(‘cachedpagelist’);
// fetch all caches
window.caches.keys()
  .then(cacheList => {
    // find caches by and order by most recent
    cacheList = cacheList
      .filter(cName => cName.includes(CACHE))
      .sort((a, b) => a – b);
    // open first cache
    caches.open(cacheList[0])
      .then(cache => {
        // fetch cached pages
        cache.keys()
          .then(reqList => {
            let frag = document.createDocumentFragment();
            reqList
              .map(req => req.url)
              .filter(req => (req.endsWith(‘/’) || req.endsWith(‘.html’)) && !req.endsWith(offlineURL))
              .sort()
              .forEach(req => {
                let
                  li = document.createElement(‘li’),
                  a = li.appendChild(document.createElement(‘a’));
                  a.setAttribute(‘href’, req);
                  a.textContent = a.pathname;
                  frag.appendChild(li);
              });
            if (list) list.appendChild(frag);
          });
      })
  });

(4)cache html

上边第(3)步把图纸、js、css缓存起来了,可是只要把页面html也缓存了,例如把首页缓存了,就会有一个狼狈的题材——ServiceWorker是在页面注册的,但是将来拿走页面的时候是从缓存取的,每一趟都以相同的,所以就导致力不从心立异ServiceWorker,如变成sw-5.js,然则PWA又必要大家能缓存页面html。那怎么做吧?谷歌(Google)的开发者文档它只是提到会存在那些题目,但并从未证实怎么消除那几个题材。这么些的题材的化解就必要我们要有二个体制能明了html更新了,从而把缓存里的html给替换掉。

Manifest更新缓存的编制是去看Manifest的文本内容有没有发生变化,如果发生变化了,则会去革新缓存,ServiceWorker也是基于sw.js的文书内容有没有发生变化,大家可以借鉴这几个思想,假使请求的是html并从缓存里取出来后,再发个请求获取贰个文书看html更新时间是否发生变化,假如暴发变化了则印证发生转移了,进而把缓存给删了。所以可以在服务端通过控制那些文件从而去立异客户端的缓存。如下代码:

JavaScript

this.add伊夫ntListener(“fetch”, function(event) { event.respondWith(
caches.match(event.request).then(response => { // cache hit if
(response) { //假如取的是html,则看发个请求看html是或不是更新了 if
(response.headers.get(“Content-Type”).indexOf(“text/html”) >= 0) {
console.log(“update html”); let url = new UQashqaiL(event.request.url);
util.updateHtmlPage(url, event.request.clone(), event.clientId); }
return response; } return util.fetchPut(event.request.clone()); }) );
});

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
this.addEventListener("fetch", function(event) {
 
    event.respondWith(
        caches.match(event.request).then(response => {
            // cache hit
            if (response) {
                //如果取的是html,则看发个请求看html是否更新了
                if (response.headers.get("Content-Type").indexOf("text/html") >= 0) {
                    console.log("update html");
                    let url = new URL(event.request.url);
                    util.updateHtmlPage(url, event.request.clone(), event.clientId);
                }
                return response;
            }
 
            return util.fetchPut(event.request.clone());
        })
    );
});

由此响应头header的content-type是还是不是为text/html,倘诺是的话就去发个请求获取八个文本,依据这一个文件的内容决定是不是必要删除缓存,那几个创新的函数util.updateHtmlPage是那样已毕的:

JavaScript

let pageUpdateTime = { }; let util = { updateHtmlPage: function (url,
htmlRequest) { let pageName = util.getPageName(url); let jsonRequest =
new Request(“/html/service-worker/cache-json/” + pageName + “.sw.json”);
fetch(jsonRequest).then(response => { response.json().then(content
=> { if (pageUpdateTime[pageName] !== content.updateTime) {
console.log(“update page html”); // 若是有创新则再次拿到html
util.fetchPut(htmlRequest); pageUpdate提姆e[pageName] =
content.updateTime; } }); }); }, delCache: function (url) {
caches.open(CACHE_NAME).then(cache => { console.log(“delete cache “

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
let pageUpdateTime = {
 
};
let util = {
    updateHtmlPage: function (url, htmlRequest) {
        let pageName = util.getPageName(url);
        let jsonRequest = new Request("/html/service-worker/cache-json/" + pageName + ".sw.json");
        fetch(jsonRequest).then(response => {
            response.json().then(content => {
                if (pageUpdateTime[pageName] !== content.updateTime) {
                    console.log("update page html");
                    // 如果有更新则重新获取html
                    util.fetchPut(htmlRequest);
                    pageUpdateTime[pageName] = content.updateTime;
                }
            });
        });
    },
    delCache: function (url) {
        caches.open(CACHE_NAME).then(cache => {
            console.log("delete cache " + url);
            cache.delete(url, {ignoreVary: true});
        });
    }
};

代码先去得到一个json文件,一个页面会对应一个json文件,那几个json的故事情节是这般的:

JavaScript

{“updateTime”:”10/2/2017, 3:23:57 PM”,”resources”: {img: [], css:
[]}}

1
{"updateTime":"10/2/2017, 3:23:57 PM","resources": {img: [], css: []}}

内部紧要有贰个update提姆e的字段,如若地点内存没有这一个页面的updateTime的数目依旧是和新星updateTime不等同,则再一次去取得
html,然后嵌入缓存里。接着需求文告页面线程数据发生变化了,你刷新下页面吗。那样就毫无等用户刷新页面才能行之有效了。所以当刷新完页面后用postMessage文告页面:

JavaScript

let util = { postMessage: async function (msg) { const allClients =
await clients.matchAll(); allClients.forEach(client =>
client.postMessage(msg)); } }; util.fetchPut(htmlRequest, false,
function() { util.postMessage({type: 1, desc: “html found updated”, url:
url.href}); });

1
2
3
4
5
6
7
8
9
let util = {
    postMessage: async function (msg) {
        const allClients = await clients.matchAll();
        allClients.forEach(client => client.postMessage(msg));
    }
};
util.fetchPut(htmlRequest, false, function() {
    util.postMessage({type: 1, desc: "html found updated", url: url.href});
});

并显然type: 1就代表那是三个立异html的音讯,然后在页面监听message事件:

JavaScript

if(“serviceWorker” in navigator) {
navigator.serviceWorker.addEventListener(“message”, function(event) {
let msg = event.data; if (msg.type === 1 && window.location.href ===
msg.url) { console.log(“recv from service worker”, event.data);
window.location.reload(); } }); }

1
2
3
4
5
6
7
8
9
if("serviceWorker" in navigator) {
    navigator.serviceWorker.addEventListener("message", function(event) {
        let msg = event.data;
        if (msg.type === 1 && window.location.href === msg.url) {
            console.log("recv from service worker", event.data);
            window.location.reload();
        }  
    });
}

接下来当大家须求更新html的时候就更新json文件,那样用户就能来看最新的页面了。恐怕是当用户重新起动浏览器的时候会造成ServiceWorker的周转内存都被清空了,即存储页面更新时间的变量被清空了,这么些时候也会重新请求页面。

要求小心的是,要把这么些json文件的http
cache时间设置成0,那样浏览器就不会缓存了,如下nginx的配置:

JavaScript

location ~* .sw.json$ { expires 0; }

1
2
3
location ~* .sw.json$ {
    expires 0;
}

因为那些文件是亟需实时获取的,无法被缓存,firefox暗许会缓存,Chrome不会,加上http缓存时间为0,firefox也不会缓存了。

再有一种更新是用户更新的,例如用户发表了评论,须要在页面布告service
worker把html缓存删了重复拿到,这是三个扭曲的新闻文告:

JavaScript

if (“serviceWorker” in navigator) {
document.querySelector(“.comment-form”).addEventListener(“submit”,
function() { navigator.serviceWorker.controller.postMessage({ type: 1,
desc: “remove html cache”, url: window.location.href} ); } }); }

1
2
3
4
5
6
7
8
9
10
if ("serviceWorker" in navigator) {
    document.querySelector(".comment-form").addEventListener("submit", function() {
            navigator.serviceWorker.controller.postMessage({
                type: 1,
                desc: "remove html cache",
                url: window.location.href}
            );
        }
    });
}

Service Worker也监听message事件:

JavaScript

const messageProcess = { // 删除html index 1: function (url) {
util.delCache(url); } }; let util = { delCache: function (url) {
caches.open(CACHE_NAME).then(cache => { console.log(“delete cache “

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const messageProcess = {
    // 删除html index
    1: function (url) {
        util.delCache(url);
    }
};
 
let util = {
    delCache: function (url) {
        caches.open(CACHE_NAME).then(cache => {
            console.log("delete cache " + url);
            cache.delete(url, {ignoreVary: true});
        });
    }
};
 
this.addEventListener("message", function(event) {
    let msg = event.data;
    console.log(msg);
    if (typeof messageProcess[msg.type] === "function") {
        messageProcess[msg.type](msg.url);
    }
});

基于不一样的音讯类型调区其他回调函数,假诺是1的话就是剔除cache。用户公布完评论后会触发刷新页面,刷新的时候缓存已经被删了就会再一次去哀告了。

诸如此类就消除了实时更新的难点。

概述

  PWA 的要害特点包蕴上面三点:

  ① 、可信 – 尽管在不安宁的互联网环境下,也能弹指间加载并突显

  二 、体验 – 火速响应,并且有平整的卡通响应用户的操作

  ③ 、粘性 –
像装备上的原生应用,具有沉浸式的用户体验,用户可以添加到桌面

  首要功效包蕴站点可添加至主显示屏、全屏形式运营、资助离线缓存、消息推送等

【PRPL模式】

  “P君越PL”(读作 “purple”)是 谷歌(Google) 的工程师指出的一种 web
应用架构方式,它意在利用现代 web 平台的新技巧以大幅度优化移动 web
的习性与感受,对什么协会与规划高质量的 PWA 系统提供了一种高层次的架空

  “PEvoquePL”实际上是 Push/Preload、Render、Precache、Lazy-Load 的缩写

  一 、PUSH/PRELOAD,推送/预加载早先 UCRUISERL 路由所需的严重性财富

  ② 、RENDE卡宴,渲染初阶路由,尽快让使用可被交互

  ③ 、PRE-CACHE,用 Service Worker 预缓存剩下的路由

  四 、LAZY-LOAD 按需懒加载、懒实例化剩下的路由

【Service workers】

  Service Workers 是谷歌 chrome 团队提议并大力推广的一项 web 技术。在
二零一六 年,它插手到 W3C 标准,进入草案阶段

  PWA 的关键在于 Service Workers 。就其主旨来说,Service Workers
只是后台运转的 worker 脚本。它们是用 JavaScript
编写的,只需短短几行代码,它们便可使开发者能够阻止互连网请求,处理推送消息并举办许多别样职分

  Service Worker 中用到的有的全局变量:

self: 表示 Service Worker 作用域, 也是全局变量
caches: 表示缓存
skipWaiting: 表示强制当前处在 waiting 状态的脚本进入 activate 状态
clients: 表示 Service Worker 接管的页面

  Service Worker 的行事机制大约如下:用户访问三个独具 Service Worker
的页面,浏览器就会下载那个 瑟维斯 Worker
并尝试安装、激活。一旦激活,Service Worker
就到后台初步工作。接下来用户访问那个页面或者每隔壹个时刻浏览器都会下载这几个Service Worker,如若监测到 Service Worker 有更新,就会重新安装并激活新的
Service Worker,同时 revoke 掉旧的 Service Worker,那就是 SW 的生命周期

  因为 Service Worker 有着目前的权力接触数据,由此 Service Worker
只好被装置在 HTTPS 加密的页面中,纵然无形当中升高了 PWA
的要诀,不过也是为了安全做考虑

 

PWA 特性

PWA 不是只是的某项技术,而是一堆技术的聚集,比如:ServiceWorker,manifest 添加到桌面,push、notification api 等。

而就在最近期子,IOS 11.3 刚刚帮忙 Service worker 和接近 manifest
添加到桌面的天性,所以这一次 PWA
改造紧要如故兑现那两有的机能,至于其它的风味,等 iphone 援救了再进步吗。

其次步:创建三个应用程序清单(Manifest)

应用程序清单提供了和近来渐进式Web应用的相关新闻,如:

实为上讲,程序清单是页面上用到的图标和主旨等资源的元数据。

程序清单是多少个位居您使用根目录的JSON文件。该JSON文件重临时必须抬高Content-Type: application/manifest+json 或者 Content-Type: application/jsonHTTP头音讯。程序清单的文本名不限,在本文的演示代码中为manifest.json

{ “name” : “PWA Website”, “short_name” : “PWA”, “description” : “An
example PWA website”, “start_url” : “/”, “display” : “standalone”,
“orientation” : “any”, “background_color” : “#ACE”, “theme_color” :
“#ACE”, “icons”: [ { “src” : “/images/logo/logo072.png”, “sizes” :
“72×72”, “type” : “image/png” }, { “src” : “/images/logo/logo152.png”,
“sizes” : “152×152”, “type” : “image/png” }, { “src” :
“/images/logo/logo192.png”, “sizes” : “192×192”, “type” : “image/png” },
{ “src” : “/images/logo/logo256.png”, “sizes” : “256×256”, “type” :
“image/png” }, { “src” : “/images/logo/logo512.png”, “sizes” :
“512×512”, “type” : “image/png” } ] }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
{
  "name"              : "PWA Website",
  "short_name"        : "PWA",
  "description"       : "An example PWA website",
  "start_url"         : "/",
  "display"           : "standalone",
  "orientation"       : "any",
  "background_color"  : "#ACE",
  "theme_color"       : "#ACE",
  "icons": [
    {
      "src"           : "/images/logo/logo072.png",
      "sizes"         : "72×72",
      "type"          : "image/png"
    },
    {
      "src"           : "/images/logo/logo152.png",
      "sizes"         : "152×152",
      "type"          : "image/png"
    },
    {
      "src"           : "/images/logo/logo192.png",
      "sizes"         : "192×192",
      "type"          : "image/png"
    },
    {
      "src"           : "/images/logo/logo256.png",
      "sizes"         : "256×256",
      "type"          : "image/png"
    },
    {
      "src"           : "/images/logo/logo512.png",
      "sizes"         : "512×512",
      "type"          : "image/png"
    }
  ]
}

程序清单文件建立完未来,你须要在逐个页面上引用该文件:

<link rel=”manifest” href=”/manifest.json”>

1
<link rel="manifest" href="/manifest.json">

以下属性在程序清单中时时接纳,介绍表明如下:

(1)注册一个Service Worker

Service Worker对象是在window.navigator里面,如下代码:

JavaScript

window.addEventListener(“load”, function() { console.log(“Will the
service worker register?”); navigator.serviceWorker.register(‘/sw-3.js’)
.then(function(reg){ console.log(“Yes, it did.”); }).catch(function(err)
{ console.log(“No it didn’t. This happened: “, err) }); });

1
2
3
4
5
6
7
8
9
window.addEventListener("load", function() {
    console.log("Will the service worker register?");
    navigator.serviceWorker.register(‘/sw-3.js’)
    .then(function(reg){
        console.log("Yes, it did.");
    }).catch(function(err) {
        console.log("No it didn’t. This happened: ", err)
    });
});

在页面load完以往注册,注册的时候传壹个js文件给它,这几个js文件就是ServiceWorker的运转环境,假若不能打响注册的话就会抛极度,如Safari
TP尽管有那么些目的,可是会抛非凡不可以采用,就可以在catch里面处理。那里有个难题是为啥必要在load事件运营呢?因为你要万分运维三个线程,运转之后你或然还会让它去加载财富,那些都以内需占用CPU和带宽的,大家应该保险页面能健康加载完,然后再起步大家的后台线程,不可以与不荒谬的页面加载爆发竞争,这么些在低端移动设备意义比较大。

再有有些亟需注意的是ServiceWorker和Cookie一样是有Path路径的概念的,如若你设定二个cookie即使叫time的path=/page/A,在/page/B那一个页面是不可见拿走到那一个cookie的,假如设置cookie的path为根目录/,则持有页面都能拿到到。类似地,要是注册的时候利用的js路径为/page/sw.js,那么这么些ServiceWorker只可以管理/page路径下的页面和能源,而不可知处理/api路径下的,所以一般把ServiceWorker注册到五星级目录,如上边代码的”/sw-3.js”,那样这几个ServiceWorker就能接管页面的有着财富了。

离线缓存

  上面来通过service worker完毕离线缓存

  一般地,通过sw-precache-webpack-plugin插件来兑现动态生成service
worker文件的作用

  但是,首先要在index.html中援引service worker

    <script>
      (function() {
        if('serviceWorker' in navigator) {
          navigator.serviceWorker.register('/service-worker.js');
        }
      })()
    </script>

【SPA】

  通过create-react-app生成的react
SPA应用暗中认可就进展了sw-precache-webpack-plugin的装置。可是,其只对静态能源拓展了安装

  若是是接口财富,则相似的拍卖是优先通过互联网访问,倘诺网络堵塞,再通过service
worker的缓存举行走访

  webpack.config.prod.js文件的计划如下

    const SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin');
    new SWPrecacheWebpackPlugin({
      // By default, a cache-busting query parameter is appended to requests
      // used to populate the caches, to ensure the responses are fresh.
      // If a URL is already hashed by Webpack, then there is no concern
      // about it being stale, and the cache-busting can be skipped.
      dontCacheBustUrlsMatching: /\.\w{8}\./,
      filename: 'service-worker.js',
      logger(message) {
        if (message.indexOf('Total precache size is') === 0) {
          // This message occurs for every build and is a bit too noisy.
          return;
        }
        if (message.indexOf('Skipping static resource') === 0) {
          // This message obscures real errors so we ignore it.
          // https://github.com/facebookincubator/create-react-app/issues/2612
          return;
        }
        console.log(message);
      },
      minify: true,
      // For unknown URLs, fallback to the index page
      navigateFallback: publicUrl + '/index.html',
      // Ignores URLs starting from /__ (useful for Firebase):
      // https://github.com/facebookincubator/create-react-app/issues/2237#issuecomment-302693219
      navigateFallbackWhitelist: [/^(?!\/__).*/],
      // Don't precache sourcemaps (they're large) and build asset manifest:
      staticFileGlobsIgnorePatterns: [/\.map$/, /asset-manifest\.json$/],
      runtimeCaching: [{
          urlPattern: '/',
          handler: 'networkFirst'
        },
        {
          urlPattern: /\/api/,
          handler: 'networkFirst'
        }
      ]
    })

【SSR】

  若是是劳动器端渲染的使用,则配备中央类似。但出于无法利用代理,则需求安装网站实际路径,且由于静态资源已经存到CDN,则缓存不再通过service
worker处理

  配置如下

    new SWPrecacheWebpackPlugin({
      dontCacheBustUrlsMatching: /\.\w{8}\./,
      filename: 'service-worker.js',
      logger(message) {
        if (message.indexOf('Total precache size is') === 0) {
          return;
        }
        if (message.indexOf('Skipping static resource') === 0) {
          return;
        }
        console.log(message);
      },
      navigateFallback: 'https://www.xiaohuochai.cc',
      minify: true,
      navigateFallbackWhitelist: [/^(?!\/__).*/],
      dontCacheBustUrlsMatching: /./,
      staticFileGlobsIgnorePatterns: [/\.map$/, /\.json$/],
      runtimeCaching: [{
          urlPattern: '/',
          handler: 'networkFirst'
        },
        {
          urlPattern: /\/(posts|categories|users|likes|comments)/,
          handler: 'networkFirst'
        },
      ]
    })
  ]

 

相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图