采纳PWA性子完毕离线化

by admin on 2019年10月16日

迈向PWA!利用serviceworker的离线访问模式

2017/02/08 · JavaScript
· PWA

本文作者: 伯乐在线 –
pangjian
。未经作者许可,禁止转载!
欢迎加入伯乐在线 专栏作者。

微信小程序来了,可以利用WEB技术在微信打造一个有着Native应用体验的应用,业界非常看好这种形式。但是你们也许不知道,Google早已有类似的规划,甚至层次更高。那就是PWA(渐进式增强WEB应用)。
PWA有以下几种特性:

  • Installablity(可安装性)
  • App Shell
  • Offline(离线能力)
  • Re-engageable(推送通知能力)

所有这些特性都是“优雅降级、渐进增强的”,给支持的设备更好的体验,不支持的设备也不会更差。这就和微信小程序这种二流设计的根本不同之处。

本博客也在向着PWA的方向迈进,第一步我选择了Offline,也就是离线能力。可以让客户在没有网络连接的时候仍然可以使用部分服务。这个能力利用了Service
Worker技术。

实现思路就是,利用service
worker,另起一个线程,用来监听所有网络请求,讲已经请求过的数据放入cache,在断网的情况下,直接取用cache里面的资源。为请求过的页面和图片,展示一个默认值。当有网络的时候,再重新从服务器更新。
图片 1
代码这里就不贴了,以后可能会专门写一篇来详细介绍Service
Worker,有兴趣的可以直接参考源码。
注册起来也非常方便

JavaScript

// ServiceWorker_js (function() { ‘use strict’;
navigator.serviceWorker.register(‘/sw.js’, {scope:
‘/’}).then(function(registration) { // Registration was successful
console.log(‘ServiceWorker registration successful with scope: ‘,
registration.scope); }).catch(function(err) { // registration failed 🙁
console.log(‘ServiceWorker registration failed: ‘, err); }); })();

1
2
3
4
5
6
7
8
9
10
11
12
// ServiceWorker_js
(function() {
    ‘use strict’;
    navigator.serviceWorker.register(‘/sw.js’, {scope: ‘/’}).then(function(registration) {
      // Registration was successful
      console.log(‘ServiceWorker registration successful with scope: ‘, registration.scope);
    }).catch(function(err) {
      // registration failed 🙁
      console.log(‘ServiceWorker registration failed: ‘, err);
    });
 
})();

这里需要注意的是,sw.js所在的目录要高于它的控制范围,也就是scope。我把sw.js放在了根目录来控制整个目录。

接下来看看我们的最终效果吧,你也可以在自己的浏览器下断网尝试一下。当然有部分浏览器目前还不支持,比如大名鼎鼎的Safari。

引入

PWA(渐进式网页应用)对于关注新技术得同学想必已不陌生。14年至今,其应用不如应有的那么广泛,最大的障碍:在iOS平台缺乏支持,近期已被打破
—— Safari技术预览版已经默认开启 Service Worker。

从开发者角度,缺少一个开箱即用的方案,且规范本身在快速发展,则是技术人员选择观望的重要原因。本文将以专属海报为例,介绍通过
workbox 工具, 快速为项目启用 PWA 中离线特性的方法,以及技巧总结。

本文是《PWA学习与实践》系列的第三篇文章。

 

离线有缓存情况

图片 2

workbox介绍

workbox 是用来实现网页应用离线化的构建工具,通过生成的 service worker
文件,让你的离线静态资源管理策略得以在用户端实现。由于 service worker
本身是飞速发展规范,且客户端支持程度不一,通过调用 workerbox 的
API,可以最大程度的屏蔽这些兼容问题,从这个方面理解,有点像 jQuery 在 ie
时代的作用,差别是前者解决的是 service work 运行环境的兼容性问题,而
jQuery 解决的事浏览器兼容性问题。

workbox 本身集成了常用的五套缓存策略

Cache only;

Cache first, falling back to network;

Cache, with network update;

Network only;

Network first, falling back to cache

策略详情以及 API 可参考文档「 」这里不再赘述。

workbox 底层整合了sw-precache , sw-toolbox
等工具,对于熟悉这些工具的同学,理解接口和排查问题时应该会轻松些。

图片 3

