您的位置:新葡亰496net > 新葡亰官网 > 离线网页应用,入门教程

离线网页应用,入门教程

发布时间:2019-06-16 08:35编辑:新葡亰官网浏览(95)

    React 同构应用 PWA 晋级指南

    2018/05/25 · JavaScript · PWA, React

    最初的作品出处: 林东洲   

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

    2018/05/25 · 基础技艺 · PWA

    原来的书文出处: Craig Buckler   译文出处:赐紫含桃城控件   

    上篇文章大家对渐进式Web应用(PWA)做了有个别宗旨的介绍。

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

    在这一节中,大家将介绍PWA的规律是怎么样,它是怎么开头专门的工作的。

    采纳 Service Worker 做贰个 PWA 离线网页应用

    2017/10/09 · JavaScript · PWA, Service Worker

    原作出处: 人人网FED博客   

    在上一篇《自己是怎么让网址用上HTML5 Manifest》介绍了怎么用Manifest做三个离线网页应用,结果被大规模网络死党作弄说那么些东西已经被deprecated,移出web标准了,未来被ServiceWorker代替了,不管怎么,Manifest的一对思虑依然得以借用的。小编又将网址晋级到了ServiceWorker,假设是用Chrome等浏览器就用ServiceWorker做离线缓存,假设是Safari浏览器就依旧用Manifest,读者可以打开那一个网址感受一下,断网也是能不荒谬张开。

    关于PWA

    PWA(Progressive Web App), 即渐进式web应用。PWA本质上是web应用,指标是由此多项新本事,在安康、品质、体验等地点给用户原生应用的经验。而且不必要像原生应用那样繁琐的下载、安装、进级等操作。

    此间表达下概念中的“渐进式”,意思是这一个web应用还在不断地开辟进取中。因为眼前来讲,PWA还平昔不成熟到一举成功的水准,想在锡林郭勒盟、品质、体验上达到规定的标准原生应用的效果照旧有无数的进级换代空间的。一方面是营造PWA的老本难题,另一方面是近些日子各大浏览器、安卓和IOS系统对此PWA的支撑和包容性还应该有待增进。

    本文小编将从网址缓存、http请求拦截、推送到主屏等职能,结合实例操作,一步步引领大家飞快玩转PWA技艺。

    前方的话

      渐进式网络应用 ( Progressive Web Apps ),即大家所精晓的 PWA,是 谷歌(Google) 提议的用前沿的 Web 手艺为网页提供 App 般使用体验的一密密麻麻方案。PWA 本质上是 Web App,借助一些新才具也具有了 Native App 的有些风味。本文将详细介绍针对现成网址的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

    1. 什么是Service Worker

    Service Worker是谷歌(谷歌)倡导的兑现PWA(Progressive Web App)的二个重中之重角色,PWA是为着缓和古板Web 应用程式的短处:

    (1)未有桌面入口

    (2)不能够离线使用

    (3)没有Push推送

    那Service Worker的具体表现是怎么样的呢?如下图所示:

    新葡亰496net 1

    ServiceWorker是在后台运行的一条服务Worker线程,上海体育场地小编开了多个标签页,所以体现了七个Client,可是不管开多少个页面都唯有三个Worker在担负管理。这几个Worker的做事是把一些能源缓存起来,然后拦截页面包车型客车请求,先看下缓存Curry有未有,即便有些话就从缓存里取,响应200,反之未有的话就走符合规律的呼吁。具体来说,ServiceWorker结合Web App Manifest能产生以下职业(那也是PWA的质量评定正式):

    新葡亰496net 2

    归纳能够离线使用、断网时重回200、能唤醒用户把网址加多贰个Logo到桌面上等。

    Service Worker

    ServiceWorker是PWA的大旨本事,它可感到web应用提供离线缓存作用,当然不唯有如此,上边列举了某个ServiceWorker的性状:

    据说HTTPS 遇到,那是营造PWA的硬性前提。(LAMP碰到网址晋级HTTPS化解方案)

    是一个单身的 worker 线程,独立于如今网页进度,有和煦单独的 worker context

    可掣肘HTTP请求和响应,可缓存文件,缓存的文件能够在网络离线状态时取到

    能向客户端推送音信

    无法从来操作 DOM

    异步达成,内部大都是因而 Promise 实现

    作用演示

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

    【增多到桌面】

    新葡亰496net 3

    【离线缓存】

       由于手提式无线电电话机录屏选取不能够进行离线录像,改由模拟器模拟离线效果

    新葡亰496net 4

     

    PWA 特性

    PWA 不是仅仅的某项才干,而是一群手艺的成团,譬喻:ServiceWorker,manifest 加多到桌面,push、notification api 等。

    而就在近些日蛇时间,IOS 11.3 刚刚帮助 Service worker 和好像 manifest 增加到桌面的特征,所以此番 PWA 改造首要依然达成这两部分机能,至于其余的特性,等 iphone 支持了再升级吗。

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

    应用程序清单提供了和当下渐进式Web应用的连带信息,如:

    • 应用程序名
    • 描述
    • 具备图片(包涵主显示器Logo,运维荧屏页面和用的图片可能网页上用的图片)

    本质上讲,程序清单是页面上用到的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途锐L范围时,那些利用就能平素在这么些目录中。
    • 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普拉多L,大小和类型。

    2. Service Worker的支撑景况

    Service Worker最近唯有Chrome/Firfox/Opera援助:

    新葡亰496net 5

    Safari和艾德ge也在希图协助Service Worker,由于ServiceWorker是谷歌(Google)着力的一项规范,对于生态比较封闭的Safari来讲也是迫于时局初步企图帮衬了,在Safari TP版本,能够见见:

    新葡亰496net 6

    在尝试功效(Experimental Features)里早已有ServiceWorker的菜单项了,只是即便张开也是不可能用,会提醒您还未有完结:

    新葡亰496net 7

    但不论是什么样,至少注明Safari已经计划协理ServiceWorker了。其余还足以看到在当年二〇一七年7月宣布的Safari 11.0.1版本已经援救WebRTC了,所以Safari还是三个向上的男女。

    艾德ge也图谋援救,所以Service Worker的前景十分美好。

    Service Worker生命周期

    serviceworker的采纳流程能够简轻松单总括为注册--安装--激活。

    挂号其实正是报告浏览器serviceworkerJS文件存放在哪些职位,然后浏览器下载、分析、实行该文件,进而运营安装。这里小编创设四个app.js文件,注册代码如下,将该文件在网站的head标签里引进。

    if ('serviceWorker' in navigator) {

    window.addEventListener('load', function () {

    navigator.serviceWorker.register

    .then(function (registration) {

    // 注册成功

    console.log('ServiceWorker registration successful with scope: ', registration.scope);

    })

    .catch(function {

    // 注册退步:(

    console.log('ServiceWorker registration failed: ', err);

    });

    });

    }

    当执行serviceworkerJS文件时,首先触及的是install事件,实行设置。安装的经过便是将点名的有的静态能源开始展览离线缓存。上边是自己的sw.js文件中的安装代码:

    var CACHE_VERSION = 'sw_v8';

    var CACHE_FILES = [

    '/js/jquery/min.js',

    '/js/zui/min.js',

    '/js/chanzhi.js',

    ];

    self.addEventListener('install', function {

    event.waitUntil(

    caches.open(CACHE_VERSION)

    .then(cache => cache.addAll(CACHE_FILES)

    ));

    });

    当安装成功后,serviceworker就能激活,那时就能够管理 activate 事件回调 (提供了翻新缓存攻略的火候)。并能够拍卖功效性的事件 fetch 、sync 、push 。

    self.addEventListener('activate', function {

    event.waitUntil(

    caches.keys().then(function{

    return Promise.all(keys.map(function{

    if(key !== CACHE_VERSION){

    return caches.delete;

    }

    }));

    })

    );

    });

    概述

      PWA 的要紧特征包涵上面三点:

      1、可信 - 即便在不牢固的网络情状下,也能弹指间加载并显示

      2、体验 - 迅速响应,并且有平整的卡通响应用户的操作

      3、粘性 - 像配备上的原生应用,具有沉浸式的用户体验,用户能够拉长到桌面

      首要功效包涵站点可增添至主显示器、全屏格局运维、协助离线缓存、消息推送等

    【PRPL模式】

      “PRPL”(读作 “purple”)是 谷歌 的程序员提议的一种 web 应用架构格局,它意在利用今世 web 平台的新手艺以大幅度优化移动 web 的属性与体会,对哪些组织与统一准备高质量的 PWA 系统提供了一种高档次的画饼充饥

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

      1、PUSH/PRELOAD,推送/预加载起始 U昂CoraL 路由所需的首要能源

      2、RENDE帕杰罗,渲染初阶路由,尽快让动用可被交互

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

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

    【Service workers】

      Service Workers 是谷歌(谷歌(Google)) chrome 团队提议并大力推广的一项 web 手艺。在 二零一五 年,它参加到 W3C 标准,进入草案阶段

      PWA 的关键在于 Service Workers 。就其大旨来讲,Service Workers 只是后台运转的 worker 脚本。它们是用 JavaScript 编写的,只需短短几行代码,它们便可使开荒者能够堵住互联网请求,管理推送新闻并实施好多其余职务

      瑟维斯 Worker 中用到的一些全局变量:

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

      瑟维斯 Worker 的职业体制大约如下:用户访问两个颇具 Service Worker 的页面,浏览器就能下载那些 Service Worker 并尝试安装、激活。一旦激活,Service Worker 就到后台开首职业。接下来用户访问那么些页面只怕每隔三个时段浏览器都会下载这一个Service Worker,倘使监测到 Service Worker 有更新,就能够重新安装并激活新的 Service Worker,同期 revoke 掉旧的 Service Worker,那便是 SW 的生命周期

      因为 Service Worker 有着近来的权柄接触数据,由此 瑟维斯 Worker 只好棉被服装置在 HTTPS 加密的页面中,固然无形其中进步了 PWA 的技法,可是也是为了安全做思虑

     

    Service Worker

    service worker 以我之见,类似于几个跑在浏览器后台的线程,页面第叁回加载的时候会加载那几个线程,在线程激活之后,通过对 fetch 事件,能够对各类收获的财富拓展调节缓存等。

    其三步:创立三个 瑟维斯 Worker

    Service Worker 是二个可编制程序的服务器代理,它能够阻挡或然响应互联网请求。Service 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. 使用Service Worker

    瑟维斯Worker的应用套路是先注册一个Worker,然后后台就可以运转一条线程,能够在那条线程运营的时候去加载一些能源缓存起来,然后监听fetch事件,在这么些事件里拦截页面包车型大巴伸手,先看下缓存里有未有,假使有直接重回,不然平时加载。只怕是一初步不缓存,每一种能源请求后再拷贝一份缓存起来,然后下贰次呼吁的时候缓存里就有了。

    serviceworker的缓存功能

    安装时,serviceworker将我们钦赐的静态财富开始展览缓存,你也许会问,假设是实时的动态内容怎么做,大家极小概预先将具有的公文财富提前线指挥部定好,让serviceworker缓存。那就要涉及serviceworker的遏止HTTP请求响应的性状了。

    serviceworker拦截http请求,首先去反省请求的能源在缓存中是否存在,借使存在,则一贯从缓存中调用,而且固然是无互连网状态下,serviceworker也能调用缓存中的能源。假使缓存中从未请求的财富,则通过互联网去服务器上搜索,而且在找到并响应时,serviceworker会将其存入缓存中,以备后一次再请求时一贯从缓存中调用。

    新葡亰496net 8serviceworker缓存流程

    serviceworker文件中fetch事件代码如下:

    self.addEventListener('fetch', function {

    event.respondWith(

    caches.match(event.request).then(function{

    if{

    return response;

    }

    var requestToCache = event.request.clone();

    return fetch(requestToCache).then(

    function{

    if(!response || response.status !== 200){

    return response;

    }

    var responseToCache = response.clone();

    caches.open(CACHE_VERSION)

    .then(function{

    cache.put(requestToCache, responseToCache);

    });

    return response;

    }

    );

    })

    );

    });

    在网址前台通过浏览器开垦者工具,大家能够看下从缓存中调用资源的作用:

    新葡亰496net 9serviceworker缓存调用新葡亰496net 10缓存文件

    离线网页应用,入门教程。本身这里操作演示用的是谷歌浏览器,上面是各大浏览器对于serviceworker的支撑景况:

    新葡亰496net 11serviceworker浏览器协助情状

    离线缓存

      下边来通过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'
            },
          ]
        })
      ]
    

     

    显然什么财富供给被缓存?

    那么在起初使用 service worker 此前,首先供给驾驭什么能源需求被缓存?

    Install事件

    该事件将要运用设置实现后触发。我们一般在此地运用Cache API缓存一些必需的公文。

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

    1. 缓存名称(CACHE)以及版本(version)。应用能够有多个缓存存款和储蓄,不过在使用时只会动用其中二个缓存存款和储蓄。每当缓存存款和储蓄有生成时,新的版本号将会钦赐到缓存存款和储蓄中。新的缓存存款和储蓄将会作为当下的缓存存储,以前的缓存存款和储蓄将会被作废。
    2. 叁个离线的页面地址(offlineUCR-VL):当用户访问了事先从未访问过的地址时,该页面将会来得。
    3. 一个包含了富有必须文件的数组,包括保持页面平常机能的CSS和JavaScript。在本示例中,作者还加多了主页和logo。当有不相同的UGL450L指向同一个能源时,你也得以将那个U昂CoraL分别写到那些数组中。offlineU奥迪Q5L将会进入到这一个数组中。
    4. 咱俩也得以将部分非须要的缓存文件(installFilesDesirable)。那个文件在设置进程上将会被下载,但假如下载失利,不会接触安装退步。

    // 配置文件 const version = '1.0.0', CACHE = version '::PWAsite', offlineU奥迪Q7L = '/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())
      );
    });

    (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就能够接管页面包车型客车有所财富了。

    加上到主屏

    PWA辅助将web应用在主屏桌面上增添一个神速格局,以福利用户火速访问,同一时候进步web应用重复访问的概率。你可能会说,未来运动端上的浏览器功能列表里一般都提供了“增加到桌面”的效果呀,可是PWA与浏览器自带的丰硕到桌面包车型地铁贯彻格局差异。

    PWA没有要求用户刻意去成效列表中使用这几个功能按键,而是在用户访问web应用时,直接在分界面中唤醒三个增加到主屏桌面包车型的士横幅,从web应用角度来讲,那实在就是高歌猛进与低沉的区分。

    PWA完结该意义特别轻便,只供给四个manifest.json文件,文件中用户能够自定义应用的运营页面、模板颜色、图标等剧情。下边是自个儿的manifest.json文件设置,我们可作参照他事他说加以考查:

    {

    "short_name": "蝉知能源站",

    "name": "蝉知财富站",

    "icons": [

    {

    "src": "chanzhiicon.png",

    "type": "image/png",

    "sizes": "48x48"

    },

    {

    "src": "192.png",

    "type": "image/png",

    "sizes": "192x192"

    },

    {

    "src": "512.png",

    "type": "image/png",

    "sizes": "512x512"

    },

    {

    "src": "144.png",

    "type": "image/png",

    "sizes": "144x144"

    }

    ],

    "start_url": "/index.html",

    "display": "standalone",

    "background_color": "#2196F3",

    "orientation": "landscape",

    "theme_color": "#2196F3"

    }

    亟待提示的是,近日运动端IOS系统的支撑并糟糕,安卓手提式有线电话机上须动用57版本以上的谷歌(Google)浏览器能够支撑该意义,下边是笔者在安卓手机上的操作演示:

    新葡亰496net 12增多到主屏

    丰硕到显示器

      没人愿意神经过敏地在移动设备键盘上输入长长的网站。通过抬高到荧屏的功能,用户能够像从使用商号安装本机应用那样,选取为其设施加上二个连忙链接,并且经过要顺遂得多

    【配置项表明】

      使用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"
        }
      ]
    }
    

    【注意事项】

      1、在 Chrome 上首推使用 short_name,固然存在,则先行于 name 字段使用

      2、Logo的类别最棒是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')))
    

     

    缓存静态能源

    先是是像 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文件。其余请求将会回来 offlineU昂CoraL 页面。

    Chrome开拓者工具中的ServiceWorker部分提供了有关当前页面worker的消息。其中会来得worker中发出的谬误,还足以强制刷新,也能够让浏览器进入离线方式。

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

    (2)Service Worker安装和激活

    挂号完事后,ServiceWorker就会开始展览设置,那个时候会触发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调节台所示:

    新葡亰496net 13

    ServiceWorker的API基本上都以回到Promise对象制止堵塞,所以要用Promise的写法。上边在安装ServiceWorker的时候就把首页的呼吁给缓存起来了。在ServiceWorker的运维条件之中它有多少个caches的全局对象,这几个是缓存的输入,还应该有八个常用的clients的大局对象,一个client对应一个标签页。

    在ServiceWorker里面能够选拔fetch等API,它和DOM是与世隔膜的,未有windows/document对象,不恐怕间接操作DOM,不可能间接和页面交互,在ServiceWorker里面不可能获知当前页面打开了、当前页面的url是什么样,因为二个ServiceWorker管理当前张开的多少个标签页,能够因此clients知道全数页面包车型大巴url。还会有能够通过postMessage的章程和主页面相互传送新闻和数量,进而做些调控。

    install完今后,就能够触发瑟维斯 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");
    });

    ServiceWorker激活之后就可知监听fetch事件了,大家期望每获得多个财富就把它缓存起来,就不用像上一篇涉嫌的Manifest须要先生成一个列表。

    你大概会问,当自家刷新页面包车型地铁时候不是又再度挂号安装和激活了三个ServiceWorker?就算又调了叁回注册,但并不会重复登记,它发掘”sw-3.js”这一个曾经登记了,就不会再登记了,进而不会触发install和active事件,因为脚下ServiceWorker已经是active状态了。当必要更新瑟维斯Worker时,如形成”sw-4.js”,也许改变sw-3.js的公文内容,就能够重新挂号,新的ServiceWorker会先install然后进入waiting状态,等到重启浏览器时,老的ServiceWorker就能够被替换掉,新的ServiceWorker进入active状态,假诺不想等到再也启航浏览器能够像上边同样在install里面调skipWaiting:

    JavaScript

    this.skipWaiting();

    1
    this.skipWaiting();

    小结

    PWA工夫正被周边企业及开垦者们关注,尽管日前各配备的支撑同盟有待增长,但这个都正在不断的立异进步。作者也信任在不久的未来,PWA本通晓日趋广泛推广,为分布集团和用户带来便利。本文和豪门一块享受了PWA的相干本领与实例操作,前面还有大概会就新闻推送、数据同步等效果做进一步探寻沟通。若是大家在攻读PWA的长河中相见其余标题,应接一齐谈谈沟通。

    缓存页面

    缓存页面显明是少不了的,那是最基本的有的,当您在离线的情况下加载页面会之后现身:

    新葡亰496net 14

    究其原因正是因为您在离线状态下无法加载页面,以后有了 service worker,固然你在没网络的意况下,也能够加载以前缓存好的页面了。

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

    离线页面能够是静态的HTML,一般用于提示用户日前呼吁的页面暂且不只怕利用。不过,大家可以提供部分足以阅读的页面链接。

    Cache API能够在main.js中选拔。不过,该API使用Promise,在不援救Promise的浏览器中会退步,全数的JavaScript实践会为此遭受震慑。为了防止这种情况,在拜访/js/offlinepage.js的时候我们增加了一段代码来检查当前是否在离线情况中:

    /js/offlinepage.js 中以版本号为名称保存了不久前的缓存,获取具备U福睿斯L,删除不是页面包车型大巴U驭胜L,将那几个U福睿斯L排序然后将有所缓存的U奥德赛L浮以后页面上:

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

    (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,假若跨域的能源援救CO奥迪Q5S,那么能够把request的mod改成cors。要是请求战败了,如404要么是晚点等等的,那么也直接回到response让主页面管理,不然的话表达加载成功,把那么些response克隆三个置于cache里面,然后再回到response给主页面线程。注意能放缓存里的财富一般只可以是GET,通过POST获取的是无法缓存的,所以要做个判定(当然你也足以手动把request对象的method改成get),还应该有把一些个体不指望缓存的能源也做个剖断。

    那样一旦用户打开过贰回页面,ServiceWorker就安装好了,他刷新页面恐怕张开第三个页面包车型大巴时候就可知把请求的财富一一做缓存,包罗图形、CSS、JS等,只要缓存里有了随意用户在线大概离线都能够健康访问。那样大家当然会有三个题材,那个缓存空间到底有多大?上一篇大家关系Manifest也毕竟地点存储,PC端的Chrome是5Mb,其实这些说法在新本子的Chrome已经不可相信了,在Chrome 61版本能够观察地面存储的上空和接纳状态:

    新葡亰496net 15

    在那之中Cache Storage是指ServiceWorker和Manifest占用的半空中尺寸和,上海体育场合能够见到总的空间大小是20GB,差不离是unlimited,所以基本上不用思念缓存会远远不足用。

    缓存后端接口数据

    缓存接口数据是要求的,但也不是必须透过 service worker 来兑现,前端存放数据的地点有那多少个,比方通过 localstorage,indexeddb 来开始展览仓库储存。这里作者也是通过 service worker 来落到实处缓存接口数据的,假使想通过此外方法来促成,只须求小心好 url 路线与数码对应的照耀关系就能够。

    开辟者工具

    Chrome浏览器提供了一多级的工具来帮衬您来调度ServiceWorker,日志也会一贯显示在调节台上。

    新葡亰496net,您最棒使用匿超级模特式来开始展览付出工作,那样能够裁撤缓存对开采的搅拌。

    最后,Chrome的Lighthouse扩大也足以为你的渐进式Web应用提供部分更上一层楼消息。

    (4)cache html

    上边第(3)步把图纸、js、css缓存起来了,然而假设把页面html也缓存了,比如把首页缓存了,就能够有三个窘迫的题目——ServiceWorker是在页面注册的,可是今后到手页面包车型客车时候是从缓存取的,每一次没什么不一致的,所以就导致力不从心立异ServiceWorker,如产生sw-5.js,可是PWA又供给大家能缓存页面html。那如何做吧?谷歌的开拓者文书档案它只是提到会存在那几个主题素材,但并不曾认证怎么解决那个标题。那一个的难题的缓慢解决就要求大家要有一个建制能分晓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 UTiggoL(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); pageUpdateTime[pageName] = content.updateTime; } }); }); }, delCache: function (url) { caches.open(CACHE_NAME).then(cache => { console.log("delete cache "

    • url); cache.delete(url, {ignoreVary: true}); }); } };
    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: []}}

    里头首要有叁个updateTime的字段,假如地点内部存款和储蓄器没有那个页面包车型客车update提姆e的多寡大概是和最新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 "

    • 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
    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。用户公布完谈论后会触发刷新页面,刷新的时候缓存已经被删了就能够再一次去央浼了。

    如此那般就一举成功了实时更新的主题素材。

    缓存战略

    一览无遗了何等财富须要被缓存后,接下去就要研究缓存战略了。

    渐进式Web应用的要义

    渐进式Web应用是一种新的本领,所以利用的时候分明要小心。相当于说,渐进式Web应用可以让您的网址在多少个钟头内获取改正,并且在不协理渐进式Web应用的浏览器上也不会潜移默化网址的显得。

    但是我们供给考虑以下几点:

    4. Http/Manifest/Service Worker三种cache的关系

    要缓存能够使用三种花招,使用Http Cache设置缓存时间,也能够用Manifest的Application Cache,还是能够用ServiceWorker缓存,假诺三者都用上了会怎么样呢?

    会以瑟维斯 Worker为事先,因为ServiceWorker把请求拦截了,它起始做管理,借使它缓存Curry一些话一贯再次来到,未有的话符合规律请求,就一定于尚未ServiceWorker了,那一年就到了Manifest层,Manifest缓存里要是有的话就取这些缓存,假设未有的话就一定于尚未Manifest了,于是就能够从Http缓存里取了,要是Http缓存里也平昔不就能够发请求去获得,服务端根据Http的etag或许Modified Time大概会回去304 Not Modified,不然不奇怪再次来到200和数量内容。那正是整三个赢得的进度。

    之所以假若既用了Manifest又用ServiceWorker的话应该会促成同一个财富存了五遍。不过足以让支持ServiceWorker的浏览器选用Service Worker,而不协助的施用Manifest.

    页面缓存战略

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

    1. 互连网优先的格局,即优先获得互连网上风行的资源。当互连网请求败北的时候,再去获取 service worker 里从前缓存的能源
    2. 当互联网加载成功之后,就更新 cache 中对应的缓存能源,保险后一次历次加载页面,都以上次作客的新颖财富
    3. 设若找不到 service worker 中 url 对应的能源的时候,则去得到 service worker 对应的 /index.html 暗中同意首页

    // sw.js self.add伊夫ntListener('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);
        }
      }
    }

    那样就有限支撑了用户率先次加载页面,立即离线访问站点后,就算不恐怕像第贰回同样可以服务端渲染数据,不过之后能经过取得页面,异步加载数据的措施创设离线应用。

    新葡亰496net 16

    用户率先次访问站点,假设在不刷新页面包车型地铁气象切换路由到任何页面,则会异步获取到的多寡,当后一次做客对应的路由的时候,则失败到异步获取数据。

    新葡亰496net 17

    当用户第一遍加载页面的时候,因为 service worker 已经决定了站点,已经有所了缓存页面包车型客车力量,之后在拜访的页面都将会被缓存恐怕更新缓存,当用户离线访问的的时候,也能访问到服务端渲染的页面了。

    新葡亰496net 18

    URL隐藏

    当你的行使正是二个单U哈弗L的应用程序时(举个例子游戏),小编建议您隐藏地址栏。除外的情形自己并不建议你隐藏地址栏。在Manifest中,display: minimal-ui 或者 display: browser对此多数状态的话足够用了。

    5. 运用Web App Manifest加多桌面入口

    在意这里说的是其余一个Manifest,那个Manifest是叁个json文件,用来放网址icon名称等新闻以便在桌面增加四个Logo,以及创建一种张开这几个网页就好像张开App同样的法力。下边一贯说的Manifest是被撇下的Application Cache的Manifest。

    以此Maifest.json文件能够这样写:

    JavaScript

    { "short_name": "人人FED", "name": "人人网FED,专注于前者技艺", "icons": [ { "src": "/html/app-manifest/logo_48.png", "type": "image/png", "sizes": "48x48" }, { "src": "/html/app-manifest/logo_96.png", "type": "image/png", "sizes": "96x96" }, { "src": "/html/app-manifest/logo_192.png", "type": "image/png", "sizes": "192x192" }, { "src": "/html/app-manifest/logo_512.png", "type": "image/png", "sizes": "512x512" } ], "start_url": "/?launcher=true", "display": "standalone", "background_color": "#287fc5", "theme_color": "#fff" }

    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
    {
      "short_name": "人人FED",
      "name": "人人网FED,专注于前端技术",
      "icons": [
        {
          "src": "/html/app-manifest/logo_48.png",
          "type": "image/png",
          "sizes": "48x48"
        },
        {
          "src": "/html/app-manifest/logo_96.png",
          "type": "image/png",
          "sizes": "96x96"
        },
        {
          "src": "/html/app-manifest/logo_192.png",
          "type": "image/png",
          "sizes": "192x192"
        },
        {
          "src": "/html/app-manifest/logo_512.png",
          "type": "image/png",
          "sizes": "512x512"
        }
      ],
      "start_url": "/?launcher=true",
      "display": "standalone",
      "background_color": "#287fc5",
      "theme_color": "#fff"
    }

    icon供给预备各类口径,最大要求512px * 512px的,那样Chrome会自动去挑选合适的图纸。若是把display改成standalone,从变化的Logo张开就能够像张开一个App一样,未有浏览器地址栏那个东西了。start_url钦定张开今后的入口链接。

    接下来增加二个link标签指向那么些manifest文件:

    JavaScript

    <link rel="manifest" href="/html/app-manifest/manifest.json">

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

    这么组合Service Worker缓存:
    新葡亰496net 19把start_url指向的页面用ServiceWorker缓存起来,那样当用户用Chrome浏览器张开那个网页的时候,Chrome就能够在底层弹二个唤起,询问用户是或不是把这么些网页增添到桌面,假诺点“增加”就能够扭转三个桌面Logo,从这一个Logo点进去就像展开八个App同样。感受如下:

    新葡亰496net 20

    正如为难的是Manifest方今唯有Chrome援助,并且只可以在安卓系统上应用,IOS的浏览器不可能增多三个桌面Logo,因为IOS未有开放这种API,可是自身的Safari却又是足以的。

    综上,本文介绍了怎么用Service Worker结合Manifest做二个PWA离线Web APP,首纵然用ServiceWorker调控缓存,由于是写JS,比较灵活,还足以与页面进行通讯,其余通过请求页面包车型大巴更新时间来判别是或不是须求立异html缓存。ServiceWorker的包容性不是专程好,不过前景相比光明,浏览器都在预备帮衬。现阶段得以结合offline cache的Manifest做离线应用。

    连锁阅读:

    1. 干什么要把网址进级到HTTPS
    2. 什么样把网址进级到http/2
    3. 自己是怎么样让网址用上HTML5 Manifest

    1 赞 1 收藏 评论

    新葡亰496net 21

    接口缓存计谋

    谈完页面缓存,再来说讲接口缓存,接口缓存就跟页面缓存很周边了,唯一的例外在于:页面第二遍加载的时候不断定有缓存,但是会有接口缓存的留存(因为伪造了 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 收藏 评论

    新葡亰496net 22

    毫无强缓存 sw.js

    用户每一趟访问页面包车型大巴时候都会去重新得到sw.js,依据文件内容跟在此以前的版本是否一致来剖断 service worker 是不是有创新。所以只要你对 sw.js 开启强缓存的话,就将陷入死循环,因为老是页面获得到的 sw.js 都是同一,那样就不可能进级你的 service worker。

    除此以外对 sw.js 开启强缓存也是一贯不要求的:

    1. 小编 sw.js 文件本人就比相当的小,浪费不了多少带宽,感觉浪费能够选拔协议缓存,但附加增支肩负
    2. sw.js 是在页面空闲的时候才去加载的,并不会潜移默化用户首屏渲染速度

    防止退换 sw 的 U帕杰罗L

    在 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: 应用名称,正是图标下边包车型客车显得名称
    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 文件是还是不是见效:

    新葡亰496net 23

    如此那般表明您的配备生效了,安卓机会自动识别你的配备文件,并询问用户是或不是丰盛。

    结尾

    讲到那基本上就完了,等随后 IOS 援助 PWA 的任何成效的时候,到时候作者也会相应地去奉行别的 PWA 的性状的。未来 IOS 11.3 也不过协助 PWA 中的 service worker 和 app manifest 的功效,不过相信在不久的今后,其它的效应也会相应获得援助,到时候相信 PWA 将会在运动端绽放异彩的。

    1 赞 收藏 评论

    新葡亰496net 24

    本文由新葡亰496net发布于新葡亰官网,转载请注明出处:离线网页应用,入门教程

    关键词:

上一篇:浅谈前端工程化,前端优化手段

下一篇:没有了