背景
基于前后端分离且没有固定运维人员的情况下,对于不熟悉前端打包的其他开发人员,对整个打包流程一知半解,即使你手把手的教操作、写文档还是弥补不了,为此开发了一个在服务端打包的傻瓜式流程工具。
思路
需求其实不复杂,只需要把在本地开发打包的流程用代码的形式运行到服务端即可,这里涉及到node相关的文件操作及子进程的应用。
本篇文章内容过长,请耐心阅读!
技术栈
服务端:Egg
之所以选择Egg是折服于其MVC的框架设计,很方便
前端:vue 、ant-design-vue
老vue玩家 + 尝鲜UI库 ,体验一番element-ui和 ant-design-vue的不同之处
服务端
由于接口太多,这里只拿一个接口为例,大同小异。
请求全部项目列表:
- service层:
async getprojects(token){
const {ctx} = this;
return await this.curl(
`/api/user/projects?page=1&pageSize=1000&type=joined`,
{
method: 'GET',
}
)
}
- controller层:
async getprojects(){
const { ctx, service } = this;
const data = await service.coding.index.getprojects(this.token);
this.send(data);
}
- router层:
router.get('/getprojects',index.getprojects);
主要逻辑
检查服务端是否安装git 和 yarn
// 前置依赖及变量
const { execSync } = require('child_process');
let _hasYarn;
let _hasGit;
- 检查是否安装yarn
module.exports.hasYarn = function hasYarn(){
if (_hasYarn != null) {
return _hasYarn
}
try {
execSync('yarnpkg --version', { stdio: 'ignore' })
return (_hasYarn = true)
} catch (e) {
return (_hasYarn = false)
}
}
- 检查是否安装git
module.exports.hasGit = function hasYarn(){
if (_hasGit != null) {
return _hasGit
}
try {
execSync('git --version', { stdio: 'ignore' })
return (_hasGit = true)
} catch (e) {
return (_hasGit = false)
}
}
- 设置淘宝源
const execa = require('execa');
const local = 'http://39.106.109.144:4000/' // 这里是本地搭建的npm仓库,可换成淘宝镜像源
module.exports = async function setOrigin() {
const {stdout} = await execa('yarn',['config', 'set', 'registry',local]);
return stdout;
}
- clone项目及安装依赖
这一步的逻辑主要为一下几步步:
1.如果目录文件下存在该工程即直接运行下一步操作(安装依赖);
2.如果没有直接clone下来
3.当前项目分支是否为选择分支,是:直接安装依赖;否:切换分支
代码如下:
const execa = require('execa');
const fs = require('fs');
const path = require('path');
// 克隆项目
function gitClone(project, branch,gitUrl){
const workSpce = path.resolve(__dirname, WORK_SPACE);
return execa.sync('git',['clone', gitUrl],{
cwd: workSpce,
stdio: 'inherit'
})
};
// 安装并更新依赖
function installOrUpdate(projectPath, project, branch, gitUrl){
const exists= fs.existsSync(projectPath);
if(!exists){
gitClone(project, branch, gitUrl)
}else{
execa.sync('git',['pull'],{
cwd: projectPath
});
}
return upgrade(projectPath, branch)
};
// 检查分支并更新安装依赖包
function upgrade( projectDir, branch = "master") {
const currentBrach = getGitCurrentBranch(projectDir);
if(currentBrach !== branch){
execa.sync('git',['checkout', branch],{
cwd: projectDir
});
}
return execa('yarn',['--production=false','--force'],{
cwd: projectDir
});
};
// 查看项目当前分支
function getGitCurrentBranch(projectDir){
return execa.sync('git',['symbolic-ref', '--short', '-q', 'HEAD'],{
cwd: projectDir
}).stdout;
};
// 更新依赖核心代码
function upgrade( projectDir, branch = "master") {
const currentBrach = getGitCurrentBrach(projectDir);
if(currentBrach !== branch){
execa.sync('git',['checkout', branch],{
cwd: projectDir
});
}
return execa('yarn',['--production=false','--force'],{
cwd: projectDir
});
};
// 安装依赖
function install(project, branch, gitUrl){
const workSpce = path.join(__dirname, WORK_SPACE, project);
return installOrUpdate(workSpce,project,branch,gitUrl)
};
// clone项目
function clone(project, branch,gitUrl){
return install(project, branch,gitUrl)
}
- 项目打包
// 执行打包
function build(project, port){
const workSpce = path.join(__dirname, WORK_SPACE, project);
return cloudBuild(workSpce, port)
}
// port为页面中iframe的端口
function cloudBuild(workSpce, port = '7777'){
return execa('npm',['run', 'build', `--env.SERVER_URL=${port}`],{
cwd: workSpce
});
}
- 下载打包成果zip包,依赖adm_zip模块
const adm_zip = require('adm-zip');
// 根据项目vue.config配置读取项目打包成果对应的文件,并压缩为zip包
function download(projectDir){
const workSpce = path.join(__dirname, WORK_SPACE, projectDir);
const output = require(workSpce+'/vue.config.js').outputDir;
const resolve = path.resolve(workSpce, output);
const zip = new adm_zip();
zip.addLocalFolder(resolve);
zip.writeZip(`${resolve}.zip`);
return {
file:fs.createReadStream(`${resolve}.zip`),
name: `${output}.zip`
};
}
返回相应执行进度给前端
仅仅展示安装依赖的相关逻辑,打包逻辑与此一样
const { install } = require('./install')
class InstallEvent {
constructor(){
this.message = '';
this.finish = false
}
get output(){
return this.message;
}
handleInstall(project, branch, gitUrl){
const {stdout, stderr} = install(project, branch, gitUrl); // 执行上面安装依赖js
stdout.on('data', (buffer)=>{
const msg = buffer.toString();
if(!msg || msg === '')return;
this.message += msg;
});
stdout.on('end', (buffer)=>{
this.finish = true
});
stderr.on('data', (buffer) =>{
this.message += buffer.toString();
});
stderr.on('end', (buffer)=>{
this.finish = true
});
}
}
module.exports = new InstallEvent()
- 在相应的controller层编写相应逻辑
const eventer = require('../../helper/installEvent')
async cloudInstall() {
const { ctx } = this;
const { branch, project, gitUrl} = ctx.request.body;
this.send({
data:{
data:{
step:3,
message: '正在下载'
},
code:0
}
});
eventer.message = '';
eventer.finish = false
await eventer.handleInstall(project, branch, gitUrl);
}
返回执行安装进度,前端通过定时请求执行
async getInstallMessage(){
const message = eventer.message;
this.send({
data:{
data:{
step:3,
message: message,
finish: eventer.finish
},
code:0
}
});
}
- 在controller层下载zip包
async download(){
const stream = download(this.ctx.request.body.project)
this.ctx.set('Content-Type', 'application/octet-stream');
this.ctx.set('Content-Disposition',`attachment; filename=${stream.name}`);
this.ctx.body = stream.file;
}
最后
挂载相应接口
router.post('/clone',appLication.clone);
router.post('/install',appLication.cloudInstall);
router.get('/getInstallMessage',appLication.getInstallMessage);
router.post('/download',appLication.download);
PS:欢迎大家关注我的公众号【前端专刊】,一起加油吧~
前端专刊
网友评论