当前位置: 首页 > Web前端 > HTML

前端自动化打包实践

时间:2023-03-28 17:53:54 HTML

前言分为三部分:gulp介绍自动打包脚本编写;gerrit引入工作流;gitlab-ci介绍平台搭建。每一部分都会对做过的两个项目进行总结,希望对大家有所帮助。第一个gulp项目使用gulp是因为当时比较“新”,所以尝试一下。第二个项目还在用,因为那段时间在学习函数式编程,接触到了Monadic。我想到了gulp作为一个例子可以匹配这个概念,所以我想用它来深入理解这个概念。当然,最重要的原因是它熟悉且易于使用。Monadic编程的概念有点抽象,但是可以直观的展示gulp的编程范式。const{src}=require('gulp')src('glob格式').pipe(func1).pipe(func2).pipe(func3)这个".pipe(func1).pipe(func2).pipe(func3)“即使是Monadic,其实也和promise的then一样。Promise.resolve().then(func1).then(func2).then(func3)之所以可以链式调用是因为func1,func2,fnc3...这些函数按要求返回相同的接口(对象)。//gulp大致是这样的varthrough=require('through2');functionfunc1(){returnthrough.obj(function(file,enc,cb){//through.obj返回的对象有一个pipe方法//只是调用cd表示完成})}//promise大致像这样Promise.prototype.then=function(func){letresult=func()if(result.then&&typeofresult.then=='function'){returnresult}else{returnPromise.resolve(result)}}在写第一个项目的打包步骤时,大致是这样的。步骤太多,看代码很难理解步骤之间的关系。vargulp=require('gulp')gulp.task('step1',function(){//步骤1})gulp.task('step2',function(){//步骤2})gulp.task('default',['step1','step2'],function(){//step1和step2并行运行,在step1和step2完成后执行此步骤})第二个项目使用新版gulp在API层面解决了这个问题,并行用parallel,串行用series。const{parallel,series}=require('gulp')functionstep1(){//step1}functionstep2(){//step2}functionstep3(){//step3}exports.default=series(parallel(step1,step2),step3)//step1和step2并行运行,step1和step2执行完后,转到step3如果step1和step2有相同的部分,只有部分参数不同,我应该怎么办?学过的函数式又派上用场了,高阶函数就用上了。const{parallel,series}=require('gulp')exports.step=function(parameter){returnfunctionstep(){//step1和step2是同一个部分,用参数区分区别}}conststep1=step(参数1)conststep2=step(参数2)functionstep3(){//步骤3}exports.default=series(parallel(step1,step2),step3)//步骤1andstep2是并行运行的,在step1和step2完成之后,step3可以用高阶函数封装相似的step,很自然的想到模块化。所以第二个项目进行了模块化:比如需要操作两个git库,涉及到gitpull和push;例如,文件需要在不同的阶段进行操作,这涉及到文件的移动和删除。Gulp模块化需要将gulpfile.js文件改为一个目录名,入口文件为该目录下的index.js,其他模块放在该目录下随意命名。//gulpfile.js/git.jsexports.pull=function(parameter){returnfunctionpull(){//拉取代码}}exports.push=function(parameter){returnfunctionpush(){//可以添加提交推送一起写}}//gulpfile.js/file.jsexports.move=function(parameters){returnfunctionmove(){//将文件从一个目录移动到另一个目录}}exports.del=function(parameters){returnfunctiondel(){//删除文件}}当然,我觉得模块化比较好的地方是config和task的分离。将入口文件中的config和task组合成一个具体的task。//配置gulpfile.js/config.jsconstconfig={//配置}export.getConfig=function(){//通过方法获取配置}export.getCombine=function(){//通过方法获取动态配置}//Taskgulpfile.js/task.jsconst{parallel,series}=require('gulp')exports.task=function(name,config,combine){constarr1=[...]//根据config生成任务1,combineif(name=='Task1'){returnseries(...arr1)}constarr2=[...]//根据config,combine为task2生成后续任务if(name=='task2'){returnseries(...arr1,...arr2)}returnseries(...)//根据配置组合任务的任意组合}//入口gulpfile.js/index.jsconst{getConfig,getCombine}=require('./config')const{task}=require('./task')constconfig=getConfig()constcombine=getCombine(parameter)//根据不同的task释放对应的注释//exports.default=task('Task1',config,combine)//简单任务1//exports.default=task('task2',config,combine)//复杂任务2,第一个项目遇到问题,有Stepgulp插件做不来,我也没有能力完成开发插件,所以gulp没法把所有步骤串起来,最后用命令行脚本串起来。#mac或linux使用shellgulp--parameters#混淆压缩zip-parameters#制作zip包java-jar--parameters#jdbc修改数据库版本gulpftp#ftp到服务器#windows使用batchgulp--parameters#混淆并压缩haozip.exe-Parameter#zippackagejava-jar--parameter#jdbcmodifydatabaseversiongulpftp#ftptoserver第二个项目也遇到这个问题,不过这次找到了解决办法。首先,需要在命令行运行的步骤,在nodejs中都可以运行。事实证明nodejs本身就支持,不需要其他npm包。const{execSync,exec}=require('child_process')execSync('command',configuration)exec('command',configuration)其次,本次使用的gulp版本支持异步任务,只要任务返回stream,promise、事件发射器、子进程或可观察对象都可以。如果是上面提到的命令行任务,就按照他们的接口范式写代码,知道任务什么时候完成,这样就可以执行下一个任务了。在多人开发的情况下,gerrit还需要一个工作流来管理代码的合并。第一个项目的工作流程大致是这样的:一个需求一个分支,bug修改也算一个需求,每个测试环境一个分支,测试环境分支和需求分支合并。之所以采用这种管理方式,是因为有些需求正在开发中,但不知道什么时候上线,或者有些需求因为特殊情况推迟了。这样每次上线只需要确认上线的内容,并合并相应的分支即可,准生产分支几乎可以做到这一点。但是,通常的测试分支在开发时被合并。有冲突的时候,有的人在test分支解决,但是自己的分支上没有修改记录,重新组合的时候还要解决冲突。越来越复杂了,连大家自己的branch跑起来都没问题。只有测试分支有问题。删除测试分支并再次合并。第二个项目的工作流程大致是这样的:每个月的线上内容都在一个feature分支上开发,拉取别人代码的方式是rebase,降低了分支之间关系的复杂度,不会出现第一个项目需要删除重组情况,push代码被gerrit限制,所以才作为标题。具体来说,本地代码不能直接推送到远程分支,而是发布到gerrit。发布的代码在提交到远程分支之前需要审核通过。第二个项目一开始是使用工具开发发布的,但是自动打包也需要把这些步骤写成脚本。gulp部分介绍的“子进程”虽然只有几行代码,但是却让我付出了不小的代价。事倍功半,下面同样的脚本就那么几行,可是费了我不少功夫。#Yanhegitpull--rebase--progress"origin"#发布代码到gerritgitpush--progressoriginHEAD:refs/for/refs/heads/branch这个"HEAD:refs/for/refs/heads/"是By默认情况下,它也可以在gerrit中配置(我猜我还没有真正做过)。这两种工作流程各有特点。第二个工作流的分支虽然复杂度小,但是构建起来比较复杂(需要构建+1自动审核),管理需要更多的人力(多一个步骤+2人工审核)。虽然第一个工作流有时需要删掉重新组装,但是出现这个问题的概率很小,适合3-5人的小团队。我个人认为这两个工作流程结合起来更好。存量函数用第二种,增量函数用第一种比较好。新功能的风险很高。如不能如期完成交货,您可不同意先行处理。功能分支在达到可交付成果时重新组合。而且,有了增量功能分支,公共模块代码可以尽快提交共享,不用担心影响功能分支的交付。有了gitlab-ci自动打包脚本和工作流程,接下来就是最关键的一步,如何自动打包呢?其实原理就是给远程仓库添加git-hooks。某些动作,比如推送远程仓库中的一些分支标签,会触发与git-hooks关联的自动化平台。自动化平台接收到信号,开始执行相应的自动化脚本。当然,触发方式不止一种,还有定时任务等,不在本文讨论范围之内。第一个项目使用gitblit+genkins。首先下载jar包:http://mirrors.jenkins.io/war...然后启动:java-jarjenkins.war--httpPort=8080在浏览器中输入:http://localhost:8080配置密码:按照页面引导找到initialAdminPassword文件,复制密码首先配置插件:第一次可以跳过插件安装,进入jenkins后配置配置插件:ManageJenkins/ManagePlugins/Advanced/UpdateSite填写http://mirrors.tuna.tsinghua....安装插件:在ManageJenkins/ManagePlugins/Available中找到自动打包必须安装的必备插件:git,nodejs,如果需要写pipeline脚本,需要安装pipeline相关的插件(这个项目没用),那么远程仓库和jenkins如何触发自动打包呢?这是git-hooks。点击NewItem填写项目名称,点击ok,在SourceCodeManagement中选择Git,输入远程仓库url,点击“Add”,输入用户名和密码。选择输入的用户名和密码,在BuildTriggers中设置触发器。这个在设置gitblit的时候会用到(token可以随意输入)。在Build中填写要触发的脚本点击Save进入gitblit的groovy目录编辑jenkins.groovy文件。在jenkinsUrl中输入BuildTriggers来设置触发器。保存生成的地址,重启gitblit进入gitblit管理页面,点击Edit,选择Receive,选择jenkins.groovy,移动到Selected,点击Save,填写的脚本会在gitblit推送后触发,代码可以参考:https://www.jianshu.com/p/9a3...第二个项目使用gitlabgitlabrunner下载:https://docs.gitlab.com/runne...gitlab安装:运行gitlab-runnerinstallgitlab-runner启动。gitlab-ci在下载目录下.yml:存放在项目中仓库的根目录包含项目自动化如何工作的描述语句。stages:-buildbuild-gulp:tags:-labelstage:buildscript:-gulponly:-webpipeline:是作业的集合,分为不同的阶段;stage:yes作业的逻辑划分,例如“stage:build”;job:Runner要执行的指令集,必须包含script,"build-gulp:..."就是job,可以自定义多个;stages:定义指定作业执行顺序,默认包括构建、测试、部署三个阶段;script:Runner执行的shell脚本;only:web:在gitlab页面按下runpipeline时执行;tags:要执行的Runner标签,需要在“Homepage=>setting=>CI/CD=>Runners=>SpecificRunners”中添加;可以参考:https://zhuanlan.zhihu.com/p/...https://juejin.cn/post/701814...