您的位置:澳门新葡8455最新网站 > Web前端 > 同构应用,入门教程

同构应用,入门教程

发布时间:2019-10-10 21:44编辑:Web前端浏览(147)

    React 同构应用 PWA 进级指南

    2018/05/25 · JavaScript · PWA, React

    原稿出处: 林东洲   

    渐进式Web应用(PWA)入门教程(下)

    2018/05/25 · 基本功才能 · PWA

    原来的小讲出处: Craig Buckler   译文出处:蒲陶城控件   

    上篇文章大家对渐进式Web应用(PWA)做了部分骨干的介绍。

    渐进式Web应用(PWA)入门教程(上)

    在此一节中,大家将介绍PWA的规律是何等,它是何等初步专门的学问的。

    前言

    近年在给自己的博客网址 PWA 进级,顺便就记录下 React 同构应用在动用 PWA 时蒙受的标题,这里不会从头开头介绍如何是 PWA,假设你想学习 PWA 相关文化,能够看下上边小编收藏的有些篇章:

    • 你的率先个 Progressive Web App
    • 【ServiceWorker】生命周期那么些事儿
    • 【PWA学习与实施】(1) 2018,初始你的PWA学习之旅
    • Progressive Web Apps (PWA) 中文版

    第一步:使用HTTPS

    渐进式Web应用程序须要采用HTTPS连接。即便选拔HTTPS会让您服务器的支付变多,但使用HTTPS能够让您的网址变得更安全,HTTPS网址在Google上的排名也会更靠前。

    是因为Chrome浏览器会暗许将localhost以致127.x.x.x地方视为测验地方,所以在本示例中你并没有要求开启HTTPS。此外,出于调节和测量试验目标,您能够在起步Chrome浏览器的时候使用以下参数来关闭其对网站HTTPS的自己探究:

    • –user-data-dir
    • –unsafety-treat-insecure-origin-as-secure

    PWA 特性

    PWA 不是仅仅的某项工夫,而是一群本事的联谊,比方:ServiceWorker,manifest 增加到桌面,push、notification api 等。

    而就在这里段日申时光,IOS 11.3 刚刚协理 Service worker 和好像 manifest 增多到桌面包车型地铁特点,所以此次 PWA 退换首要依旧兑现这两有个别机能,至于别的的特色,等 iphone 辅助了再升格吗。

    其次步:创造一个应用程序清单(Manifest)

    应用程序清单提供了和脚下渐进式Web应用的连锁音讯,如:

    • 应用程序名
    • 描述
    • 富有图片(包蕴主荧屏图标,运转显示器页面和用的图纸也许网页上用的图纸)

    真相上讲,程序清单是页面上用到的Logo和宗旨等财富的元数据。

    程序清单是二个放在您使用根目录的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" : "72x72", "type" : "image/png" }, { "src" : "/images/logo/logo152.png", "sizes" : "152x152", "type" : "image/png" }, { "src" : "/images/logo/logo192.png", "sizes" : "192x192", "type" : "image/png" }, { "src" : "/images/logo/logo256.png", "sizes" : "256x256", "type" : "image/png" }, { "src" : "/images/logo/logo512.png", "sizes" : "512x512", "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"         : "72x72",
          "type"          : "image/png"
        },
        {
          "src"           : "/images/logo/logo152.png",
          "sizes"         : "152x152",
          "type"          : "image/png"
        },
        {
          "src"           : "/images/logo/logo192.png",
          "sizes"         : "192x192",
          "type"          : "image/png"
        },
        {
          "src"           : "/images/logo/logo256.png",
          "sizes"         : "256x256",
          "type"          : "image/png"
        },
        {
          "src"           : "/images/logo/logo512.png",
          "sizes"         : "512x512",
          "type"          : "image/png"
        }
      ]
    }

    程序清单文件建设构造完以往,你须要在每种页面上援用该公文:

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

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

    以下属性在程序清单中不常利用,介绍表达如下:

    • name: 顾客看见的运用名称
    • short_name: 应用短名称。当展现应用名称的地点远远不足时,将选择该名称。
    • description: 利用描述。
    • start_url: 运用起初路线,相对路线,默感觉/。
    • scope: U汉兰达L范围。举个例子:假设你将“/app/”设置为U福特ExplorerL范围时,那个利用就能够直接在这里个目录中。
    • background_color: 应接页面包车型大巴背景颜色和浏览器的背景颜色(可选)
    • theme_color: 使用的核心颜色,平时都会和背景颜色同样。这么些装置决定了接纳怎么样体现。
    • orientation: 初期旋转方向,可选的值有:any, natural, landscape, landscape-primary, landscape-secondary, portrait, portrait-primary, and portrait-secondary
    • display: 突显格局——fullscreen(无Chrome),standalone(和原生应用同样),minimal-ui(最小的一套UI控件集)大概browser(最古老的运用浏览器标签展现)
    • icons: 一个暗含全数图片的数组。该数组中每种成分包罗了图片的U奥迪Q5L,大小和花色。

    Service Worker

    service worker 在作者眼里,类似于一个跑在浏览器后台的线程,页面第三遍加载的时候会加载那些线程,在线程激活之后,通过对 fetch 事件,能够对每一种收获的财富开展支配缓存等。

    其三步:创制贰个 Service Worker

    Service 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。

    显而易见哪些能源须求被缓存?

    那便是说在早先选择 service worker 在此之前,首先必要驾驭怎么能源须要被缓存?

    Install事件

    该事件就要使用设置落成后触发。大家平常在那地运用Cache API缓存一些必得的公文。

    率先,大家须求提供如下配置

    1. 缓存名称(CACHE)以致版本(version)。应用能够有四个缓存存款和储蓄,不过在运用时只会动用在这之中四个缓存存款和储蓄。每当缓存存款和储蓄有生成时,新的本子号将会钦命到缓存存款和储蓄中。新的缓存存款和储蓄将会作为当下的缓存存款和储蓄,以前的缓存存款和储蓄将会被作废。
    2. 叁个离线的页面地址(offlineUEvoqueL):当顾客访谈了事先从未访谈过的地点时,该页面将会来得。
    3. 三个暗含了装有必需文件的数组,包蕴保持页面正常机能的CSS和JavaScript。在本示例中,笔者还增多了主页和logo。当有两样的U奥德赛L指向同多个能源时,你也足以将那个U大切诺基L分别写到这几个数组中。offlineULX570L将会投入到这几个数组中。
    4. 我们也足以将有个别非要求的缓存文件(installFilesDesirable)。那几个文件在安装进度中校会被下载,但假设下载失利,不会触发安装失败。

    // 配置文件 const version = '1.0.0', CACHE = version + '::PWAsite', offlineU福睿斯L = '/offline/', installFilesEssential = [ '/', '/manifest.json', '/css/styles.css', '/js/main.js', '/js/offlinepage.js', '/images/logo/logo152.png' ].concat(offlineURL), installFilesDesirable = [ '/favicon.ico', '/images/logo/logo016.png', '/images/hero/power-pv.jpg', '/images/hero/power-lo.jpg', '/images/hero/power-hi.jpg' ];

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // 配置文件
    const
      version = '1.0.0',
      CACHE = version + '::PWAsite',
      offlineURL = '/offline/',
      installFilesEssential = [
        '/',
        '/manifest.json',
        '/css/styles.css',
        '/js/main.js',
        '/js/offlinepage.js',
        '/images/logo/logo152.png'
      ].concat(offlineURL),
      installFilesDesirable = [
        '/favicon.ico',
        '/images/logo/logo016.png',
        '/images/hero/power-pv.jpg',
        '/images/hero/power-lo.jpg',
        '/images/hero/power-hi.jpg'
      ];

    installStaticFiles() 方法应用基于Promise的格局选择Cache API将文件存款和储蓄到缓存中。

    // 安装静态能源 function installStaticFiles() { return caches.open(CACHE) .then(cache => { // 缓存可选文件 cache.addAll(installFilesDesirable); // 缓存必须文件 return cache.addAll(installFilesEssential); }); }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // 安装静态资源
    function installStaticFiles() {
      return caches.open(CACHE)
        .then(cache => {
          // 缓存可选文件
          cache.addAll(installFilesDesirable);
          // 缓存必须文件
          return cache.addAll(installFilesEssential);
        });
    }

    末段,大家抬高一个install的风云监听器。waitUntil方法有限帮衬了service worker不会设置直到其辅车相依的代码被实施。这里它会进行installStaticFiles()方法,然后self.skipWaiting()艺术来激活service worker:

    // 应用设置 self.add伊芙ntListener('install', event => { console.log('service worker: install'); // 缓存首要文件 event.waitUntil( installStaticFiles() .then(() => self.skipWaiting()) ); });

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 应用安装
    self.addEventListener('install', event => {
      console.log('service worker: install');
      // 缓存主要文件
      event.waitUntil(
        installStaticFiles()
        .then(() => self.skipWaiting())
      );
    });

    缓存静态财富

    率先是像 CSS、JS 那些静态能源,因为本身的博客里援引的脚本样式都以由此 hash 做长久化缓存,类似于:main.ac62dexx.js 那样,然后张开强缓存,那样下一次客户下一次再寻访作者的网址的时候就无须再行哀告财富。直接从浏览器缓存中读取。对于那有的能源,service worker 没要求再去处理,直接放行让它去读取浏览器缓存就可以。

    作者以为一旦您的站点加载静态能源的时候自身未有开启强缓存,並且你只想通过前端去达成缓存,而无需后端在参预实行调节,那能够应用 service worker 来缓存静态能源,否则就有一些画蛇添足了。

    Activate 事件

    以这一件事件会在service worker被激活时发出。你大概无需这些事件,可是在示范代码中,大家在该事件产生时将老的缓存全体清理掉了:

    // clear old caches function clearOldCaches() { return caches.keys() .then(keylist => { return Promise.all( keylist .filter(key => key !== CACHE) .map(key => caches.delete(key)) ); }); } // application activated self.addEventListener('activate', event => { console.log('service worker: activate'); // delete old caches event.waitUntil( clearOldCaches() .then(() => self.clients.claim()) ); });

    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
    // clear old caches
    function clearOldCaches() {
      return caches.keys()
        .then(keylist => {
          return Promise.all(
            keylist
              .filter(key => key !== CACHE)
              .map(key => caches.delete(key))
          );
        });
    }
    // application activated
    self.addEventListener('activate', event => {
      console.log('service worker: activate');
        // delete old caches
      event.waitUntil(
        clearOldCaches()
        .then(() => self.clients.claim())
        );
    });

    注意self.clients.claim()试行时将会把当下service worker作为被激活的worker。

    Fetch 事件 该事件将会在网络开首央浼时发起。该事件管理函数中,大家能够利用respondWith()方法来威迫HTTP的GET央求然后归来:

    1. 从缓存中取到的能源文件
    2. 假设第一步退步,能源文件将会从互连网中运用Fetch API来获得(和service worker中的fetch事件毫不相关)。获取到的财富将会步入到缓存中。
    3. 比如第一步和第二步均战败,将会从缓存中回到正确的财富文件。

    // application fetch network data self.addEventListener('fetch', event => { // abandon non-GET requests if (event.request.method !== 'GET') return; let url = event.request.url; event.respondWith( caches.open(CACHE) .then(cache => { return cache.match(event.request) .then(response => { if (response) { // return cached file console.log('cache fetch: ' + url); return response; } // make network request return fetch(event.request) .then(newreq => { console.log('network fetch: ' + url); if (newreq.ok) cache.put(event.request, newreq.clone()); return newreq; }) // app is offline .catch(() => offlineAsset(url)); }); }) ); });

    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
    // application fetch network data
    self.addEventListener('fetch', event => {
      // abandon non-GET requests
      if (event.request.method !== 'GET') return;
      let url = event.request.url;
      event.respondWith(
        caches.open(CACHE)
          .then(cache => {
            return cache.match(event.request)
              .then(response => {
                if (response) {
                  // return cached file
                  console.log('cache fetch: ' + url);
                  return response;
                }
                // make network request
                return fetch(event.request)
                  .then(newreq => {
                    console.log('network fetch: ' + url);
                    if (newreq.ok) cache.put(event.request, newreq.clone());
                    return newreq;
                  })
                  // app is offline
                  .catch(() => offlineAsset(url));
              });
          })
      );
    });

    offlineAsset(url)方法中动用了部分helper方法来回到精确的数据:

    // 是不是为图片地址? let iExt = ['png', 'jpg', 'jpeg', 'gif', 'webp', 'bmp'].map(f => '.' + f); function isImage(url) { return iExt.reduce((ret, ext) => ret || url.endsWith(ext), false); } // return 再次回到离线财富 function offlineAsset(url) { if (isImage(url)) { // 再次回到图片 return new Response( '<svg role="img" viewBox="0 0 400 300" xmlns=" d="M0 0h400v300H0z" fill="#eee" /><text x="200" y="150" text-anchor="middle" dominant-baseline="middle" font-family="sans-serif" font-size="50" fill="#ccc">offline</text></svg>', { headers: { 'Content-Type': 'image/svg+xml', 'Cache-Control': 'no-store' }} ); } else { // return page return caches.match(offlineURL); } }

    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
    // 是否为图片地址?
    let iExt = ['png', 'jpg', 'jpeg', 'gif', 'webp', 'bmp'].map(f => '.' + f);
    function isImage(url) {
      
      return iExt.reduce((ret, ext) => ret || url.endsWith(ext), false);
      
    }
      
      
    // return 返回离线资源
    function offlineAsset(url) {
      
      if (isImage(url)) {
      
        // 返回图片
        return new Response(
          '<svg role="img" viewBox="0 0 400 300" xmlns="http://www.w3.org/2000/svg"><title>offline</title><path d="M0 0h400v300H0z" fill="#eee" /><text x="200" y="150" text-anchor="middle" dominant-baseline="middle" font-family="sans-serif" font-size="50" fill="#ccc">offline</text></svg>',
          { headers: {
            'Content-Type': 'image/svg+xml',
            'Cache-Control': 'no-store'
          }}
        );
      
      }
      else {
      
        // return page
        return caches.match(offlineURL);
      
      }
      
    }

    offlineAsset()艺术检查乞请是还是不是为贰个图纸,然后回到三个分包“offline”文字的SVG文件。其他诉求将会再次来到offlineURAV4L 页面。

    Chrome开辟者工具中的ServiceWorker部分提供了有关当前页面worker的音信。此中会来得worker中发出的失实,还是能够强制刷新,也得以让浏览器步向离线方式。

    Cache Storage 部分例举了现阶段持有曾经缓存的能源。你能够在缓存需求更新的时候点击refresh按键。

    缓存页面

    缓存页面鲜明是必备的,那是最中央的有的,当你在离线的情形下加载页面会之后出现:

    图片 1

    究其原因正是因为您在离线状态下不能够加载页面,以后有了 service worker,即便你在没互联网的图景下,也得以加载在此以前缓存好的页面了。

    第四步:创造可用的离线页面

    离线页面能够是静态的HTML,日常用于提示顾客如今央求的页面一时不恐怕利用。然则,大家能够提供部分足以阅读的页面链接。

    Cache API能够在main.js中使用。然则,该API使用Promise,在不协理Promise的浏览器中会失利,全体的JavaScript试行会因而碰着震慑。为了防止这种状态,在拜谒/js/offlinepage.js的时候大家增添了一段代码来检查当前是或不是在离线情状中:

    /js/offlinepage.js 中以版本号为名称保存了不久前的缓存,获取具备U安德拉L,删除不是页面包车型大巴U纳瓦拉L,将那个U奥迪Q5L排序然后将具有缓存的UEscortL体未来页面上:

    // 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);
              });
          })
      });

    缓存后端接口数据

    缓存接口数据是急需的,但亦不是必得经过 service worker 来达成,前端存放数据的地方有众多,比方通过 localstorage,indexeddb 来开展仓储。这里作者也是经过 service worker 来促成缓存接口数据的,倘诺想经过任何措施来兑现,只须要注意好 url 路线与数量对应的照耀关系就能够。

    开荒者工具

    Chrome浏览器提供了一多重的工具来协助您来调整ServiceWorker,日志也会直接突显在调整台上。

    你最棒应用无名氏情势来举行付出专门的职业,这样能够防去缓存对开垦的干扰。

    最后,Chrome的Lighthouse扩充也足以为您的渐进式Web应用提供一些更进一竿消息。

    缓存战略

    总来说之了什么财富须要被缓存后,接下去将要商讨缓存战略了。

    渐进式Web应用的核情绪想

    渐进式Web应用是一种新的技巧,所以使用的时候势须求小心。也正是说,渐进式Web应用可以让你的网址在多少个小时内获得革新,并且在不帮助渐进式Web应用的浏览器上也不会影响网址的呈现。

    然则我们须求想念以下几点:

    页面缓存战略

    因为是 React 单页同构应用,每趟加载页面的时候数据都以动态的,所以本身使用的是:

    1. 网络优先的法子,即优先获得网络上风行的能源。当互连网央求失利的时候,再去获取 service worker 里从前缓存的能源
    2. 当网络加载成功之后,就更新 cache 中对应的缓存财富,保障下一次历次加载页面,都是上次访谈的时尚财富
    3. 举个例子找不到 service worker 中 url 对应的能源的时候,则去获取 service worker 对应的 /index.html 暗许首页

    // sw.js self.addEventListener('fetch', (e) => { console.log('以往正在呼吁:' + e.request.url); const currentUrl = e.request.url; // 相配上页面路径 if (matchHtml(currentUrl)) { const requestToCache = e.request.clone(); e.respondWith( // 加载互连网上的资源fetch(requestToCache).then((response) => { // 加载战败 if (!response || response.status !== 200) { throw Error('response error'); } // 加载成功,更新缓存 const responseToCache = response.clone(); caches.open(cacheName).then((cache) => { cache.put(requestToCache, responseToCache); }); console.log(response); return response; }).catch(function() { // 获取对应缓存中的数据,获取不到则战败到收获默许首页 return caches.match(e.request).then((response) => { return response || caches.match('/index.html'); }); }) ); } });

    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
    // sw.js
    self.addEventListener('fetch', (e) => {
      console.log('现在正在请求:' + e.request.url);
      const currentUrl = e.request.url;
      // 匹配上页面路径
      if (matchHtml(currentUrl)) {
        const requestToCache = e.request.clone();
        e.respondWith(
          // 加载网络上的资源
          fetch(requestToCache).then((response) => {
            // 加载失败
            if (!response || response.status !== 200) {
              throw Error('response error');
            }
            // 加载成功,更新缓存
            const responseToCache = response.clone();
            caches.open(cacheName).then((cache) => {
              cache.put(requestToCache, responseToCache);
            });
            console.log(response);
            return response;
          }).catch(function() {
            // 获取对应缓存中的数据,获取不到则退化到获取默认首页
            return caches.match(e.request).then((response) => {
               return response || caches.match('/index.html');
            });
          })
        );
      }
    });

    何以存在命中不断缓存页面包车型客车情景?

    1. 先是要求鲜明的是,客商在第壹遍加载你的站点的时候,加载页面后才会去运营sw,所以首先次加载不容许通过 fetch 事件去缓存页面
    2. 本身的博客是单页应用,然则客商并不一定会通过首页进入,有比相当的大可能率会通过别的页面路径踏入到小编的网址,那就导致本人在 install 事件中一贯不可能内定供给缓存那个页面
    3. 末段促成的效用是:用户率先次展开页面,霎时断掉网络,仍旧能够离线访谈作者的站点

    组成方面三点,作者的办法是:第二次加载的时候会缓存 /index.html 那么些能源,并且缓存页面上的数码,要是客户及时离线加载的话,那时候并不曾缓存对应的路子,比方 /archives 财富访问不到,那再次回到 /index.html 走异步加载页面包车型大巴逻辑。

    在 install 事件缓存 /index.html,有限支撑了 service worker 第二次加载的时候缓存暗中认可页面,留下退路。

    import constants from './constants'; const cacheName = constants.cacheName; const apiCacheName = constants.apiCacheName; const cacheFileList = ['/index.html']; self.addEventListener('install', (e) => { console.log('Service Worker 状态: install'); const cacheOpenPromise = caches.open(cacheName).then((cache) => { return cache.addAll(cacheFileList); }); e.waitUntil(cacheOpenPromise); });

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import constants from './constants';
    const cacheName = constants.cacheName;
    const apiCacheName = constants.apiCacheName;
    const cacheFileList = ['/index.html'];
     
    self.addEventListener('install', (e) => {
      console.log('Service Worker 状态: install');
      const cacheOpenPromise = caches.open(cacheName).then((cache) => {
        return cache.addAll(cacheFileList);
      });
      e.waitUntil(cacheOpenPromise);
    });

    在页面加载完后,在 React 组件中立刻缓存数据:

    // cache.js import constants from '../constants'; const apiCacheName = constants.apiCacheName; export const saveAPIData = (url, data) => { if ('caches' in window) { // 伪造 request/response 数据 caches.open(apiCacheName).then((cache) => { cache.put(url, new Response(JSON.stringify(data), { status: 200 })); }); } }; // React 组件 import constants from '../constants'; export default class extends PureComponent { componentDidMount() { const { state, data } = this.props; // 异步加载数据 if (state === constants.INITIAL_STATE || state === constants.FAILURE_STATE) { this.props.fetchData(); } else { // 服务端渲染成功,保存页面数据 saveAPIData(url, data); } } }

    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
    // cache.js
    import constants from '../constants';
    const apiCacheName = constants.apiCacheName;
     
    export const saveAPIData = (url, data) => {
      if ('caches' in window) {
        // 伪造 request/response 数据
        caches.open(apiCacheName).then((cache) => {
          cache.put(url, new Response(JSON.stringify(data), { status: 200 }));
        });
      }
    };
     
    // React 组件
    import constants from '../constants';
    export default class extends PureComponent {
      componentDidMount() {
        const { state, data } = this.props;
        // 异步加载数据
        if (state === constants.INITIAL_STATE || state === constants.FAILURE_STATE) {
          this.props.fetchData();
        } else {
            // 服务端渲染成功,保存页面数据
          saveAPIData(url, data);
        }
      }
    }

    这般就确认保障了客户率先次加载页面,霎时离线访谈站点后,即便不可能像第贰次同样能够服务端渲染数据,可是之后能透过获取页面,异步加载数据的诀要创设离线应用。

    图片 2

    客户率先次访问站点,假如在不刷新页面包车型地铁意况切换路由到其余页面,则会异步获取到的数目,当后一次做客对应的路由的时候,则战败到异步获取数据。

    图片 3

    当顾客第一回加载页面包车型大巴时候,因为 service worker 已经决定了站点,已经具有了缓存页面包车型客车力量,之后在做客的页面都将会被缓存也许更新缓存,当客户离线访问的的时候,也能访谈到服务端渲染的页面了。

    图片 4

    URL隐藏

    当你的施用便是二个单UCRUISERL的应用程序时(比方游戏),小编提议你掩盖地址栏。除了这一个之外的事态本人并不指出您掩饰地址栏。在Manifest中,display: minimal-ui 或者 display: browser对于大多数情景来讲丰硕用了。

    接口缓存攻略

    谈完页面缓存,再来说讲接口缓存,接口缓存就跟页面缓存很类似了,独一的不相同在于:页面第三遍加载的时候不自然有缓存,可是会有接口缓存的存在(因为伪造了 cache 中的数据),所以缓存计谋跟页面缓存类似:

    1. 互连网优先的主意,即优先得到互联网上接口数据。当网络乞请战败的时候,再去取得service worker 里此前缓存的接口数据
    2. 当网络加载成功以往,就更新 cache 中对应的缓存接口数据,保证后一次历次加载页面,都以上次访谈的新式接口数据

    故而代码就如那样(代码类似,不再赘言):

    self.add伊芙ntListener('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); }) ); } });
    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);
          })
        );
      }
    });

    此间实在可以再拓宽优化的,比如在获取数据接口的时候,能够先读取缓存中的接口数据开展渲染,当真正的网络接口数据重回之后再张开替换,那样也能有效削减顾客的首屏渲染时间。当然那说不定会生出页面闪烁的效果与利益,能够增进一些卡通来拓宽交接。

    缓存过大

    您无法将您网址中的全部内容缓存下来。对于小片段的网站以来缓存全部剧情并非一个主题材料,可是要是一个网址满含了上千个页面吗?很显眼不是全部人对网址中的全数内容都感兴趣。存款和储蓄是有限定的,假如您将全部访谈过的页面都缓存下来的话,缓存大小会增加额一点也不慢。

    你能够那样制订你的缓存战略:

    • 只缓存首要的页面,比方主页,联系人页面和不久前浏览小说的页面。
    • 不用缓存任何图片,摄像和大文件
    • 定时清理旧的缓存
    • 提供贰个“离线阅读”按键,那样用户就能够选取须求缓存哪些内容了。

    任何难题

    到前几天截至,已经大半能够实现 service worker 离线缓存应用的职能了,不过还会有依然存在一些难点:

    缓存刷新

    演示代码中在提倡呼吁在此之前会先查询缓存。当客商处于离线状态时,那很好,不过一旦客户处于在线状态,那她只会浏览到相比较老旧的页面。

    各个财富举例图片和录像不会转移,所以平时都把那一个静态财富设置为漫漫缓存。这一个能源能够直接缓存一年(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

    您也足以在历次网站揭橥时,通过更名的点子强制浏览珍视新伏乞财富。

    迅猛激活 service worker

    默许情形下,页面包车型大巴央求(fetch)不会因而 sw,除非它本身是通过 sw 获取的,相当于说,在安装 sw 之后,须要刷新页面本领有功用。sw 在装置成功并激活在此以前,不会响应 fetch或push等事件。

    因为站点是单页面应用,那就产生了您在切换路由(未有刷新页面)的时候未有缓存接口数据,因为那时 service worker 还尚无起来工作,所以在加载 service worker 的时候须求快速地激活它。代码如下:

    self.addEventListener('activate', (e) => { console.log('Service Worker 状态: activate'); const cachePromise = caches.keys().then((keys) => { return Promise.all(keys.map((key) => { if (key !== cacheName && key !== apiCacheName) { return caches.delete(key); } return null; })); }); e.waitUntil(cachePromise); // 快捷激活 sw,使其能够响应 fetch 事件 return self.clients.claim(); });

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    self.addEventListener('activate', (e) => {
      console.log('Service Worker 状态: activate');
      const cachePromise = caches.keys().then((keys) => {
        return Promise.all(keys.map((key) => {
          if (key !== cacheName && key !== apiCacheName) {
            return caches.delete(key);
          }
          return null;
        }));
      });
      e.waitUntil(cachePromise);
      // 快速激活 sw,使其能够响应 fetch 事件
      return self.clients.claim();
    });

    一对小说说还索要在 install 事件中添加 self.skipWaiting(); 来跳过等待时间,然则作者在施行中发掘固然不增多也足以健康激活 service worker,原因不详,有读者领悟的话能够沟通下。

    近年来当你首先次加载页面,跳转路由,立即离线访问的页面,也足以顺遂地加载页面了。

    小结

    现今,相信你一旦遵照本文一步一步操作下来,你也能够快捷把团结的Web应用转为PWA。在转为了PWA后,假使有应用满意PWA 模型的前端控件的必要,你能够试试纯前端表格控件SpreadJS,适用于 .NET、Java 和移动端等平台的表格控件一定不会令你失望的。

    原著链接:

    1 赞 1 收藏 评论

    图片 5

    绝不强缓存 sw.js

    客户每回访问页面的时候都会去重新获得sw.js,依照文件内容跟在此以前的本子是还是不是一致来判断 service worker 是还是不是有更新。所以尽管你对 sw.js 开启强缓存的话,就将陷入死循环,因为每回页面获得到的 sw.js 皆以大同小异,那样就不能提高你的 service worker。

    另外对 sw.js 开启强缓存也是绝非须要的:

    1. 自己 sw.js 文件自个儿就十分的小,浪费不了多少带宽,认为浪费能够运用公约缓存,但附加扩充费用负责
    2. sw.js 是在页面空闲的时候才去加载的,并不会耳濡目染客商首屏渲染速度

    防止改造 sw 的 U宝马X3L

    在 sw 中那样做是“最差实行”,要在原地点上改变 sw。

    举个例证来证实为何:

    1. index.html 注册了 sw-v1.js 作为 sw
    2. sw-v1.js 对 index.html 做了缓存,也便是缓存优先(offline-first)
    3. 您更新了 index.html 重新登记了在新地点的 sw sw-v2.js

    只要您像下边那么做,客商永世也拿不到 sw-v2.js,因为 index.html 在 sw-v1.js 缓存中,这样的话,假若您想翻新为 sw-v2.js,还索要更换原本的 sw-v1.js。

    测试

    未来,大家早就成功了应用 service worker 对页面进行离线缓存的功能,如若想感受效果的话,访谈笔者的博客:

    随机浏览大肆的页面,然后关掉网络,再度做客,在此之前您浏览过的页面都能够在离线的情状下开展访谈了。

    IOS 要求 11.3 的本子才支撑,使用 Safari 举办访谈,Android 请选拔帮助service worker 的浏览器

    manifest 桌面应用

    前边讲罢了什么样接纳 service worker 来离线缓存你的同构应用,可是 PWA 不仅仅限于此,你还是能够选取安装 manifest 文件来将您的站点加多到移动端的桌面上,进而达到趋近于原生应用的心得。

    使用 webpack-pwa-manifest 插件

    自身的博客站点是经过 webpack 来构建前端代码的,所以自身在社区里找到 webpack-pwa-manifest 插件用来生成 manifest.json。

    率先安装好 webpack-pwa-manifest 插件,然后在你的 webpack 配置文件中增添:

    // webpack.config.prod.js const WebpackPwaManifest = require('webpack-pwa-manifest'); module.exports = webpackMerge(baseConfig, { plugins: [ new WebpackPwaManifest({ name: 'Lindz's Blog', short_name: 'Blog', description: 'An isomorphic progressive web blog built by React & Node', background_color: '#333', theme_color: '#333', filename: 'manifest.[hash:8].json', publicPath: '/', icons: [ { src: path.resolve(constants.publicPath, 'icon.png'), sizes: [96, 128, 192, 256, 384, 512], // multiple sizes destination: path.join('icons') } ], ios: { 'apple-mobile-web-app-title': 'Lindz's Blog', 'apple-mobile-web-app-status-bar-style': '#000', 'apple-mobile-web-app-capable': 'yes', 'apple-touch-icon': '//xxx.com/icon.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
    // webpack.config.prod.js
    const WebpackPwaManifest = require('webpack-pwa-manifest');
    module.exports = webpackMerge(baseConfig, {
      plugins: [
        new WebpackPwaManifest({
          name: 'Lindz's Blog',
          short_name: 'Blog',
          description: 'An isomorphic progressive web blog built by React & Node',
          background_color: '#333',
          theme_color: '#333',
          filename: 'manifest.[hash:8].json',
          publicPath: '/',
          icons: [
            {
              src: path.resolve(constants.publicPath, 'icon.png'),
              sizes: [96, 128, 192, 256, 384, 512], // multiple sizes
              destination: path.join('icons')
            }
          ],
          ios: {
            'apple-mobile-web-app-title': 'Lindz's Blog',
            'apple-mobile-web-app-status-bar-style': '#000',
            'apple-mobile-web-app-capable': 'yes',
            'apple-touch-icon': '//xxx.com/icon.png',
          },
        })
      ]
    })

    大约地演说下安插消息:

    1. name: 应用名称,正是Logo上面包车型客车显得名称
    2. short_name: 应用名称,但 name 不可能出示完全时候则展示那么些
    3. background_color、theme_color:循名责实,相应的颜色
    4. publicPath: 设置 cdn 路径,跟 webpack 里的 publicPath 一样
    5. icons: 设置Logo,插件会活动帮你转移分裂 size 的图片,不过图片大小必得高于最大 sizes
    6. ios: 设置在 safari 中什么去增加桌面应用

    设置完以往,webpack 会在营造进程中变化对应的 manifest 文件,并在 html 文件中援用,上边正是生成 manifest 文件:

    { "icons": [ { "src": "/icons/icon_512x512.79ddc5874efb8b481d9a3d06133b6213.png", "sizes": "512x512", "type": "image/png" }, { "src": "/icons/icon_384x384.09826bd1a5d143e05062571f0e0e86e7.png", "sizes": "384x384", "type": "image/png" }, { "src": "/icons/icon_256x256.d641a3644ce20c06855db39cfb2f7b40.png", "sizes": "256x256", "type": "image/png" }, { "src": "/icons/icon_192x192.8f11e077242cccd9c42c0cbbecd5149c.png", "sizes": "192x192", "type": "image/png" }, { "src": "/icons/icon_128x128.cc0714ab18fa6ee6de42ef3d5ca8fd09.png", "sizes": "128x128", "type": "image/png" }, { "src": "/icons/icon_96x96.dbfccb1a5cef8093a77c079f761b2d63.png", "sizes": "96x96", "type": "image/png" } ], "name": "Lindz's Blog", "short_name": "Blog", "orientation": "portrait", "display": "standalone", "start_url": ".", "description": "An isomorphic progressive web blog built by React & Node", "background_color": "#333", "theme_color": "#333" }

    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
    {
      "icons": [
        {
          "src": "/icons/icon_512x512.79ddc5874efb8b481d9a3d06133b6213.png",
          "sizes": "512x512",
          "type": "image/png"
        },
        {
          "src": "/icons/icon_384x384.09826bd1a5d143e05062571f0e0e86e7.png",
          "sizes": "384x384",
          "type": "image/png"
        },
        {
          "src": "/icons/icon_256x256.d641a3644ce20c06855db39cfb2f7b40.png",
          "sizes": "256x256",
          "type": "image/png"
        },
        {
          "src": "/icons/icon_192x192.8f11e077242cccd9c42c0cbbecd5149c.png",
          "sizes": "192x192",
          "type": "image/png"
        },
        {
          "src": "/icons/icon_128x128.cc0714ab18fa6ee6de42ef3d5ca8fd09.png",
          "sizes": "128x128",
          "type": "image/png"
        },
        {
          "src": "/icons/icon_96x96.dbfccb1a5cef8093a77c079f761b2d63.png",
          "sizes": "96x96",
          "type": "image/png"
        }
      ],
      "name": "Lindz's Blog",
      "short_name": "Blog",
      "orientation": "portrait",
      "display": "standalone",
      "start_url": ".",
      "description": "An isomorphic progressive web blog built by React & Node",
      "background_color": "#333",
      "theme_color": "#333"
    }

    html 中会援引那几个文件,何况增进对 ios 加多桌面应用的支持,就如那样。

    <!DOCTYPE html> <html lang=en> <head> <meta name=apple-mobile-web-app-title content="Lindz's Blog"> <meta name=apple-mobile-web-app-capable content=yes> <meta name=apple-mobile-web-app-status-bar-style content=#838a88> <link rel=apple-touch-icon href=xxxxx> <link rel=manifest href=/manifest.21d63735.json> </head> </html>

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <!DOCTYPE html>
    <html lang=en>
    <head>
      <meta name=apple-mobile-web-app-title content="Lindz's Blog">
      <meta name=apple-mobile-web-app-capable content=yes>
      <meta name=apple-mobile-web-app-status-bar-style content=#838a88>
      <link rel=apple-touch-icon href=xxxxx>
      <link rel=manifest href=/manifest.21d63735.json>
    </head>
    </html>

    就这么简单,你就足以运用 webpack 来增添你的桌面应用了。

    测试

    增添完之后您能够通过 chrome 开辟者工具 Application – Manifest 来查阅你的 mainfest 文件是或不是见效:

    图片 6

    与此相类似表达你的配备生效了,安卓机缘自动识别你的布置文件,并询问客商是还是不是丰裕。

    结尾

    讲到那大概就完了,等现在 IOS 帮助 PWA 的别的功效的时候,到时候笔者也会相应地去实施另外 PWA 的特点的。今后 IOS 11.3 也无非协助 PWA 中的 service worker 和 app manifest 的功力,然而相信在不久的将来,其余的效劳也会相应获得扶持,到时候相信 PWA 将会在活动端盛放异彩的。

    1 赞 收藏 评论

    图片 7

    本文由澳门新葡8455最新网站发布于Web前端,转载请注明出处:同构应用,入门教程

    关键词: