【必发88】升级指南,入门教程

by admin on 2019年1月31日

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,读者可以打开这几个网站感受一下,断网也是能健康打开。

前方的话

  渐进式网络应用 ( 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网站在谷歌上的排名也会更靠前。

由于Chrome浏览器会默许将localhost以及127.x.x.x地点视为测试地方,所以在本示例中你并不须求开启HTTPS。别的,出于调试目的,您可以在启动Chrome浏览器的时候使用以下参数来关闭其对网站HTTPS的检查:

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

1. 什么是Service Worker

Service Worker是谷歌(Google)发起的兑现PWA(Progressive Web
App)的一个要害角色,PWA是为了然决传统Web APP的弱项:

(1)没有桌面入口

(2)不可以离线使用

(3)没有Push推送

那Service Worker的具体表现是何等的吧?如下图所示:

必发88 1

ServiceWorker是在后台启动的一条服务Worker线程,上图我开了三个标签页,所以体现了几个Client,可是不管开多少个页面都唯有一个Worker在负责管理。那几个Worker的做事是把一些资源缓存起来,然后拦截页面的央浼,先看下缓存库里有没有,如果有些话就从缓存里取,响应200,反之没有的话就走正规的伸手。具体来说,ServiceWorker结合Web App Manifest能完毕以下工作(那也是PWA的检测标准):

必发88 2

概括可以离线使用、断网时重返200、能唤醒用户把网站添加一个图标到桌面上等。

意义演示

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

【添加到桌面】

必发88 3

【离线缓存】

   由于手机录屏选拔不可能展开离线录制,改由模拟器模拟离线效果

必发88 4

 

PWA 特性

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

而就在近年来时间,IOS 11.3 刚刚协理 Service worker 和好像 manifest
添加到桌面的特点,所以本次 PWA
改造首要仍旧贯彻那两局地机能,至于其他的特色,等 iphone 辅助了再升级吗。

第二步:创设一个应用程序清单(Manifest)

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

  • 应用程序名
  • 描述
  • 有着图片(包蕴主显示器图标,启动屏幕页面和用的图样或者网页上用的图样)

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

程序清单是一个坐落您使用根目录的JSON文件。该JSON文件再次来到时务必抬高Content-Type: application/manifest+json 或者 Content-Type: application/jsonHTTP头消息。程序清单的公文名不限,在本文的演示代码中为manifest.json

{ “name” : “PWA Website”, “short_【必发88】升级指南,入门教程。name” : “PWA”, “description” : “An
example PWA website”, “start_url” : “/”, “display” : “standalone”,
“orientation” : “any”, “background_color” : “#ACE”, “theme_color” :
“#ACE”, “icons”: [ { “src” : “/images/logo/logo072.png”, “sizes” :
“72×72”, “type” : “image/png” }, { “src” : “/images/logo/logo152.png”,
“sizes” : “152×152”, “type” : “image/png” }, { “src” :
“/images/logo/logo192.png”, “sizes” : “192×192”, “type” : “image/png” },
{ “src” : “/images/logo/logo256.png”, “sizes” : “256×256”, “type” :
“image/png” }, { “src” : “/images/logo/logo512.png”, “sizes” :
“512×512”, “type” : “image/png” } ] }

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

程序清单文件建立完之后,你须求在每个页面上引用该公文:

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

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

以下属性在程序清单中时常应用,介绍表明如下:

  • name: 用户观望的使用名称
  • short_name: 应用短名称。当显示应用名称的地点不够时,将利用该名称。
  • description: 运用描述。
  • start_url: 动用初叶路径,相对路径,默许为/。
  • scope: URL范围。比如:如若你将“/app/”设置为URL范围时,那个应用就会一向在那一个目录中。
  • 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: 一个分包所有图片的数组。该数组中各种元素包括了图片的URL,大小和品种。

2. Service Worker的支撑情状

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

必发88 5

Safari和Edge也在备选辅助瑟维斯 Worker,由于ServiceWorker是谷歌基本的一项正式,对于生态相比较封闭的Safari来说也是迫于时局开头准备支持了,在Safari
TP版本,能够观望:

必发88 6

在尝试作用(Experimental Features)里早就有ServiceWorker的菜单项了,只是就算打开也是不可以用,会唤醒您还未曾落实:

