菜单

React 同构应用 PWA 升级指南

2019年9月29日 - CSS/CSS3

React 同构应用 PWA 进级指南

2018/05/25 · JavaScript
· PWA,
React

初稿出处:
林东洲   

React/Redux塑造的同构Web应用

2018/07/30 · CSS ·
React,
Redux

原稿出处: 原 一成(Hara
Kazunari)
   译文出处:侯斌   

世家好,小编是原一成(@herablog),目前在CyberAgent首要担任前端开垦。

Ameblo(注: Ameba博客,Ameba
Blog,简称Ameblo)于2015年12月,将前端部分由原先的Java架构的应用,重构成为以node.js、React为底蕴的Web应用。那篇小说介绍了此次重构的缘起、目的、系统规划以及最后落得的结果。

新连串公布后,登时就有人注意到了这些变化。

 图片 1

twitter_msg.png

前言

近来在给自家的博客网址 PWA 晋级,顺便就记下下 React 同构应用在行使 PWA
时遭受的标题,这里不会从头伊始介绍怎么样是 PWA,借使您想学学 PWA
相关文化,能够看下上边笔者收藏的部分作品:

系统重构的导火线

二零零二年起,Ameblo成为了日本国内最大规模的博客服务。但是随着系统规模的增高,以及无数连锁职员不断充实种种模块、页面指点链接等,最后使得页面表现缓慢、对网页浏览量(PV)产生了丰硕沉痛的影响。并且页面表现速度方面,绝大多数是前面一个的标题,并不是是后端的标题。

听大人讲以上那几个标题,我们决定以增进页面表现速度为重中之重目的,对系统进行通透到底重构。与此同一时候后端系统也在举行重构,将未来的数额部分开展API化改动。此时正是三个将All-in-one的大型Java应用举办适度分割的绝佳良机。

PWA 特性

PWA 不是只是的某项技巧,而是一批本事的联谊,举个例子:ServiceWorker,manifest 增加到桌面,push、notification api 等。

而就在近年来时刻,IOS 11.3 刚刚协助 Service worker 和周边 manifest
加多到桌面包车型的士特色,所以此番 PWA
改变入眼仍然落成这两有个别成效,至于别的的风味,等 iphone 援助了再升格吗。

目标

此番系统重构确立了以下多少个目标。

Service Worker

service worker
在作者眼里,类似于三个跑在浏览器后台的线程,页面第一遍加载的时候会加载那么些线程,在线程激活之后,通过对
fetch 事件,可以对各个收获的能源进行支配缓存等。

页面表现速度的改正(总之越快越好)

用以测定顾客体验的指标有多数,我们以为在那之中对顾客最重大的指标正是页面表现速度。页面表现速度越快,目的内容就会越快达到,让职分在短期内做到。本次重构的指标是拼命三郎的保持博客小说、以及在Ameblo内所显示的五花八门的剧情的本来方式,在不破坏现存价值、体验的根基上,进步表现和页面行为的快慢。

公开场地什么能源须求被缓存?

那么在上马使用 service worker 在此以前,首先须求明白什么资源须求被缓存?

系统的当代化(搭乘生态系统)

往年的Web应用是将数据以HTML的款型重返,那一年并从未什么样难点。可是,随着剧情的增加,体验的丰硕化,以及设备的种种化,使得前端所占的比例进一步大。从前要开销贰个好的Web应用,假使要高质量,就势必不要将左右端分隔断。当年以这些必要支付的系统,在经验了10年过后,已经远远不或许适应当前的生态系统。

「跟上脚下生态系统」,以此来创设系统会拉动巨额的裨益。因为作为着力的生态系统,其付出特别活跃,每一日都会有大宗新的idea。由此新颖的本领和成效更易于被接受,同一时候落到实处高品质也更是便于。同期,那一个「新」对于身强力壮的技术新人也越加重要。仅知道旧法则旧本事的伯父对于三个地道的团队来讲是不曾前途的(自觉本身膝盖也中了一箭)。

缓存静态财富

