为了偷懒,我用google/zx一键自动打包编译了前后端项目并发布到指定环境

2,197 阅读6分钟

本文正在参加「金石计划 . 瓜分6万现金大奖」 

大家好,我是 那个曾经的少年回来了。10年前我也曾经年轻过,如今已步入被淘汰的年龄,但现在幡然醒悟,所以活在当下,每天努力一点点,来看看2024年的时候自己会是什么样子吧,2024年的前端又会是什么样子,而2024年的中国乃至全球又会变成什么样子,如果你也有想法,那还不赶紧行动起来。期待是美好的,但是更重要的是要为美好而为之奋斗付诸于行动。

1、前言

由于正在负责的一个项目,就说前端涉及到PC端、公众号端、APP端的H5、小程序端、可视化大屏端,而PC和APP又通过qiankun引入了微前端的理念。整体一圈下来可能光前端编译打包就要build差不多二十次。而有时候经常性的bug改动,这个时候便只需要进行测试后需要进行小范围的测试。

还有一个不得不说,用JavaScript就能写出自动化编译打包的脚本,对于一个前端程序猿来说还是非常的nice。上手也是非常的快。欢迎你也来一起体验加入。

先说一下目前我代码里已经实现的功能 我已将所有代码上传到github了,github地址:github.com/aehyok/zx-d… ,有兴趣的话可以一起研究学习一下

2、实现的功能

2.1、 拉取代码

实现cd到应用后,先git pull拉取代码(没有实现安装依赖的功能,需要手动实现检查)

export const gitPullBy = async(projectName: string, path: string) => {
  try {
     writerLog(projectName, `git pull start`, global.version);
    const gitPullInfo = await $`cd ${path}; git pull;`;
    console.log(gitPullInfo, "pullInfo");
    if (gitPullInfo.exitCode === 0) {
       writerLog(projectName, `git pull end success`, global.version);
    } else {
      console.log("fail", $`$?`);
    }
  } catch {
     writerLog(projectName, `git pull error`, global.version);
  }
};

这里目前可以进行优化处理:比如配置好git仓库地址和对应所在目录后,再自动安装依赖,build编译打包项目

2.2、设置版本号

这里简单啰嗦一下,比如在我们前端项目中一般都有一个显示版本号的地方,可能在设置中,或者我的最下面的某个地方。这是我目前开发的一个项目,如下图所示是微信小程序中显示版本号的地方

image.png

首先我通过 standard-version 这个依赖来更新版本号,自动根据当前版本号进行匹配生成。 我的版本号是通过读取package.json中的version的,所以我可以上面 2.1、 拉取代码后,通过代码先将package.json中的 version版本号进行修改

// 显示版本号的地方
// 引入package.json文件

import config from '../../package.json'
this.version = config.version
export const updateVersion = (path: string) => {
  cd(`${ path }`);
  const packageString = fs.readFileSync(`${path}\\package.json`).toString();
  let packageJson = JSON.parse(packageString)
  packageJson.version = global.version;

  fs.writeFileSync(`${path}\\package.json`, JSON.stringify(packageJson, null, 2))
}

主要通过 nodejsfs模块的 readFileSync ,读取package.json文件,转换为JSON对象,进行修改,然后再通过 fs 模块的 writeFileSync将版本号写回。

当然版本号的获取还有别的很多方式,这里只是我自己想出来比较简单的一种方式,如果你有更简单的方式,咱们可以留言学习一波。

2.3、编译打包项目

修改完前端项目的版本号以后,就是要对项目进行build编译打包了

export const yarnBuildBy = async (path: string) => {
    try {
         oneLogger(`yarn build start ${path}`);
        const buildInfo = await $` cd ${path};yarn build;`;
        console.log(buildInfo, "buildInfo");
        if (buildInfo.exitCode === 0) {
           oneLogger(`yarn build end success`);
        } else {
           oneLogger(`yarn build error: ${buildInfo.stderr}`);
        }
      } catch(error) {
          console.log('yarn build error', error)
           oneLogger("yarn build error");
      }
}

