本文基于笔者在腾讯的项目经验,从真实场景出发分析一个中型 Web 应用从立项到上线稳定运行的平稳解决方案,力求既不太空泛以至于看完了仍然找不到落地的点,也尽量不会特别纠结于个别细节导致没有相关使用经历的同学无法感同身受,而是从宏观到方法论,分析整个流程中我们需要用到的工具、方法与规范,给大家提供一个参考。

本文适合具有一定经验的初中级前端开发者,如果有相关问题,也欢迎与我交流。

目录

  • 项目构建的搭建,关键词:webpackreact/vue clisteamer组件库
  • 代码的规范约束,关键词:typescripteslintprettier
  • 测试与测试部署,关键词:测试部署方案docker
  • 日志查看与脚本错误的监控,关键词:sentryvconsolemlogger
  • 版本发布更新,关键词:发布系统灰度发布
  • 访问量实时监控

起步:项目构建的搭建

使用 webpack 搭建脚手架

目前在一般的项目中,我们都会使用 webpack 作为搭建开发环境的基础,而 react 和 vue 也各自提供了 cli 工具用于开发一个中小型项目,react 提供了 eject 功能让我们可以更加自由的配置 webpack,而 vue 脚手架虽然没有提供类似命令,但是借助 webpack 工具链我们几乎也可以自由定制每一个角落。

不过,这里我的建议是,如果是个人项目或小型项目,我们可以基于 react 或 vue 的脚手架进行更改使用,对于一个具备一定规模的项目团队,建议还是自己维护一套单独的 webpack 构建环境,原因如下:

  • 由于我们一般需要在项目中接入各类司内工具、支持高级API和语法、同时支持 react/vue、构建目录定制化等各类工作,实际上 80% 以上的工作我们都需要在模版之上自行添加,这个时候我们再用脚手架带来的收益已经非常小了,反而还会受制于项目的初始目录结构。

我们在自定义脚手架的 webpack 构建的时候,也需要梳理出一定的目录规范与约束,这样也有利于提高后期脚手架的可维护性和扩展性,一般来说,我们也要对脚手架中的公共部分和项目私有部分进行分离,对于一个具体项目而言,可以不用改动 webpack 的项目公共部分,这样也有利于减少不同项目之间的切换成本,对于我们目前的项目,一般会有如下两个目录:

1
2
3
4
5
6
7
8
- project
- project.js
- config
- feature
- plugins
- rules
- script.js
- webpack.base.js

对于一个项目,只需更改 project 下的配置。

这里我也推荐一个前同事做的steamer研发体系,在从中也可以找到很多相关参考,最简单的方式,就是直接在steamer-simple 的基础上进行扩展。

定制生成目录

生成目录的格式,这里需要单独讲一下。

一般来说,我们生成目录的格式都是要跟发布系统进行结合的,不过也有的时候,我们做项目的时候还没有明确要接入发布系统,或者尚不知道发布系统的格式要求,但是一般情况下我们应当遵循下面的约定:

  • js/css/img 等资源文件和 html 文件分离,前者发布到 CDN,后者发布到 http 服务器。
  • html 中引入的文件地址,应当是在构建过程中更新的 CDN 地址,而不是相对路径地址。
  • 如果有离线包(offline 能力需要对应的客户端 webview 支持)等,需要单独一个目录。

对于我们目前的项目而言,一般情况下会有三个生成目录:

1
2
3
- cdn
- offline # 需要客户端支持该能力
- webserver

如果一开始我们把所有内容生成到一个目录中了,这给我们后期的改动和维护,都带来很大的隐患。

组件库

组件库这一部分,适合项目开始变得复杂的情况下进行启动,而不建议一开始进行过渡设计,建设组件库能够通过组件复用降低我们的开发成本,同时,组件库也需要专人维护,保持更新。

开发:代码的规范约束

对于 js 文件的代码格式,诸如要不要分号、两个还是四个字符缩进等,一只争议不断,本文也不对此进行讨论,但是对于一个团队的项目集合(而不是单个项目)而言,代码风格的统一,是一个非常有必要而且必须要做的事情。

typescript

关于 typescript 的相关文章实在太多了,这里也不对此进行详细的说明,其对代码的可读性、规范约束、降低报错风险等都有很好的改进,对于越复杂的项目其效果越明显。

另外, typescript 入门教程的作者也在我们团队中,这里我想说,如果现在还没有开始使用 typescript,请开始学习并使用 typescript 吧。

