距离上次首页改版,已有2年3个月零五天。相比上次改版对首页整体框架、开发流程的大刀阔斧(前两次改版总结传送门:2016版,2017版),这次的改版看起来显得有点像跳水——没什么水花。在站在巨人肩膀上的小巨人的叮咛与期盼下,这次改版在延续17版的框架与流程的基础之上,为首页的稳定性、安全性、视觉体验、无障碍体验方面见缝插针地添了砖加了瓦。
本文将从以下几个方面进行阐述
● 引入强类型校验
● 升级资源构建方案
● 接入自动化测试
● 完善监控体系
● 优化页面加载体验:骨架屏
● 优化信息无障碍体验
引入强类型校验
在性能几近无懈可击的情况下,我们决定从稳定性入手,为项目引入强类型校验,弥补 JavaScript 这种弱类型语言在不可预测性上的缺陷。
强类型语言 TypeScript 已发布6年有余,国内应用的开发者也在慢慢增长。一般来说,业务开发周期短,迭代频繁,TypeScript 的引入对于很大一部分开发者来说是一件费时费力的事,用的话业务可以上线,不用的话业务照样可以上线,因此团队极少在业务生产中应用。但秉承着不折腾不凹凸的理念,新版首页不负使命的,进行了基于 TS 的重构。
做 TS 重构并不难,把 js 后缀改成 ts 就好了。完。
当然是开玩笑的啦!显然,这样的 TS 是没有意义的。只有严格遵循 TS 标准的代码才能最大化 TS 的效用。在项目中,我们对 TS 的检查开启 strict 模式,每次提交时,都会对代码做一次完整的检查,只要有 TS 报错就禁止提交,旨在向成员传达一个信息——写强类型语言就该
有觉悟,否则就是耍流氓。
没有深入使用过 TS 的同学在前期可能会感到人生的艰难,但这些都是为你好为了保证代码的健壮性。例如,在以往难以定位、查找的 window 全局变量的管理上,十分令开发者头疼,而引入了 TS 之后,只要对全局变量进行了接口设置,各个组件中再也不会出现多余或是未知全局变量的情况。再例如,在写一个拥有 get、set 方法的存贮类的时候,TS 能帮助检测获取内容的类型:
interface MemoryState { testa: boolean testb: string } class Controller { state: StateType constructor() { this.state = { state: {}, } } get<K extends MemoryStateKeys>(key: K) { return this.state.memory[key] } set<K extends MemoryStateKeys>(key: K, value: MemoryState[K]): MemoryState[K] { this.state.memory[key] = value return value } }
当我们使用 new Controller().get("testb") 的时候,TS 能够在开发阶段检测 testb 是否是 string 类型。通过 TS 的检测插件,我们能放心的使用 string 类型对象的方法,简化繁复的判断逻辑,同时保证代码在获取到非期望值时能及时通过报错发现,一切的输入和输出都是稳定可预测的,四舍五入就是在写代码的时候自动走了一部分测试,为项目的开发与迭代保驾护航。
升级资源构建方案
旧版首页项目使用的构建工具 Athena,推进了开发流程自动化,但是涉及到定制化的构建流程时,由于 Athena 的通用性,不方便直接做改动。首页包含直出、同步、异步三种类型的资源引用,需对资源的打包进行特殊处理,所以我们这次回归 Webpack,基于 Webpack 4.0 做了以下的方案优化:
发布流程优化
旧版的发布流程中,每次发布需要对改动的文件进行 diff 检查,避免产生不符合预期的误改动。Webpack 默认打包机制的特点,是根据模块的打包顺序为每一个模块提供一个按顺序编号的 ID,对文件的包进行依赖管理。旧版首页的入口文件包含依赖包管理的执行环境,因此任何一个包引入顺序发生变动时,入口文件都会发生变动。以上的打包机制会出现一个文件发生引入顺序变动时,可能会影响到编译后的几个甚至十几个文件发生变动的情况,而这些文件中的逻辑代码部分其实并不需要更新,这就降低了 diff 代码的准确性,使得这一中间检查措施失去了原本的意义。首页缓存机制与资源懒加载机制使得静态资源在发布时,需要对发生变动的文件进行 CDN 缓存清除的操作,也就意味着,改动文件越多,需要清除的缓存资源链接就越多,而链接越多,由于缓存清除不同步引起的资源异步加载出错的概率就越高,每次上线发布都存在一定的风险。
为了减少发布风险,新版首页的打包机制改变了 Webpack 的打包逻辑,通过设置,每个模块不再通过顺序编号的 ID 管理依赖包,而是通过文件目录生成哈希编码的专有 ID,并把依赖包的执行环境从入口文件中抽离出来作为一个单独的资源请求,这样每次改动文件时,可以只针对改动的文件 diff,剔除了其他非预期的 diff 情况。通过新的构建方案,使代码改动控制在预期的范围内,保证部署流程的稳定。
项目架构优化
旧版页面的性能优化方案中,包含了部分 js 片段直出,这些代码是项目所依赖的基函数,需要在核心 js 代码执行之前启动。但这样的方案也有一些不尽人意的地方:
● 由于代码需要直出于页面模版中,考虑到兼容性,这些代码不能使用一些高级语法,每次改动都需要确保自己的代码没有兼容问题,导致维护成本巨高。同时,首页页面模版由后台负责管理,直出代码的改动需经过后台发布,迭代成本略高,风险也不小;
● 由于打包的限制,核心代码与模板代码存在同一套公用代码,代码冗余不说,一旦这部分代码发生变动还需要同时修改与发布两部分的代码,使得代码的维护成本增高。
针对以上问题,新版中我们将这些代码重新放入核心代码,模板代码中不再承载任何逻辑代码,迭代发版不再涉及模版发布,只需进行静态资源的发布即可,开发过程中统一使用 JS 高级语法,去除人工维护兼容语法代码的过程。
至此,我们通过增强资源打包的可预测性、以及优化项目资源架构两个方面对资源的发布方案进行了优化。
接入自动化测试
一个页面开发完成后,在对其进行提测之前,对页面进行自测是一个必不可少的环节。一方面,保证页面所开发的功能能正常运作;另一方面,保证在对一个功能进行开发时,没有影响到页面其他区域功能的正常使用。
一般情况下,自测需要人为手动地进行测试,但这样会有两个缺点,第一,需要测试的区域数量过于巨大,相似的测试操作过于频繁,浪费了人力,也影响了测试的效率;第二,人为的自测由于没有统一的自测规范,因此在测试时很容易有所疏漏,从而忽视了一些看似微小,实则影响巨大的 bug,花费了大量的时间,却得不到自测所需要的效果。针对这种情况,我们产生了实施自动化测试的想法。以新版首页为例,我们通过使用 Nightwatch.js,为新版首页创建了一个自动化测试脚本,对新版的首页的73项用例进行自动化测试。
结果显示,通过自动化测试,在不到三分钟的时间内,完成了对新版首页73项用例的测试,这也意味着,若要通过自动化测试,来对任一页面进行自测,自测的时间都可控制在五分钟以内,并且准确性更高。将自动化测试应用在发布前以及上线后5分钟之内,及时检查测试用例,保证每次发版的安全。
完善监控体系
旧版页面的前端监控体系覆盖了浏览器信息、页面加载测速、楼层隐藏方面,但信息通知较为滞后,且仅覆盖了页面 onLoad 时间,收到告警信息时,无法做到快速定位问题。
参考京东购物小程序目前的监控机制,新版首页针对代码报错、接口可用性增加了上报监控。
代码报错监控:BadJS
通过 BadJS 框架捕获页面报错,并分析处理报错信息上报至京东 BadJS 服务。通过上报数据,我们可以得到报错的详细信息以及发生次数。通过分析上报数据,可以发现一些潜在的问题,及时修复,保证首页代码的健壮性。同时根据上报数,还可以预估出一个问题所造成的影响范围,便于预估损失。
业务可用性监控
本次改版,在可用率上报系统中为首页补充了特定判定规则,包含调用次数、可用率、和 TP(性能指标)三个维度,在此基础上还可以对这三个维度进行环比,以减少误报的可能性,近期系统还上线了红灯告警-语音通知功能。
可用率上报系统一般被用来监听接口可用性,但对于首页来说,除了接口,还需关注楼层隐藏的情况。目前的兜底方案中,每个楼层中的模块接口兜底全部失效的情况下,会隐藏当前楼层。楼层一旦发生隐藏,则意味着出现了比较严重的问题,需快速关注并解决。可用率上报系统可做到触发告警规则时,1分钟之内即推送通知,精确到接口,便于及时发现问题,及时止损。需要注意的是,如何设置一套能够较为精确反映问题发生、减少假报警的阈值尤为重要,毕竟狼来了喊多了,也就等于没有监控。
测速上报
这一部分延用了旧版的 Athena 测速上报方案,并对一些与业务数据上报重复的部分做了减法,同时增加了接口的测速上报,完善故障追溯数据体系。
优化页面加载体验:骨架屏
旧版页面懒加载的占位方案采用了统一区域 loading 动画的方式,这种方式的优势在于复用成本低,适配性强。但如果遇到较大面积的模块或是模块较为密集的情况时,区域 loading 动画的体验有所下降————要么是空白区域过大,要么是 loading 动画过于密集,模块加载过程造成的视觉差异感知较为明显。而对于 PC 首页来说,空白区域过大是主要存在的问题。
低网速下旧版首页的 loading 体验
这次改版,我们引入了骨架屏方案,最终目的是以灰色豆腐块的形式尽量缩小真实模块结构与加载占位之间的视觉差异。执行起来可以按照视觉差异分为两种对应关系:
● 弱对应关系:只对模块进行标题、子项等主要内容进行块化处理,复用性较高,适配性中等;
● 强对应关系:以视觉效果为基础,对子项进一步作出图片、文案的块化处理,针对占位面积较大、内容更为复杂的子项进行更细化的块化拆分,复用性低,适配性高。
考虑到首页的特殊性,我们最终选择了强对应关系的骨架屏方案,并为了可扩展性,使用的是使用样式渲染的骨架屏,而不是直接使用图片占位。除了开发成本的上升,页面首屏加载代码量也有所增加。
项目结构
使用骨架屏所要达到的效果包含以下几点:
● 提前占位,在页面的加载中滚动条不发生较为明显的跳动;
● 页面快速滚动时也能看到骨架屏样式的占位。
也就意味着骨架屏的内容需要与页面做同步加载处理,结合懒加载组件,骨架屏组件需提前作为 loading 结构传入,并保证样式在页面渲染的第一时间进行加载,否则就失去了骨架屏的意义。
每个需要骨架屏样式的组件,单独拆分出一个 placeholder 组件。组件内的占位结构包含两类样式——颜色与尺寸定位,加上容器外层的动画效果样式。颜色样式全页公用,尺寸定位样式与正式组件公用:
尺寸定位样式与正式组件公用的目的是为了在将来组件样式发生变化时,保证骨架屏与正式样式的统一修改,避免出现样式修改上的遗漏,但同时增加了样式的维护成本。同时样式编写与拆分的过程中也需要开发者注意兼容骨架屏的样式,例如需要占位豆腐块的容器间距 padding、margin 的选择都很重要。因此这次首页的骨架屏尝试并不适合快速复用至其他项目。
新版首页骨架屏 loading 体验
优化信息无障碍体验
互联网信息无障碍,即针对视力障碍人士所提供的辅助。系统级别的辅助主要依赖读屏工具,读屏工具可以解决网页端信息无障碍 60%的阻碍,剩余的 40%需要在网页开发的过程中由开发者进行体验优化。
没有做任何信息无障碍处理的网页,使用读屏工具访问时一般存在以下几个问题:
● 多余无用信息的播报,例如:跳转链接、图片名称;
● 弹出浮层无法访问;
● 懒加载内容直接跳过;
为了造福国内一百一十人中的一个视障人士(数据来自这里),本次改版,我们决定在 PC 首页开启京东商城桌面端首个信息无障碍实践。
桌面端视障用户的操作主要通过键盘进行。针对刚才提出的几个问题,PC 首页初步的无障碍体验优化方案分为几个阶段。
第一阶段,语义化一切 tab 可及的元素——包含页面外跳转链接的 a 标签统一添加 aria-label属性,以便读屏软件能够简化读取元素信息;
第二阶段,保证页面主要模块的访问——懒加载内容占位容器将 tab-index 设置为大于 0 的值,使得 tab 键能够遍历到,以便触发页面懒加载,避免 tab 直接跳过;
第三阶段,扩展带弹出浮层等元素的操作——针对无障碍增加弹出浮层交互逻辑,入口增加 aria-haspopup 属性,告诉读屏软件这里是弹出浮层的入口,将 tab-index 设置为大于 0 的数值使得 tab 操作可聚焦到,浮层弹出后焦点自动聚焦至浮层;
第四阶段,为视障用户额外增加快捷跳转——参考 Google 搜索结果页,可在页面的顶部,增加一些隐藏的快捷跳转。PC 首页本次对搜索框以及底部的“为你推荐”位置增加了隐藏跳转链接,只有使用键盘操作的用户能够定位到。
对于商城页面来说,第一阶段能满足基本的内容访问,而如果能做到第四阶段,才能算一个完整的信息无障碍网站。商城业务中,无障碍体验一直缺乏相应的规范与测试流程,因此通过本次 PC 首页的改版实践,输出了一份针对商城频道页的信息无障碍开发规范,内容包含:
● 访问路径设计规范
● 语义化规范
● 读屏测试规范
未来将借由这份规范,陆续实现商城其他业务的无障碍体验优化。
综上,本次改版对于开发者来说最大的变化,就是本地开发体验更加舒服、发布风险有所降低、故障追溯更加完善,而对用户来说,页面加载跳动感大大减小,视障用户的体验终于得以照顾到。作为商城桌面端的入口与门面,首页的改进一定不止于此,希望每一次的改版都能有一丝的优化,使得首页这个项目趋近完美。