必发88 7

但不管什么,至少表明Safari已经准备匡助ServiceWorker了。其余还是可以看来在二零一九年前年11月披露的Safari
11.0.1本子现已支撑WebRTC了,所以Safari仍旧一个前进的子女。

Edge也准备协理,所以Service Worker的前景分外美好。

概述

【必发88】升级指南,入门教程。  PWA 的要害特征包罗下面三点:

  1、可信 – 固然在不安静的网络环境下,也能刹那间加载并显示

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

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

  主要功能包蕴站点可添加至主屏幕、全屏格局运行、支持离线缓存、新闻推送等

【PRPL模式】

  “PRPL”(读作 “purple”)是 谷歌(Google) 的工程师提出的一种 web
应用架构情势,它目的在于利用现代 web 平台的新技巧以大幅优化移动 web
的性质与体会,对怎样协会与统筹高质量的 PWA 系统提供了一种高层次的肤浅

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

  1、PUSH/PRELOAD,推送/预加载开始 URL 路由所需的第一资源

  2、RENDER,渲染开头路由,尽快让使用可被交互

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

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

【Service workers】

  Service Workers 是谷歌 chrome 团队建议并大力推广的一项 web 技术。在
2015 年,它出席到 W3C 标准,进入草案阶段

  PWA 的关键在于 Service Workers 。就其大旨来说,Service Workers
只是后台运行的 worker 脚本。它们是用 JavaScript
编写的,只需短短几行代码,它们便可使开发者可以阻挡网络请求,处理推送新闻并推行许多其余任务

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

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

  Service Worker 的办事体制大约如下:用户访问一个持有 Service Worker
的页面,浏览器就会下载这几个 Service Worker
并尝试安装、激活。一旦激活,Service Worker
就到后台开头工作。接下来用户访问这么些页面或者每隔一个时刻浏览器都会下载那一个Service Worker,固然监测到 Service Worker 有立异,就会重新安装并激活新的
Service Worker,同时 revoke 掉旧的 Service Worker,那就是 SW 的生命周期

  因为 Service Worker 有着方今的权位接触数据,由此 Service Worker
只可以被装置在 HTTPS 加密的页面中,即使无形当中提升了 PWA
的三昧,可是也是为着安全做考虑

 

Service Worker

service worker
在我看来,类似于一个跑在浏览器后台的线程,页面第四回加载的时候会加载这一个线程,在线程激活之后,通过对
fetch 事件,可以对每个收获的资源开展支配缓存等。

其三步:创造一个 Service 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事件,在那一个事件里拦截页面的央浼,先看下缓存里有没有,假设有直接重临,否则正常加载。或者是一起头不缓存,每个资源请求后再拷贝一份缓存起来,然后下四回呼吁的时候缓存里就有了。

离线缓存

  上面来经过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. 一个离线的页面地址(offlineURL):当用户访问了事先没有访问过的地点时,该页面将会来得。
  3. 一个富含了装有必须文件的数组,包涵保持页面正常机能的CSS和JavaScript。在本示例中,我还添加了主页和logo。当有两样的URL指向同一个资源时,你也足以将那些URL分别写到那么些数组中。offlineURL将会参与到这几个数组中。
  4. 大家也得以将部分非需要的缓存文件(installFilesDesirable)。这个文件在设置进度元帅会被下载,但要是下载战败,不会接触安装败北。

// 配置文件 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’ ];

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就能接管页面的有着资源了。

丰裕到显示器

  没人愿意多此一举地在活动装备键盘上输入长长的网址。通过丰盛到屏幕的功用,用户可以像从利用企业安装本机应用那样,采取为其设备拉长一个火速链接,并且经过要顺遂得多

【配置项表明】

  使用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、图标的门类最好是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文件。其余请求将会回去
offlineURL 页面。

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控制台所示:

必发88 8

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

瑟维斯Worker激活之后就可知监听fetch事件了,我们愿意每得到一个资源就把它缓存起来,就不用像上一篇涉嫌的Manifest须求先生成一个列表。

