如何从 0 到 1 打造团队 PC/H5 构建工具
前端早早聊大会,前端成长的新起点,与掘金联合举办。加微信 codingdreamer 进大会专属内推群,赢在新的起跑线。
第十六届|前端搞组件化专场,10-17 本周六即将直播,5 位讲师(百度/1688/腾讯等),点我上车? (报名地址):
正文如下
本文是第十三届 - 前端早早聊构建专场,也是早早聊第 90 场,来自 百度 — 鳗鱼 的分享。
一、前言
大家好,我叫鳗鱼,这次分享的主题是如何从 0 到 1 打造适合自己的构建部署方案。
先例行的自我介绍,大概 14 年开始接触前端,曾经在菜鸟实习过,现在是在百度我也不知道算啥 title 的前端开发,带了个小团队。前两期在女生职业发展道路上已经分享过一次,然后当时说过自己对工程化这一块兴趣比较大,相信懒是程序员的美德之一,这次又过来做一次分享。
这次的话题其实说大不大,说小也有很多零零散散的东西可以讲,自己公司内部也有一定的实战经验甚至完善的平台。不过调研发现其实大部分中小型公司并没有专门的人去折腾这些事情,也不知道怎么做或者说没精力搞那么复杂的东西。
所以这次分享的话题可能比较轻松,刚好大家午休时间,可以边吃饭边看,也不用那么烧脑。主要受众可能是一些对构建部署还停留在听说过,但是没有实际用过的偏小白。时间关系也没有像其他讲师画那么多高大上的架构图。但是会比较细粒度的解析构建部署的过程,希望能帮助形成一个大体的概念,便于梳理能去选择合适自己的方案。具体的平台实现还可以参考其他讲师们的 PPT,也都非常不错。
这次主体大致包含两部分:
第一块就是,为什么我们要花那么多功夫去做构建部署的事情。
第二块是我们可以怎么去做,分为构建和部署两个部分。
二、为什么要做?
项目开发流程
从前端进入现代社会之后,无论是看社区论坛还是各种分享,都能看到各种各样讲构建部署的东西。那么它到底有什么魔力吸引大家都纷纷驻足呢?我们可以从项目的整体结构来看,前端其实就是软件开发交付过程中的一个小小的环节而已。通常来说,一个互联网软件开发的流程如下:
大多数都是本地编码完毕,然后执行 npm run build
去做打包构建,通过一定的方式推送到一个测试环境去看看有没有问题。遇到 bug 就改 bug,没有 bug 就喊相关人员验收,并进行部署上线。这整个流程可能有自动化的部分,也有手动的部分,每个团队应该多少都有点区别 ,通常来说自动化程度跟团队规模还有业务复杂度都有关系。
但是业务如果持续发展,可能一般都会经历一个过程,就是刚开始我觉得这么玩玩就好了,我觉得好像也没什么问题,实际上会变得越来越复杂。
我们理想中,可能会觉得 A 项目做完了,就上到 A 项目的服务上去,B 做完上到 B 的服务,C 做完上到 C 的服务 但是实际情况下,可能会出现不同的项目模块,有相互依赖的关系,比如 B 服务要在 A 上线之后才能上线,D 又依赖 C,最可怕的还是相互依赖,递归依赖,传说中依赖黑洞的现象。那么,这可能会出现什么问题呢?
软件危机
我们看几个历史上的实际的例子,都是计算机早期的时候真实发生的故事:
美国银行信托软件系统开发案:美国银行 1982 年进入信托商业领域,并规划发展信托软件系统。项目原订预算 2 千万美元,开发时程 9 个月,预计于 1984 年 12 月 31 日以前完成,后来至 1987 年 3 月都未能完成该系统,期间已投入 6 千万美元。美国银行最终因为此系统不稳定而不得不放弃,并将 340 亿美元的信托账户转移出去,并失去了 6 亿美元的信托生意商机。 阿丽亚娜-5 运载火箭爆炸事件:96 年 6 月,阿丽亚娜-5 运载火箭首次测试发射,在发射后 37 秒被迫自行引爆,原因由于 64 位的运算错误地变为 16 位的运算,造成程序崩溃,火箭感测角度失常,从而触发自毁装置的启动。
以上软件问题均出现在计算机诞生早期的时候,计算机刚刚投入实际使用,软件的规模比较小,很少使用系统化的开发方法。但是由于计算机的飞速发展,软件系统的规模越来越大,复杂程度越来越高,产生的问题也越来越突出。其实在早 1968 年的时候,北大西洋公约组织在一次国际会议上,就已经创造了软件危机这一词,并且连续召开过几次会议,提出了软件工程的概念。
那么软件危机是什么呢?那与我们今天要说的工程化或者构建部署有什么关系呢?
其实它需要解决的基本上是一类问题,主要是日益庞大的软件系统,其复杂度已经远超我们人脑可以直接控制的程度,很难说我们靠人力靠时间就能比较好的去保证所有东西都是没问题的。
那么对于前端来说,他面领哪些问题呢?当你代码写久了可能会有一些感觉,我们日常遇到的技术问题可能少一些或者说好解决一些,但是工程问题反而较多,而且大多缺乏系统性和通用性。
比如说我们要开发一款完整的 Web 应用时,我们不光要书写 HTML/CSS/JS 之类的,来完成功能的开发。也将会面临更多的工程问题,比如:
体量比较大的系统,有很多功能,有很多页面,有各种状态,要怎么去把他们组织起来; 再比如对于大型系统多人团队,我们作为承上启下的环节,怎么去比较好的进行多人甚至多团队合作开发; 再有就是我们经常说的性能优化问题,比如说 CDN 部署、缓存控制、按需加载、首屏渲染等等。
所以,这个时候其实我们就需要工程化了,前端工程化的定义就是借助软件工程的技术、方法和思想,来优化前端侧的流程、效率和规范等。参考网上将前端工程化分四个能力点:规范化、模块化、组件化、自动化,个人是比较认同。
但是其中的每个点要细讲,都能牵扯出不少的东西,这次的分享只是讲其中的一部分,就是如何自动化,更具体来说是构建部署阶段的自动化。
工程化&自动化
说到自动化,有一个可能都听说过的词,CI/CD。但是他具体是指带什么?我们看一下:
CI/CD 是指在应用开发过程中,通过自动化手段来频繁向客户交付应用的方法。核心概念是持续集成、持续交付和持续部署。
CI(Continuous Integration)简称持续集成,原意是各个开发者在开发过程中,需要将代码集合到一起,比如我们的 GitLab,GitHub 等。现在大多会包含集成之后,自动触发编译,测试等过程,帮助开发者及时收集问题。 CD(Continuous Delivery)指的是持续交付和持续部署,经过 CI 后,代码自动部署到服务器上的一个过程。
然后看一下市面上对于项目的开发流程过程中,会涉及到的一些常见的平台(这里是网上找的一张图比较糊,没来得及找高清的,大概看一下后面都会有详细说明):
代码托管:我们有 GitHub/GitLab/还有国内的码云,Coding 之类的平台; 构建:对于 Java 有 Maven,对于前端是 npm; 集成:有 Jenkins,Travis,测试对于前端是 Mocha/Jest 之类的工具; 部署:物理机/虚拟机/Docker K8s 等等。
CI/CD 主要是自动化地在云端平台去完成这些事情。一方面来说像早上冬冬说的保持环境的一致性,提高构建部署的稳定性。另外一方面就是通过自动化去减少重复机械的劳动。
所以从收益来讲,肯定是项目越多,模块关系越复杂,带来的收益越明显。
三、怎么做?
第二个环节,我们大概知道了要做什么,那么具体怎么做呢?
构建篇
首先是构建,刚刚说过,构建指从本地开发提交到代码库,以及完成远程构建,自动化测试等一系列的过程。我们按顺序来看:
首先,当你完成一个功能,本地看着没问题之后,去做 git commit 的时候,从自动化的角度来说,我们可以做什么事情?
这里有一个很重要的点,也是会贯穿构建流的一个点。就是我们要知道,git 不光只是代码存储,还有一系列钩子,能允许你在特定的时期,做一些想做的事情。例如 precommit,就是代码提交前,我们可以用它来做一些本地代码检查,比如 ESLint,再有比如自动化测试,在检查通过之后再允许代码提交。通常对于前端来说,我们会结合 husky 来使用。
作为一个合格的校验系统,当然不光只是让用户自己做本地检查就够了,毕竟在某些特殊状态下,大家可以有各种手段去绕过自己的检查。因此对于一些关键平台或者项目,就需要服务端检查的东西了。比如目前我们自己这边的系统,在代码提交之后但是还没入库之前,会自动的去做一些复用率检查,安全扫描等等,这个就是 pre-receive 钩子可以做到的事。当然,对于一些小型系统这个环节其实可以直接跳过。
另外,我们可能还会有 Code Review 环节,代码被人审查过之后才允许合入。以及在合入之后,也能有一定事件,比如去通知合作方及时更新代码,去通知流水线继续走后续构建的流程等等。
当我们提交完代码,作为一个合格的 CI 系统,还需要做什么事情呢?就是打包构建了。
这里的打包其实就是 npm run build
的过程,只是执行的环境不是你本地的机器,而是换成了在线的统一环境的机器,比如项目测试环境的机器,公司内统一的编译机等。但是他做的事还是一样的。
通常大多数情况来说,我们执行 npm run build
,然后去拿 dist 的产物就结束了。但是实际如果你玩 CI 的过程中,你很有可能遇到一些环境不一致,版本不一致等莫名其妙报错的问题,这个时候如果你能对构建过程有一定了解,在定位问题层面可能就能有一定帮助了。
比如在现代化前端的演进中,我们早就从解释性语言,变成了半编译型语言。因此在打包核心功能时就是要把我们自己理解的代码,变成浏览器可认识的代码,这里举个实际的例子。
比如对于 TypeScript 的代码,我们有 Interface,有变量类型定义,转化器也是会先将它解析成 AST 的语法树,具体解析成啥样有个在线的网站可以查询,具体的解析原理其实网上也很多资料,然后根据 AST 去掉给我们看的内容,变成可执行的 JS 代码。
当然在编译过程中,肯定不止是代码转换这么简单,很多时候我们有很多很多其他的配置,例如 icon,我们可能需要把零零散散分散在各处的图标,生成 iconfont 或者雪碧图。还有代码合并压缩,静态变量替换等等,具体可能会做什么事情,以 Webpack 为例,可以参照其常见的 loader。
总的来说,当我们执行 npm run build
的时候,无论你用什么工具什么方式,我们核心需要把零零散散的资源打包汇总到一起,让其可执行。
那么常见的打包工具有哪些呢?比如早一点的有 Browserify,Gulp,Grunt,新一点的有 Webpack,Rollup,Parcel 等等。
打包这块好了,那 CI 的一个核心点,是自动化,就是在代码提交之后能自动去运行打包的脚本,而不是我在本地打包之后一起提交带代码库里面。
通常来说我们可以去选择各大平台提供的现成工具,例如 Travis CI,这是一个跟 GitHub 强集成的 CI 平台,对于一些开源项目,或者自己想要练练手的时候,就可以用它去玩了。左边是一份它的 CI 的配置文件,大致功能就是能配置在什么阶段,可以去做什么事情。
另外还有 Jekins,这是一个开源的基于 Java 的一种持续集成平台,对于一些私有化部署的项目应该算是用得非常多的一个平台了。还有基于 GitLab 内置的平台,国内的几个代码托管平台好像也有类似的服务。当然对于一些大型公司,还会有自研的平台,这里就不细说。
具体要怎么选?个人觉得:
对于一些中小型团队,建议直接找市面上成熟的工具,然后结合这次大家的分享,可以做一些简单的封装; 对于成熟度比较高业务复杂的大型团队,有较强的技术中台或者有专门的人力去做这一块的事情的话,可以考虑自研或者高度定制化。
具体怎么做?无论哪种方式,其实他的思路都差不多,自研需要的东西也很简单:
首先,你要有一个运行环境,就是能让你执行 npm run install
和npm run build
不会报错的地方。无论是物理机,还是虚拟机,还是容器都没问题。其次,你需要一个代码托管平台,无论是现成的,还是自己私有化搭建的,只要能保证以能执行 git 的各种钩子就行。
然后大致的一个流程,当你提交代码之后,可能会去做一些本地检查,这个应该是配置在项目代码库中的,比如用 husky 结合 ESLint 做代码规范性检查。检查过了之后,git 会自动推送代码到远程代码库,这个时候可以结合服务端的 git-hook
做检查,当然没有也没问题。
当你收到代码提交通知的时候,需要有一个 Web hook 连接通知到你配置的可用编译环境里面,告诉它我有新的代码提交了,你可以开始一系列后续任务。那么通常来说编译机会要做什么呢?首先需要将代码 clone 下来,并安装所需要的依赖,对于前端来说就是 npm install
,然后去打包就是 npm run build
。这样你在服务器上就已经得到了你想要的产出,构建过程就已经完毕。
看起来是不是觉得很简单 :)
但是当系统复杂度升高,业务多了之后,实际情况可能会遇到的一些思考点,也是我们目前有的一些东西,这里只是抛出来大家可以想一想。
首先 Git hook 触发方式,代码提交了之后是自动触发的,那怎么去做到定时触发,怎么去在一个代码库编译完了之后,通知另外一个代码库去做编译。其次还有分支,有时候我们只想 dev 分支去做自动编译,master 分支需要有特殊控制,怎么去更好的监听分支的变化,除了精确匹配是否还可以有前缀匹配,正则匹配等方式。再有就是实际业务是怎样的,我需要用到哪些钩子等等...
然后在编译构建层面,可能还会涉及到,环境变量有哪些,不同流程之间怎么做到一些通用变量共享。我们实际有哪些环境,比如说 node8,node10,怎么去特定换定编译,怎么切换?
编译所有都串型感觉太慢了,我们怎么去做并行编译。机器对编译产物怎么去存储,是否有定时清理机制过期时间等,还有一些异常的处理,超时机制,各种检查具体怎么做?
这些都是一些细节问题,也没办法在此次分享给出一个标准答案。它是一个跟业务密切相关的过程,只要做了多多少少都会碰到这些问题。但相信对于一些积极思考热爱学习的同学,比如能周末来听早早聊的,应该能自己去探索得出答案。
部署篇
然后部署,当我们拿到了产出,怎么把产出给弄到不同的环境,怎么优雅的上线,也是一个值得思考的问题。
我们再看看历史,一直在说前后端分离不分离,那么具体常见的前后端合作模式有哪些呢?
前后端完全不分离,前后端在一个代码库里面,前端在本地跑的时候很有可能要配置后端的服务环境,提交代码后端帮忙一起部署; 前后端代码库分离了,但是部署到机器中还是在同个服务内,这个时候可能我们会把代码产出本地打包丢给后端,或者说通过一系列自动化工具直接推到服务器对应的静态资源目录里面; 前端后端代码分离了,部署也都分离开了,前端这个时候通常来说可能就是一个简单的 HTTP,中间可能通过一些后端路由转发的形式做关联; 对于一些现在的大型团队,可能前端不直接跟后端对接,还会有一个 BFF 中间层去做一些转发配置,甚至身份校验之类的事情。
以上,应该是一个前端开发的历史进程的缩影,我们来来回回的折腾,看着越来越复杂,但是其实无论哪种,部署都是大同小异的。
来看看我们的目标,最前面也有说,所谓的构建部署,就是把各种手动操作的东西,变成自动化规范化的流程。
因此我们的目标,就是需要把曾经手动丢服务器,需要多人协同操作口口相传的这种情况。变成能由较少人可以操作,由工具去帮我们自动做分发,部署之类的事情,从而来提高部署的效率和稳定性。
具体怎么丢,其实很简单,可以理解成是一个文件上传的过程。所以无论你使用 FTP,还是有个 HTTP 请求的接口,都没什么大的问题,这里不做仔细描述。
那么我们遇到的问题可能是什么呢,对于前后端分开部署前端有自己独立服务的应用,可能更大的问题是应该要怎么发布,毕竟服务重启这回事,如果挂了分分钟影响的都是流量。这也是为什么大部分业务上线时间可能都是挑晚上上线。
我们看看市面上常见的一些发布策略,这个对于前端来说可能接触得并不是很多。
首先说蓝绿部署,指我们同时运行两个版本的应用,蓝绿部署的时候,并不停止掉老版本,而是直接部署一套新版本,等新版本运行起来后,再将流量切换到新版本上。
这就要求在升级过程中,同时运行两套程序,对硬件的要求就是日常所需的二倍,比如日常运行时,需要 10 台服务器支撑业务,那么使用蓝绿部署,你就需要购置 20 台服务器,显然,比较耗费资源。
滚动升级,就是在升级过程中,并不一下子启动所有新版本,是先启动一台新版本,再停止一台老版本,然后再启动一台新版本,再停止一台老版本,直到升级完成,这样的话,如果日常需要 10 台服务器,那么升级过程中也就只需要 11 台就行了。
再有就是灰度发布,这个其实就已经不算是发布策略了,而是一个测试的策略。也是日常会用得比较多的一个点,先启动一个新版本应用,但是并不会像是滚动升级一样,不断的对所有服务进行切换,而是会将小部分了流量导入到新版本的环境中,测试人员对新版本进行线上测试。如果没有问题,那么可以将少量的用户流量导入到新版本上。这种发布方式也叫做金丝雀发布,具体为什么叫金丝雀有兴趣的同学可以自己搜一搜。
如果这个时候我们对新版本做运行状态观察,收集各种数据,并做新旧数据的对比,这就是所谓的 A/B 测试。
说了那么多部署方式,那么对我们前端来说有什么用呢,一方面来说更好的了解后端的一个部署模式在跟后端或者做沟通的时候可以稍微多一些底气,另外也可以借鉴借鉴,知道前端要怎么玩。
通常来说,前端部署可能可以划分为覆盖式部署和非覆盖式部署。覆盖式部署大多就是资源在服务器上,每次部署把之前的内容都删了重新替换。而非覆盖式会存储多套资源,在恰当的时候去控制做资源的切换,有点类似蓝绿,但是没有销毁的过程。
对于个人来说,我们这边可能目前用的比较多的是非覆盖式,将资源都部署到对象存储上面,例如阿里的 OSS,百度的 BOS,去使用它们的 CDN 功能。然后在 Nginx 或者哪一层去控制资源的版本切换,或者说默认使用其 lastest 的版本。
那么对于普通的业务开发,我们应该怎么选呢?上面说的各种部署方式,市面上很多云平台都有一定的支持。因此对一些业务简单的小团队,可以考虑直接找个云平台,然后撸一撸公共脚本或者服务,串一串就可以开心的玩耍了。
对于一些业务庞大复杂,有人力精力的情况下,也可以考虑弄一个可视化的流水线平台,像是早上其他讲师分享的。这建议是在有一定规模之后,且简单模式 run 了一定时候再去搞。
具体可能会涉及到哪些东西呢,首先呢还是一样,需要有一个环境,可以跟上面说的编译环境保持一致。然后拉取打包后的产物,并将产物发送到指定的机器/CDN,期间可能会涉及到一些服务之间的关联,部署模式等。
这是我们这边的一个流水线平台的大概例子,它大概的功能可以远远不止 Web 的构建和打包,还有像是客户端,Server 类型的不同环境的编译,都是以插件的形式集成到其中。
另外还可以做一些常见跟发布相关的功能,比如打版本号,提测,环境切换,发单,审批等都可以在这里进行。
然后对于部署,同样有一些思考点,这里也是列举不做详细的解答,比如:怎么制定部署到不同环境,怎么去做权限的控制,然后部署成功的通知,失败的回滚和通知,怎么去打版本号,还有对于前端 Sourcemap 怎么存,怎么做关联,日志怎么去做上报存储分析,对于物理机,虚拟机,Docker 等不同形式我们怎么去做部署。
另外,如果自己搞平台,那么我们的关注点,除了构建部署,我们还能做什么别的事情,比如说:代码跟需求卡片关联,自动提测,各种服务端的检查,各种测试,审批等等。
思路层面这里基本就讲完了。
四、总结
总的来说,以上就是构建部署的一个简单介绍。对于具体业务,不同的方向的需求多多少少都有些不同,这也是软件开发中一个比较难的部分。
这次分享只是做了细粒度的思路拆解,具体方案其他讲师也都说得很清楚。这里引用人月神话中一句话,“软件开发中最困难的部分是规格说明,设计和测试这些概念上的结构,而不是对概念进行表达和对表达完整程度进行验证。所以软件开发天生就没有银弹”。
这里核心想说明的就是构建部署没有银弹,大家需要根据自身情况做各种调整,希望大家都能早日造就适合自己的方式。
既然说到这里了,那么分享例行需要推荐的书就是这本《人月神话》。被很多人都推荐以及评分都特别高的书,刚开始看可能看不懂,当你有了一定经验,项目达到一定规模之后再回头来看,可能会有不一样的体会。
最后就是广告时间啦,左边是上次分享建的交流群,右边是我的个人微信。
另外团队招人,具体的在语雀空间有详细的介绍,我们深圳北京上海都有 HC,有兴趣的同学可以随时交流。
加微信 codingdreamer ,备注进群,加入大会专属内推群,及讲师团队钉钉群