前端技术栈演进之路

在随手科技这几年,兼任了金融前端团队的负责人,将团队从只有 1 名前端开始,扩展到了 10 多人的前端团队,推动了整个金融前端技术栈的建设及发展,是一段挑战自己未知领域的有趣之旅。

我总结了一下,这几年团队共经历了这样几个阶段:

  • 单块项目(服务端渲染) > 项目分层(前后端分离) > 项目拆分(按业务拆分)
  • 前端模块化 > 组件化 > 工程化
  • 事件驱动 > 数据驱动
  • 后台选型:JSP 服务端渲染 + EasyUI > 前后端分离 + Element
  • 浏览器 > 服务端

下面分几个阶段总结下。

阶段1 服务端渲染

2016 年之前,由于团队和项目规模所限,人员构成以后端开发为主、前端开发为辅(就一个前端开发),只能通过最基础的技术栈,以后端人员最熟悉的技术着手进行业务开发并快速上线,因此技术选型都是偏向服务端的:前端只需按照设计师要求切图并输出静态页面(HTML + CSS),加上一些基础的 ES5 实现所需的动画效果和基础交互效果,后端套成 JSP (或 freemarker velocity thymeleaf)进行服务端渲染。后端开发一般会这样解决问题:

  • 通过 SiteMesh 等框架在 JSP 中将网页内容和页面结构分离,以达到页面结构共享的目的;
  • 通过 tld 文件自定义标签,给 JSP 页面提供一些便捷工具(如货币、时间、字符串格式化);
  • 对于一些复杂的页面交互逻辑,在 JSP 页面上通过 <script> 标签直接引用所需的 JavaScript 文件。

作为后端开发人员会觉得:这么写代码也没什么问题啊,毕竟身边的同事都是这么写的,项目也跑得好好的。但问题在于,后端开发写 JS 都是很业余的,而且随着功能越做越多,业务越做越深,前端脚本开始变得难以扩展与维护:

  • 脚本间依赖关系脆弱,加载顺序需要手工维护,一不小心顺序乱了就 JS 报错;
  • 脚本中潜藏着各种全局变量(函数),导致命名冲突、作用域污染,没有合理的进行前端模块化;
  • 各页面没有主入口脚本,代码不知从何看起……

阶段2 前端模块化

2016 年初,我着手重构前端的第一件事就是将前端模块化。

JavaScript 这门语言(或者说老版本 ES5),最为糟糕的地方就是基于全局变量的编程模型(如何避免使用全局变量?),并且由于不支持“类”与“模块”,使得稍具规模的应用都难以扩展。

一番对比和调研 AMD 和 CMD 规范的相关框架之后,第二阶段决定引入 Require.js(英文中文)这个前端框架。Require.js 以模块化的方式组织前端脚本,通过 AMD 规范定义的两个关键函数 define()require() ,我们可以很轻松的在老版本 ES5 上实现模块化功能,解决模块依赖、全局变量与命名冲突的问题,并提供了统一的脚本主入口。

Require.js 入门教程参考此前博文

阶段3 项目分层(前后端分离)

前端模块化虽然提升了项目的可维护性,但由于此阶段前后端项目仍然强耦合,项目和团队仍存在以下问题:

  • 前端完成的 HTML 页面需交付给后端转换为 JSP 页面,多一道无谓的工序。更重要的是,后续前端任何页面修改,都需要通知后端进行同步修改,操作繁琐且易出错;
  • 由于 JSP 页面由后端编写,后端开发如果觉悟不够或者贪图方便,在 JSP 页面中各种 JavaScript 代码信手拈来、Java 变量和 JS 变量混用,导致前后端难以解耦、代码后续难以维护;
  • 后端开发无法专注于业务开发,大量精力浪费于编写前端样式及脚本,分工不明确、不专业。

更为重要的是,当下前端领域日新月异,ES 新版本、层出不穷的新框架,SPA 单页技术、CSS 预处理语言、前端性能优化、自动化构建…… 受限于项目结构、迫于后端人员能力,新技术无法推广落地,前端人员能力也无法完全施展。

2016 年中,我开始渐进式的推动前后端分离,为了不让步子太大扯着蛋,前端主体技术栈仍采用 HTML + Require.js + Zepto,后台采用 EasyUI,并重点解决下面两类问题:

引入自动化构建工具