您或许会问,当自家刷新页面的时候不是又再一次注册安装和激活了一个ServiceWorker?就算又调了一回注册,但并不会再次注册,它发现”sw-3.js”那一个曾经登记了,就不会再登记了,进而不会触发install和active事件,因为如今ServiceWorker已经是active状态了。当需求更新ServiceWorker时,如变成”sw-4.js”,或者转移sw-3.js的文本内容,就会再一次登记,新的ServiceWorker会先install然后进入waiting状态,等到重启浏览器时,老的瑟维斯Worker就会被沟通掉,新的ServiceWorker进入active状态,假如不想等到再也开动浏览器可以像上边一样在install里面调skipWaiting:

JavaScript

this.skipWaiting();

1
this.skipWaiting();

缓存页面

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

必发88 9

究其原因就是因为您在离线状态下不可能加载页面,现在有了 service
worker,固然你在没互连网的图景下,也得以加载此前缓存好的页面了。

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

离线页面可以是静态的HTML,一般用于提示用户眼前央浼的页面暂时不可能运用。可是,大家可以提供部分方可翻阅的页面链接。

Cache
API可以在main.js中使用。可是,该API使用Promise,在不扶助Promise的浏览器中会战败,所有的JavaScript执行会为此受到震慑。为了避免那种状态,在造访/js/offlinepage.js的时候大家添加了一段代码来检查当前是不是在离线环境中:

/js/offlinepage.js 中以版本号为名称保存了近期的缓存,获取具有URL,删除不是页面的URL,将这么些URL排序然后将具有缓存的URL体现在页面上:

// 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,若是跨域的资源襄助CORS,那么可以把request的mod改成cors。若是请求败北了,如404要么是晚点等等的,那么也平素回到response让主页面处理,否则的话表达加载成功,把这几个response克隆一个内置cache里面,然后再回来response给主页面线程。注意能减缓存里的资源一般只好是GET,通过POST获取的是无法缓存的,所以要做个判断(当然你也能够手动把request对象的method改成get),还有把一部分私家不愿意缓存的资源也做个判断。

必发88,如此只要用户打开过五遍页面,ServiceWorker就设置好了,他刷新页面或者打开第三个页面的时候就可以把请求的资源一一做缓存,包罗图形、CSS、JS等,只要缓存里有了随便用户在线或者离线都能够健康访问。那样大家本来会有一个题材,这么些缓存空间到底有多大?上一篇大家关系Manifest也算是地方存储,PC端的Chrome是5Mb,其实这几个说法在新本子的Chrome已经不确切了,在Chrome
61本子可以看到地方存储的空间和使用状态:

必发88 10

其间Cache Storage是指ServiceWorker和Manifest占用的上空尺寸和,上图可以看来总的空间大小是20GB,大致是unlimited,所以基本上不用顾虑缓存会不够用。

缓存后端接口数据

缓存接口数据是急需的,但也不是必须经过 service worker
来促成,前端存放数据的地点有那个,比如通过 localstorage,indexeddb
来进行仓储。那里自己也是通过 service worker
来贯彻缓存接口数据的,要是想透过此外方法来落实,只要求小心好 url
路径与数码对应的投射关系即可。

开发者工具

Chrome浏览器提供了一多重的工具来协理你来调节ServiceWorker,日志也会一直显示在控制台上。

你最好利用匿超级模特式来展费用付工作,这样可以解除缓存对开发的纷扰。

最后,Chrome的Lighthouse扩大也得以为您的渐进式Web应用提供部分勘误音信。

(4)cache html

地点第(3)步把图片、js、css缓存起来了,不过要是把页面html也缓存了,例如把首页缓存了,就会有一个狼狈的题材——ServiceWorker是在页面注册的,可是现在到手页面的时候是从缓存取的,每一次都是一样的,所以就招致不能创新瑟维斯Worker,如变成sw-5.js,不过PWA又必要大家能缓存页面html。那怎么办呢?谷歌(Google)的开发者文档它只是提到会存在那个难题,但并从未声明怎么解决这些题材。这一个的题材的解决就须求大家要有一个编制能精晓html更新了,从而把缓存里的html给替换掉。

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

JavaScript

