## 苏宁前端基础工具集
文 / 禹立彬
苏宁前端是一个有着100多人的团队,承担着整个苏宁的前台页面开发,在开发中,不可避免的遇到了传统 Web 前端的模块化、工程化难题。而随着前端开发的进步,Node.js 后端开发,Hybrid、Weex 等新技术也对工程师提出了更高的要求,一方面,代码的质量需要保证,需要一定的规范,另一方面新技术的使用又要求一定的灵活性,在技术上一直是一个不好平衡的问题。
作为一家电商企业,苏宁有着与其他电商网站相同的特点,展示性页面非常多,开发非常碎片化,开发一个强约束性的前端框架,无法应对反复变化的产品需求和多样的页面展示模式,最终我们选择了另外一条路,通过开发大量的前端工具和配套的组件,让工程师自由搭配,完成想要的功能。
本文主要介绍苏宁前端常用的几个前端工具,包括自研和开源工具。
苏宁的前端工具和苏宁的前端技术栈是贴合的,主要分为几个大的部分,下文将一一阐述。
### 通用依赖类
#### SNPM(Node.js 私库)
npm 已经成为现在前端代码部署的主要方式。经过代码复制,苏宁前端的代码部署方式,已由 yeoman 全面转变为 npm 部署。在经过 Webpack、Gulp 等流行工具构建后,已可应用于生产环境。Node.js 的组件,更要部署在 npm 上,统一的部署方式,也可以减少学习成本。对于企业来说,并不是所有的代码都可以开源,所以部署一个 npm 私有仓库成为必须,并且在苏宁的生产服务器上,由于安全原因,服务器并不能直接访问外网,不能通过 npm 官网完成 node_modules 安装,只能通过建立一个 npm 私库来解决问题。
苏宁的 npm 私库使用了 Sinopia 搭建,接入了公司内部的 Passport,工程师通过用户名和密码实现登录,发布也非常自由,所有人都可以自由地发布自己的组件。
在部署 npm 私库时,特别需要留意的是,需将 npm 私库所在的服务器,设置为全网可访问,否则可能会出现由于连接失败,导致 node_modules 安装错误,Node.js 项目无法运行的情况。
需要注意的是,npm 私库并不能解决所有的 npm 包安装问题,比如对于 PM2 来说,安装 PM2 会安装自己的插件,会连接 PM2 的官网下载,而不是通过npm私库,好在这个插件不是必须的,下载失败后依然会完成安装。
Sinopia 有个 bug,当安装 scoped package 时,比如安装@types/node,会将@转义,而 npmjs 官网是不能转义@的,需要手动修改文件,具体可见 https://github.com/rlidwka/sinopia/pull/280/files。
### Node.js 工具
#### Node-PM2-PVD(PM2 的豆芽报警)
PVD.js 是苏宁 Node.js 项目标准配置中的一个重要的工具,它基于 PM2 开发,通过侦听 PM2 的进程信息,发送 Node.js 应用的信息到豆芽上。豆芽是苏宁内部使用的 IM 软件,每天工程师都使用豆芽来互相沟通工作。PVD.js 获取到报错信息后,可以用最快的方式发送错误信息到干系人那里,联系开发者尽快进行线上修改,保障线上 Node.js 项目稳定运行。
进程错误是最高级的 Node.js 错误,由于 Node.js 本身的机制,报错后,只能依赖
PM2 的重启,相比较错误日志,PVD.js 没有延时问题,从根本上解决 Node.js 严重错误报告的问题。
其他企业可以将豆芽替换成其他各种 IM,如 QQ、微信等。通过对 IM 端进行定制开发,提高报警的及时性。
#### NODE-snRequest(自带长短缓存的 Request)
Node.js 在苏宁主要用来做前后端分离,功能上做前台页面渲染,所需要的数据都来自于请求服务,snRequest 是一个基于这样业务场景的请求组件,配置了统一的缓存策略,实现基于 Redis 的长短缓存。
短缓存策略主要是针对需要频繁请求,对于不会经常改变的接口,尽量使用本地缓存来代替发送一次请求,加快用户请求的响应时间,提高性能。
策略如下:
- 使用 snRequest 发起请求。
- 判断请求参数中,是否使用短缓存。如果不使用短缓存,则直接发起 Request 请求。
- 获取请求参数中的短缓存有效时间,如果没有配置,则取默认时间。
- 连接 Redis,获取 Redis 缓存。如果 Redis 中不存在缓存或者 Redis 连接失败,则进入第6步。
- 获取缓存存入时间,判断是否过期。没有过期,则返回缓存作为结果。
- 发起请求到后端服务器。
- 返回数据作为结果,并更新 Redis 缓存。
长缓存策略主要是为了高可用,针对的是由于各种故障导致的后台服务不可用,请求失败等情况。这时通过缓存最近一次的数据,提供给前台用户,防止用户访问时开天窗,造成整个网站不可用的情况。
长缓存策略也比较简单,见下:
- 使用 snRequest 发起请求。
- 发起 Request 请求。
- 如果成功,则返回数据作为结果。
- 比对返回数据与 Redis 数据是否一致,如果不一致,则更新长缓存。
- 请求失败,判断请求参数中,是否使用长缓存。
- 连接 Redis,获取 Redis 长缓存。
如果 Redis 中不存在缓存或者 Redis 连接失败,则读取本地硬盘文件。
本地硬盘文件中存在长缓存,则返回缓存作为结果。
本地硬盘文件不存在长缓存数据,则返回失败结果。
- 返回缓存作为结果。
长短缓存策略业界中已有成熟的解决方案,只是在某些细节策略上不同,在 Node.js 中适合苏宁业务场景的比较难找,所以最终我们选择自研带缓存的请求工具。
### Web工具
#### MOCKTOOLS(前后端联调平台)
在开发业务的过程中,前端工程师经常会遇到前后端联调的难题,主要是以下三种场景:1. 后端工程师无法提前开发完依赖的接口,所以前端工程师需要等待;2. 后端工程师修改了数据结构,但是没有对前端工程师说明;3. 由于网络的关系,前端工程师无法请求到后端工程师的接口。
为了解决这些问题,我们开发了一个联调工具(如图1所示),类似于 Mockjs,在流程上,优先让后端工程师在平台上把接口定义写好,前端工程师直接调用联调平台的 Mock 数据来开发,开发完成,测试上线前,再将接口地址更改为真正的请求地址。MOCKTOOLS 实现了对接口入参的数据类型验证等功能,保证了接口地址更改后,一次联调成功。
图1 MOCKTOOLS 平台上的接口详情页
像 Mock.js 的本地 Mock 工具,它虽方便易用,但对于多人联调力不从心,所以我们用
Node.js 研发了这个在线的 Mock 平台,主要的出发点就是一份模拟接口,可以被多人使用和维护,有利于多人开发协作。
#### Minify(合并工具)
Minify 是 Google 开发的一个动态合并 CSS、JavaScript 的一个工具,使用 PHP
开发,可以动态地合并文件。优点是合并完文件后,可以动态修改引用的文件路径。比起
Nginx 的简单合并,Minify 在 CSS 合并上有独到的优势。
例如在目录下有 a.jpg、a.css 、folder/b.css,a.css 内容是:
b.css 内容是:
如果简单通过 Nginx 合并后,会导致其中的一个 URL 路径不正确:
而通过 Minify 合并后,会替换路径为绝对路径,解决路径不正确的问题。
Minify 是开源软件,配置都有完善的文档,但是 Minify 基于 PHP 开发,在前端部署上还是有一定的不方便,Nginx 服务器上还是通过 FPM 方式。在后面,我们会将它改写为 Node.js 版本。
#### 前端信息收集工具
前端页面的打开速度也依赖工具去收集,对于前端来说,在内网访问自己网站的速度自然很快,但用户端到底是怎么样的,就不那么清楚了。为了帮忙自己做清晰判断,苏宁使用了很多的网络工具。
基于服务器机房的拨测,通过采购云供应商的服务,可以查看到从每个地区的服务器机房访问苏宁易购网站时的速度,设定采集策略,苏宁主要收集的是首屏速度。首屏速度提升了,说明改版成功。另一方面,通过拨测,也可以更早地发现劫持等安全问题。
基于访问者本身的信息收集,对于用户侧来说,根据用户本身的访问速度,来判断用户端真正的性能,好处是处理掉噪点后,数据比较真实,可以反映真正的用户状况。可以同时收集
JavaScript 错误,快速解决问题。坏处是,数据量非常大,全量收集对数据收集服务器的压力很大,数据的处理也是需要考虑的一个方面。
对于机房的拨测,业界都有成熟的解决方案。如果对网站可用性要求非常高的话,可以根据需要采购一些这类的产品。对于浏览器侧的信息采集,成本会高很多,建议有条件的公司可以选择自研。
### 组件工具
#### FE_PACKAGES (组件库)
npm 私库只具有很有限的功能,所有的信息只能从 README.md 中获取,对历史版本的查阅也不是很方便。在 snpm 的基础上,我们开发了苏宁的前端组件库 FE_PACKAGES(以下简称 FP)来解决这些问题。
FP 从 snpm 中同步组件到自己的界面中,格式化 README.md 作为展示,并且可以方便的选择历史版本,提供了对组件的分类和搜索功能,查找自己想要的组件非常方便,如图2所示。
图2 前端组件库 FP 界面展示
在分类上,提供了默认的 Node.js 、Web、Weex 选项。
对于 Web 组件,当 Web 组件开发者在 package.json 信息中注明了包含 Demo 时,FP 会读取配置的 demo.html,直接预览,并且可以像 codePen 一样在线调试代码。
### 脚手架工具
现代前端使用 Node.js 可以方便地实现环境的搭建和安装,其中一种方法就是开发脚手架,使用命令行命令,完成想要的项目搭建。比起 Electron 来,命令行的优势主要是文件小,可以快速地下载。
#### NODE-Generator(Node.js 脚手架)
Node.js 脚手架是初始化一个标准 Node.js 项目时使用的工具,只包含了最少的文件,并不含有框架、库等任何依赖,把更多的自主选择权,留给 Node.js 开发工程师。
苏宁的 Node.js 标准服务器,使用 PM2 管理进程,集成了标准的 Node.js 重启的
shell 命令,所以 Node.js 脚手架支持 CI/CD 平台的要求,包含了
package.json,文件里写入了对应环境的标准 scripts,用来针对不同服务器环境的启动,如图3所示。例如,对于测试环境对应的 npm run sit,生产环境对应的则是 npm run prd,分别执行注册在 package.json 里 sit 和 prd 分支下的脚本。Node.js 脚手架还写入了发布打包的配置文件,和 PM2 启动配置文件。同样的 Node.js 脚手架也集成了豆芽报警,作为其中的标配。
图3 Node.js 脚手架初始化项目的过程
在 Node.js 标准脚手架的基础上,还有小伙伴们开发了基于 Express、Thinkjs 等
Node.js 框架的专用版脚手架,应用到自己的项目中。
#### Webpack-Generator(Webpack 脚手架)
针对不同的业务场景,苏宁前端也开发了很多针对 Web 的脚手架,这些脚手架都是基于
webpack 的,实现一键 build,比如针对 Weex 项目的 Weex 脚手架,针对 CMS 项目的 CMS 脚手架。通过这些脚手架,同化各种开发环境,各种插件的版本也得到很好的控制。
### 后记
将来,我们还计划开发更多的工具,来面对新的挑战和新的技术,老的工具也与时俱进的升级,为工程师开发消除障碍。
对于工具开发来说,最危险的是弃坑,当我们使用外网上的开源组件时,遇到组件停止更新,Bug 不修改,就会对自己的项目开发带来很大的困难。对此,使用 React Native 开发 App,并用过某些组件的开发者想必都感同身受。而对于公司内部的工具和组件也是这样,技术积累不是一朝一夕能达到的。保持稳定的开发团队,当团队成员发生变动时,保证工具开发的正常交接,这一点必须优先保证。
同时,技术的敏锐度和团队的风气也非常重要,良好的技术氛围,会让工程师更多的关注于技术,更愿意研究与分享,为技术大厦添砖加瓦。