首先是像 CSS、JS 那一个静态能源,因为自个儿的博客里援引的脚本样式都以透过 hash
做悠久化缓存,类似于:main.ac62dexx.js 那样,然后张开强缓存,那样下一次顾客后一次再走访小编的网址的时候就无须再行央浼财富。直接从浏览器缓存中读取。对于那有的财富,service
worker 没须求再去处理,直接放行让它去读取浏览器缓存就能够。

自己认为若是你的站点加载静态财富的时候小编并未有拉开强缓存,并且你只想透过前端去落到实处缓存,而没有要求后端在加入举办调解,这能够运用
service worker 来缓存静态能源,不然就有一点画蛇添足了。

升迁分界面设计、客户体验(二零一六年版Ameblo)

Ameblo的无绳电话机版在二零零六年经验了二回改版之后,就大致未有太大的转移。这里面比相当多客户都早已不足为奇了原生应用的布署性和经验。那一个类型也是为了不让人认为很土很难用,达到顺应时代的2015年版分界面设计和客商体验。

OK,接下去让自个儿切实详尽聊聊。

缓存页面

缓存页面显然是十分重要的,那是最宗旨的片段,当您在离线的意况下加载页面会之后出现:

图片 2

究其原因便是因为您在离线状态下无法加载页面,未来有了 service
worker,即便你在没网络的气象下,也足以加载以前缓存好的页面了。

页面加载速度的勘误

缓存后端接口数据

缓存接口数据是内需的,但亦非必得通过 service worker
来达成,前端存放数据的地点有广大,比方通过 localstorage,indexeddb
来进展仓库储存。这里小编也是通过 service worker
来贯彻缓存接口数据的,如若想经过另外措施来落到实处,只要求当心好 url
路线与数据对应的映照关系就能够。

改善点

系统重构前,通过
SpeedCurve
进行剖判,得出了上边结论:

依赖这几个规定了上面这几项基本计划:

缓存计策

公共场馆了如何财富需求被缓存后,接下去将要斟酌缓存计谋了。

SSR还是SPA

近年比较于加多到收藏夹中,客户更赞成于通过搜寻结果、推特(TWTR.US)、照片墙等社交媒体上的享用链接张开博客页面。谷歌(Google)和推特(TWTR.US)的AMP,
Facebook的Instant
Article
声明第一页的变现速度大幅震慑到顾客满足度。

其他,从GoogleAnalytics等日志记录中打探到在篇章列表页面和左右文章间实行跳转的客商也相当多。那大概是因为博客作为个体媒体,当某一顾客观看一篇不错的小说,特别感兴趣的时候,他也还要想看一看同一博客内的其它著作。也正是说,博客这种服务
第一页火速加载与页面间连忙跳转同等重要

为此,为了让双方都能表达最棒质量,大家决定在首先页使用服务器端渲染(Server-side
Rendering, SSTiguan),从第二页起选取单页面应用(Single Page Application,
SPA)。那样一来,不只能确定保障率先页的突显速度和机器可读性(Machine-Readability)(含SEO),又能获得SPA带来的飞速展现速度。

BTW,对于近年来的架构,由于服务器和顾客端应用一样的代码,全体拓宽SS奥迪Q7或是全体实行SPA也是唯恐的。如今曾经完结即使在无法运营JavaScript的条件中,也得以健康通过SSPAJERO来浏览。能够预感现在等到ServiceWorker广泛之后,起首页面将进而高速化,并且能够兑现离线浏览。

图片 3

z-ssrspa.png

此前的系统完全使用SS奥迪Q5,而以后的种类从第二页起变为SPA。

 图片 4

z-spa-speed.gif

SPA的魔力在于显示速度之快。因为独有通过API获取所需的不可或缺数据,所以速度非常的慢!

页面缓存计策

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

那般就确认保证了客商率先次加载页面,立时离线访谈站点后,即便不可能像第二次同样能够服务端渲染数据,可是之后能透过获取页面,异步加载数据的章程创设离线应用。

图片 5

客商率先次访谈站点,假诺在不刷新页面包车型地铁动静切换路由到其余页面,则会异步获取到的多少,当后一次拜见对应的路由的时候,则战败到异步获取数据。

图片 6