this.add伊夫ntListener(“fetch”, function(event) { event.respondWith(
caches.match(event.request).then(response => { // cache hit if
(response) { //如若取的是html,则看发个请求看html是还是不是更新了 if
(response.headers.get(“Content-Type”).indexOf(“text/html”) >= 0) {
console.log(“update html”); let url = new URL(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.update提姆e) {
console.log(“update page html”); // 假设有立异则另行赢得html
util.fetchPut(htmlRequest); pageUpdate提姆e[pageName] =
content.updateTime; } }); }); }, delCache: function (url) {
caches.open(CACHE_NAME).then(cache => { console.log(“delete cache “

  • 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: []}}

内部首要有一个update提姆e的字段,借使地点内存没有那些页面的updateTime的数量仍然是和最新update提姆e分化,则再一次去取得
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缓存,若是三者都用上了会什么啊?

会以Service Worker为事先,因为ServiceWorker把请求拦截了,它开始做处理,倘若它缓存库里部分话一贯重回,没有的话正常请求,就相当于尚未ServiceWorker了,那个时候就到了Manifest层,Manifest缓存里要是部分话就取这么些缓存,倘使没有的话就相当于尚未Manifest了,于是就会从Http缓存里取了,借使Http缓存里也尚无就会发请求去取得,服务端依据Http的etag或者Modified
提姆e可能会回来304 Not
Modified,否则正常重回200和数据内容。那就是整一个赢得的进度。

故而假如既用了Manifest又用瑟维斯Worker的话应该会招致同一个资源存了三遍。不过可以让协理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);
    }
  }
}

如此就确保了用户率先次加载页面,马上离线访问站点后,即便不可能像第三次一样可以服务端渲染数据,不过之后能由此获取页面,异步加载数据的不二法门创设离线应用。

必发88 11

用户率先次访问站点,要是在不刷新页面的图景切换路由到别的页面,则会异步获取到的数量,当下次访问对应的路由的时候,则战败到异步获取数据。

必发88 12

当用户第二次加载页面的时候,因为 service worker
已经决定了站点,已经颇具了缓存页面的力量,之后在做客的页面都将会被缓存或者更新缓存,当用户离线访问的的时候,也能访问到服务端渲染的页面了。

必发88 13

URL隐藏

当您的采用就是一个单URL的应用程序时(比如游戏),我提出你隐藏地址栏。除此之外的处境我并不提出你隐藏地址栏。在Manifest中,display: minimal-ui 或者 display: browser对于多数动静来说丰富用了。

5. 用到Web App Manifest添加桌面入口

小心那里说的是其它一个Manifest,这么些Manifest是一个json文件,用来放网站icon名称等音信以便在桌面添加一个图标,以及创造一种打开这一个网页就好像打开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”: “48×48” }, { “src”:
“/html/app-manifest/logo_96.png”, “type”: “image/png”, “sizes”: “96×96”
}, { “src”: “/html/app-manifest/logo_192.png”, “type”: “image/png”,
“sizes”: “192×192” }, { “src”: “/html/app-manifest/logo_512.png”,
“type”: “image/png”, “sizes”: “512×512” } ], “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": "48×48"
    },
    {
      "src": "/html/app-manifest/logo_96.png",
      "type": "image/png",
      "sizes": "96×96"
    },
    {
      "src": "/html/app-manifest/logo_192.png",
      "type": "image/png",
      "sizes": "192×192"
    },
    {
      "src": "/html/app-manifest/logo_512.png",
      "type": "image/png",
      "sizes": "512×512"
    }
  ],
  "start_url": "/?launcher=true",
  "display": "standalone",
  "background_color": "#287fc5",
  "theme_color": "#fff"
}

icon需求未雨绸缪各类尺度,最大必要512px *
512px的,那样Chrome会自动去选取合适的图纸。借使把display改成standalone,从转变的图标打开就会像打开一个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缓存:
必发88 14把start_url指向的页面用瑟维斯Worker缓存起来,那样当用户用Chrome浏览器打开那些网页的时候,Chrome就会在底部弹一个唤起,询问用户是或不是把那些网页添加到桌面,若是点“添加”就会转移一个桌面图标,从那个图标点进去似乎打开一个App一样。感受如下:

必发88 15

正如为难的是Manifest方今唯有Chrome辅助,并且只可以在安卓系统上应用,IOS的浏览器不能添加一个桌面图标,因为IOS没有开放那种API,可是自己的Safari却又是足以的。

