前端自动化打包实践

前言

分三部分: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 大致这样
var through = require('through2');
function func1() {
    return through.obj(function(file, enc, cb) { // through.obj返回的对象有pipe方法
        // 调用cd就表示完成了
    })
}
// promise 大致这样
Promise.prototype.then = function(func) {
    let result = func()
    if(result.then && typeof result.then == 'function') {
        return result
    } else {
        return Promise.resolve(result)
    }
}

第一个项目编写打包步骤时大致是下边这样的,步骤多了靠看代码很难缕清步骤之间的关系。

var gulp = require('gulp')
gulp.task('步骤1', function() {
    // 步骤1
})
gulp.task('步骤2', function() {
    // 步骤2
})
gulp.task('default', ['步骤1', '步骤2'], function() {
    // 步骤1和步骤2是并行运行的,步骤1步骤2都结束之后进行这个步骤
})

第二个项目用了gulp新的版本,在API层面上解决了这个问题,并行用parallel,串行用series。

const { parallel, series } = require('gulp')
function 步骤1() {
    // 步骤1
}
function 步骤2() {
    // 步骤2
}
function 步骤3() {
    // 步骤3
}
exports.default = series(parallel(步骤1, 步骤2), 步骤3) // 步骤1和步骤2是并行运行的,步骤1步骤2都结束之后进行步骤3

假如步骤1和步骤2有相同的部分,只是某些参数不同,咋办?学的函数式又派上用场了,高阶函数用上。

const { parallel, series } = require('gulp')
exports.步骤 = function(参数) {
    return function 步骤() {
        // 步骤1 步骤2 相同的部分,用参数来区分不同
    }
}
const 步骤1 = 步骤(参数1)
const 步骤2 = 步骤(参数2)
function 步骤3() {
    // 步骤3
}
exports.default = series(parallel(步骤1, 步骤2), 步骤3) // 步骤1和步骤2是并行运行的,步骤1步骤2都结束之后进行步骤3

用高阶函数可以把类似步骤封装起来,而且很自然的就想到模块化了。
所以第二个项目就做了模块化:比如需要操作两个git库,就涉及git的pull和push;比如不同阶段都需要操作文件,就涉及文件的移动和删除。
gulp模块化需要把gulpfile.js文件改成目录名,入口文件是这个目录下的index.js,其他模块则放到这个目录下随意命名了。

// gulpfile.js/git.js
exports.pull = function(参数) {
    return function pull() {
        // 拉代码
    }
}
exports.push = function(参数) {
    return function push() {
        // 可以把 add commit push 写到一起
    }
}
// gulpfile.js/file.js
exports.move = function(参数) {
    return function move() {
        // 文件从一个目录移动到另一个目录
    }
}
exports.del = function(参数) {
    return function del() {
        // 删除文件
    }
}

当然我觉的模块化做的比较好的地方就是config和task的分离,在入口文件组合config和task,组成具体task。

// 配置 gulpfile.js/config.js
const config = {
    // 配置
}
export.getConfig = function() {
    // 通过方法获得配置
}
export.getCombine = function() {
    // 通过方法获得动态配置
}
// 任务 gulpfile.js/task.js
const { parallel, series } = require('gulp')
exports.task = function(name, config, combine) {
    const arr1 = [...] // 根据 config, combine 生成任务1
    if(name == '任务1') {
        return series(...arr1)
    }
    const arr2 = [...] // 根据 config, combine 生成任务2后续的任务
    if(name == '任务2') {
        return series(...arr1, ...arr2)
    }
    return series(...) // 根据 config, combine 任意组合的任务
}
// 入口 gulpfile.js/index.js
const { getConfig, getCombine } = require('./config')
const { task } = require('./task')
const config = getConfig()
const combine = getCombine(参数)
// 根据不同任务放开相应注释
// exports.default = task('任务1', config, combine) // 简单的任务1
// exports.default = task('任务2', config, combine) // 复杂的任务2

第一个项目遇到了一个问题,就是有的步骤gulp插件做不到,自己也没有能力完成插件的开发,因此gulp不能串起来所有步骤,最后是用命令行脚本串起来的。

# mac或linux用shell
gulp --参数 # 混淆压缩
zip -参数 # 打zip包
java -jar --参数 # jdbc 修改数据库版本
gulp ftp # ftp到服务器
# windows用batch
gulp --参数 # 混淆压缩
haozip.exe -参数 # 打zip包
java -jar --参数 # jdbc 修改数据库版本
gulp ftp # ftp到服务器

第二个项目也同样面临这样的问题,但是这次却找到了解决办法。

首先需要在命令行运行的步骤可以在nodejs里运行,原来nodejs自己本身就支持,不需要npm别的包。

const { execSync, exec } = require('child_process')
execSync('命令', 配置)
exec('命令', 配置)

其次是这次用的gulp版本对异步任务的支持,只要任务返回stream、promise、event emitter、child process或observable就可以了,如果是上述命令行任务,按照他们接口的范式编写代码就知道这任务啥时候执行完毕,好执行下个任务。

gerrit

在多人开发的情况下,还需要工作流来管理代码的合并。

