新浦京娱乐场官网-301net-新浦京娱乐www.301net
做最好的网站

这里不会从头开始介绍什么是 PWA

React 同构应用 PWA 进级指南

2018/05/25 · JavaScript · PWA, React

原著出处: 林东洲   

前言

近年在给自家的博客网址 PWA 晋级,顺便就记下下 React 同构应用在选取 PWA 时碰着的标题,这里不会从头开头介绍怎样是 PWA,若是你想上学 PWA 相关文化,能够看下下边我收藏的一对篇章:

  • 您的率先个 Progressive Web App
  • 【ServiceWorker】生命周期那个事儿
  • 【PWA学习与实践】(1) 2018,开首你的PWA学习之旅
  • Progressive Web Apps (PWA) 中文版

PWA 特性

PWA 不是单独的某项本事,而是一批手艺的集中,比如:ServiceWorker,manifest 加多到桌面,push、notification api 等。

而就在前段时间时光,IOS 11.3 刚刚辅助 瑟维斯 worker 和相近 manifest 增加到桌面包车型大巴风味,所以此次 PWA 改动首要依然落成这两部分机能,至于别的的性状,等 iphone 援助了再升格吗。

Service Worker

service worker 以作者之见,类似于叁个跑在浏览器后台的线程,页面第一次加载的时候会加载这一个线程,在线程激活之后,通过对 fetch 事件,能够对各种收获的财富进行调整缓存等。

鲜明什么能源要求被缓存?

这便是说在起初采纳 service worker 以前,首先要求通晓如何财富须要被缓存?

缓存静态财富

先是是像 CSS、JS 这个静态财富,因为本人的博客里援用的剧本样式都以经过 hash 做悠久化缓存,类似于:main.ac62dexx.js 那样,然后张开强缓存,那样后一次客商后一次再拜见小编的网址的时候就不要再行乞求财富。直接从浏览器缓存中读取。对于那部分能源,service worker 没供给再去管理,间接放行让它去读取浏览器缓存就可以。

作者觉着一旦您的站点加载静态财富的时候笔者未有开启强缓存,並且你只想透过前端去落成缓存,而没有要求后端在参加进行调解,那能够利用 service worker 来缓存静态能源,不然就有一些画蛇添足了。

缓存页面

缓存页面明显是不能缺少的,这是最基本的有些,当您在离线的事态下加载页面会之后出现:

图片 1

究其原因正是因为你在离线状态下不可能加载页面,以往有了 service worker,固然你在没网络的动静下,也能够加载从前缓存好的页面了。

缓存后端接口数据

缓存接口数据是索要的,但亦非必得透过 service worker 来贯彻,前端寄放数据的地点有数不完,例如通过 localstorage,indexeddb 来扩充仓储。这里本身也是透过 service worker 来完结缓存接口数据的,要是想透过其余方法来兑现,只必要注意好 url 路线与数码对应的投射关系就可以。

缓存战术

显明了怎么财富须要被缓存后,接下去将在研讨缓存战略了。

页面缓存战略

因为是 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);
    }
  }
}

如此就确认保障了客商率先次加载页面,即刻离线访谈站点后,纵然不能够像第一回一样能够服务端渲染数据,可是随后能经过获取页面,异步加载数据的章程创设离线应用。

图片 2

客商率先次访谈站点,借使在不刷新页面包车型客车图景切换路由到任何页面,则会异步获取到的数量,当下一次拜候对应的路由的时候,则败北到异步获取数据。

图片 3

当客户第壹遍加载页面包车型客车时候,因为 service worker 已经调整了站点,已经具备了缓存页面包车型客车技能,之后在拜候的页面都将会被缓存恐怕更新缓存,当客户离线访问的的时候,也能访谈到服务端渲染的页面了。

图片 4

接口缓存战略

谈完页面缓存,再来说讲接口缓存,接口缓存就跟页面缓存很左近了,独一的不如在于:页面第一次加载的时候不自然有缓存,可是会有接口缓存的存在(因为伪造了 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 离线缓存应用的作用了,不过还恐怕有依然存在一些难点:

急速激活 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,原因不详,有读者理解的话能够调换下。

现行反革命业你首先次加载页面,跳转路由,立刻离线访问的页面,也能够安枕而卧地加载页面了。

并不是强缓存 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: 应用名称,就是Logo下边包车型地铁来得名称
  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": "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 文件是还是不是见效:

图片 5

那般表达您的布置生效了,安卓机缘自动识别你的计划文件,并领悟顾客是不是充裕。

结尾

讲到那基本上就完了,等以往 IOS 支持 PWA 的另外作用的时候,到时候小编也会相应地去实行别的 PWA 的性状的。今后 IOS 11.3 也唯有协助 PWA 中的 service worker 和 app manifest 的遵从,但是相信在不久的后天,另外的成效也会相应获得帮忙,到时候相信 PWA 将会在运动端盛放异彩的。

1 赞 收藏 评论

图片 6

本文由新浦京娱乐场官网-301net-新浦京娱乐www.301net发布于301net网站建设,转载请注明出处:这里不会从头开始介绍什么是 PWA

您可能还会对下面的文章感兴趣: