起因
最近因为公司组织上的变动,将我们负责的系统独立为一个部门,独立进行开发,之前所有的代码库和组件库以及npm私服等等的一套全部都要迁移到新的环境,为的就是独立开来。在此之前初到新环境一直都是在进行业务码农,照着需求无脑照搬,也是这次调整,开发人员变少了,大佬也没有了。突然有一种失去靠山的感觉,也不得不开始熟悉现在项目的架子。
技术栈
node.js以及node各种中间件 koa、commander、gaze, lerna等
脚手架功能
- 检查配置的ESlint、stylelint、commitMsg是否合法
- 交互式项目启动(多项目管理,每个项目有自己的index,项目启动时没有跟项目的index就会进行交互式命令窗口进行对应项目选择启动)
- 可配置编译(项目为lerna管理的多包项目,每个包下边没有一个配置文件,用于开启实时编译功能)
- 增量编译(配合git的文件diff功能进行对已开启实时编译的包并有文件改动的包进行重新编译)
- 客户端编译强制刷新(通过socket.io包实现实时通信,在编译中进行对页面发出通知,告知即将进行强制刷新并刷新页面)
- 配置gitlab ranner进行自动化编译(以下gitlab终端ci/cd模块还在研究,后边更新会继续进行补充)
- 通过自动编译后生成镜像,进行自动部署实现持续发布持续集成
开始梳理
本次分析从本地启动服务开始
项目启动
yarn start [index]
- checkNodeVersion 项目启动后先进行了基本环境的检查,判断node的版本是否属于当前要求的版本
- checkYarnVersion 检查yarn版本
- checkHuksy 检查是否安装了Huksy(用于进行对git commit进行检查配置的工具,Huksy只需要配置即可,可以查看官网进行配置)
以上多没有问题就进入到项目的开始阶段 start.js
.task(`解析所有包`, getAllPkg)
.task(`获取启动包`, getStartPkgs(port))
.task(`解析已编译版本`, getLatestVersion(tagVersion))
.task(`安装依赖`, installDep)
.task(`执行钩子`, runHook('devServer'))
.start()
上边使用了一个任务是的编程,好处在于一目了然,可以很好的进行项目的跟踪,原理也很简单,大概原理可以看得出就是想list中进行push方法,然后在调用start的时候遍历执行这些方法。
解析所有的包
此方法主要是在做一个遍历目录的工作,将当前使用lerna多包管理的项目进行全文件遍历,通过递归将所有的包进行规整为一个大的json串,写入一个文件中方便后边使用并将其放入上下文ctx中为allPkgs中
获取启动包
- 首先拿到上一步中allPkgs变量,此为全部的包,将包中标记为主项目的包进行过滤
allPkgs.filter(item => item.mainObject)
- 之后进行判断在输入yarn start 时有没有输入[index],如果输入直接find查找到这个项目,如果没有输入则使用inquirer中间件实现交互式命令行,得到我们需要的项目编号
const rs = await inquirer.prompt([
{
type: 'list',
name: 'name',
message: '选择你要启动的项目,回车(快捷启动 yarn start [左侧对应的数字])',
pageSize: 9999,
mainObjects
}
])
name = rs.name
- 将其查找到的包赋值为全局向下文供后边使用
ctx.diffPkgs = {[name]: ctx.allPkgs[name]}
解析已编译版本
此方法是为了预防在第一次所有包没有打开实时编译,然后又需要直接运行的情况
- 会像gitlab中拉去相同pull的环境编译的最新tag
- 进行解压,然后分发放在对应的每个包的目录下的dist文件中,在平时经常使用所有的模块或者是运行过一次后这个方法可以暂时性的注释掉
安装依赖
此方法也是见名知意,就是想所有包的依赖进行检查安装
执行钩子
前边都是基础项目数据的准备,后边将实现架子的主要功能
- 利用git的diff文件的功能,将变动的文件进行筛选
- 将需要运行的项目放在taskQueue中
- 遍历taskQueue,通过child_process fork对每个任务启动独立的线程运行
- 每个线程会去执行当前上下文ci文件夹中的项目启动文件
项目启动
- 项目启动文件中,使用nodemon先设置了watch确定在脚手架层(node层面)的监听范围
- 监听nodemon的start事件,会调用到ci文件中的watch文件
// 启动前端层编译并监听变化
Object
.keys({ ...pkg.dependencies, ...pkg.devDependencies })
.filter(key => key.startsWith('@support-dianmi/front') || key.startsWith('@support-dianmi-front/'))
.forEach(pkgName => {
const watch = require(`${pkgName}/ci/watch`)
const root = resolve(`node_modules/${pkgName}`)
watch({ root, port })
})
- 在监听文件中执行launch(启动整个实时编译的模块)
- 其中最主要的是通过gaze进行对实时编译开关配置文件的监听工作
gaze(watch.switchFile, function(err, watcher) {
if (err) {
console.log(err)
}
this.on('changed', filepath => {
self.check(option)
})
})
......
check() {
......
if (runtime) {
// 开启webpack watch
console.log(`${watch.name} 初次编译,请等待...`)
} else if (this.task) {
this.task.io.close()
this.task.watching.close(() => {
console.log(`${watch.name} 关闭监听`)
this.task = null
})
}
}
- 利用webpack的钩子,对编译完成进行监听
const { invalid } = compiler.hooks
invalid.tap('dev', () => {
console.log(`${watch.name} 编译中...`)
io.emit('compiling', channel)
})
- 可以看出上边还有一个io.emit('compiling', channel)操作,此操作是为通知客户端,正在编译中,即将编译完成并进行页面刷新动作
const socket = require('socket.io-client')
const io = socket(`http://${host}:${port}`)
......
compiler.watch({ aggregateTimeout: 600, ignored: /node_modules/ }, (err, stats) => {
console.log(`${watch.name} 编译完成!`)
io.emit('refresh', channel)
})
整个脚手架的基础结构到此就基本完成了 后边还有将项目启动后使用koa进行静态资源的分发以及在gitlab上的CI/CD配置,将在后换我熟悉理通后进行记录和分享
初次写技术分享文章,主要还是想让自己迈出这一步,差不多大半年前都想着自己要开始坚持写文章分享,同时可以鞭策自己,一直拖到现在,同样也希望可以能一直坚持下去吧。可能还是有些粗糙和抽象,阅读后发现里边还是有一些可以优化的地方,后期自己试着搭建一个脚手架后将分享写的更加详细一些。