传统的前端是无需构建的:前端开发编写的 HTML、JS、CSS 可以直接运行在浏览器中,代码所见即所得。但这种传统方式也带来了以下问题:

  • 无法根据不同环境构建代码,解决各环境间的差异。例如不同环境下资源引用路径是不同的,生产往往会使用 CDN 域名;
  • HTML 页面之间各种代码重复,例如一些全局 rem 设置、全局变量、事件,公共样式、脚本、页面布局,提升了维护成本;
  • JS 脚本没被检查(如静态语法分析),团队协作时代码规范程度无法保证;没有单元测试,潜藏缺陷容易直接流到生产环境;
  • CSS 样式无法扩展、浏览器兼容性问题处理复杂(如需手工添加厂商前缀);
  • 静态资源没被合并、压缩,体积大、数量多,导致用户请求慢;
  • 静态资源没被 hash,带来版本管理和缓存问题,更新困难;
  • 静态资源需手工打包上传,操作繁琐;

为了解决这些问题,这个阶段我引入了自动化构建工具 Gulp.js,一些使用实践请参考此前博文。对于一个前端新手来说,这是一个很大的思维转变,自动化构建极大提升前端项目的工程能力,“构建”阶段能够实现很多之前无法实现的效果。

引入 CSS 预处理语言

前后端分离后,由于引入构建工具,前端开发能够自由发挥的空间更多了,这个阶段我们还引入了 less,一门 CSS 预处理语言,提升了编写前端样式的效率。

API 接口设计

前后端分离的另一个重点,在于数据与页面交互方式的改变——服务端渲染 > 前端渲染。因此定义一套统一的 API 接口规范尤其重要。这个阶段我解决掉的问题:

  • 跨域方案选型:代理、JSONP、CORS,平衡了浏览器兼容性和开发便利性最终采用 CORS 方案;
  • 接口规范:编写后端 API 网关层框架,大一统全公司项目的接口入参、出参规范及处理流程;
  • 文档管理:前期手工编写 Markdown 文档 > 后期使用 SwaggerUI 自动生成文档;
  • 搭建 API Mock Server,前期 gulp-mock-server > 后期 RAP Mock Server ,大大提升前后端并行开发效率。

阶段4 项目拆分

2017 年开始,随着业务做大(新业务越做越多,每周还搞各种运营活动)、人员增多,原来的一两个前端项目已经不能满足快速增长的需求了。这个阶段浮现出来的新问题:

  • 人员多、特性多,由于只有几个前端项目,并行开发时 git 分支难以管理,代码合版时容易发生冲突;
  • 测试环境当时只有两套,测试时容易发生代码被覆盖的问题,特性间不好并行测试;
  • 生产发版风险较大,出问题时只能整体回滚,粒度太大,影响前端项目内的其它正常特性。

为了解决上述问题,2017 年我们按业务、活动两个维度进行了项目分拆:

  • 业务
    • 帐户项目
    • 非标项目
    • 基金项目
    • XX 项目 …
  • 活动
    • 首投活动
    • 邀请活动
    • XX 活动 …

各业务、各项目分而治之,由专门的前端组长统筹、排期、开发、发版,满足各业务的个性化需求及节奏差异。

为了进一步提升开发效率,解决模块及组件的复用问题,这个阶段还:

  • 引入了新版 ES6 + Babel 编译器提升 JavaScript 开发效率;
  • 引入了 MV* 库 Vue.js + 自动化构建工具 Webpack,解决之前的 DOM 节点操作 + 事件驱动机制的开发效率低的问题。
  • 引入了 NPM 包管理器,搭建团队专属的仓库(控件库 + 组件库),提升代码复用性。

阶段5 重回服务端渲染

前端技术近年来日新月异,目前 Node.js 的应用已经铺天盖地,Node.js 中间层的出现改变了前后端的合作模式,各大公司前端都把 Node.js 作为前后端分离的新手段,并且在测试、监控等方面沉淀了大量内容。

2018 年起,前端团队也开始在预研 Node.js 技术、搭建各类基础库并尝试在生产中投入使用。以史为鉴,展望未来,只要我们有不断突破自我的勇气,一定能克服困难,让新技术在公司中落地开花,进一步提升团队的开发效率,为公司创造更大的价值。

待续。

参考

Web前后端分离开发思路

前后端分离后的契约

什么是基于数据驱动的前端框架?