美文网首页前端成长路PWA机器学习及大数据
PWA官方文档翻译之二Your First Progressiv

PWA官方文档翻译之二Your First Progressiv

作者: 短衣匹马 | 来源:发表于2017-03-04 21:08 被阅读1924次

    1.介绍

    • PWA结合了最好的web体验和最好的app体验,它对第一次使用某个app的用户来说是非常有用的,因为不需要安装用户仅在浏览器中访问就可以,并且随着用户在PWA上的操作越来越多,交互越来越频繁,PWA会变得越来越强大,即便在不稳定的网络下,它也可以快速加载,并且可以向用户发送通知,以及在主屏幕创建一个图标,并且可以全屏使用,提供沉浸式的体验。
    什么是PWA
    • 渐进式的,每个人都可以使用PWA,无论你使用什么浏览器,因为PWA的最终是想渐进式的增强你的用户体验。
    • 多平台,PWA适用于个人PC,平板式设备,智能手机,甚至我们不知道的下一种设备。
    • 独立的网络连接,增强式的服务使PWA可以在无线环境下或网络及其不稳定的环境下工作。
    • 类本地应用,因为PWA就是按照本地app来设计的,所以你会觉着你在使用一个本地app。
    • 保持最新,serive worker使得应用总是保持在最新版本的状态。
    • 安全,PWA使用https进行通信加密,防止了被第三方获取数据以及数据被篡改。
    • 寻找方式非常简单,通过W3Cmanifests缓存的数据和serive worker的登记,PWA可以非常容易的搜索引擎里找到打开。
    • 可复用性,通过PWA推送的通知,用户可以再次访问PWA。
    • 可留存性,允许用户将PWA在桌面上创建图标,并且不必到应用商店去下载搜索下载应用。
    • 易分享,通过URL就可以将PWA分享出去,不需要复杂的安装。
    下面的代码实例将会和你一起创一个PWA,包括PWA创建的设计及规范以及注意事项,来确保你的PWA符合应用标准。
    我们将要做什么

    在这个代码实例中,你将学会使用PWA技术去建立一个天气APP,你将学到:

    • 怎么用"app shell"去设计和开发一个PWA。
    • 怎么让你的app可以离线工作。
    • 如何存储数据以便在离线时也可以使用。
    环境和要求
    • 谷歌浏览器版本52或更高
    • Web server for chrome或其他的网络服务器。
    • 实例代码
    • 代码编辑器
    • html,css,javascript以及调试工具的基本知识。

    2.开始开发

    下载源码,你可以下载本项目的代码通过 项目代码

    解压你下载压缩文件,将会解压出来一个(your-first-pwapp-master)文件夹,这个文件夹包含了所有这个项目所需要的资源文件。
    而名为step-NN的文件夹包含了这个项目所需要的步骤,你可以把它当做参考。

    安装web server for chrome。

    你可以通过chrome应用商店安装web server for chrome(具体方式是不可描述的—译者语)。
    安装完成后,点击


    9efdf0d1258b78e4.png

    在chrome应用商店会出现这个图标


    icon.png
    点击它,你会看到下面的对话框,来配置你的本地web服务器。 home.png

    点击CHOOSE FOLDER按钮,选择工作文件夹,就是刚才解压出来的文件夹,这样可以让你在调试中,直观地看出url来观察PWA的运行。

    在选项中勾上Automatically show index.html,如图所示。


    然后通过Web Server:STARTED来开启服务。

    homescreen.png

    现在你就可以看到打开的第一个画面,使用浏览器访问工作文件夹就可以(点击高亮的web server url)
    显然这个页面上什么也没有,它只是这个app的小骨架,接下来我们将会给这个app添加UI和各种功能。

    3.开发你的APP Shell

    什么是app shell
    intro.png

    app的shell包含了构建一个PWA所需要的最基本的html,css,javascript文件,并且是确保app有良好的性能的必要组件之一,它的第一次加载非常的快速,并且第一次加载后就能被缓存下来,这意味着在app shell第一次加载完成后,用户再打开app后,app shell将从本地缓存中加载,这是非常快速的。
    app shell架构将app的基础架构和ui分离,所有的基础架构和ui都将在本地缓存,这样在后续加载的时候,PWA只需要检索必要的数据,并不需要再次加载所有数据。
    换句话说,app shell相当于那些被存于应用商店从来没有被打开过的app,其中没有数据,一旦打开这个app就会记录数据并且使用。

    为何要使用app shell架构?

    使用app shell架构,可以使你专注于速度,并且赋予PWA近似于本地app的属性,热加载和定期更新,但是不需要应用商店。

    开发 app shell

    第一步是设计核心组件
    问问自己?

    • 什么是需要立刻呈现在屏幕上的
    • 这个app需要什么重要的ui组件
    • app shell需要什么js,css以及图片等
      我们将开发一个天气app作为我们的第一个progress web app,关键的组件包括:
    • 头部的标题,添加以及刷新按钮
    • 天气预报的卡片式容器
    • 卡片式模板
    • 添加城市时的对话框
    • 加载时的动画效果
    wet.png

    当你在设计更加复杂的应用时,第一次加载时可以不必加载不需要的资源,例如我们第一次可以不加载添加城市弹出的对话框,只有用户在发起点击时开始加载。

    4.开始实现你的APP Shell

    你的项目可以通过多种项目开始,我们通常使用Web Starter Kit,但是在这个例子里,为了让你专注于PWA的开发,我们为你提供可了所有的资源。

    创建app shell的html部分

    现在我们将添加app shell 架构的核心部分,组件包括:

    • 头部的标题,添加以及刷新按钮
    • 天气预报的卡片式容器
    • 卡片式模板
    • 添加城市时的对话框
    • 加载时的动画效果
    <!DOCTYPE html>
    <html>
    <head>
      <meta charset="utf-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Weather PWA</title>
      <link rel="stylesheet" type="text/css" href="styles/inline.css">
    </head>
    <body>
      <header class="header">
        <h1 class="header__title">Weather PWA</h1>
        <button id="butRefresh" class="headerButton"></button>
        <button id="butAdd" class="headerButton"></button>
      </header>
    
      <main class="main">
        <div class="card cardTemplate weather-forecast" hidden>
        . . .
        </div>
      </main>
    
      <div class="dialog-container">
      . . .
      </div>
    
      <div class="loader">
        <svg viewBox="0 0 32 32" width="32" height="32">
          <circle id="spinner" cx="16" cy="16" r="14" fill="none"></circle>
        </svg>
      </div>
    
      <!-- Insert link to app.js here -->
    </body>
    </html>
    

    这是工作目录的index.html文件(这只是整体项目的一部分,它已经存在了,不需要再赋值。)

    注意!默认情况下,加载动画是存在的,确保用户在加载页面时可以立即看到加载器,让用户清楚地明白内容正在被加载。
    为了节省时间,我们已经创建了样式表文件供你使用。

    开始主要的javascript内容

    现在主要的ui已经成功的构建起来了(上文所提到的样式表文件),看起来应该添加一些代码让他开始工作了,就像前文所说,哪些文件应该在首次运行的时候就应该被加载,而哪些可以延时加载。
    在你的工作目录里,打开,script/app.js文件

    • 一个包含整个PWA的关键信息的app对象。
    • 添加,刷新,取消城市的监听函数(add/refresh,add/cancel)。
    • 添加或者更新天气预报的方法(app.updateForecastCard)。
    • 一个更新所有卡片数据的方法(app.getForecast,app.updateForecasts)。
    • 一个从Firebase公开的天气API上获取数据的方法(app.updateForecasts)。
    • 一些作为实例的假数据(fakeForecast)。
    测试一下

    现在你已经完成了完整的html,css和javascript,是时候去测试这个app了。
    如果你想看看假数据是怎么被渲染的,可以在index.html中取消以下代码的注释。

    <!--<script src="scripts/app.js" async></script>-->
    

    然后从app.js中取消一下代码的注释

    // app.updateForecastCard(initialWeatherForecast);
    

    然后刷新下你的应用,你将会看到下面这个漂亮的卡片。

    ex.png

    当你验证其工作正常后,可以app.updateForecastCard的假数据清除,我们仅仅是想确保每个组件都可以正常工作。

    5.从第一次快速加载开始

    Progressive Web Apps 应该快速启动并且立即可以使用,在当前的状态下,我们的天气app启动的非常快速,但是它还是不能够使用,因为没有数据,这时我们可以创建一个ajax请求来获取数据,但是额外的请求就会使加载时间变长,我们想一个办法,在初次加载的时候,就给用户提供真实的数据。

    加入天气预报数据

    在这个代码实例中,我们模拟服务器直接将数据注入javascript,但是在用户的设备上运行的时候,最新的天气预报数据将根据用户的ip来确定位置以便注入。
    代码已经包括了我们要注入的数据,就是我们上一步所使用的方法(initialWeatherForecast)。

    如何区分是不是首次运行

    但是我们不知道什么时候展示这些信息,将数据版存到本地以供下次使用么?如果用户下次使用,城市发生了变更该如何,我们需要的是加载本城市的信息,而不是之前的城市。
    用户的第一选项如已经订阅的城市列表应该使用IndexedDB或者其他快速的存储方式存储到本地,但是为了简化代码实例,我们使用了localstorage的方法来存储数据,但是这在实际运行中并不是理想的环境,因为它是阻塞型同步机制,在某些设备上可能会很慢。
    下面让我们来添加存储用户订阅城市的代码,找到以下注释

    // TODO add saveSelectedCities function here
    

    然后将下列代码复制到该注释下,如下

    //  TODO add saveSelectedCities function here
    app.saveSelectedCities = function() {
        var selectedCities = JSON.stringify(app.selectedCities);
        localStorage.selectedCities = selectedCities;
    };
    

    接下来我们需要添加一些代码检查用户是否已经添加了某城市,并且渲染这些城市的数据,找到以下注释。

    // TODO add startup code here
    

    然后在注释下添加这些代码

    /************************************************************************
       *
       * Code required to start the app
       *
       * NOTE: To simplify this codelab, we've used localStorage.
       *   localStorage is a synchronous API and has serious performance
       *   implications. It should not be used in production applications!
       *   Instead, check out IDB (https://www.npmjs.com/package/idb) or
       *   SimpleDB (https://gist.github.com/inexorabletash/c8069c042b734519680c)
       ************************************************************************/
    
      app.selectedCities = localStorage.selectedCities;
      if (app.selectedCities) {
        app.selectedCities = JSON.parse(app.selectedCities);
        app.selectedCities.forEach(function(city) {
          app.getForecast(city.key, city.label);
        });
      } else {
        /* The user is using the app for the first time, or the user has not
         * saved any cities, so show the user some fake data. A real app in this
         * scenario could guess the user's location via IP lookup and then inject
         * that data into the page.
         */
        app.updateForecastCard(initialWeatherForecast);
        app.selectedCities = [
          {key: initialWeatherForecast.key, label: initialWeatherForecast.label}
        ];
        app.saveSelectedCities();
      }
    

    该代码用来检查该城市是否保存在订阅列表中,如果有,渲染出该城市的卡片,如果没有则渲染假数据,并保存到默认卡片中。

    保存所添加的数据

    最后,你需要添加 ’添加城市‘ 按钮,将所要的添加的城市保存到本地。
    更新 butAddCity 里的代码如下

    document.getElementById('butAddCity').addEventListener('click', function() {
        // Add the newly selected city
        var select = document.getElementById('selectCityToAdd');
        var selected = select.options[select.selectedIndex];
        var key = selected.value;
        var label = selected.textContent;
        if (!app.selectedCities) {
          app.selectedCities = [];
        }
        app.getForecast(key, label);
        app.selectedCities.push({key: key, label: label});
        app.saveSelectedCities();
        app.toggleAddDialog(false);
      });
    

    如果该app存在将会初始化app.selectedCities,并且执行app.selectedCities.push() 和app.saveSelectedCities()。

    测试
    • 当第一次运行时,应用立刻向用户展示,initialWeatherForecast 中的天气数据。
    • 添加一个新的城市后确保会展示两个卡片。
    • 刷新浏览器来确保加载了最新的数据。

    6使用serive worker来缓存app shell

    Progressive Web Apps是非常快速并且可以加载在本地的,这意味着它可以在在线,离线,以及不稳定的网络情况下使用,为了实现这个目标,我们使用了一个serive worker(PWA服务)来缓存app shell,来确保始终保持可用状态并且机器可靠。
    如果你对service worker不熟悉,你可以通过阅读service worker 来了解它可以做什么并且它的生命周期是怎么工作的等等,如果你完成看了这个代码实例,一定要查看 Debugging Service Workers code lab 来深入了解。
    service workers提供了一种奖金是增强的特征,这些特性仅仅作用于支持service workers的浏览器,比如,使用service workers你可以缓存app shell和你的应用所需要的数据,所以这些数据即便在离线的环境下也可以工作。如果浏览器不支持service workers,支持离线的代码并没有工作,用户也能得到一个基本的用户体验,并且检测你所使用的浏览器的时候会花费近本可以忽略不计的性能,对你所使用的浏览器也没有任何影响。

    注册service worker

    为了让应用可以离线工作,要做的第一件事情就是注册一个service worker,这是一段在后台运行的脚本程序,并不要用户去打开它,也不需要任何的操作。
    这只需要两步

    1. 创建一个js文件来运行service worker。
    2. 声明这个js文件是service worker。
      第一步,在跟目录下创建一个空文件叫做service-worker.js。这个文件必须放在根目录。因为service worker的作用域范围是跟它所在的位置来决定的。
      然后,需要检查浏览器是不是支持service worker,如果支持会注册service worker,将下面的代码添加至app.js中。
    if('serviceWorker' in navigator) {  
        navigator.serviceWorker  
            .register('/service-worker.js')  
            .then(function() { console.log('Service Worker Registered'); });  
    }
    
    缓存站点的资源

    当service worker被注册以后,用户首次访问页面的时候,一个install事件函数就会被触发。在这个事件的回调函数中,我们能够缓存所有的应用需要用到的资源。
    当service worker被激活后,他应该打开缓存的对象,并且将其所需要i的资源存储进去。将下面的代码加入到service-worker.js(你可以在your-first-pwapp-master/work中找到) :

    var cacheName = 'weatherPWA-step-6-1';
    var filesToCache = [];
    self.addEventListener('install', function(e) {
      console.log('[ServiceWorker] Install');
      e.waitUntil(
        caches.open(cacheName).then(function(cache) {
          console.log('[ServiceWorker] Caching app shell');
          return cache.addAll(filesToCache);
        })
      );
    });
    

    首先我们需要使用caches.open()打开cache对象,并且定义一个cache的名称,这样我们就可以给cache文件迭代本本,或者将数据分离,以至于我们能够轻松地升级数据而不影响其他数据。
    一旦cache数据被打开,我们可以调用 cache.addAll() 并且往其中传入一个url列表,然后加载这些资源。但是,一旦 cache.addAll() 操作失败,那么整个cache加载都会失败(原文是cache.addAll() is atomic,我的理解是单线程的,就是说一旦其中一个缓存失败,那么其他都会无法继续--译者)。
    ok,让我们来熟悉控制台并学习怎么调试service workers。在你刷新页面之前,打开控制台,找到Appliction,并且打开Service worker的选项。如图

    dev.png

    如果你看到的是上图这样的页面的话,说明你打口的页面没有已经注册的Service worker
    你需要重新加载页面,Service worker的选项应该如下图所示。

    dev2.png

    当你看到控制台如上图显示的话,这意味着service worker已经正在工作了。

    现在让我们开始展示你在使用service worker可能遇到的问题,为了演示问题所在,请在service-worker.js的install事件下面添加一个activate监听器。

    self.addEventListener('activate', function(e) {
      console.log('[ServiceWorker] Activate');
    });
    

    在service-worker开始运行的时候,activate监听事件就会被激活。
    打开控制台,然后重新刷新下网页,然后找到Application选项,找到service workers选项,在已经被激活的service workers上,点击inspect(如果你没找到点击show all,然后每个服务都有一个start,点击start就有inspect了-译者语),理论上说,控制开会出现[ServiceWorker] Activate这样的信息,但是并没有出现,现在你回到service worker选项,你会发现一个新service worker正处于等待的状态(包括activate监听事件也在这个状态)。

    listen.png

    一般来说,只要页面的还有一个tab的话,service worker会一直工作,所以你可以关闭然后再重新打开页面或者点击skipWaiting按钮,但是有一个更加简单的办法可以让你不必这么麻烦的操作,启用update on reload(在service worker下第一行第二个选项--译者语),当你启用update on reload这项服务后,页面在每次刷新后都会强制更新。

    现在开启update on reload并且确定新service worker已经被激活了。

    注意:你可能会看到service worker出现一个错误,忽略这个错误就行,它是安全的(如下图)。
    error.png

    以上就是控制台关于调试app的一些方法,稍后哦我们会向你展示一些技巧。现在让我们回到怎么构建应用程序。

    好了,现在让我们来完成activate事件监听函数的一些逻辑代码用来更新缓存,用下面的代码来更新。

    self.addEventListener('activate', function(e) {
      console.log('[ServiceWorker] Activate');
      e.waitUntil(
        caches.keys().then(function(keyList) {
          return Promise.all(keyList.map(function(key) {
            if (key !== cacheName) {
              console.log('[ServiceWorker] Removing old cache', key);
              return caches.delete(key);
            }
          }));
        })
      );
      return self.clients.claim();
    });
    

    一旦你的app shell变化,上述的代码会确保你的service worker缓存也可以跟着更新。
    最后,让我们更新一下app shell所要求的文件列表,这需要你的app所用的所有文件,包括图片,js文件,css文件等,在你的service-worker.js文件头,用以下代码代替 var filesToCache = []

    var filesToCache = [
      '/',
      '/index.html',
      '/scripts/app.js',
      '/styles/inline.css',
      '/images/clear.png',
      '/images/cloudy-scattered-showers.png',
      '/images/cloudy.png',
      '/images/fog.png',
      '/images/ic_add_white_24px.svg',
      '/images/ic_refresh_white_24px.svg',
      '/images/partly-cloudy.png',
      '/images/rain.png',
      '/images/scattered-showers.png',
      '/images/sleet.png',
      '/images/snow.png',
      '/images/thunderstorm.png',
      '/images/wind.png'
    ];
    

    到这里我们的app其实还不能工作,我们已经缓存了app shell的组件,但是我们仍然需要从本地缓存中加载他们。

    从缓存中加载app shell

    service worker可以收到我们从PWA中发起的请求,并且响应,这意味着我们可以怎样处理这些请求,并且什么样的请求可以被缓存下来。
    例如:

    self.addEventListener('fetch', function(event) {
      // Do something interesting with the fetch here
    });
    

    让我们来更新app shell,将下面的代码加入service-worker.js 中

    self.addEventListener('fetch', function(e) {
      console.log('[ServiceWorker] Fetch', e.request.url);
      e.respondWith(
        caches.match(e.request).then(function(response) {
          return response || fetch(e.request);
        })
      );
    });
    

    从里到外,caches.match()方法从请求触发的fetch事件中拿到情怯的内容,并且去判断请求的资源是否存在于缓存中,然后以我们缓存中的文件作为响应,或者使用fetch函数来加载资源(如果缓存中没有该资源的话)。而返回的数据最终通过e.respondWith()返回给页面。

    测试吧

    现在你的app已经可以在离线模式下使用了!让我们来试一试吧!
    首先你要刷新下你的页面,然后点击Application面板找到cache storage,并且展开该部分,你应该在左边会看到你的app shell缓存的名称。当你惦记你的app shell缓存,你将会看到所有已经被缓存的资源。

    test1.png

    现在,让我们开始离线测试。回到控制台的service worker选项,启动offline的复选框,你将会看到network选项边上有一个黄色的警告图标,这表示你处于离线状态。

    test2.png

    然后刷新页面,你就会发现你的页面也可以正常的去操作。

    test3.png

    而下一步就是修改app本身的和service worker的逻辑,让天气对象的数据可以被保存下来,并且可以在app处于离线状态的时候,将最新的缓存数据显示出来。
    TIPS:如果你要清除所有保存的数据的话(localstorage,indexDB以及缓存的文件),并且删除任何service worker,你可以控制台的Application选项上点击Clear storage清除数据。

    当心边界条件问题

    之前提到过,这段代码一定不要用在生产环境下,因为有许多的边界条件问题。

    • 缓存依赖于每次修改内容后缓存键的改变
      例如,缓存的方法要求你在每次内容变化之后更新键值,否则,缓存不会变化,并且重新提供旧的内容,所以确保你的项目中键值在每次内容更新后都会变化。

    • 每次修改后缓存的资源都会被重新下载
      另一个问题就是,当一个文件被修改后,整个缓存也要被重新的下载。这就意味着你即使有一个简单的拼写错误,也会让整个缓存重新的下载,这很影响效率。

    • 浏览器的缓存可能会阻止service worker缓存的更新
      还有一个重要的问题,第一次访问的时候,请求的资源是直接经过htpps加密的,这个时候可能不会返回缓存的资源,除此之外,浏览器可能返回旧的缓存资源,这就导致了service worker不会被更新。

    • 在生产环境中采取cache-first的策略
      我们的app使用了优先缓存的策略,这导致了所有的后续请求,都会从缓存中返回而不会去请求网络。cache-first的策略很容易实现,但是也会为将来带来诸多问题。一旦主页和注册的service worker被缓存下来,去修改service worker的配置非常困难(因为service worker的配置依赖于它的位置),你就会发现你的app很难进行迭代升级。

    我应该如何避免这些问题呢。

    我们应该如何避免这些问题呢?比如使用一个叫sw-precache的库,他可以帮助你精密地控制资源的生命周期,能够确保请求直接访问网络,并且帮你处理所有棘手的问题。

    实时调试service worker

    调试service worker是一件有挑战性的东西,当你涉及到缓存之后,你想要缓存进行更新,但是实际上它没有进行更新,事情就会变得像一场噩梦一样。在service worker典型的生命周期和你的代码之间,你很快就会受挫。但是幸运的是,有一些工具可以帮助你处理这些事情。

    重新开始

    TIPS:如果你要清除所有保存的数据的话(localstorage,indexDB以及缓存的文件),并且删除任何service worker,你可以控制台的Application选项上点击Clear storage清除数据。(上面有了,只是又出现了,只好跟着又翻译一遍--译者语)

    一些其他的问题
    • 一旦service worker被注销掉,它会一直保留直到浏览器被关闭。
    • 如果你打开了多个窗口,除非你对其进行了刷新,使用了新的service worker,新的service worker才会开始工作,
    • 注销一个service worker不会清空缓存,所以如果缓存的键值没有进行修改的话,你可能获得的还是旧的数据。
    • 如果一个service worker已经存在,除非你使用immediate control的方式或者刷新页面,否则新注册的service worker不会立即接替控制。

    7.使用Service Worker来缓存应用数据

    选择一个正确的 caching strategy 很重要,它取决你在应用中使用的数据类型。比如像天气信息,股票信息这种对实时性要求很高的数据,应该经常被刷新,但是例如用户的头像或者文字的内容应该以较低的频率来刷新一样。
    cache-first-then-network这个策略是一个理想的选择(钦点的(๑• . •๑)--译者语),这个方法拿到数据非常的快速,然后返回新的数据,与现请求网络再去缓存相比,用户不需要等很长时间就可以拿到数据。
    cache-first-then-network需要我们发起两个异步请求,一个请求缓存,一个请求网络。我们应用中的网络请求不能被修改,但是我们要修改一下service-worker.js的缓存请求代码。
    一般情况下,请求应该立即返回缓存的数据,提供app能够使用的最新的数据,然后当网络请求返回后存到缓存中以供下次调用。(就是说刷新一下,拿到的是上次网络请求返回的数据,而后台这边又会发起一个请求拿到目前最新的数据保存到缓存中,一定程度上解决了网络请求慢的问题--译者语)

    拦截网络请求之后使用缓存来响应

    我们需要修改service worker来拦截对天气API的请求,然后把其请求该API的结果存储下来,以便我们以后调用,那么在 cache-first-then-network的策略下,我们希望请求返回给我们的是最新的数据,如果不是,那么也没事,因为我们已经把数据存在了缓存里,直接调用就行了。
    在service worker里,我们添加一个dataCacheName变量,以至于我们可以从app shell中将应用数据分离出来。当你的app shell更新了,其缓存消失了,但是你的数据还在,不会受到影响,并可以随时调用。记住,若是将来你的数据格式改变了,你需要一种能让app shell和应用数据保持同步的办法。
    将下面的代码加入到service-worker.js中
    var dataCacheName = 'weatherData-v1';
    接下来,我们需要更新activate的回调,以防删除appshell的缓存之后,应用数据也会被删除。
    if (key !== cacheName && key !== dataCacheName) {
    (原文中就是这样的,不过我认为应该是少了一个括号--译者语)
    最后,来修改fetch事件的回调,添加代码来将请求数据API和其他的请求分开来。

    self.addEventListener('fetch', function(e) {
      console.log('[Service Worker] Fetch', e.request.url);
      var dataUrl = 'https://query.yahooapis.com/v1/public/yql';
      if (e.request.url.indexOf(dataUrl) > -1) {
        /*
         * When the request URL contains dataUrl, the app is asking for fresh
         * weather data. In this case, the service worker always goes to the
         * network and then caches the response. This is called the "Cache then
         * network" strategy:
         * https://jakearchibald.com/2014/offline-cookbook/#cache-then-network
         */
        e.respondWith(
          caches.open(dataCacheName).then(function(cache) {
            return fetch(e.request).then(function(response){
              cache.put(e.request.url, response.clone());
              return response;
            });
          })
        );
      } else {
        /*
         * The app is asking for app shell files. In this scenario the app uses the
         * "Cache, falling back to the network" offline strategy:
         * https://jakearchibald.com/2014/offline-cookbook/#cache-falling-back-to-network
         */
        e.respondWith(
          caches.match(e.request).then(function(response) {
            return response || fetch(e.request);
          })
        );
      }
    });
    
    

    上面的代码对请求进行拦截,判断请求的URL是否为天气API,如果是的话,我们就使用fetch发起新的请求,一旦有响应返回,就会将其存入到缓存,然后把请求返回给原请求。
    其实应用到现在还不能正式运行呢(啊,我去--译者语),我们虽然已经实现了从app shell拿取缓存,但是即使我们缓存了数据,依然需要通过网络来发起请求。(第一次需要通过网络,但是在这之后,你每次查看天气,它不光会缓存目前的,还会缓存未来几个小时的,也就是说未来几个小时即使你没有网络也能看天气,哇,牛!--译者语)

    发起网络请求

    之前提到过,app需要启动两个异步请求,一个访问缓存,一个访问网络。可以访问最新的缓存亦可以访问网络,这就是渐进式增强的一个很好的例子,因为缓存可能不是在所有的浏览器都可以使用,若不能使用的话,网络请求仍然可以很好地使用。

    为了实现网络请求,我们需要做
    • 检查window全局对象是否有caches对象
    • 向缓存发起请求
    • 如果服务器的请求没有返回任何结果,需要使用本地缓存。
    • 向服务器发起请求。
    • 保存在数据本地以便调用。
    • 热更新。
    从缓存抓取数据

    接下来我们要检查是否存在caches这个对象并且拿到最新的缓存数据,找到TODO add cache logic here comment 中,它在app.getForecast()方法中,加入下面的代码

     if ('caches' in window) {
          /*
           * Check if the service worker has already cached this city's weather
           * data. If the service worker has the data, then display the cached
           * data while the app fetches the latest data.
           */
          caches.match(url).then(function(response) {
            if (response) {
              response.json().then(function updateFromCache(json) {
                var results = json.query.results;
                results.key = key;
                results.label = label;
                results.created = json.query.created;
                app.updateForecastCard(results);
              });
            }
          });
        }
    

    这时应用会做出两个请求,一个是XHR到天气API,一个是缓存请求,若缓存中有数据,其返回非常快,然后更新卡片的数据,这都是当天气API的请求未返回的情况下,一旦天气数据从服务端返回,就会更新卡片的信息。

    注意!我们知道网络请求和缓存请求都更新了天气数据,但是app怎么知道哪一个是最新的呢?下面的代码就解决了这个问题,将它添加到app.updateForecastCard:里。

     var cardLastUpdatedElem = card.querySelector('.card-last-updated');
        var cardLastUpdated = cardLastUpdatedElem.textContent;
        if (cardLastUpdated) {
          cardLastUpdated = new Date(cardLastUpdated);
          // Bail if the card has more recent data then the data
          if (dataLastUpdated.getTime() < cardLastUpdated.getTime()) {
            return;
          }
        }
    

    每次卡片数据更新后,app存储的时间戳卡片的数据中,app只要判断时间戳是不是已经存在就行了。

    试一试吧

    现在这个app已经可以实现完整的离线功能了,保存几个城市,然后点击刷新按钮来刷新数据,然后在离线的环境下刷新新app再试试。
    之后去cache storage页面(控制台的application下),展开观察,在左边,你应该可以看到你的app shell缓存的名称,当你点击它,你会看到所有已经被缓存的资源。

    fin.png

    8.如何在原生应用集成PWA

    9.上线和庆祝。。。

    未完待续。。。。

    相关文章

      网友评论

        本文标题:PWA官方文档翻译之二Your First Progressiv

        本文链接:https://www.haomeiwen.com/subject/rxmdgttx.html