前端页面热更新实现方案

前端页面热更新

了解过前端性能优化的同学应该清楚,给页面加载提速的终极方案就是CDN,这是BS架构本身的特点决定的,无论什么前端提速手段,最终都会回到客户端文件的传输上来;与之相对的CS架构则不存在加载压力,但CS架构的问题是更新不灵活,那么有没有一种方法能结合这两种架构的优点,在加载速度和更新灵活性之间找到一个平衡点呢?这就是本文要探讨的一种方案:前端热更新。

方案概述

“前端”和“热更新”这两个词通常很少一起出现,提到热更新一般都是指APP的一种静默更新方式,这种方式会在用户使用时悄悄检测并下载增量更新包,当用户下次打开APP时自动应用更新,从而将APP“更新”这个破坏连贯性的动作隐藏于无形;前端页面的加载则相当于每次都是“全量更新”,如果能让前端页面也能用上“本地模板”,那将极大缩短前端加载时间,而且以此为前提,我们也可以实现一个前端的模板热更新机制,做到不影响页面更新的实时性。

应用场景

场景一:APP内嵌页面。

比如电商类APP的首页,经常需要改版或者做活动皮肤,如何减少更新成本就成了一个大问题。使用了热更新方案我们就可以用HTML实现APP首页,页面内容以模板的形式存进localStorage,后台静默更新模板,下次启动自动生效;针对具有一定时效性的活动皮肤,我们以补丁的形式发布,补丁文件叠加在模板上产生最终的活动模板效果,对于补丁包我们可以提前加载并预存在本地,补丁包应该包含自身的生效时段信息,前端检测到时间处于活动周期内时应用补丁。最终可以做到热更新页面无论改版还是做活动,只需要前端发版就可以,完全不需要APP端参与。

场景二:追求加载速度的web页面。

对于web页面来说更新不是问题,加载才是最大的问题,如果个别页面希望极致提升页面展现速度,那么也可以使用该方案作为提速手段,但因为页面的所有代码都将存进localStorage,所以不适合大范围使用。

需求细化

综合以上场景和需求,最终我们要做的东西是一个“壳”页面,该页面没有具体业务内容,只实现热更新功能,每次加载都先检查localStorage中是否存在模板,如果有则立即应用模板,此时页面展现出来,如果没有则进入下一步;下一步页面会请求模板管理接口获取最新模板信息,拿到模板信息后如果本地已有模板,则与本地模板比对版本信息,如果版本一致说明缓存命中,流程结束;如果本地版本不是最新,则获取最新模板并存进本地,下次页面加载时将应用最新的模板,流程结束;另一种情况是首次加载本地没有任何模板,那么将获取最新模板,保存到本地,然后应用模板,流程结束。

前面说的是稳定模板的更新流程,稳定模板流程结束后会进入补丁模板更新流程。首先仍然是检查本地是否存在补丁模板,如果已存在则检测当前时间是否匹配补丁的生效时段,匹配则应用补丁,不匹配将进入下一步;下一步将获取最新补丁模板并存到本地,然后检测当前时间是否匹配最新补丁的生效时段,如果匹配则应用模板,不匹配流程结束。

完整流程如图所示:
hot patch

实现细节

接口数据

根据功能需求我们需要接口返回稳定模板信息和活动模板信息,分别都包含idurl两个字段,id用于版本校验,url指向模板文件下载地址,活动模板信息还需要额外提供cycle字段,定义活动模板的生效时段,与之相对的我们还需要接口返回服务器当前时间,用于匹配活动模板的生效时段,最终完整的数据结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"status": "Y",
"data": {
"stableVersion": {
"id": "17",
"url": ""
},
"activeVersion": {
"id": "18",
"url": "",
"cycle": "2018,02,01-2018,02,10"
},
"today": "2018,02,06"
}
}

本地数据

保存到本地的数据大致跟接口数据保持一致,只保留stableVersionactiveVersion信息,字段在idurl基础上再增加template用于保存模板字符串,完整本地数据结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"stableVersion": {
"id": "17",
"url": "",
"template": ""
},
"activeVersion": {
"id": "18",
"url": "",
"cycle": "2018,02,01-2018,02,10",
"template": ""
}
}

模板文件

前端页面由三种语言构成,但我们希望只用一次请求就把模板文件拿到,所以模板是一个包含了html/css/js的文本文件,标签格式就保持普通HTML文件的写法,考虑到模板应用部分的实现,需要约定一下标签的写法,例如css必须用<style></style>标签包裹,js必须用<script style="text/javascript"></script>标签包裹,这样一来用正则表达式就很容易提取到各部分代码段。

模板应用

如上段所说,获得模板文件后可以使用正则表达式拿到三种语言代码,然后只需要按照css > html > js的顺序依次将他们插入页面相应位置,就完成了模板应用,唯一不同的是html代码将以innerHTML的方式覆盖进body元素。在应用顺序上,将css放在html之前是为了避免重绘,将js放在html之后是为了能够在js中操作DOM。

活动模板虽然定义为补丁,但模板构成跟稳定模板其实是相同的,应用方式也完全相同,只不过由于活动模板在稳定模板之后应用,所以活动模板的css和js都将以补丁的方式影响页面,对于普通的换皮肤需求只需要css和js就足够了,但如果希望html也能发生一些改变,根据html的覆盖式应用方式,活动模板中就需要给出一份完整的html代码,以达到修改html的目的。

效果展示

示例展示

http://refined-x.com/WEB-OTA/

qrcode

实际应用

一健康网上商城APP首页即采用WEB-OTA方案实现,应付日常迭代游刃有余。

yijiankang

后记

整个方案的流程比较琐碎,但实现过程其实很简单,部署成本也不高,只需要后端把模板管理起来,再提供一个更新接口就行了,但这套更新机制还是有一个小问题,那就是当有新版本发布时用户并不能第一时间看到新版本,必须下次访问才能更新到新版本,这算是静默更新要付出的一点点代价吧,如果实在介意这个问题其实也容易解决,只需要在检测到远程有新版本时提示用户重启/刷新就可以了。

相比较HTML5的manifest缓存方案,我认为灵活性要更高一些,但不足之处在于不支持静态文件的碎片化管理,但扩展这个功能也不复杂,无非模板信息里再扩展几个字段而已。

代码在这里了,更细节的东西自己看代码吧:https://github.com/tower1229/WEB-OTA

前端路上原创技术文章,转载请注明出处:http://refined-x.com/2018/02/07/前端页面热更新实现方案/

对文章内容有任何疑问欢迎留言讨论,或者扫描下方二维码加入“前端路上-知识星球”付费提问。

前端路上-知识星球