eslint 与 prettier

除了 typescript 以外,在代码格式方面还建议使用 eslint 和 prettier 来进行代码格式的约束,这里虽然 eslint 和 prettier 两者在某些情景下会有些重叠,但是两者的侧重点不同,eslint 侧重于代码质量的检查并且给出提示,在某种层面上,可以看作是 typescript 的补充,而 prettier 在格式化代码方面更具有优势,并且 prettier 在设计之初就考虑了和 eslint 的集成,所以你可以完全放心的在项目中使用两者,而不用担心出现无法解决的冲突。

另外,eslint 社区中已经形成了很多种最佳实践,而我们团队也设计出了自己的一套 eslint 规则,可以按需取用

p.s. 目前 tslint 后续计划不在维护,转向 eslint 增强,因此我们在项目中可以不再使用 tslint。

以上这几种代码风格方面的约束,适合项目之初即开始约束,这样在中后期会有巨大的时间成本的节省和效率的提升。

协作:使用 git

使用 git 进行协作这里其实包括两个点,使用 git 管理项目与自建 gitlab,后者是一个比较基础性的工作,并且实际上难度并不大,我认为每一个公司都可以使用自建的 gitlab 进行版本管理,这个实际上难度并不大,并且可以有效的保护公司的代码财产,如果你所在的公司还没有,那么这也是你的机会。

在具体的使用 git 中,对于git的分支/TAG管理、PR规范、提交文件约束等都应当有一套合理的流程,这里我对几点比较重要的进行说明:

  • 锁定主干与分支开发,我们在日常开发中禁止直接提交主干,而是只能在分支中进行开发,并且通过 MR 的方式提交到主干。
  • git hooks 检查:我们应该通过 git hooks 进行必要的检查,比如自动化测试、eslint 检查、以及一些项目中必要的检查。
  • MR 检查与 Code Review,这里建议在 Merge Request 的时候做两件事情,一件是 Code Review,不过这个在某些特殊情况下不具备条件,尤其是团队人力紧张的时候,另外一个则是 MR 的 HOOK 触发检查,这个一般需要借助一些持续集成工具来完成,可以说是我们代码在合并主干之前的最后一个关卡。

测试:测试与测试部署

测试是代码开发中重要的一个环节,但实际上对于前端开发来说,前端开发工程师一般较少书写测试用例,也并没有专业的测试开发工程师来辅助工作,不过,一般会有配备系统测试工程师在黑盒的情况下进行冒烟测试和功能测试以及整体链路的验收,俗称“点点点”。而这个时候,前端开发要做的就是把程序代码部署到测试服务器上,同时提供一个尽可能真实的场景供测试进行测试。

在笔者经历的项目中,虽然也使用了单元测试、端对端测试,不过这一部分体系并不十分完备,并且可能也不是大多数前端开发者感兴趣的内容,所以这里主要总结如何进行高效的测试部署与发布对接。

一般来说,我们一般会有一台到多台 Linux 测试机,供测试环境部署使用,对于前端项目而言,一般不需要特殊环境,可以进行 webpack 构建以及有 nginx 进行转发即可。

而测试环境的部署,如果是让我们手动登录去部署,显然是不合理的,如果我们纯粹使用 CI 来完成这件事,则对 CI 工具的能力和项目人员素质有一定要求,并且不具备可视化管理能力,容易出错,这里我建议可以维护一个可视化系统来进行测试环境的部署和管理,其整个环节应该是这样的:

1
本地代码 -> gitlab -> 测试系统部署 -> 对接发布系统 

这里的测试系统,实际上是从 gitlab 拉取代码,并且本地执行 build 命令(一般是 npm run build)并把构建结果存储在 nginx 可代理的目录即可,出于系统完备性考虑,一般我们会有多台测试机,这里我建议一般拿其中的一台作为构建机,其他的测试机仅提供 nginx 代理能力即可,我们在一台构建机中进行构建,并且将构建结果通过系统命令发送到其他的测试机。

一台构建机可以服务于所有的项目,这里还可能涉及到 webpack、nodejs 版本的维护,我们需要约束各个测试项目构建处在一个相对独立的环境中,我们也可以使用过 Docker 来进行构建,保证隔离。

构建完成后,一般我们借助 Fiddler、Charles、Whistle 等任意一款代理工具,即可以进行测试。

