HBuilderX uniapp项目转 cli 项目实现在容器中打包
HBuilderX 目前仅支持 Windows 和 macOS,这给发布流水线造成不小的阻碍,我们最初的做法是在一台 Windows 上安装 HBuilderX、Jenkins(下文称之为HXJenkins),HXJenkins 使用 HBuilder cli 命令行打包,主 Jenkins 调用 HXJenkins API 进行构建。我们实践之后,发现不少问题:
- HXJenkins 打包 h5,我们要制作成容器镜像,打包后的 dist 需要回传给 Linux 服务器,Linux 服务器再制作容器镜像。流程非常复杂,还需要考虑文件如何回传等问题;
- HBuilder cli 时不时会出现打不开项目的问题,需要重启 HBuilderX
- 有时打包会特别的慢,且容易报错
- 主 Jenkins 调用 HXJenkins API 进行构建,HXJenkins 构建报错上游无法知道,增加心智负担
- ……
我们希望能实现基于容器的 uniapp 打包方案,经过在 DCloud 官网及搜索一番了解后,主要的方案有两个:
- HBuilderX 项目转为 vue cli 项目
- 底层都是 node,HBuilderX 只是把操作封装罢了,因此研究 HBuilderX 的打包原理,通过一些技术手段分析 HBuilderX 最终是执行的什么命令来打包的
方案一相对简单,也是本文所探讨的方案,但是可能在你的项目上实践不会轻而易举就能成功,一般会在依赖包以及依赖包的版本上会踩坑。
方案二相对复杂,但是通用性应该更好,方案二已经有大佬写了博客以及在 github 上维护了源码,有兴趣的读者请参考 漫谈Uniapp App热更新包-Jenkins CI/CD打包工具链的搭建_uniapp jenkins-CSDN博客。
创建项目脚手架
官方文档有详细的使用 cli
脚手架创建 uni-app
项目的教程,uni-app官网
我的是 Vue3/Vite 以 javascript 开发的工程,因此执行以下命令创建项目脚手架:
npx degit dcloudio/uni-preset-vue#vite my-vue3-project
将原项目的代码复制到脚手架项目src目录
HBuilderX 项目的目录结构是源码直接放在顶层目录下的,而 cli 项目的源码是放在 src 目录下,因此 HBuilderX 项目转 cli 项目需要将 HBuilderX 项目的所有文件复制到 cli 项目的 src 目录。
cd my-vue3-project
git clone https://git.com/my-name/my-uni-app-project.git
cp -R my-uni-app-project/* src
改造 package.json
以我这个项目为例,原项目 package.json 内容如下:
{
"dependencies": {
"axios": "^1.4.0",
"dayjs": "^1.11.10",
"esdk-obs-browserjs": "^3.23.5",
"jsencrypt": "^3.3.2",
"mimetype": "^0.0.8",
"moment": "^2.29.4",
"mp-html": "^2.4.2",
"pinia": "2.0.33",
"pinia-plugin-persist-uni": "1.2.0",
"postal-mime": "^1.0.16",
"vant": "^4.6.4",
"vue": "^3.4.15",
"watermark-js-plus": "^1.3.18"
},
"devDependencies": {
"@dcloudio/types": "^3.4.0",
"@dcloudio/uni-app": "3.0.0-3090620231104002",
"@types/html5plus": "^1.0.3",
"@types/uni-app": "^1.4.5",
"@types/vue": "^2.0.0",
"unplugin-vue-components": "^0.25.1",
"vite": "^4.4.11"
}
}
改造后的 package.json 内容如下:
{
"dependencies": {
"@dcloudio/uni-app": "3.0.0-3090620231104002",
"@dcloudio/uni-app-plus": "3.0.0-3090620231104002",
"@dcloudio/uni-components": "3.0.0-3090620231104002",
"@dcloudio/uni-h5": "3.0.0-3090620231104002",
"@dcloudio/uni-mp-alipay": "3.0.0-3090620231104002",
"@dcloudio/uni-mp-baidu": "3.0.0-3090620231104002",
"@dcloudio/uni-mp-jd": "3.0.0-3090620231104002",
"@dcloudio/uni-mp-kuaishou": "3.0.0-3090620231104002",
"@dcloudio/uni-mp-lark": "3.0.0-3090620231104002",
"@dcloudio/uni-mp-qq": "3.0.0-3090620231104002",
"@dcloudio/uni-mp-toutiao": "3.0.0-3090620231104002",
"@dcloudio/uni-mp-weixin": "3.0.0-3090620231104002",
"@dcloudio/uni-mp-xhs": "3.0.0-3090620231104002",
"@dcloudio/uni-quickapp-webview": "3.0.0-3090620231104002",
"axios": "^1.4.0",
"dayjs": "^1.11.10",
"esdk-obs-browserjs": "^3.23.5",
"jsencrypt": "^3.3.2",
"mimetype": "^0.0.8",
"moment": "^2.29.4",
"mp-html": "^2.4.2",
"pinia": "2.0.33",
"pinia-plugin-persist-uni": "1.2.0",
"postal-mime": "^1.0.16",
"vant": "^4.6.4",
"vue": "^3.4.15",
"watermark-js-plus": "^1.3.18"
},
"devDependencies": {
"@dcloudio/types": "^3.4.0",
"@dcloudio/uni-app": "3.0.0-3090620231104002",
"@dcloudio/uni-automator": "3.0.0-3090620231104002",
"@dcloudio/uni-cli-shared": "3.0.0-3090620231104002",
"@dcloudio/uni-stacktracey": "3.0.0-3090620231104002",
"@dcloudio/vite-plugin-uni": "3.0.0-3090620231104002",
"@types/html5plus": "^1.0.3",
"@types/uni-app": "^1.4.5",
"@types/vue": "^2.0.0",
"@vue/runtime-core": "^3.3.11",
"less": "^4.2.0",
"sass": "^1.74.1",
"unplugin-vue-components": "^0.25.1",
"vite": "^4.4.11"
},
"scripts": {
"dev:app": "uni -p app",
"dev:app-android": "uni -p app-android",
"dev:app-ios": "uni -p app-ios",
"dev:custom": "uni -p",
"dev:h5": "uni",
"dev:h5:ssr": "uni --ssr",
"dev:mp-alipay": "uni -p mp-alipay",
"dev:mp-baidu": "uni -p mp-baidu",
"dev:mp-jd": "uni -p mp-jd",
"dev:mp-kuaishou": "uni -p mp-kuaishou",
"dev:mp-lark": "uni -p mp-lark",
"dev:mp-qq": "uni -p mp-qq",
"dev:mp-toutiao": "uni -p mp-toutiao",
"dev:mp-weixin": "uni -p mp-weixin",
"dev:mp-xhs": "uni -p mp-xhs",
"dev:quickapp-webview": "uni -p quickapp-webview",
"dev:quickapp-webview-huawei": "uni -p quickapp-webview-huawei",
"dev:quickapp-webview-union": "uni -p quickapp-webview-union",
"build:app": "uni build -p app",
"build:app-android": "uni build -p app-android",
"build:app-ios": "uni build -p app-ios",
"build:custom": "uni build -p",
"build:h5": "uni build",
"build:h5:ssr": "uni build --ssr",
"build:mp-alipay": "uni build -p mp-alipay",
"build:mp-baidu": "uni build -p mp-baidu",
"build:mp-jd": "uni build -p mp-jd",
"build:mp-kuaishou": "uni build -p mp-kuaishou",
"build:mp-lark": "uni build -p mp-lark",
"build:mp-qq": "uni build -p mp-qq",
"build:mp-toutiao": "uni build -p mp-toutiao",
"build:mp-weixin": "uni build -p mp-weixin",
"build:mp-xhs": "uni build -p mp-xhs",
"build:quickapp-webview": "uni build -p quickapp-webview",
"build:quickapp-webview-huawei": "uni build -p quickapp-webview-huawei",
"build:quickapp-webview-union": "uni build -p quickapp-webview-union"
}
}
HBuilderX 项目转 cli 项目步骤简单,前两个步骤是固定的,改造 package.json 可能会遇到很多报错,总体的改造思路如下:
以原项目的 package.json 为基础,加上脚手架的 package.json 中的 uni-*
等依赖,注意版本要和原项目的版本保持一致,否则可能会编译报错。如遇到报错,根据报错信息处理即可。
Jenkins pipeline
def GIT_AUTH = "xxx"
def HARBOR = "xxx"
pipeline {
agent {
kubernetes {
yaml """
apiVersion: v1
kind: Pod
spec:
containers:
- name: nodejs
image: ${HARBOR}/library/node:18-alpine
stdin: true
tty: true
resources:
requests:
cpu: "1"
memory: 4Gi
limits:
cpu: "2"
volumeMounts:
- name: deployyaml
subPath: deployment.yaml
mountPath: /home/deployment.yaml
- name: data
mountPath: /home/jenkins/agent
- name: kubectl
image: ${HARBOR}/library/kubectl:1.24.7
stdin: true
tty: true
volumeMounts:
- name: deployyaml
subPath: vue-deploy.yaml
mountPath: /home/vue-deploy.yaml
- name: data
mountPath: /home/jenkins/agent
- name: jq
image: ${HARBOR}/library/jq:1.7.1
stdin: true
tty: true
volumeMounts:
- name: data
mountPath: /home/jenkins/agent
- name: jnlp
image: ${HARBOR}/library/jenkins-slave:jdk17
imagePullPolicy: Always
volumeMounts:
- name: docker-sock
mountPath: /var/run/docker.sock
- name: docker
mountPath: /usr/bin/docker
- name: data
mountPath: /home/jenkins/agent
volumes:
- name: docker-sock
hostPath:
path: /var/run/docker.sock
- name: docker
hostPath:
path: /usr/bin/docker
- name: config
configMap:
name: mvn-cm
- name: deployyaml
configMap:
name: vue-deploy
- name: data
persistentVolumeClaim:
claimName: jenkins
"""
defaultContainer 'jnlp'
}
}
stages {
stage('拉取代码') {
steps {
checkout([$class: 'GitSCM', branches: [[name: "vite"]], userRemoteConfigs: [[credentialsId: "${GIT_AUTH}", url: "https://git.domain/uni-preset-vue.git"]]])
checkout([$class: 'GitSCM', extensions: [[$class: 'RelativeTargetDirectory', relativeTargetDir: 'xxx']], branches: [[name: "${BRANCH}"]], userRemoteConfigs: [[credentialsId: "${GIT_AUTH}", url: "https://git.domain/xxx.git"]]])
}
}
stage('代码编译') {
steps {
container('jq') {
sh """
cp package.json package.json.back
jq '{"dependencies": .dependencies, "devDependencies": .devDependencies, "scripts": input.scripts}' xxx/package.json package.json.back > package.json
sed -i '/dependencies/a"@dcloudio/uni-app": "3.0.0-3090620231104002",\\n "@dcloudio/uni-app-plus": "3.0.0-3090620231104002",\\n "@dcloudio/uni-components": "3.0.0-3090620231104002",\\n "@dcloudio/uni-h5": "3.0.0-3090620231104002",\\n "@dcloudio/uni-mp-alipay": "3.0.0-3090620231104002",\\n "@dcloudio/uni-mp-baidu": "3.0.0-3090620231104002",\\n "@dcloudio/uni-mp-jd": "3.0.0-3090620231104002",\\n "@dcloudio/uni-mp-kuaishou": "3.0.0-3090620231104002",\\n "@dcloudio/uni-mp-lark": "3.0.0-3090620231104002",\\n "@dcloudio/uni-mp-qq": "3.0.0-3090620231104002",\\n "@dcloudio/uni-mp-toutiao": "3.0.0-3090620231104002",\\n "@dcloudio/uni-mp-weixin": "3.0.0-3090620231104002",\\n "@dcloudio/uni-mp-xhs": "3.0.0-3090620231104002",\\n "@dcloudio/uni-quickapp-webview": "3.0.0-3090620231104002",' package.json
sed -i '/devDependencies/a"@dcloudio/uni-automator": "3.0.0-3090620231104002",\\n"@dcloudio/uni-cli-shared": "3.0.0-3090620231104002",\\n"@dcloudio/uni-stacktracey": "3.0.0-3090620231104002",\\n"@dcloudio/vite-plugin-uni": "3.0.0-3090620231104002",\\n"@vue/runtime-core": "^3.3.11",' package.json
"""
}
container('nodejs') {
sh """
npm install
npm i sass less -D
cp -R xxx/* src
npm run build:h5
"""
}
}
}
stage('构建镜像') {
steps {
...
}
}
stage('部署'){
steps{
...
}
}
}
}