这里我目前使用的 yarn build进行编译前端项目,根据具体的项目可以进行适当的修改,或者进行配置即可。

2.4、对代码仓库进行git tag打标签

因为有时候编译打包后,可能会发现线上的版本代码是有bug的,那么就需要对线上版本的代码进行bug的修复,如果我们自动打好标签,跟线上版本比对后,直接根据当前线上版本的tag生成对应分支的代码即可。

const addTag = async (path: string, isExist: boolean) => {
  const result = await $` cd ${path};
                           git tag -a ${global.version} -m 'chore:version ${global.version}版本号'; 
                           git push origin ${global.version};`;
  if (result && result.exitCode === 0) {
    if (isExist) {
      await oneLogger(`re create tag [${global.version}] success`);
    } else {
      await oneLogger(`create tag [${global.version}] success`);
    }
  }
};

这里我只是简单的进行tag的标记,并推送,其实我们在打tag的时候,首先要尽量检查一下tag是否已经存在,如果存在,考虑一下是否删除重新生成,或者使用新的tag名称等等。

其实这里有一个注意事项: tag标签和分支名称进行不要重复,首先说明一下重复是没问题的,毕竟一个是tag标签,一个是分支,但是有时候我们推送代码的时候没有明确的指定 就会出现问题。当然如果你的git 操作的非常流弊,各种姿势都非常熟练,那肯定是没问题的。比如像我可能就会注意一下尽量保证分支名和tag标签名是不一致的,避免后期产生一些头疼的问题。

2.5、推送源代码到git仓库

上面我们的git tag其实就已经推送到了代码仓库,道理是一样的,加入上面修改版本号以后,我们想将代码文件推送到仓库

const message=`chore: ${buildProject}::commit-version-${global.version}`
const result = await $`cd ${releasePath}; git add . ; sleep 3; git commit -m ${message}; git push origin;`
if(result && result.exitCode === 0 ) {
    await writerLog(name, `git push end success`, global.version);
} else {
    await writerLog(name, `git push error: ${result.stderr}`, global.version); 
}

这里我一键三连, git add . git commit git push 中间使用sleep 3,沉睡了三秒钟,这样执行 git commit 的时候一般不会出现问题。

2.6、拷贝编译后的打包文件

代码打包编完成之后,是在本地服务器指定位置,假如我们想拷贝到远程服务器

export const copyFile = async() => {
    try {
        const path = global.project.projectName
        const ipAddress = 139.9.184.171
        // const ipAddress = '139.9.184.171'
        const result = await $`scp -r /e/work/git-${global.environment}/release/cms/${path}/* root@${ipAddress}:/usr/local/sunlight/dvs/dvs-ui/${path}/`
        if(result.exitCode === 0) {
            oneLogger(`copy file  [${global.version}] end success`)
        }
        else {
            console.log("fail", $`$?`);
        }
    } catch {
        oneLogger(`copy file [${global.version}] end error`)
    }
}

这里我是将本地代码拷贝到了远程的linux服务器,可以将本地的ssh秘钥拷贝到远程服务器的指定目录。

2.7、将编译的项目文件,单独存放到一个代码仓库

比如这个仓库叫 release仓库

因为有时候一个项目可能涉及到多个端的代码,比如PC有项目,APP的H5也有项目,而小程序里也有H5的项目要打包,项目大了以后,整个编译打包也变的复杂多变,这样就有一个单独仓库当前项目打包编译后的仓库,方便记录,以及回滚,单独某个小项目发布的需求。

这里的代码就比较简单了,上面的代码中也有提到,其实就是将一键三连将代码提交并推送到远程git仓库

export const gitPullBy = async(projectName: string, path: string) => {
  try {
     writerLog(projectName, `git pull start`, global.version);
    const gitPullInfo = await $`cd ${path}; git pull;`;
    console.log(gitPullInfo, "pullInfo");
    if (gitPullInfo.exitCode === 0) {
       writerLog(projectName, `git pull end success`, global.version);
    } else {
      console.log("fail", $`$?`);
    }
  } catch {
     writerLog(projectName, `git pull error`, global.version);
  }
};