综上,本文介绍了怎么用Service Worker结合Manifest做一个PWA离线Web
APP,紧即使用瑟维斯Worker控制缓存,由于是写JS,比较灵敏,还足以与页面举行通讯,其它通过请求页面的更新时间来判定是或不是须要立异html缓存。瑟维斯Worker的兼容性不是特地好,然而前景比较光明,浏览器都在预备帮衬。现阶段能够组成offline
cache的Manifest做离线应用。

连锁阅读:

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

1 赞 1 收藏
评论

必发88 16

接口缓存策略

谈完页面缓存,再来讲讲接口缓存,接口缓存就跟页面缓存很相近了,唯一的分化在于:页面首次加载的时候不肯定有缓存,可是会有接口缓存的留存(因为伪造了
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 收藏
评论

必发88 17

永不强缓存 sw.js

用户每一回访问页面的时候都会去重新得到sw.js,按照文件内容跟此前的版本是或不是一律来判断 service worker
是不是有立异。所以只要您对 sw.js
开启强缓存的话,就将沦为死循环,因为老是页面获得到的 sw.js
都是一致,那样就不可能升高你的 service worker。

其余对 sw.js 开启强缓存也是绝非须求的:

  1. 我 sw.js
    文件本身就很小,浪费不了多少带宽,觉得浪费可以运用协议缓存,但附加增加开支负担
  2. sw.js 是在页面空闲的时候才去加载的,并不会潜移默化用户首屏渲染速度

幸免改变 sw 的 URL

在 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: 设置图标,插件会活动帮你转移不相同 size
    的图样,但是图片大小必须超出最大 sizes
  6. ios: 设置在 safari 中什么去添加桌面应用

设置完事后,webpack 会在创设进度中变化对应的 manifest 文件,并在 html
文件中援引,下边就是生成 manifest 文件:

{ “icons”: [ { “src”:
“/icons/icon_512x512.79ddc5874efb8b481d9a3d06133b6213.png”, “sizes”:
“512×512”, “type”: “image/png” }, { “src”:
“/icons/icon_384x384.09826bd1a5d143e05062571f0e0e86e7.png”, “sizes”:
“384×384”, “type”: “image/png” }, { “src”:
“/icons/icon_256x256.d641a3644ce20c06855db39cfb2f7b40.png”, “sizes”:
“256×256”, “type”: “image/png” }, { “src”:
“/icons/icon_192x192.8f11e077242cccd9c42c0cbbecd5149c.png”, “sizes”:
“192×192”, “type”: “image/png” }, { “src”:
“/icons/icon_128x128.cc0714ab18fa6ee6de42ef3d5ca8fd09.png”, “sizes”:
“128×128”, “type”: “image/png” }, { “src”:
“/icons/icon_96x96.dbfccb1a5cef8093a77c079f761b2d63.png”, “sizes”:
“96×96”, “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": "512×512",
      "type": "image/png"
    },
    {
      "src": "/icons/icon_384x384.09826bd1a5d143e05062571f0e0e86e7.png",
      "sizes": "384×384",
      "type": "image/png"
    },
    {
      "src": "/icons/icon_256x256.d641a3644ce20c06855db39cfb2f7b40.png",
      "sizes": "256×256",
      "type": "image/png"
    },
    {
      "src": "/icons/icon_192x192.8f11e077242cccd9c42c0cbbecd5149c.png",
      "sizes": "192×192",
      "type": "image/png"
    },
    {
      "src": "/icons/icon_128x128.cc0714ab18fa6ee6de42ef3d5ca8fd09.png",
      "sizes": "128×128",
      "type": "image/png"
    },
    {
      "src": "/icons/icon_96x96.dbfccb1a5cef8093a77c079f761b2d63.png",
      "sizes": "96×96",
      "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 文件是还是不是见效:

必发88 18

诸如此类表明您的安顿生效了,安卓机遇自动识别你的安插文件,并领悟用户是或不是足够。

结尾

讲到那大致就完了,等将来 IOS 协助 PWA
的其余功用的时候,到时候我也会相应地去执行其余 PWA 的特性的。现在 IOS
11.3 也唯有帮忙 PWA 中的 service worker 和 app manifest
的功力,可是相信在不久的以后,别的的效劳也会相应得到协助,到时候相信 PWA
将会在移动端绽放异彩的。

1 赞 收藏
评论

必发88 19

发表评论

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

网站地图xml地图