Default.aspx

离线无缓存情况

会展示一个默认的页面

图片 4

-EOF-

打赏支持我写出更多好文章,谢谢!

打赏作者

结合专属海报

原文中此处为链接,暂不支持采集

可以看出,专属海报属于小型网页应用,本身没有很复杂的构建过程,所以我选择gulp作为构建工具。

专属海报实现离线化,其资源可分为三类:

1、应用自身逻辑和样式资源做预缓存(precaching):在页面加载完成后就缓存到
Cache Storage,之后除非部署新版,都将从缓存读取资源

图片 5

2、cdn库文件使用运行时缓存(runtime caching),读取时采用缓存优先(cache
first)策略:使用到到时候从网络加载,第二次起从缓存加载

  {
      urlPattern : 'https://vendor-Url/(.*)',
      handler: 'cacheFirst',
      options: {
        cacheableResponse: {
          statuses: [0, 200]
        }
      }
    },
    {
      urlPattern : 'https://CDN-Url/(.*)',
      handler: 'cacheFirst',
      options: {
        cacheableResponse: {
          statuses: [0, 200]
        }
      }
    }

3、请求接口的数据使用运行时缓存(runtime
caching),网络优先策略(network
first):优先通过网络读取,断网后从缓存读取,用于实现离线浏览(不可提交)

  {
      urlPattern : 'https://API-Url/(.*)',
      handler: 'networkFirst',
      options: {
        cacheableResponse: {
          statuses: [0, 200]
        }
      }
    }

完整的 gulp task 可参考配置「 」

PWA作为今年最火热的技术概念之一,对提升Web应用的安全、性能和体验有着很大的意义,非常值得我们去了解与学习。

图片 6图片 7View Code

打赏支持我写出更多好文章,谢谢!

任选一种支付方式

图片 8
图片 9

1 赞 1 收藏
评论

其他场景

对于更加复杂的项目,可能 workbox
提供的缓存策略无法满足你的需求,这就需要自己定制一些路由逻辑。

对于复杂的全新项目,则可以考虑直接拿 lavas
生成脚手架,降低初始成本,不过感觉后续遇到问题,这些“省”下的时间还是要还回来的。

本系列文章《PWA学习与实践》会逐步拆解PWA背后的各项技术,通过实例代码来讲解这些技术的应用方式。也正是因为PWA中技术点众多、知识细碎,因此我在学习过程中,进行了整理,并产出了《PWA学习与实践》系列文章,希望能带大家全面了解PWA中的各项技术。对PWA感兴趣的朋友欢迎关注。

<%@ Page Language="C#" AutoEventWireup="true"  CodeFile="Default.aspx.cs" Inherits="_Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>无标题页</title>

    <style type="text/css">
        .style1
        {
            width: 62px;
        }
        .style2
        {
            width: 60%;
        }
        .style3
        {
            width: 968px;
        }
    </style>

</head>
<body>
    <form id="form1" runat="server">
    <table cellpadding="0" cellspacing="0" >
        <tr>
            <td colspan="2">
                <asp:Image ID="Image1" runat="server" ImageUrl="~/header.bmp" />
            </td>
        </tr>
        <tr>
            <td class="style1">
                <asp:Image ID="Image2" runat="server" ImageUrl="~/left.bmp" />
            </td>
            <td valign="top" class="style3">
                    &nbsp;<asp:Image ID="Image3" runat="server" ImageUrl="~/right.bmp" />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
                    <table align="left" class="style2">
                        <tr>
                            <td>
                    <asp:Label ID="Label1" runat="server" style="font-size: medium"></asp:Label>
                            </td>
                        </tr>
                        <tr>
                            <td>
                                &nbsp;</td>
                        </tr>
                    </table>
                    &nbsp;</td>
        </tr>
    </table>
    <p>
        &nbsp;</p>
    </form>
</body>
</html>

关于作者:pangjian

图片 10

庞健,金融IT男。
个人主页 ·
我的文章 ·
5 ·
  

图片 11

总结

专属海报在开发中期就已接入
workbox1.X,但考虑到项目自身还未进入稳定状态,另一方面技术规范和客户端支持程度也不完备,而且缺乏效果监控方法。综合考虑收益和风险点,一直未在生产环境启用此特性,而目前随着
iOS 的支持和技术逐渐成熟,项目中启用 PWA 的时机将趋近成熟。