当客商第三回加载页面包车型地铁时候,因为 service worker
已经决定了站点,已经怀有了缓存页面包车型地铁力量,之后在访谈的页面都将会被缓存或然更新缓存,当客户离线访谈的的时候,也能访问到服务端渲染的页面了。

图片 7

推迟加载

咱俩接纳SS奥迪Q7+SPA的不二秘诀来优化页面间跳转这种横向移动的进程,並且选取延缓加载来改良页面包车型客车纵向移动速度。一开头要表现的剧情以及导航,还恐怕有博客作品等最初突显,在这个内容之下的附带内容随着页面的轮转渐渐显现。那样一来,首要的内容不会受页面上面内容的熏陶而越来越快的展示出来。对于那个想趁早读小说的客户来说,既不增添客户体验上的压力,又能完全的提供页面下方的剧情。

 图片 8

z-lazyload.png

后面的系统因为将页面内的全部内容都放置HTML文档里,所以使得HTML文书档案体量相当大。而前些天的系统,仅仅将器重内容放到HTML里重返,降低了HTML的体量和数量央求的分寸。

接口缓存战略

谈完页面缓存,再来说讲接口缓存,接口缓存就跟页面缓存很类似了,独一的例外在于:页面第一次加载的时候不必然有缓存,可是会有接口缓存的存在(因为伪造了
cache 中的数据),所以缓存计谋跟页面缓存类似:

  1. 网络优先的法子,即优先获得互联网上接口数据。当网络须求失利的时候,再去得到service worker 里在此之前缓存的接口数据
  2. 当互联网加载成功今后,就更新 cache
    中对应的缓存接口数据,保险后一次历次加载页面,都是上次访问的新式接口数据

因此代码就疑似这么(代码类似,不再赘言):

