作者:张新
日期:2017-10-19
在活跃网络(成都)呆了三年半左右,搬过砖,踩过雷,架过构,管过人,想聊聊这些年在活跃网络的前端工程化实践之路。
在 2014 年之前,活跃网络(ACTIVE Network)的前端技术架构基本可以分为以下两种类型,后端技术包括但不限于 Java, .NET, PHP, RoR,Adobe Flex 等等。
很明显,在第一种方案中,是以后端为主的 MVC,前端开发重度依赖于后端的开发环境,并且前后端的职责纠缠不清(路由,模板到底应该放在前端还是后端?),很多时候前后端的代码会严重耦合。
而对于业务场景较复杂的系统,前后端的代码经常会混杂在一起,长此以往,可维护性以及可扩展性都将无从谈起,后面功能的开发也只能是在以前代码的基础上修修补补,直到无法忍受的一天就会产生重构的想法。
第二种方案中,是随着AJAX的兴起而带来的前端SPA时代。前端负责视图+路由+模板+AJAX,后端只是负责提供数据接口(RESTFul API,WebSocket 等等),从此前后端职责更加清晰,前端可以自由选择自己的框架,库,及至模块引擎,也可以进行合理的垂直分层,让各层各司其职,互相配合。
同时,在工程化实践过程中,我们遇到的挑战也是很明显的,主要有以下几个方面:
所有这些都是我们目前所遇到的挑战和必须要解决的难题,但是,有一点是始终没有变的,那就是最终前端工程化实践的核心诉求都是一致的:
减少开发技术成本,提升产品迭代效率以及产品体验!!!
对于前端标准化来说,我们要做的就是建立好前端的基础底层架构,规范开发技术选型,达到最大程度的复用,从而减少我们的开发技术成本,进而提升产品的迭代效率。
在这一方面,最近一年半时间内,我们做了很多工作,也有了很多产出。
CSS
框架 #Active.css 是活跃网络内部的企业级 CSS
框架,它确保了整个活跃网络产品线可以保持一致的外观和行为,并且让我们可以很容易的去对整个产品线做更新。它所提供的数十几种灵活实用且可重用的组件,可以帮助我们在之后的开发过程中进一步提高生产力,并且,它也让所有的开发者,设计师,产品经理,以及市场人员可以参考同一套标准,做到有据可依,有理可循。
在过去,每个产品团队会实现一套自己的 CSS
样式库,所以常常会遇到一些问题:
但是,有了 Active.css 帮助我们统一产品的风格指南,我们会有如下的优势:
整个公司可以共用一套 CSS 样式库,从而可以继续维持一个更高标准和最佳实践
针对开发者,设计师,产品经理,以及市场人员等等,建立了一套公共语言,并且让它应用到不同的产品的同时,还能继续保持一致性
每个产品团队不用自己去开发和维护公共样式库,只需要重用一套代码标准规范。也就意味着,开发人员可以花更多的时间在构建业务上,而不是花在考虑应该如何写基础样式上
以组件为最小单元来构建每个 CSS 组件,通过组合进而构建更为复杂的组件或者业务单元
另外,为了维持整体的CSS风格统一,也推出了 CSS 风格指南,它会提供一些关于CSS的最佳实践,如文件目录结构组织,命名,样式规则等等。
从 2016 年初开始,团队开始采用 React 作为构建前端视图的基础库,在这过程中,逐渐把公用组件剥离出来,使用得这些组件可以在各个项目中能够得到最大程度的复用。
组件主要包括以下几类:
Button
,Input
,Dropdown
,Table
,Modal
,DatePikcer
,Pagination
等等Form
,CheckboxGroup
,RadioGroup
,SearchInput
,Tabs
,Table
,TagEditor
等等AddressEditor
,以及与i18n相关的组件 APIShowAt
,HideAt
,Viewport
等等每个组件所对应的样式定义都是单独放在 Active.css 中,也就是说,JS 和 CSS 是分开单独维护开发的。
为什么需要这样做呢?
活跃网络有很多存在了十几年的老项目,他们的实现方式多种多样,但是随着客户的需求变化,近年来他们都有一个统一的诉求,那就是『改头换面』,重新换上符合公司产品风格指南的全新的样式风格。但是为了尽量减少工作量,我们一般只做样式上的修改,而不是对整个应用做重构,内部称之为『Reskin』。
同时,我们也针对这套 UI 库,构建了它的线上 API文档,组件使用者可以通过它快速查询某个组件的API 接口规范,并且提供很方便的在线 REPL。
为了提高开发效率,形成规范化的代码管理,活跃网络已经完成了从 SVN 到 Git 的迁移,内部采用的是功能分支工作流,也就是说所有新功能的开发都是基于一个独立的功能分支,而不是传统的master分支。这样每个工程师都可以在属于自己的功能分支上开发,而不会影响到主分支,主分支可以随时发布上线,当然这得借助于自动化的 CI/CD 来保证。
当我们提交代码时,都会涉及到一个永恒不变的话题---如何给 Commit
加一段简短且有意义的描述。
那么我们应该如何写一个 Git 提交消息呢?
请移玉步至 How to Write a Git Commit Message
TLDR:
目前团队内部参考的Git提交消息最佳实践是如下二个:
二者大同小异,完全满足上面这七大原则,都可以帮助团队统一 Git 提交消息格式,从而与他人协作更加方便。
推荐使用commitizen来帮助团队规范化整个流程,与npm scripts配合自动化整个流程。
随着团队人员增多,我们需要一种工具能够自动化帮助我们检测 Git 提交消息格式是否满足规范要求。推荐使用 marionebl/commitlint 来自动化 Lint Git 提交的消息格式,并且配合 prepush hook 一起使用。
CHANGELOG
#如果是库或是框架的开发,我们需要提供 CHANGELOG ,以让团队内部和外部人员知道我们最近所做的更新,如修改的 Bugs,新增加的功能或是其它性能上的提升等等
如果需要根据 Git 的提交和其它元数据自动生成CHANGELOG,请参考 conventional-changelog/conventional-changelog 命令行工具。
前端开发技术日新月异,在做前端技术选型架构时,团队内部会充分考虑各个框架库的优缺点,选择更加贴合项目需求的成熟技术方案。
目的很简单,希望能够最大限度的帮助我们减少技术成本,提高迭代效率,运行效率,以及用户体验。
我们经常提到SPA,那么到底什么是SPA呢?
目前并没有使用 Node.js 来作为中间的 Web Server 层,前后端之间唯一的通信桥梁就是 Web API。如果之后需要支持 SSR(server-side rendering) 或是 State Persistence 等等时,会考虑在前后端之间添加 Node.js 层。
如何管理好上万行代码前端单页应用是团队的一个核心挑战,一个良好的项目目录结构则是首要条件,具体内容,请参考 我们如何组织 React + Redux 项目结构。
使用 React 视图层来构建基于组件的富用户界面层。React是基于组件化的设计,与前端的技术趋势(模块化,组件化)非常接近,在前端社区中也非常流行,支持者众多,并且它是由Facebook官方支持和亲身实践的,最近也修改为 MIT 许可。下面是很喜欢的一些 React 的特性:
类型系统可以选择的很多,如 prop-types,flow,以及 TypeScript。
我更倾向于 prop-types,因为它的侵入性很小,但是已经为 React 组件提供了足够的类型安全,另外再加上测试和 ESLint,在最近的项目中,很难发现运行时类型错误。
使用 react-router@v4 Declarative routing for React 来作为客户端的路由解决方案,从而让用户可以在页面之间跳转而不需要重新刷新整个页面。从react-router@v4开始,它的基础代码全部被重写了,如今的Route
更偏向于组件,它只是基于当前的URL是否与所设定的路径一致,从而决定显示或是隐藏组件,这也更加React,更加贴合组件化的思想。想了解更多的改变,请移玉步至 All About React Router 4。
使用 Redux Predictable State Container 来构造和管理复杂的 Web App 状态,它能帮助我们解决:
在整个Redux Web App 中,统一使用团队内部构建的Redux Promise Middleware来处理所有的副作用(异步逻辑),比如 RESTFul API。它与 redux-thunk, redux-saga 或是 redux-obserable 相比,它更适合我们团队的需求,简单易上手,基本满足我们的需求:
Actions
,同时依据一定的命名规范会自动派发 ${REQUEST}
, ${REQUEST}_SUCCESS
和 ${REQUEST}_FAILURE
的 Actions
基于 isomorphic-fetch 封装了一套 api.js,作为 Web API 工具来处理 AJAX 的请求和返回。
Immutable.js
#使用 Immutable.js 来作为状态存储库。它提供了很多常用的 Persistent Immutable data structures 来帮助我们管理数据存储,并且保证它们是 Immutability 的。这就要求我们操作所有数据时,都要当作不可变数据(immutable data)来对待,保证每次都是全新对象,没有引用关系,这样才能保证State的独立性,便于测试和追踪变化。同样的,它也是来自于 Facebook,与 React 是天生一对。
react-aui
#使用基于 React 构建的 react-aui 作为基础的 UI 组件库,它包含了一系列满足我们的 Product Style Guide 的灵活,实用且可重用的组件。
Tempest.js
#内部开发和维护的一个基于 React + Redux 以及包含我们的一些最佳实践的框架,以帮助我们能够快速的构建 Web App.
build-tools
#之前,我们每次新建一个项目时,都需要去建立和配置很多工具集,包括测试,构建,发布,代码检查等等,因为已经有以前的成功经验,所以大多数时候直接 copy-and-paste 就搞定了。
但是,如果 Webpack 新版本性能提升了,然后需要升级了,又或是我们需要新增加一个 ESLint 规则,再或者是我们需要新增一个 prepush hook ,如 commitlint
,然后我们又需要在每个项目里面单独维护,这大大影响我们的工作效率。
build-tools
提供了我们常见的脚本 (build/release/lint/test/...),并且允许高度自定义。 如果你需要维护多个项目的话,这样的工具可以让你更容易管理你的工具依赖并随时保持更新。
比如,如果你要扩展 Jest 配置,你可以这样做:
const { jest: jestConfig } = require('build-tools/config')
const { setupFiles } = jestConfig
// Config it depending on your requirements
module.exports = Object.assign(jestConfig, {
setupFiles: [
'<rootDir>/test/polyfill.js',
'<rootDir>/test/setup.js',
],
})
对于 React 组件来说,我们可以数十种方式来为组件添加样式,比如内联样式,CSS,LESS/SASS, CSS Modules 以及各种 CSS-in-JS 实现。
目前我们选用的是 LESS + BEM,但是 LESS 我们只限定使用它的 Variables, Mixins 和 Nested Rules,其它功能不推荐使用。
使用 Yarn 来做为我们的包管理工具。
有一些特别的 Jest 配套工具推荐,以提高写测试效率:
如果想从其它测试框架迁移至 Jest,推荐 jest-codemods。
使用 Webpack 来作为 Module Bundler,它提供了丰富的 Loaders 和 Plugins 来帮助我们处理各种各样的资源,同时它还支持 Tree Shaking 和 Code Splting,可以更好的帮助我们做组件化开发与资源的管理。
根据增量的原则,我们应该精心规划每个页面的资源加载策略,使得用户无论访问哪个页面都能按需加载页面所需资源,没访问过的无需加载,访问过的可以缓存复用,最终带来流畅的应用体验。
this
.js
作为文件名后缀请移玉步至活跃网络前端开发流程。
请移玉步至我们如何组织 React + Redux 项目结构。
风格指南是代码规范的其中一种形式,它针对的是单个文件的布局,而代码风格更加强调的是一些编程最佳实践,文件和目录的设计等等。
JavaScript 是一门动态并且类型松散的语言,再加上团队成员水平参差不齐,急需要一种工具可以帮助我们发现代码中常见的 Bugs 或是潜在的问题,提高代码质量,保持团队代码风格一致。因此我们引入了ESLint作为代码的静态分析工具,并自动集成到我们的CI/CD流程中,也就是一旦发现ESLint错误,那么整个CI/CD会失败,直到修复这些错误为止。
另外,ESLint支持创建 ESLint Shareable Configs,团队内部基于 eslint-airbnb 创建了适应自己的 ESLint Shareable Config。
最近,我们也集成了 Prettier 来作为代码格式化工具,之后,Prettier 会主要负责代码风格一致,而 ESLint 会确保我们的代码质量是满足最佳实践的,各自分工,互不耽误。
前端的测试解决方案是基于 Jest + Enzyme 的,并且会添加 prepush hook,以确保在提交代码前,至少在本地机器上跑测试通过的。
基本上,我们会要求单元测试的代码覆盖率至少是要达到百分之九十的,它表示的是 Statements,Branches,Functions,Lines 这四个维度的平均值。有了这些测试后,在之后的开发,重构或是修复 Bug 中,我们就可以依据需求任意的做出相应的修改,同时,我们也有足够的信心不会破坏已有的代码。
CI 也就是我们经常说的持续集成,指的是频繁地(或者是一天多次)将代码集成到主干分支,它现在是我们开发流程中必不可少的一环。它主要可以帮助我们:
持续集成的目的,就是让产品可以快速迭代,同时还能保持高质量。它的核心措施是,代码集成到主干之前,必须通过自动化测试。只要有一个测试用例失败,就不能集成。
CD也就是持续交付(Continuous delivery),它指的是频繁地将软件的新版本,交付给质量团队或者用户,以供评审。如果评审通过,代码就进入生产阶段。
持续交付可以看作持续集成的下一步。它强调的是,不管怎么更新,软件是随时随地可以交付的。
在 ACTIVE Network,工程师在自己的分支上完成代码开发后,会先提交一个 Gitlab Merge Request(MR,类似 GitHub 的 PR),以供团队其它成员做 Code Review。代码仓库都会预先设置好 Webhook,所以只要有创建,更新或是合并 MR 时,都会自动运行仓库的 Jenkins Pipeline,下面是一个典型的 Jenkinsfile:
一般来说,它会执行如下的步骤:
通过 CI 流程,代码已经可以合并到主干分支了,我们就可以基于它做发布。一般发布流程包括:
package.json
中升级版本号(如果没有版本,则只升级PATCH版本)v*
的标签(如 v1.15.35 ),并指向上一步创建的提交v*
标签至远程仓库latest
标签至远程仓库这样一来,前端代码库拥有了自己的版本管理,并且每个打包的版本只是一些构建后的 HTML/JS/CSS/Fonts 的集合,任意的后端服务都可以使用它。
一般会在 Jenkins CI 机器上执行如下步骤:
package.json
中获取最新的版本号这方面的部署工具有 Ansible,Chef,Puppet 等,我们内部用的是 Ansible 来做部署。
如何保持团队增长是一个难题!说白了,就是想让大家一起开开心写代码,同时个人也能有所成长收获。
一个Team Leader的成功更重要的是取决于他/她的存在是否推进了公司的成功,尤其是技术相关方面的成功,另外他的存在是否帮助了团队里每个工程师的成功,帮助他/她们作为一个个体的成长和成熟。
前端技术瞬息万变,库,框架,工具集,开发流程,测试,CI/CD等等,基本上每隔三个月就会更新换代,这就要求我们时刻保持更新,自我成长,多去接触更广阔的世界。
我们内部每周四定期会有一个Tech Talk,针对某一项技术(无论前后端,新或是旧,只要有用),或是一个大家平常遇到的项目问题,或是技术调研的结果。分享的目的在于将自己所了解的东西以最简单的方式讲给大家听,然后自己和其他人在这个过程中都能有所成长。
关于技术分享更多的主题,可以参考Trello Board。
写代码可以帮助我们快速成长,同样的,作为一个代码审核人,也是一个自我学习和成长的过程。代码审核最重要的好处就是帮助我们最大限度的确保代码质量,统一代码风格,提早发现Bugs。同时,这也能帮助我们建立一个健康的工程师文化,促进团队成员之间的沟通,保持代码的简洁和可维护性。
代码审核绝对不是指责的工具,同事之间相互的CR过程中,我们也能从彼此那里学到很多编程技巧,并且培养起很好的编程习惯,学习和成长才是核心目的。
为了规范化CR流程,我们会把CR加入到我们开发流程中来,尽量自动化整个流程,另外,针对CR,我们也有一些行为准则:
pre-commit
CR),而不是合并到主干分支之后推荐使用 husky 来实现 Git hooks。
网站内容许可证:公共领域(public domain)