本文由 Worktile 产品研发部负责人 徐海峰分享
前端过去十年可所谓是百花齐放、百鸟争鸣,技术也是日新月异,前端社区已经被打上娱乐圈、学不动了的标签,也就是在这十年左右时间,前端工程化渐渐的进入了大家的视野,越来越多的公司开始重视前端工程化,那么什么是前端工程化可以参考不久之前我在知乎的一个回答 什么是前端工程,近两年前端工程化基本趋于稳定,每个公司都沉淀了自己的一套工程化实践方法和工具,那么这篇文章主要分享一下 Worktile 从2013年至2020年的前端工程化之路,介绍一下一个初创公司在过去8年的前端工程化演变的过程,一张简单的图回顾一下过去8年:
2013 Worktile 诞生
时间回到 2013年,那年我从一个服务端工程师刚开始接触前端,第一次听说 Hash 路由:浏览器 URL 变化后整个页面居然没有刷新,那年同时也是 Worktile 诞生的那一年,对于一个团队协作平台 SaaS 产品,我们主要面临的前端工程化问题就是:前端框架选型。
对于当时来说可选择的框架并不多,Vue,React 还没有出现,最终我们选择了 Angular.js 作为前端开发框架,主要原因就是:Angular.js 适合做 Web App 体验的 SPA 单页应用,支持数据双向绑定,数据驱动开发,避免手动操作 Dom。
选择了 AngularJS 后,我从开发、构建、部署三方面阐述一下当时的工程化水平。
- 开发:第三方依赖包手动拷贝和维护、目录结构按照框架功能划分、所有状态都存储在 $rootScope;
- 构建:通过 uglify-js2 手写了一个 Node 脚本压缩合并,没有使用构建工具;
- 部署:我记得印象特别深刻的就是部署,每次发版 CEO 需要通过 Beyond Compare 一个文件一个文件对比,然后我们一起看一下修改内容,决定是否同步更新到服务器,然后重启服务。
现在回过头看 2013 年,感觉当时的前端工程化基本处于原始社会,那年的 CEO 和 CTO 也都还是一个开发工程师,一共就4个人,一个前端还负责UI设计,这基本就是一个刚成立的创业公司的形态,产品能快速跑起来就行,要啥工程化。
2014-2015 年,LessChat 诞生
时间到 2014-2015年,因为要做一款 Slack 的中国版产品 LessChat(后来基于 Lesschat 的基础之上新增了任务、网盘、日历模块,也就是现在的 Worktile),因为是一个新产品,基于过去的经验肯定需要重新考虑整个前端的架构设计,我们同样从开发、构建、部署三方面阐述一下当时的工程化:
- 开发:开始引入包管理工具 Bower ,按照模块划分目录结构,模块基本意味着一个领域,避免一个组件一个功能分散在不同的文件夹,同时前端引入数据层 Service 作为管理状态,不再是所有的状态存在 $rootScope 中了,其余的就是引入了一些 Lint 工具;
- 构建:先后引入了 Grunt,Gulp 构建工具;
- 部署:使用中转机 Shell 脚本一键部署,不再使用 Beyond Compare 对比上传。
2016 Webpack + 模块化
2016年,最大变化就是构建工具和包管理工具的转变,从 Gulp 切换到 Webpack, 从 Bower 切换到 npm,其实工具的转变本质的原因就是:模块化,前端开始重视模块化开发了。
那么 Webpack 无疑是前端开启模块化的转折点,同时 Webpack 支持 CommonJS 规范,这样使得前端可以使用 NPM 的生态,Bower 自然就被淘汰了。
在前端引入模块化之前,为了避免命名冲突,一般会采用命名空间和自执行函数的方法,引入模块化后命名冲突的问题就随之而解,再也不用自己包装一个自执行函数了,那么除了解决命名冲突外,引入模块化带来的价值远不止于此。
- 便于依赖管理:再也不用手动维护 Script 引入的顺序问题
- 利于性能优化:可以利于 Webpack 打包工具实现合并,按需加载,更细粒度的模块划分搭配动态加载
- 提高可维护性:按照模块划分,每个文件单一职责,组织结构清晰合理,可维护性也就大大提高
- 利于代码复用:代码复用不是模块化的最终目标,反而是附带的价值
使用模块化和组件化最重要的意义是在于分治(分而治之),可以说中大型前端项目终于变成可维护起来,这也是前端工程化的意义所在,如果前端不是变得越来越重要,越来越重,也不会有什么前端工程化。
因为我们采用的是 Angular.js 框架,基本上可以说是不支持模块化,可以通过下图感受一下 Angular.js 的模块化。
这里顺便介绍一下模块化的发展历程:
伴随着 2009年 Node.js 的诞生,CommonJS 进入了大家的视野,CommonJS 是 JavaScript 的静态模块化规范,适合 Node.js 不适合浏览器环境,前端资源除了 JS 外还包括 CSS,图片等,同时 CommonJS 是同步阻塞式加载,无法实现按需异步加载。为了让浏览器环境也可以使用模块化,后来诞生 AMD/CMD 规范。不管是CommonJS还是AMD/CMD,他们都是在语言规范缺失时代背景下的产物,应用场景单一,模块无法跨环境运行,构建工具不统一,不同规范的模块无法混合使用,模块复用性不高。直到 2015年 ES6 Module 一统天下,前端模块化终于趋于稳定。
2017 前后端分离
2017年, Worktile 前端工程化最主要做了以下两件事:
- 全站模块化(2016年引入模块化后历史模块迁移需要时间,基本一年的时间陆陆续续全部迁移完毕)
- 前后端仓储分离(2017年之前,Worktile 前后端都是在一个仓储的,虽然是 SPA 单页应用,HTML 是服务端渲染)
说到前后端分离,这里简单介绍一下前后端分离的两种模式
全静态项目
对于前端是全静态的项目,部署后 HTML 文件是不能强制缓存的,Nginx 需要配置 try_files 指向到 index.html。
HTML 模板是服务端渲染
对于服务端渲染 HTML 模板的模式下又有两种子模式:
- 第一就是 HTML 模板是由服务端团队负责,这种模式工程化需要解决前端构建后的资源如何告知服务端更新资源的问题;
- 第二种子模式是大前端,前端团队使用 Node.js 实现渲染 HTML 模板,甚至和前端项目在一个仓储中维护。
2018 前端工程化元年
2018年,我认为是 Worktile 开启前端工程化的元年,2018年之前我们一直在使用 Angular.js ,虽然过去也一直在做架构调整,引入了模块化等等,但是不可否认 Angular.js 是已经被淘汰的前端框架,所以当时大胆的升级到了 Angular,主要考虑有以下几个原因:
- 2018 年,Worktile 产品计划做 8.0,一个高度配置化的项目管理模块,复杂度急剧上升
- 我们想开始搭建自己的组件库,基于 Angular.js 感觉好像没有太多的激情和动力
- 继续使用 Angular.js 也会面临着招人困难的问题,人才培养也是如此
- Angular 框架正式发布后也逐渐趋于稳定,可以投入生产使用
- 至于为什么没有选择 Vue 和 React,主要我们比较符合 Angular 的哲学,大而全解决我们所有的痛点,人少,没有精力折腾
基于以上几个原因,我开始通读了一遍 Angular 官方文档,初步跑通了 Angular + Angular.js 混合方案,然后做了一次技术分享,大家也比较有激情,就直接引入 Angular,同时也因为 Angular 一起引入了 TypeScript和RxJS。
除了升级 Angular 外,2018年我们还做了以下几个重大的工程化改进:
- 搭建了内部的组件库: ngx-tethys(对于当时我们团队规模和业务压力来说是不敢想象一个前后端加一起10人左右的开发团队居然搭建了内部的组件库);
- LESS 迁移 SASS,bootstrap3 升级到了 bootstrap4(样式的天坑也是需要填的,好在这一切都踩过来了);
- 使用 RxJS 作为状态管理,封装了适合我们业务的轻量级状态管理类库,更多细节可以查看 Angular 真的需要状态管理么?
升级 Angular 混合应用也会遇到了一些问题:
- 第一就是性能问题,Angular 的 Zone.js 集成到遗留系统中会大概率出现性能问题,需要把所有影响性能的绑定事件都改成
runOutsideAngular
; - Angular.js 和 Angular 路由还是会有一些冲突,同步路由等问题,需要不停的踩坑尝试,有可能写了一些现在永远看不懂的代码,Angular.js 使用的是 ui-router,并不是官方的路由;
- 虽然 Angular.js 和 Angular 之间的服务和组件是互相可以调用的,弹出模态框的机制是不同的,Angular.js 使用的是 ui-bootstrap,Angular 使用的是 CDK,所以层级的冲突问题也比较棘手,fork bootstrap 的组件库修改源代码解决模态框冲突问题。
回顾 2018年,因为技术升级和新业务确实带来了很多压力,我们很多的技术选型是加重了大家的负担,但是这一切是非常的值得,因为技术升级给整个前端团队带来了巨大的动力和能力的提升,很多小伙伴在这一年可以熟练掌握 TypeScript 了,也具备了独立搭建组件的能力,可能过去想都不敢想,这么复杂系统的升级和迁移都走过来了,还有什么迈不过去的呢?(我永远都记得前端刚开始使用 TS 的各种 any)
2019 PingCode 诞生
2019年,我们开始做研发管理工具(2020年10月独立为 PingCode),这又是一个新的跨时代的产品,之前我说过技术的变革往往和产品息息相关,在 Worktile 这8年,基本经历了四次大的产品变动,前端工程化基本也伴随着这四次产品变动而变动,对于这一年,开发、构建、部署几个环节都做了突破的变化。
开发
对于开发,除了继续选择 Angular 框架外,最重要的是引入了微前端,之前我也分享过: 使用 Angular 打造微前端架构的 ToB 企业级应用
- 自研了 ngx-planet 微前端框架,所有子产品独立仓储,独立部署
- 因为独立仓储,所以需要搭建业务组件库,抽取业务通用组件
- 开始重视单元测试,基础库强制编写单元测试,过去的组件库开始补充测试
- 引入了 wt-cronus 工具一键克隆、更新、安装、启动多个应用,解决多个仓储难以管理的问题
- 前端是全静态的 SPA 单页应用
此时你肯定会问:为什么不采用 Monorepo?最后我会详细介绍一下当时为什么采用微前端吧。
构建
2019 年前后端开始引入了 jenkins 做持续集成,因为前端采用了微前端,所以构建采用了基于 Angular CLI 的 angular-builders 扩展构建器,即能使用 Angular CLI 带来的便捷,又可灵活的扩展 Webpack 的配置适配微前端的框架。
部署
和构建一样,2019年前后端使用 jenkins 做持续部署,所有服务都采用 Docker 容器部署,同时采用 kubernetes 进行容器的部署、扩展和管理,全面实现了自动化运维。
规范和流程
除了开发、构建和部署几个方面的提升外,2019年开始引入了 《Commit Message 提交规范》、《分支管理规范》(类似 GitFlow)和 《Code Review》。
- 引入 Commit Message 规范,对于类库的项目来说可以自动生成 Changelog,对于应用来说可以更好的追踪 Git 提交记录,同时和 PingCode Agile 的用户故事进行双向关联;
- 分支管理配合自动部署和发版流程,让版本管理和自动部署更加规范和流程化;
- 起初引入 Code Review 比较担忧的问题就是:是否会加重大家的工作量,经过实际效果来看,Code Review 带来的利远远大于弊,不仅仅每个人的代码质量提升了,阅读和理解别人代码的能力也得到了提升。
为什么选择微前端?
任何技术选型核心的原因还是取决于业务需求,如果可以通过不引入微前端也能解决公司遇到的工程问题,那么最好就不要引入微前端,那么我们当时为什么没有采用 Monorepo 而采用微前端架构呢,主要原因如下:
- 兼容遗留系统:刚开始做 PingCode 的时候是计划和 Worktile 集成在一起的,Worktile 是一个庞大的 Angular + Angular.js 混合应用,一是编译打包速度已经很慢,二是很多工程的东西不规范,修改起来很麻烦,采用新的架构设计反而会更简单(后来产品上线之前最终选择了独立,并没有集成 Worktile);
- 多团队多子产品:起初就包含了4个子产品,每个子产品都是独立的 Scrum 团队负责,而且预计将来可能会有更多的团队做更多的子产品,截止到目前为止包含内测和下线的子产品共 8 款,不管是团队管理还是仓储管理肯定是独立分开比较理想;
- 应用市场:在产品规划初期,我们目标就是想要打造类似 jira 一样丰富的应用市场,等于除了加载官方的子产品外还会加载应用市场的内嵌应用,甚至以后还会有第三方开发内嵌应用,目前『待办』和『甘特图』两款内嵌应用就是我们内部做的应用。
当然选择微前端后虽然解决了很多痛点,但同时也带来了一系列的工程问题:
- 独立仓储后必须要构建业务组件库
- 业务组件库越来越庞大,打包、发布速度越来越慢?
- 不支持真正意义上的独立开发,本地开发要启动 Portal 等多个服务
- 子应用本地开发需要手动刷新页面,不支持 LiveReload 和 HMR
- 整个系统太复杂,了解整体架构变得更加的困难
- 没有样式隔离,子产品部署还需要部署 Portal
遇到问题并不可怕,一个一个的解决即可,如果当初选择 Monorepo 我相信也会遇到其他的工程问题。
2020 规范化 & 流程化
伴随着微前端带来的一些列工程问题,我们进入了2020年,2020年主要就是解决这一系列问题:
- 子产品真正意义上的独立开发,不需要启动 Portal 等多个服务
- 真正独立开发后的 LiveReload 和 HMR 也逐渐支持
- 样式隔离方案的确定和落地,每个子产品完全独立部署
- 内部的 devkit 工具集完善,通过
wpm release
和wpm publish
简化发版流程
组件文档
过去两年,我们打造了组件库和业务组件库,那么组件库的文档还是比较粗糙,2020年另一个重要的工作就是完善文档,为了完善文档开发了一款文档生成工具 Docgeni - Angular 开箱即用的组件库文档生成工具(后续会正式开源出去),基于 Docgeni 我们迁移并重构了组件库和业务组件库的文档。
测试覆盖率
组件库和业务组件库的测试覆盖率提升到了 70%,因为过去的债务太多了,我们对于服务端的测试覆盖率要求一直比前端高,服务端普通子产品的单元测试覆盖率基本都 80% 以上,补充测试是一个持之以恒的工程化问题,同时也定义了公司的测试覆盖率指标。
开发规范 & 流程制度
2020 年除了上述的以外,我觉得是 Worktile 开始走向规范化、流程化的一年,WTC(技术委员会)发布了16个开发规范和16个流程制度。
2021 以及未来
以上是 Worktile 过去8年的工程化之路,作为一个初创公司,其实很不容易,因为没有太多的资源投入,我们一直保证产品高速迭代的同时完善前端的工程化,不管组件库还是开发工具,都是所有业务团队前端成员共同努力的结果,可能横向和大厂对比,我们还很落后,但是对于前端团队规模20人还不到的企业来说已经很不容易了。
2021年才刚刚开始,我们也将会继续把前端工程做的更好,一步一步完善自己,2021年以及将来我认为主要从务实和平台化两方面持续发力。
对于务实:过去我们已经构建了组件库、微前端、Docgeni、Devkit 等基础设施,那么未来需要持续完善,测试和文档的持续改进,其二就是会引入更多的自动化工具简化我们的工作流程,最后就是开源、回馈社区,过去我们开源了 微前端 ngx-planet ,之后会把我们更多关于前端的工具、类库开源出去,回馈社区,包括我写的这篇博客,也是希望让更多的人了解 Worktile 的前端工程化之路,或许对于同样是初创公司的你一些参考和帮助。
平台化:在务实的基础上,如果精力和资源允许会引入一些平台化的工具,比如:前端监控平台、灰度发布系统等等。
写在最后
我本人一直见证着 Worktile 前端工程化一步步走到现在,把我们的心路历程过分享出去需要勇气,我没有去过大厂,也不是很清楚前端工程化做的特别优秀的企业到底是什么一个状态,如果你对此有什么想法欢迎大家一起交流(特别是国内使用 Angular 框架的企业)。最后打一个广告:我们正在做一款比 Jira 更好用的研发管理工具 PingCode, 欢迎大家注册了解!