其实我这里想了一下,如果对打包编译后的仓库,也打包tag标签,保证线上版本和这个release仓库的,再做一个历史记录进行保存,这样其实很容易方便回滚,比如当前发布的版本是3.5.1,可是突然发现了重大问题,必须回滚,这个时候就需要将线上的版本马上回滚到3.5.0,OK,那么现在马上拉取release仓库中的 tag标签为3.5.0的仓库代码即可。感觉上好像没什么问题。

同时服务器也可以进行历史版本的备份动作,方便回滚操作。

2.8、后端编译也是完全没有问题的

主要就是对构建流程的梳理而已,跟前后端其实是没关系的。


import { $ } from 'zx'
import { gitPull } from './utils/git-pull.mjs';
const path = "/H/work/dvs/server-csharp/"
const collectPath= path + "Services/DVS.Collect.API"


// await gitPull();
const buildInfo = await $`cd ${collectPath}; dotnet publish -o ../../publish/dvs-collect -f net6.0 -r linux-arm64 --no-self-contained;`;
if(buildInfo.exitCode === 0) {
  console.log("build info ok")
} else {
  console.log(`build info error: ${buildInfo.stderr}`)
}

const buildpath = 'dvs-collect'

const ipAddress = '139.9.184.171'   //  139.9.184.171   // 121.37.222.1
const result = await $`scp -r /h/work/dvs/server-csharp/publish/${buildpath}/* root@${ipAddress}:/usr/local/sunlight/dvs/${buildpath}/`
if(result.exitCode === 0) {
    console.log(`copy file end success`)
}
else {
  console.log("fail", $`$?`);
}

// 可以执行本地的server.sh脚本指令 (-t保持登录状态    ssh -t root@139.9.184.171 < server.sh)
// 还可以添加脚本参数
const login = await $`ssh root@${ipAddress} < server.sh`

if(login.exitCode === 0) {
  console.log(`ssh login success`)
}
else {
  console.log("fail", $`$?`);
}

运行完本地的打包编译后,登录远程,并执行server.sh,比如来执行服务的重启等。

3、goploy

来看看这个产品,它在github上是有开源的,开箱即用,简单部署一下就可以使用,感觉还是非常方便的,这是我在自己的window本机部署了一套环境,其实跟我自己写的有很多功能是重复的,当然我做的还算是比较陋跟大佬简直不可比拟,使用上更加的人性化,毕竟通过操作界面就可以完成

image.png

github开源地址: github.com/zhenorzz/go…

前端采用的Vue3+TypeScript+ Element-plus 这个前端技术跟我还是非常的匹配,后端采用的是大热的go语言,这个对我来说可能难度稍微大了一些,毕竟没接触过。不过我觉得可能经过一个周左右的时间,应该看懂一点代码,应该问题不大吧,猜测的 当然可能是自己太自信了 哈哈, 明年有时间的话会将这个工具结合自身的使用进行升级一波。可能更贴合于现在公司的使用场景吧。

当然了目前公司也使用了这个工具,已经极大的简化了部署的繁杂,应该说是节省了很多的时间,而且按照这个步骤来,很多时候是不容易出错的。

4、总结

目的就是为了让那些重复性的工作,慢慢的通过工具来替代,将节省下来的时间去研究更有意义的事情,或者去摸摸鱼划划水,是不是更香呢?当然记得不要告诉老板哟? 哈哈 开个玩笑

我的个人博客:vue.tuokecat.com/blog

我的个人github:github.com/aehyok

我的前端项目:pnpm + monorepo + qiankun + vue3 + vite3 + 工具库、组件库 + 工程化 + 自动化
不断完善中,整体框架都有了
在线预览:vue.tuokecat.com
github源码:github.com/aehyok/vue-…