self.addEventListener(‘fetch’, (e) => { console.log(‘未来正在呼吁:’

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

此地其实可以再举行优化的,举个例子在获取数据接口的时候,能够先读取缓存中的接口数据实行渲染,当真正的互连网接口数据重返之后再扩充轮换,那样也能一蹴而就压缩客户的首屏渲染时间。当然那大概会发出页面闪烁的效果,可以增多一些动画片来张开连接。

HTML缓存

博客文章是静态文书档案,对于特定U路虎极光L的央浼会回来固定的原委,由此特别相符举办缓存。缓存使得服务器管理内容降低,在增高页面响应速度的还要减轻了服务器的担任。大家将不改变的剧情(作品等)生成的HTML举办缓存重回,对于由于变化的内容能过JavaScript、CSS等实行操作(比方展现、隐蔽等)。

 图片 9

z-newrelic-entrylist.png

那张图展现了2014年11月最后七日New
relic上的总括数据。小说列表页面包车型地铁HTML的响应时间基本在50ms以下。

 图片 10

z-newrelic-entry.png

那张图是文章详细页面包车型客车统计数据。能够见见,那一个页面包车型客车响应时间也大约是在50ms以下。由于存在小说过长的时候会促成页面体量变大,以及小说页面不可能一心缓存等景色,所以比较列表页面会存在越来越多不快的响应。

对于因诉求的客商端而发出变化部分的管理,大家在HTML的body标签中经过到场相应的class,然后在客商端通过JavaScript和CSS等展开操作。比方,一些剧情不想在少数操作系统上突显,大家就用CSS对那个剧情开展隐讳。由于CSS样式表会先载入,页面布局明确下来以往再打开页面渲染,所以那个也能够消除后面要提到的「咯噔」难题。

<!– html –> <body class=”OsAndroid”>

1
2
3
<!– html –>
 
<body class="OsAndroid">

CSS

/* main.css */ body.OsAndroid .BannerForIos { dsplay: none; }

1
2
3
4
5
/* main.css */
 
body.OsAndroid .BannerForIos {
  dsplay: none;
}

别的难点

到近日终止,已经大概能够兑现 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,原因不详,有读者了然的话能够调换下。

当今当您首先次加载页面,跳转路由,马上离线访谈的页面,也得以顺遂地加载页面了。

本领选型

此次项目标技巧选取时,遵守了尽量使用当下当前市集晚春经存在的宽广使用的本事这一尺度。旗号正是:「活脱脱像轨范应用同样Start」。那样一来,无论是什么人都能够轻巧的获取到对应的文书档案等新闻,相同的时间别的的团体和商社只要要参预到花色中来也能一点也不慢的左侧。可是在真正开展支付的时候,一些细节落成上因为各式各样的缘由存在一些不等的事态,然而在特大程度上保持了一一模块的独立性。最后系统的大约构成如下图所示:

 图片 11

z-bigpicture.png

(有个别地点做了归纳)

永不强缓存 sw.js

客户每便访问页面的时候都会去重新获得sw.js,依据文件内容跟从前的本子是还是不是一致来判定 service worker
是或不是有更新。所以假让你对 sw.js
开启强缓存的话,就将沦为死循环,因为老是页面获得到的 sw.js
都是同一,那样就不能够晋升你的 service worker。

另外对 sw.js 开启强缓存也是从未须求的:

  1. 本人 sw.js
    文件自己就非常的小,浪费不了多少带宽,感到浪费能够使用合同缓存,但附加扩大开支负责
  2. sw.js 是在页面空闲的时候才去加载的,并不会听得多了自然能详细说出来顾客首屏渲染速度

React with Redux

使用React和React举行开采的的时候,相当多地点能够用 纯函数
的情势张开整合。纯函数是指特定的参数总是回到特定的结果,不会对函数以外的界定形成污染。使用纯函数举行付出能够确定保障各样管理模块最小化,不用驰念会无意改动援用对象的值。那样一来,拾叁分促进大范围开辟以及在同样客商端中维系多少个情景。

分界面更新的流水生产线是:
Action(Event) -> Reducer (返回新的state(状态)) -> React (基于更新后的store内的state更新显示内容)

这是一个Redux Action的事例,演示了React Action (Action Creator)
基于参数重返贰个Plain Object。处理异步乞求的时候,我们参照他事他说加以考察
合法语档
,分别定义了中标央求和挫败央浼。获取数据时选择了
redux-dataloader

JavaScript

// actions/blogAction.js export const FETCH_BLOG_REQUEST =
‘blog/FETCH_BLOG/REQUEST’; export function fetchBlogRequest(blogId) {
return load({ type: FETCH_BLOG_REQUEST, payload: { blogId, }, }); }

1
2
3
4
5
6
7
8
9
10
11
12
// actions/blogAction.js
 
export const FETCH_BLOG_REQUEST = ‘blog/FETCH_BLOG/REQUEST’;
 
export function fetchBlogRequest(blogId) {
  return load({
    type: FETCH_BLOG_REQUEST,
    payload: {
      blogId,
    },
  });
}

Redux
Reducer是一一心基于Action中带走的数据,对已有state进行复制并更新的函数。

JavaScript

// reducers/blogReducer.js import as blogAction from
‘../actions/blogAction’; const initialState = {}; function
createReducer(initialState, handlers) { return (state = initialState,
action) => { const handler = (action && action.type) ?
handlers[action.type] : undefined; if (!handler) { return state; }
return handler(state, action); }; } export default
createReducer(initialState, { [blogAction.FETCH_BLOG_SUCCESS]:
(state, action) => { const { blogId, data } = action.payload; return
{ …state, [blogId]: 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
// reducers/blogReducer.js
 
import  as blogAction from ‘../actions/blogAction’;
 
const initialState = {};
 
function createReducer(initialState, handlers) {
  return (state = initialState, action) => {
    const handler = (action && action.type) ? handlers[action.type] : undefined;
    if (!handler) {
      return state;
    }
    return handler(state, action);
  };
}
 
export default createReducer(initialState, {
  [blogAction.FETCH_BLOG_SUCCESS]: (state, action) => {
    const { blogId, data } = action.payload;
    return {
      …state,
      [blogId]: data,
    };
  },
});

React/Redux基于更新后的store中的数据,对UI实行翻新。种种零部件依赖传递过来的props值,总是以同一的结果再次回到HTML。React将View组件也当作函数来对待。

JavaScript

// main.js <SpBlogTitle blogTitle=”渋谷のブログ” /> //
SpBlogTitle.js import React from ‘react’; export class SpBlogTitle
extends React.Component { static propTypes = { blogTitle:
React.PropTypes.string, }; shouldComponentUpdate(nextProps) { return
this.props.blogTitle !== nextProps.blogTitle; } render() { return (
<h1>{this.props.blogTitle}</h1> ); } }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// main.js
<SpBlogTitle blogTitle="渋谷のブログ" />
 
// SpBlogTitle.js
import React from ‘react’;
 
export class SpBlogTitle extends React.Component {
  static propTypes = {
    blogTitle: React.PropTypes.string,
  };
 
  shouldComponentUpdate(nextProps) {
    return this.props.blogTitle !== nextProps.blogTitle;
  }
 
  render() {
    return (
      <h1>{this.props.blogTitle}</h1>
    );
  }
}

关于Redux的音讯在
法定文书档案
中表明得可怜详细,推荐随时仿效一下以此文书档案。

幸免更换 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。

同构Web应用(Isomorphic web app)

Ameblo
二〇一六年版基本上完全部都以用JavaScript重写的。无论是Node服务器上或许客商端上都选拔了扳平的代码和流程,也正是所谓的同构Web应用。项目的目录结构大要上上如下所示,服务器端的入口文件是
server.js ,浏览器的输入文件是 client.js

 图片 12

z-isomorphic.png

写好的JavaScript同一时间运转在劳务器端依旧顾客端上的运作行为、以及从数额读取直到在页面上出示截至的全部浏程,都是平等的款型展开。

图片 13

z-code-stats.png

接纳Github的语言计算可以看看
,JavaScript占了全数项目标94.0%,大约全是由JavaScript写成的。

测试

从此现在,大家曾经做到了选取 service worker
对页面进行离线缓存的法力,如果想体验效果的话,访谈笔者的博客:https://lindongzhou.com

随便浏览任性的页面,然后关掉互连网,再度做客,从前你浏览过的页面都能够在离线的气象下进展拜见了。

IOS 供给 11.3 的版本才支撑,使用 Safari 进行拜望,Android 请选用协助service worker 的浏览器

原子设计(Atomic Design)

对于组件的安顿性,我们运用了
原子设计
理念。其实项目并从未一初阶就接纳原子设计,而是基于 Presentational and
Container
Components

,对 containercomponent
进行了两层划分。可是Ameblo中的组件实在是太多,很轻松导致职责不明明的情事,因而最后使用了原子设计意见。项目标骨子里运用中,采纳了以下的条条框框。

 图片 14

z-atomic-design.png

manifest 桌面应用

前方讲罢了如何选用 service worker 来离线缓存你的同构应用,可是 PWA
不独有限于此,你还足以采纳安装 manifest
文件来将您的站点增加到活动端的桌面上,进而达到趋近于原生应用的心得。

Atoms

组件的一丁点儿单位,比如Icon、Button等。原则上不享有状态,从父组件中拿走传递过来的props,并重回HTML。

使用 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: 设置Logo,插件会自动帮您转移区别 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 来增添你的桌面应用了。

Molecules

以复用为前提的零件,比如List、Modal、User
thunmbnail等。原则上不享有状态,从父组件中收获传递过来的props,并重回HTML。

测试

加多完之后你能够透过 chrome 开荒者工具 Application – Manifest 来查看你的
mainfest 文件是不是见效:

图片 15

那般表明您的安插生效了,安卓机缘自动识别你的安排文件,并打听顾客是还是不是丰裕。

Organisms

页面上相当的大的一块组件,举例Header,Entry,Navi等。对于这一层的组件,能够在在那之中举办多少得到管理,以及采用Redux
State 和
connect
,维护组件的处境。这里获得的机件状态以props的款式,传递给 Molecules
Atom

JavaScript

// components/organisms/SpProfile.js import React from ‘react’; import {
connect } from ‘react-redux’; import { routerHooks } from
‘react-router-hook’; import { fetchBloggerRequest } from
‘../../../actions/bloggerAction’; // 数据获得管理(使用react-router-hook) const defer = async ({ dispatch }) => { await
dispatch(fetchBloggerRequest()); }; // Redu store的state作为props const
mapStateToProps = (state, owndProps) => { const amebaId =
owndProps.params.amebaId; const bloggerMap = state.bloggerMap; const
blogger = bloggerMap[amebaId]; const nickName = blogger.nickName;
return { nickName, }; }; @connect(mapStateToProps) @routerHooks({ done
}) export class SpProfileInfo extends React.Component { static propTypes
= { nickName: React.PropTypes.string.isRequired, }; render() { return (
<div>{this.props.nickName}</div> ); } }

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
// components/organisms/SpProfile.js
 
import React from ‘react’;
import { connect } from ‘react-redux’;
import { routerHooks } from ‘react-router-hook’;
 
import { fetchBloggerRequest } from ‘../../../actions/bloggerAction’;
 
// 数据获取处理 (使用react-router-hook)
const defer = async ({ dispatch }) => {
  await dispatch(fetchBloggerRequest());
};
 
// Redu store的state作为props
const mapStateToProps = (state, owndProps) => {
  const amebaId = owndProps.params.amebaId;
  const bloggerMap = state.bloggerMap;
  const blogger = bloggerMap[amebaId];
  const nickName = blogger.nickName;
 
  return {
    nickName,
  };
};
 
@connect(mapStateToProps)
@routerHooks({ done })
export class SpProfileInfo extends React.Component {
  static propTypes = {
    nickName: React.PropTypes.string.isRequired,
  };
 
  render() {
    return (
      <div>{this.props.nickName}</div>
    );
  }
}

结尾

讲到那大概就完了,等随后 IOS 协理 PWA
的其他成效的时候,到时候小编也会相应地去实行别的 PWA 的风味的。未来 IOS
11.3 也只有扶助 PWA 中的 service worker 和 app manifest
的职能,可是相信在不久的今后,其余的成效也会相应获得协理,到时候相信 PWA
将会在活动端怒放异彩的。

1 赞 收藏
评论

图片 16

Template

逐个供给路线(UXC90L)所对应的机件。其任务是将所需的预制构件从Organisms中import过来,以自然的种种和格式整合在一块。

Pages

用作页面包车型地铁页面组件。基本上是把传递过来的 this.props.children
原原本本的显得出来。由于Ameblo是单页面应用,因此唯有贰个页面组件。

CSS Modules

CSS样式表使用 CSS
Modules

将CSS样式准绳的意义范围严苛限定到了种种零部件内。各样样式法规的机能范围扩充界定使得样式的更换和删除越发轻便。因为Ameblo是由众几个人同台开荒成功,不自然每一种人都理解CSS,并且不免要时时对一部分不知是什么人哪天写的代码实行改变,在这一年将功能范围限定到零部件的CSS
Modules就发布其遵循了。

CSS

/ components/organisms/SpNavigationBar.css / .Nav { background: #fff;
border-bottom: 1px solid #e3e5e4; display: flex; height: 40px; width:
100%; } .Logo { text-align: center; }

1
2
3
4
5
6
7
8
9
10
11
12
13
/ components/organisms/SpNavigationBar.css /
 
.Nav {
  background: #fff;
  border-bottom: 1px solid #e3e5e4;
  display: flex;
  height: 40px;
  width: 100%;
}
 
.Logo {
  text-align: center;
}

JavaScript

// components/organisms/SpNavigationBar.js import React from ‘react’;
import style from ‘./SpNavigationBar.css’ export class SpBlogInfo
extends React.Component { render() { return ( <nav
className={style.Nav}> <div className={style.Logo}> <img
alt=”Ameba” height=”24″ src=”logo.svg” width=”71″ /> </div>
<div …> </nav> ); } }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// components/organisms/SpNavigationBar.js
 
import React from ‘react’;
import style from ‘./SpNavigationBar.css’
 
export class SpBlogInfo extends React.Component {
  render() {
    return (
      <nav className={style.Nav}>
        <div className={style.Logo}>
          <img
            alt="Ameba"
            height="24"
            src="logo.svg"
            width="71"
           />
        </div>
        <div …>
      </nav>
    );
  }
}

各种class的称号经过webpack编写翻译之后,造成像
SpNavigationBar__Nav___3g5MH 那样含hash值的全局独一名称。

ESLint, stylelint

此番的花色将ESLint和stylelint放到了总得的地方,即使贰个假名出错,整个项目也无力回天测验通过。目标就在于统一代码风格,节约代码核实时的劳动。具体法则分别承继自
eslint-config-airbnb

stylelint-config-standard
,对于有个别必得的内部原因做了个别定制。因为法规较严,起先的时候恐怕有一点困难。新成员加入项目组时,代码通过Lint测量检验便成了要通过的率先关。

 图片 17

z-code-review.png

防止了代码考察时对于那一个一线写法挑错。被机器告知错误时,情感上会感到稍好有的。

图片 18

z-ci-error.png

步向项目组之后,最早的这段时日里发生Lint错误是一向的事。

CI, Build, Tesing

代码的
构建
测试

部署
统一使用CI(公司里面选取
CircleCI
)来产生。种种分支向GHE(Github
Enterprise)PUSH之后,依附种种分支爆发区别的动作。那几个流程的裨益便是创设相关的管理不需求特意人士来实现,而是统一写在
circle.ymlpackage.json (node环境下)里。

Docker

此番系统重构,也对node.js应用进行docker化创设。此番重构的是后面一个系统,大家盼望得以在微小考订之后随即伸开布置。docker化之后,一旦将镜像构建产生,能够不受node模块版本的左右开展配备,回滚也很轻便。

除此以外,node.js本身发表特别频仍,假设放置不管,不知不觉之间系统就成古董了。docker化之后,能够不受各主机意况的震慑自由的进行进级换代。

更珍视的是,设置docker容器数是相比较轻便的,那对于系统横向扩大体积以及对服务器配置作优化时也特别有益于。

晋级分界面设计、客商体验(2014年版Ameblo)

不再「咯噔」

系统重构此前的Ameblo由于存在部分莫斯中国科学技术大学学未有牢固的模块,出现了「咯噔」现象。这种「咯噔」会促成误点击以及页面包车型客车重绘,十三分令人恨入骨髓。而此模块高度牢固也做为此番系统重构的UI设计的前提。特别是页面间导航作为十一分人命关天的成分,大家经过努力使得在页面跳转时老是都能够触击到均等的职分。

图片 19

z-gatan.gif

「咯噔」的二个例子。点击[次のページ](下一页)的时候,额外的要素由于加载缓慢,变成误点击。

 图片 20

z-paging-fixed.gif

系统重构之后,成分的职责被定位下来,缓慢解决了页面跳转时给顾客思维上带来的肩负。

智能手提式有线话机时期的客商界面

2015年在活动意况下使用的客商大致都在运用智能手提式有线电话机。在智能手提式有线电话机上,由于各样平台的提供者制订了个别不相同的客商分界面标准,客户已经习感到常并适应了客商分界面。相比较之下,虽说浏览器上的科班比较少,可是如若和今后风行的分界面差别太大的话,就能够变得很难用。

Ameblo的无绳电话机版在二〇一〇年开展改版之后,自然对有的细节实行了勘误,可是出于未有太大的变动,所以未来总的来讲众多地点已经给人一种很旧的回忆。顾客在浏览的时候,对于分界面并不区分是原生应用依然浏览器,由此制作出适应当下一代那么些平台的客商界面显得特别关键。这里介绍一下此番重构中,对于分界面包车型大巴有个别进步。

 图片 21

z-update-design.png

内容吞没界面上横向整个空间。二零一零年的时候,通常采纳脸谱倡导的「将逐条模块圈起来的设计」。

图片 22

z-searchbar.gif

日增了导航栏,把导航相关操作聚集停放在这里。

可访问性

本次系统重构正值可访谈性成为热门话题的时候。细心的为HTML增添一定标签属生就足以使全体系列足够可访谈。首先在HTML标签属性加多上时要用心切磋。对于标题、
img 等丰裕适当的 alt 属性,对于可点击的要素必须要动用 a button
等可点击的价签。倘若能半自动对可访谈性进行核准就再好可是了,ESlint的
jsx-a11y
插件能够协理成功那或多或少。

在档期的顺序实行的时候,正好集团内张开了贰回可访谈性的学习活动( Designing
Web
Accessibility

的撰稿人太田先生和伊原太守也参加了本次活动),在此番活动上也尝尝了Ameblo到这两天截至未有专一过的语音朗读器。那时候用语音朗读器在Ameblo上举办朗读时,有几处至极的地点,使用
WAI-ARIA
对这几处加以校正(与 data-* 相同,JSX也支持 aria-* 属性)。

这里
的PPT中有详细的介绍,迎接观察(匈牙利(Magyarország)语)。

结果

OK,上面介绍了这次重构带来的重重变动,那么结果如何呢?

首先是性质相关指标(测验的ULANDL都以Ameblo中单一页面恳求财富最多,呈现速度最慢的页面)。

闭塞渲染的能源(Critical Blocking Resources)

 图片 23

z-speed-blocking.png

卡住渲染的能源数 减少了75%
!JavaScript全部制改正为了异步读取与实施。CSS样式因为运维的缘由,维持了重构前的气象。

剧情必要(Content Requests)

 图片 24

z-speed-requests.png

财富须求数 减少了58.04%
!由于选择了推迟加载,首屏呈现只加载要求的能源,与此同一时候对文本进行适度的整理,并删除了一些不要求的模块,最后落得了那一个情况。

渲染(Rendering)

 图片 25

z-speed-rendering.png

渲染速度做为前端的要害品质目标,本次 提升了44.68%

页面加载时间(Page Load Time)

 图片 26

z-speed-pageload.png

页面加载时间 缩短了40.5 !别的,后端的归来时间也维持在了0.2ms ~
0.3ms之间。

接下去介绍一下连锁的职业目标。

网页浏览量(Pageviews)

 图片 27

z-ga-pv.png

因为二〇一四年三月有一人盛名的博客主成为了销路广话题,所以那个指标内包括特殊情形。网页浏览量提高了57.15%。若是将火爆话题所带来的数值除去后,实际上独有由系统重构所带来的晋级在百分之十到25%中间。

老是对话浏览页数 (Pages / Session)

 图片 28

z-ga-pps.png

Pages / Session是指在单个会话内页面包车型客车浏览数,这些指标 提升了35.54
。SPA革新了页面间跳转的进度,获取了令人瞩指标效应。

跳出率(Bounce Rate)

 图片 29

z-ga-bounce.png

跳出率指在贰个会话内,仅看了二个页面包车型大巴比率,这几个目的 改善了44.44%
。大家以为那是由于首屏和页面跳转速度的精雕细琢,客户分界面晋级(更易于通晓的分页),「咯噔」创新所带来的结果。

唯独还留存重重创新的退路,任何叁个指标都足以重新晋级。我们想以此标识
网址质量的晋级会带来业务指标的进级

上述数据是在偏下标准下获得的:

写在终极

本次系统重构的出发点是对工夫的挑战,结果获得了天衣无缝的客商举报,并对业务作出了贡献,大家自身也倍感相当有价值,拿到了特大的引以自豪。选拔新式迎合时流的手艺自然进步服务的成色,也使得这种知识在市廛在生根。在此,对尽快导入Isomorphic
JavaScript,并向南瀛境内推广的同事
@ahomu
表示多谢! 

笔者介绍:

小编:原 一成(Hara Kazunari),二〇一〇年插手东瀛CyberAgent公司。担当Ameblo
二〇一四运动前端改版项目总老板。著有《GitHubの教科書》,《CSS3逆引きデザインレシピ》,《フロントエンドエンジニア育成読本》。

翻译:侯 斌(Hou
Bin),2015年入职东瀛CyberAgent公司。现任Ameblo前端开拓。在这次Ameblo
二零一四移动前端改版项目中出任重(英文名:rèn zhòng)要支出,担任基础架商谈技术选型以及首要模块开拓等。

1 赞 收藏
评论

图片 30

相关文章

发表评论

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

网站地图xml地图