监控:日志查看与脚本错误的监控

对于前端项目而言,即使我们已经使用了 typescript、eslint 并且也有了一些测试脚本和系统测试工程师进行的功能测试,我们还是免不了会出现 js 脚本错误,特别是 js 代码的运行环境和设备的多样化,很多错误我们并没有发现,但是产品、运营同学却出现了,或者到了线上在用户设备上出现了。

所以,有两个事情我们必须要做:

  1. 日志查看功能(手机端):现在我们写的大多数 TO C 页面都是在手机端进行,查看 console 非常不方便,我们需要一个线上实时查看 console 的工具。
  2. 我们需要脚本错误日志统计系统来进行错误统计管理与具体错误查看。

对于第一个功能,进行细分,我们需要做这样几件事情:

  • 嵌入一个 console 和 网络请求查看器,并且只在特殊情况下才能触发(比如连续点击标题十次、或者使用特定交互手势)
  • 在触发查看器的时候,可以将日志完整地进行上传并分析。
  • 同时可以对该用户进行染色,会自动上传并记录该用户一定时间内后续刷新后操作的全部日志。

不过这里并没有完全实现以上三点的开源库推荐,可以在 vconsole 或者 mlogger 的基础上进行适当扩展,完成相关功能。

对于第二个功能,我们需要一个完整的统计分析与管理的错误系统,这个如果自行开发的话,难度会比较大,这里强烈推荐 sentry,可以非常方便的使用 Docker 部署到服务器端,并且拥有非常强大的日志错误分析与处理能力,通过结合 JavaScript 的 sourcemap ,可以给我们的错误定位带来极大的方便。

总之,日志查看与脚本错误监控,是比较重要但是同时容易被忽视的地方,很多时候,我们的系统在线上使用了几个月了,真正有问题反馈了,我们才会考虑加上这些功能,但这个时候通常已经造成了损失。

发布:版本发布更新

发布系统,一般作为前端最后环节的系统,通常会和测试部署系统打通(或合二为一),一般的发布系统的必要功能如下:

  • 对于前端的发布,每次只发布有改变的文件,无变动的文件则无需发布。
  • 每次发布先发布 js/css/img 等资源文件,生效之后再发布 html 文件。
  • 发布系统保留线上旧版代码,出问题后可以快速一键回滚。

至于一些其他的日志、报表等辅助性功能,则根据需要满足,这里不再赘述。

灰度发布

灰度发布是大型项目在发布时的常见方法,指在发布版本时,初始情况下,只允许小比例(比如1-5%比例的用户使用),若出现问题时,可以快速回滚使用老版本,适用于主链路和访问量较大的页面。

对于前端的灰度,实际上有以下几种方案:

  • 在代码层面进行灰度,即通过 if/else 进行判断,这样无需发布系统关注,也可以自由配置规则、比例与白名单/黑名单。
  • 在入口层面进行灰度,比如 App 内嵌的 H5 则在客户端的对应入口进行回复,这样通常也无需发布系统关注。
  • 通过发布系统,按照比例灰度,比如我们有 10 台 webserver,如果我们先发布 1 台,这样我们的灰度比例为 10%。

访问量实时监控

最后一点,我们还需要一个访问量实时监控系统,我们上述有了错误查看与脚本监控系统,但是对于我们的各个页面的访问量、点击率等指标,通常是产品/运营同学比较关心的,同时访问量的波动情况也是项目健康度的一个表征(访问量突然大幅上涨或下跌,一般都有其特定原因),所以我们需要访问量实时监控系统。

而实际上访问量监控系统也有两种不同形态:

  • 对于每一个上报 key,只进行数量上的统计
  • 对于每一个上报 key,可以携带一些信息,对携带信息进行统计分析。

通常情况下,前者的功能是实时或者低延时的,而后者由于需要一部分统计分析,通常可以接受非实时情况(一般每天出前一天的报表)。

这部分内容,需要较强的后端接口稳定性,通常前端需要和对应岗位的同学共建。

总结

总结下来,我们一个稳定的前端项目,至少涉及到以下环节:

  • 完善的项目脚手架与代码约束规范
  • 内部 gitlab
  • 可视化管理的测试部署系统
  • 实时日志查看工具
  • 脚本错误统计管理系统
  • 发布管理系统
  • 访问量实时监控系统

如果你所在的团队哪个环节还没有或者不完善,那么这也是你的机会。