将新技术引入实际项目后,理论上应该解决的问题是否如预期得到解决,解决效果如何?下一篇将介绍通过在服务端定期记录
Lighthouse 跑分结果,来度量优化效果的一些思考。

本文中的代码都可以在learning-pwa的sw-cache分支上找到(git clone后注意切换到sw-cache分支)。

Default.aspx.cs

参考

Google 开发者网站 「  」

饿了么的 PWA 升级实践 by 黄玄 「  」

借助Service Worker和cacheStorage缓存及离线开发 by 张鑫旭 「


图片 12

微信扫码,或订阅Feed获取最新动态。

1. 引言

PWA其中一个令人着迷的能力就是离线(offline)可用。

图片 13

即使在离线状态下,依然可以访问的PWA

离线只是它的一种功能表现而已,具体说来,它可以:

  • 让我们的Web
    App在无网(offline)情况下可以访问,甚至使用部分功能,而不是展示“无网络连接”的错误页;
  • 让我们在弱网的情况下,能使用缓存快速访问我们的应用,提升体验;
  • 在正常的网络情况下,也可以通过各种自发控制的缓存方式来节省部分请求带宽;
  • ……

而这一切,其实都要归功于PWA背后的英雄 —— Service Worker

那么,Service Worker是什么呢?你可以把Service
Worker简单理解为一个独立于前端页面,在后台运行的进程。因此,它不会阻塞浏览器脚本的运行,同时也无法直接访问浏览器相关的API(例如:DOM、localStorage等)。此外,即使在离开你的Web
App,甚至是关闭浏览器后,它仍然可以运行。它就像是一个在Web应用背后默默工作的勤劳小蜜蜂,处理着缓存、推送、通知与同步等工作。所以,要学习PWA,绕不开的就是Service
Worker。

图片 14

PWA背后的英雄 —— Service Worker

在接下来的几篇文章里,我会从如何使用Service
Worker来实现资源的缓存、消息的推送、消息的通知以及后台同步这几个角度,来介绍相关原理与技术实现。这些部分会是PWA技术的重点。需要特别注意的是,由于Service
Worker所具有的强大能力,因此规范规定,Service
Worker只能运行在HTTPS域下
。然而我们开发时候没有HTTPS怎么办?别着急,还有一个贴心的地方——为方便本地开发,Service
Worker也可以运行在localhost(127.0.0.1)域下

好了,简单了解了Service
Worker与它能实现的功能后,我们还是要回到这一篇的主题,也就是Service
Worker的第一部分——如何利用Service
Worker来实现前端资源的缓存,从而提升产品的访问速度,做到离线可用。

图片 15图片 16View Code

2. Service Worker是如何实现离线可用的?

这一小节会告诉大家,Service Worker是如何让我们在离线的情况下也能访问Web
App的。当然,离线访问只是其中一种表现。

首先,我们想一下,当访问一个web网站时,我们实际上做了什么呢?总体上来说,我们通过与与服务器建立连接,获取资源,然后获取到的部分资源还会去请求新的资源(例如html中使用的css、js等)。所以,粗粒度来说,我们访问一个网站,就是在获取/访问这些资源。

可想而知,当处于离线或弱网环境时,我们无法有效访问这些资源,这就是制约我们的关键因素。因此,一个最直观的思路就是:如果我们把这些资源缓存起来,在某些情况下,将网络请求变为本地访问,这样是否能解决这一问题?是的。但这就需要我们有一个本地的cache,可以灵活地将各类资源进行本地存取。

图片 17

如何获取所需的资源?

有了本地的cache还不够,我们还需要能够有效地使用缓存、更新缓存与清除缓存,进一步应用各种个性化的缓存策略。而这就需要我们有个能够控制缓存的“worker”——这也就是Service
Worker的部分工作之一。顺便多说一句,可能有人还记得
ApplicationCache
这个API。当初它的设计同样也是为了实现Web资源的缓存,然而就是因为不够灵活等各种缺陷,如今已被Service
Worker与cache API所取代了。