第一个项目的工作流大致是这样的:一个需求一个分支,修改bug也算需求,每个测试环境一个分支,测试环境分支是需求分支merge在一起的。之所以采用这样管理方式,是因为有的需求在开发,但是不知道什么时候上线,或者有的需求遇到特殊情况延期上线,这样每次上线只要确定了上线的内容,merge一下对应分支就行了,准生产分支这样做几乎没问题。但是平时的测试分支一边开发一边merge,有冲突时有的人在测试分支解决了,但是在自己分支上没有修改记录,再重新组合时还得解决冲突,而且随着时间的推移,测试分支会越来越复杂,甚至出现每个人自己分支运行没问题,只有测试分支有问题,把测试分支删了,重新merge就没问题了,这个问题一直没有解决,每次都是删了重新组合。

第二个项目的工作流大致是这样的:每个月份上线内容在一个特性分支上开发,拉别人的代码的方式是衍和(rebase),这样减少了分支的关系的复杂度,不会出现第一项目那样需要删了重新组合的情况,而且push代码是受gerrit限制的,这就是为啥用它来做标题了,具体来说本地代码不能直接push到远程分支,而是发布到gerrit,发布的代码是需要经过审核通过,才能提交到远程分支。

第二个项目刚开始是用工具操作衍和和发布的,但是自动打包也要把这些步骤写到脚本里,gulp那部分介绍的“child process”虽然就几行代码,我可是花费了我不少心血的,同样下边的脚本虽然也是短短几行,但也是花费了我不少心血的。

# 衍和
git pull --rebase --progress "origin"
# 把代码发布到 gerrit
git push --progress origin HEAD:refs/for/refs/heads/分支

这个“HEAD:refs/for/refs/heads/”是默认的,也可以在gerrit里配置(没实际操作过,我猜的)。

这两个工作流都有各自特点,第二个工作流虽然分支的复杂度小,但是搭建复杂度较高(需要搭建+1的自动审核)和管理耗费的人力较多(多了一步+2的人工审核)。第一个工作流虽然有时需要删了重新组合,但是这个问题出现概率小,适合3-5人的小团队。我个人认为这两种的结合的工作流比较不错,对于存量功能用第二种,而对于增量功能还是用第一种比较好,新功能风险高,如果不能按期完成交付,可以先不合到特性分支,等达到可交付了再合。而且增量功能分支有了,也可以尽早将公共的模块代码提交分享出去,而不用担心会影响特性分支的交付。

gitlab

自动化打包脚本有了,工作流有了,接下啦就是最关键的了,怎样自动打包?
其实原理就是远程仓库添加git-hooks,远程仓库某些分支标签的某些动作如push就会触发git-hooks关联的自动化平台,自动化平台收到信号,开始执行对应的自动化脚本。当然了不止这一种触发方式,还有定时任务等等,这些不在这篇文章讨论范围。

第一个项目用的是 gitblit + genkins

首先要下载jar包:http://mirrors.jenkins.io/war...

然后启动:java -jar jenkins.war --httpPort=8080

在浏览器输入:http://localhost:8080

配置密码:按照页面指引找到initialAdminPassword文件,复制出密码

首次配置插件:首次可以先跳过插件安装,进入jenkins后再配置

配置插件:在Manage Jenkins/Manage Plugins/Advanced/Update Site里填写
http://mirrors.tuna.tsinghua....

安装插件:在Manage Jenkins/Manage Plugins/Available里找即可

自动打包必不可少的插件必须安装:git、nodejs,如果需要写流水线脚本,需要安装pipeline相关插件(这个项目没有用)

那么远程仓库和jenkins怎么触发自动打包?这个就是git-hooks

点击New Item填入项目名称,点击ok

在Source Code Management里选择Git输入远程仓库url

点击“添加“后输入用户名密码

选择输入的用户名密码

在Build Triggers设置触发器,这个在设置gitblit的时候会用到(令牌随意输就行)

在Build里填写要触发的脚本

点击保存

进入gitblit的groovy目录

编辑jenkins.groovy文件,在jenkinsUrl输入Build Triggers设置触发器,生成的地址

保存之后重启gitblit

进入gitblit管理页面,点击编辑,选中Receive

选中jenkins.groovy,移动到Selected,点击保存

这样gitblit push代码后就会触发Build里填写的脚本

可以参考:

https://www.jianshu.com/p/9a3...

第二个项目用的是 gitlab

gitlab runner下载:https://docs.gitlab.com/runne...

gitlab安装:在下载好的目录下运行

gitlab-runner install
gitlab-runner start

.gitlab-ci.yml:存放于项目仓库的根目录,包含了项目自动化如何运行的描述语句

stages:
  - build

build-gulp:
  tags: 
    - 标签
  stage: build
  script:
    - gulp
  only:
    - web

pipeline:就是一个分成不同stage的job的集合;

stage:是job的一个逻辑上的划分,如“stage: build”;

job:就是Runner要执行的指令集合,必须包含 script,“build-gulp:...” 就是 job,可以自定义多个;

stages:定义了作业执行的顺序,默认包含build、test和deploy三个stage;

script:是一段由Runner执行的shell脚本;

only:web:在gitlab页面上按run pipline的时候执行;

tags:需要执行的Runner标签,需要在“首页=>setting=>CI/CD=>Runners=>Specific Runners”添加;

可以参考:

https://zhuanlan.zhihu.com/p/...

https://juejin.cn/post/701814...

你可能感兴趣的:(前端)