Service Worker有一个非常重要的特性:你可以在Service
Worker中监听所有客户端(Web)发出的请求,然后通过Service
Worker来代理,向后端服务发起请求。通过监听用户请求信息,Service
Worker可以决定是否使用缓存来作为Web请求的返回。

下图展示普通Web App与添加了Service Worker的Web App在网络请求上的差异:

图片 18

普通Web请求(上)与使用Service Worker代理(下)的区别

这里需要强调一下,虽然图中好像将浏览器、SW(Service
Worker)与后端服务三者并列放置了,但实际上浏览器(你的Web应用)和SW都是运行在你的本机上的,所以这个场景下的SW类似一个“客户端代理”。

了解了基本概念之后,就可以具体来看下,我们如何应用这个技术来实现一个离线可用的Web应用。

using System;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Linq;
//引入命名空间
using System.Data.SqlClient;

public partial class _Default : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        string strCon = ConfigurationSettings.AppSettings["strCon"];
        //创建数据库连接对象
        SqlConnection con = new SqlConnection(strCon);
        //创建SqlDataAdapter对象
        SqlDataAdapter ada = new SqlDataAdapter("select * from mrbccd01", con);
        //创建DataSet对象
        DataSet ds = new DataSet();
        //填充数据集
        int counter = ada.Fill(ds, "mrbccd01");
        Response.Write("获得:" + counter.ToString() + "条数据!" + "<br/>");
        foreach (DataRow mydr in ds.Tables["mrbccd01"].Rows)
        {
            Label1.Text += mydr["bccdID"].ToString() + "-"
                + mydr["bccdName"].ToString() + "--价格:"
                + mydr["bccdPrice"].ToString() + "<br/>";
        }
    }
}

3. 如何使用Service Worker实现离线可用的“秒开”应用

还记得我们之前的那个图书搜索的demo Web
App么?不了解的朋友可以看下本系列的第一篇文章,当然你可以忽略细节,继续往下了解技术原理。

没错,这次我仍然会基于它进行改造。在上一篇添加了manifest后,它已经拥有了自己的桌面图标,并有一个很像Native
App的外壳;而今天,我会让它变得更酷。

如果想要跟着文章内容一起实践,可以在这里下载到所需的全部代码。
记得切换到manifest分支,因为本篇内容,是基于上一篇的最终代码进行相应的开发与升级。毕竟我们的最终目标是将这个普通的“图书搜索”demo升级为PWA。

 

3.1. 注册Service Worker

注意,我们的应用始终应该是渐进可用的,在不支持Service
Worker的环境下,也需要保证其可用性。要实现这点,可以通过特性检测,在index.js中来注册我们的Service
Worker(sw.js):

// index.js
// 注册service worker,service worker脚本文件为sw.js
if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('./sw.js').then(function () {
        console.log('Service Worker 注册成功');
    });
}

这里我们将sw.js文件注册为一个Service Worker,注意文件的路径不要写错了。

值得一提的是,Service
Worker的各类操作都被设计为异步,用以避免一些长时间的阻塞操作。这些API都是以Promise的形式来调用的。所以你会在接下来的各段代码中不断看到Promise的使用。如果你完全不了解Promise,可以先在这里了解基本的Promise概念:Promise(MDN)和JavaScript
Promise:简介。

3.2. Service Worker的生命周期

当我们注册了Service
Worker后,它会经历生命周期的各个阶段,同时会触发相应的事件。整个生命周期包括了:installing
–> installed –> activating –> activated –>
redundant。当Service
Worker安装(installed)完毕后,会触发install事件;而激活(activated)后,则会触发activate事件。

图片 19

Service Worker生命周期

下面的例子监听了install事件:

// 监听install事件
self.addEventListener('install', function (e) {
    console.log('Service Worker 状态: install');
});

self是Service
Worker中一个特殊的全局变量,类似于我们最常见的window对象。self引用了当前这个Service
Worker。

3.3. 缓存静态资源

通过上一节,我们已经学会了如何添加事件监听,来在合适的时机触发Service
Worker的相应操作。现在,要使我们的Web
App离线可用,就需要将所需资源缓存下来。我们需要一个资源列表,当Service
Worker被激活时,会将该列表内的资源缓存进cache。

// sw.js
var cacheName = 'bs-0-2-0';
var cacheFiles = [
    '/',
    './index.html',
    './index.js',
    './style.css',
    './img/book.png',
    './img/loading.svg'
];

// 监听install事件,安装完成后,进行文件缓存
self.addEventListener('install', function (e) {
    console.log('Service Worker 状态: install');
    var cacheOpenPromise = caches.open(cacheName).then(function (cache) {
        return cache.addAll(cacheFiles);
    });
    e.waitUntil(cacheOpenPromise);
});

可以看到,首先在cacheFiles中我们列出了所有的静态资源依赖。注意其中的'/',由于根路径也可以访问我们的应用,因此不要忘了将其也缓存下来。当Service
Worker
install时,我们就会通过caches.open()cache.addAll()方法将资源缓存起来。这里我们给缓存起了一个cacheName,这个值会成为这些缓存的key。

上面这段代码中,caches是一个全局变量,通过它我们可以操作Cache相关接口。

Cache 接口提供缓存的 Request / Response 对象对的存储机制。Cache 接口像
workers 一样, 是暴露在 window 作用域下的。尽管它被定义在 service
worker 的标准中, 但是它不必一定要配合 service worker 使用。——MDN

3.4 使用缓存的静态资源

到目前为止,我们仅仅是注册了一个Service
Worker,并在其install时缓存了一些静态资源。然而,如果这时运行这个demo你会发现——“图书搜索”这个Web
App依然无法离线使用。

为什么呢?因为我们仅仅缓存了这些资源,然而浏览器并不知道需要如何使用它们;换言之,浏览器仍然会通过向服务器发送请求来等待并使用这些资源。那怎么办?

聪明的你应该想起来了,我们在文章前半部分介绍Service
Worker时提到了“客户端代理”——用Service Worker来帮我们决定如何使用缓存。

下图是一个简单的策略:

图片 20

有cache时的静态资源请求流程

图片 21

无cache时的静态资源请求流程

  1. 浏览器发起请求,请求各类静态资源(html/js/css/img);
  2. Service Worker拦截浏览器请求,并查询当前cache;
  3. 若存在cache则直接返回,结束;
  4. 若不存在cache,则通过fetch方法向服务端发起请求,并返回请求结果给浏览器

// sw.js
self.addEventListener('fetch', function (e) {
    // 如果有cache则直接返回,否则通过fetch请求
    e.respondWith(
        caches.match(e.request).then(function (cache) {
            return cache || fetch(e.request);
        }).catch(function (err) {
            console.log(err);
            return fetch(e.request);
        })
    );
});

fetch事件会监听所有浏览器的请求。e.respondWith()方法接受Promise作为参数,通过它让Service
Worker向浏览器返回数据。caches.match(e.request)则可以查看当前的请求是否有一份本地缓存:如果有缓存,则直接向浏览器返回cache;否则Service
Worker会向后端服务发起一个fetch(e.request)的请求,并将请求结果返回给浏览器。

到目前为止,运行我们的demo:当第一联网打开“图书搜索”Web
App后,所依赖的静态资源就会被缓存在本地;以后再访问时,就会使用这些缓存而不发起网络请求。因此,即使在无网情况下,我们似乎依旧能“访问”该应用。

3.5. 更新静态缓存资源

然而,如果你细心的话,会发现一个小问题:当我们将资源缓存后,除非注销(unregister)sw.js、手动清除缓存,否则新的静态资源将无法缓存。

解决这个问题的一个简单方法就是修改cacheName。由于浏览器判断sw.js是否更新是通过字节方式,因此修改cacheName会重新触发install并缓存资源。此外,在activate事件中,我们需要检查cacheName是否变化,如果变化则表示有了新的缓存资源,原有缓存需要删除。

// sw.js
// 监听activate事件,激活后通过cache的key来判断是否更新cache中的静态资源
self.addEventListener('activate', function (e) {
    console.log('Service Worker 状态: activate');
    var cachePromise = caches.keys().then(function (keys) {
        return Promise.all(keys.map(function (key) {
            if (key !== cacheName) {
                return caches.delete(key);
            }
        }));
    })
    e.waitUntil(cachePromise);
    return self.clients.claim();
});

3.6. 缓存API数据的“离线搜索”

到这里,我们的应用基本已经完成了离线访问的改造。但是,如果你注意到文章开头的图片就会发现,离线时我们不仅可以访问,还可以使用搜索功能。

图片 22

离线/无网环境下普通Web App(左)与PWA(右)的差异

这是怎么回事呢?其实这背后的秘密就在于,这个Web
App也会把XHR请求的数据缓存一份。而再次请求时,我们会优先使用本地缓存(如果有缓存的话);然后向服务端请求数据,服务端返回数据后,基于该数据替换展示。大致过程如下:

图片 23

图书查询接口的缓存与使用策略

首先我们改造一下前一节的代码在sw.js的fetch事件里进行API数据的缓存

// sw.js
var apiCacheName = 'api-0-1-1';
self.addEventListener('fetch', function (e) {
    // 需要缓存的xhr请求
    var cacheRequestUrls = [
        '/book?'
    ];
    console.log('现在正在请求:' + e.request.url);

    // 判断当前请求是否需要缓存
    var needCache = cacheRequestUrls.some(function (url) {
        return e.request.url.indexOf(url) > -1;
    });

    /**** 这里是对XHR数据缓存的相关操作 ****/
    if (needCache) {
        // 需要缓存
        // 使用fetch请求数据,并将请求结果clone一份缓存到cache
        // 此部分缓存后在browser中使用全局变量caches获取
        caches.open(apiCacheName).then(function (cache) {
            return fetch(e.request).then(function (response) {
                cache.put(e.request.url, response.clone());
                return response;
            });
        });
    }
    /* ******************************* */

    else {
        // 非api请求,直接查询cache
        // 如果有cache则直接返回,否则通过fetch请求
        e.respondWith(
            caches.match(e.request).then(function (cache) {
                return cache || fetch(e.request);
            }).catch(function (err) {
                console.log(err);
                return fetch(e.request);
            })
        );
    }
});

这里,我们也为API缓存的数据创建一个专门的缓存位置,key值为变量apiCacheName。在fetch事件中,我们首先通过对比当前请求与cacheRequestUrls来判断是否是需要缓存的XHR请求数据,如果是的话,就会使用fetch方法向后端发起请求。

fetch.then中我们以请求的URL为key,向cache中更新了一份当前请求所返回数据的缓存:cache.put(e.request.url, response.clone())。这里使用.clone()方法拷贝一份响应数据,这样我们就可以对响应缓存进行各类操作而不用担心原响应信息被修改了。

3.7. 应用离线XHR数据,完成“离线搜索”,提升响应速度

如果你跟着做到了这一步,那么恭喜你,距离我们酷酷的离线应用还差最后一步了!

目前为止,我们对Service
Worker(sw.js)的改造已经完毕了。最后只剩下如何在XHR请求时有策略的使用缓存了,这一部分的改造全部集中于index.js,也就是我们的前端脚本。

还是回到上一节的这张图:

图片 23

图书查询接口的缓存与使用策略

和普通情况不同,这里我们的前端浏览器会首先去尝试获取缓存数据并使用其来渲染界面;同时,浏览器也会发起一个XHR请求,Service
Worker通过将请求返回的数据更新到存储中的同时向前端Web应用返回数据(这一步分就是上一节提到的缓存策略);最终,如果判断返回的数据与最开始取到的cache不一致,则重新渲染界面,否则忽略。

为了是代码更清晰,我们将原本的XHR请求部分单独剥离出来,作为一个方法getApiDataRemote()以供调用,同时将其改造为了Promise。为了节省篇幅,我部分的代码比较简单,就不单独贴出了。

这一节最重要的部分其实是读取缓存。我们知道,在Service
Worker中是可以通过caches变量来访问到缓存对象的。令人高兴的是,在我们的前端应用中,也仍然可以通过caches来访问缓存。当然,为了保证渐进可用,我们需要先进行判断'caches' in window。为了代码的统一,我将获取该请求的缓存数据也封装成了一个Promise方法:

function getApiDataFromCache(url) {
    if ('caches' in window) {
        return caches.match(url).then(function (cache) {
            if (!cache) {
                return;
            }
            return cache.json();
        });
    }
    else {
        return Promise.resolve();
    }
}

而原本我们在queryBook()方法中,我们会请求后端数据,然后渲染页面;而现在,我们加上基于缓存的渲染:

function queryBook() {
    // ……
    // 远程请求
    var remotePromise = getApiDataRemote(url);
    var cacheData;
    // 首先使用缓存数据渲染
    getApiDataFromCache(url).then(function (data) {
        if (data) {
            loading(false);
            input.blur();            
            fillList(data.books);
            document.querySelector('#js-thanks').style = 'display: block';
        }
        cacheData = data || {};
        return remotePromise;
    }).then(function (data) {
        if (JSON.stringify(data) !== JSON.stringify(cacheData)) {
            loading(false);                
            input.blur();
            fillList(data.books);
            document.querySelector('#js-thanks').style = 'display: block';
        }
    });
    // ……
}

如果getApiDataFromCache(url).then返回缓存数据,则使用它先进行渲染。而当remotePromise的数据返回时,与cacheData进行比对,只有在数据不一致时需要重新渲染页面(注意这里为了简便,粗略地使用了JSON.stringify()方法进行对象间的比较)。这么做有两个优势:

  1. 离线可用。如果我们之前访问过某些URL,那么即使在离线的情况下,重复相应的操作依然可以正常展示页面;
  2. 优化体验,提高访问速度。读取本地cache耗时相比于网络请求是非常低的,因此就会给我们的用户一种“秒开”、“秒响应”的感觉。

  3. 使用Lighthouse测试我们的应用


至此,我们完成了PWA的两大基本功能:Web App Manifest和Service
Worker的离线缓存。这两大功能可以很好地提升用户体验与应用性能。我们用Chrome中的Lighthouse来检测一下目前的应用:

图片 25

Lighthouse检测结果

图片 26

Lighthouse检测结果 – PWA

可以看到,在PWA评分上,我们的这个Web
App已经非常不错了。其中唯一个扣分项是在HTTPS协议上:由于是本地调试,所以使用了

5. 这太酷了,但是兼容性呢?

随着今年(2018年)年初,Apple在iOS 11.3中开始支持Service
Worker,加上Apple一直以来较为良好的系统升级率,整个PWA在兼容性问题上有了重大的突破。

虽然Service
Worker中的一些其他功能(例如推送、后台同步)Apple并未表态,但是Web App
Manifest和Service Worker的离线缓存是iOS
11.3所支持的。这两大核心功能不仅效果拔群,而且目前看来具有还不错的兼容性,非常适合投入生产。

更何况,作为渐进式网页应用,其最重要的一个特点就是在兼容性支持时自动升级功能与体验;而在不支持时,会静默回退部分新功能。在保证我们的正常服务情况下,尽可能利用浏览器特性,提供更优质的服务。

图片 27

Service Worker兼容性

6. 写在最后

本文中所有的代码示例均可以在learn-pwa/sw-cache上找到。注意在git
clone之后,切换到sw-cache分支,本文所有的代码均存在于该分支上。切换其他分值可以看到不同的版本:

  • basic分支:基础项目demo,一个普通的图书搜索应用(网站);
  • manifest分支:基于basic分支,添加manifest等功能,具体可以看上一篇文章了解;
  • sw-cache分支:基于manifest分支,添加缓存与离线功能;
  • master分支:应用的最新代码。

如果你喜欢或想要了解更多的PWA相关知识,欢迎关注我,关注《PWA学习与实践》系列文章。我会总结整理自己学习PWA过程的遇到的疑问与技术点,并通过实际代码和大家一起实践。

最后声明一下,文中的代码作为demo,主要是用于了解与学习PWA技术原理,可能会存在一些不完善的地方,因此,不建议直接使用到生产环境。

《PWA技术学习与实践》系列

  • 第一篇:开始你的PWA学习之旅
  • 第二篇:10分钟学会使用Manifest,让你的WebApp更“Native”
  • 第三篇:从今天起,让你的WebApp离线可用(本文)
  • 第四篇:TroubleShooting: 解决FireBase
    login验证失败问题
  • 第五篇:与你的用户保持联系: 消息Push功能(写作中…)

参考资料

  • Using Service
    Workers(MDN)
  • Cache(MDN)
  • Service
    Worker使用方式
  • JavaScript
    Promise:简介
  • Promise(MDN)

发表评论

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

网站地图xml地图