前言
在使用 uni-app 构建多环境 App(如 dev/uat/prod)时,常常会面临一个棘手问题:版本号混乱 。由于 manifest.json
是每次打包都需要修改的核心配置文件,一旦在开发分支中修改了版本号,再合并到测试或生产分支时极易出现版本号被覆盖、热更新异常等问题。
本文将介绍如何借助 Node.js 编写的自动化脚本,结合项目内版本映射文件,实现一套适用于多环境的 WGT 自动打包、版本安全管理、可选 Git 提交的高效方案。
一、背景与痛点
在传统打包流程中:
- 每次打包都需要手动修改
manifest.json
的versionName
- 多个环境共用一份 manifest,极易发生版本污染
- 容易遗漏版本号提交,造成热更新失败
- 没有标准的打包规范,依赖开发者记忆和手动操作
这些问题在多人协作、频繁发布的项目中尤其明显。
二、解决方案设计
我们为此设计了一套脚本驱动的自动打包机制:
- 使用单独文件
wgt-env-version-map.json
统一管理各环境版本号 - 打包脚本会临时更新
manifest.json
中的版本号,不影响源码 - 支持一键构建并生成
.wgt
文件,产物命名规则清晰统一 - 可选
--commit
参数将版本变更提交至 Git,确保记录完整 - 最后一步自动还原
manifest.json
,防止版本号残留
三、项目结构与配置
1. 环境版本映射文件(src/wgt-env-version-map.json)
json
{
"dev": "1.0.0",
"uat": "1.1.0",
"prod": "1.2.0"
}
2. 打包命令(package.json)
json
"scripts": {
"build:wgt": "node script/build-wgt.js",
"build:wgt-dev": "node script/build-wgt.js --env dev --commit",
"build:wgt-uat": "node script/build-wgt.js --env uat --commit",
"build:wgt-prod": "node script/build-wgt.js --env prod --commit",
...
}
这些命令将根据环境读取对应版本号,并完成 WGT 包的构建。
3. 构建流程自动化脚本(script/build-wgt.js)
javascript
const fs = require('fs')
const { execSync } = require('child_process')
const join = require('path').join
const compressing = require('compressing')
// 从命令行参数获取环境变量
const args = process.argv.slice(2)
let env = 'prod'
let shouldCommit = false
// 解析命令行参数
for (let i = 0; i < args.length; i++) {
if (args[i] === '--env' && i + 1 < args.length) {
env = args[i + 1]
i++
} else if (args[i] === '--commit') {
shouldCommit = true
}
}
// 验证环境参数
if (!['dev', 'uat', 'prod'].includes(env)) {
console.error('环境参数只能是 dev/uat/prod 之一')
console.error('用法: node build-wgt.js --env [dev|uat|prod] [--commit]')
// 在npm run build:wgt 后面添加 -- 即可自定义传入参数
console.error('或者: npm run build:wgt -- --env [dev|uat|prod] [--commit]')
process.exit(1)
}
console.log('====================')
console.log(`当前环境: ${env}`)
if (shouldCommit) console.log('将更新后的版本映射文件提交到git')
console.log('====================')
// 读取版本映射文件
let versionMap
try {
const versionMapContent = fs.readFileSync(__dirname + '/../src/wgt-env-version-map.json', 'utf-8')
versionMap = JSON.parse(versionMapContent)
console.log(`读取版本映射文件成功`)
} catch (e) {
console.error('读取wgt-env-version-map.json文件失败:', e.message)
process.exit(1)
}
// 根据环境获取版本号
const targetVersion = versionMap[env]
if (!targetVersion) {
console.error(`未找到环境 ${env} 对应的版本号`)
process.exit(1)
}
console.log(`目标wgt版本: ${targetVersion}`)
// 读取manifest文件并保存原始内容
const manifestPath = __dirname + '/../src/manifest.json'
let originalManifestContent = fs.readFileSync(manifestPath, 'utf-8')
// 解析manifest内容
let manifestContent = originalManifestContent
.replace(/\/\/.*$/gm, '') // 移除单行注释
.replace(/\/\*[\s\S]*?\*\//g, '') // 移除多行注释
.replace(/,(\s*[}\]])/g, '$1') // 修复尾随逗号问题
let manifest
try {
manifest = JSON.parse(manifestContent)
console.log(`当前manifest版本: ${manifest.versionName}`)
} catch (e) {
console.error('解析manifest.json文件失败:', e.message)
// 输出处理后的内容前几行,便于调试
console.log('处理后的JSON内容片段:')
console.log(manifestContent.slice(0, 300) + '...')
process.exit(1)
}
// 临时更新manifest中的版本号
manifest.versionName = targetVersion
const updatedManifestContent = JSON.stringify(manifest, null, 2)
// 写入临时更新的manifest
fs.writeFileSync(manifestPath, updatedManifestContent, 'utf-8')
console.log(`临时更新manifest版本为: ${targetVersion}`)
console.log('====================')
// 根据环境变量选择构建命令
let buildCommand = 'npm run build:app'
if (env === 'dev') {
buildCommand = 'npm run build:app-dev'
} else if (env === 'uat') {
buildCommand = 'npm run build:app-uat'
} else if (env === 'prod') {
buildCommand = 'npm run build:app-prod'
}
try {
// 编译
console.log(`执行构建命令: ${buildCommand}`)
console.log(execSync(buildCommand, { encoding: 'utf-8' }))
console.log('====================')
console.log('编译完成,将编译后的文件打包为wgt包')
// 打包
const tarStream = new compressing.zip.Stream()
// 使用绝对路径
const rootDir = join(__dirname, '..')
const targetPath = join(rootDir, 'dist', 'build', 'app')
const wgtOutputDir = join(rootDir, 'dist', 'build', 'wgt')
const targetFile = join(wgtOutputDir, `packageName-${targetVersion}-${env}.wgt`)
// 确保源目录存在
if (!fs.existsSync(targetPath)) {
throw new Error(`目录 ${targetPath} 不存在,请先确保构建命令成功执行`)
}
// 确保输出目录存在
if (!fs.existsSync(wgtOutputDir)) {
fs.mkdirSync(wgtOutputDir, { recursive: true })
console.log(`创建目录: ${wgtOutputDir}`)
}
let paths = fs.readdirSync(targetPath)
paths.forEach(function (item) {
let fPath = join(targetPath, item)
tarStream.addEntry(fPath)
})
tarStream.pipe(fs.createWriteStream(targetFile))
console.log('====================')
console.log(`环境: ${env}`)
console.log(`版本: ${targetVersion}`)
console.log('构建完成')
console.log('====================')
// 如果需要提交到git
if (shouldCommit) {
try {
console.log('将版本映射文件提交到git...')
execSync('git add src/wgt-env-version-map.json', { encoding: 'utf-8' })
execSync(`git commit -m "chore: 更新wgt版本 ${env}环境: ${targetVersion}"`, {
encoding: 'utf-8'
})
execSync('git push', { encoding: 'utf-8' })
console.log('Git提交成功')
} catch (error) {
console.error('Git提交失败:', error.message)
}
}
} catch (error) {
console.error('构建过程出错:', error.message)
} finally {
// 无论构建成功还是失败,都要恢复原始的manifest内容
console.log('恢复manifest.json原始内容...')
fs.writeFileSync(manifestPath, originalManifestContent, 'utf-8')
console.log('manifest.json已恢复到原始状态')
}
核心逻辑如下:
- 读取版本映射文件,获取当前环境对应的版本号
- 临时修改
manifest.json
中的versionName
- 执行构建命令(如
npm run build:app-uat
) - 将产物压缩为
.wgt
文件(如:packageName-1.1.0-uat.wgt
) - 恢复
manifest.json
为原始内容 - 可选将
wgt-env-version-map.json
提交到 Git
wgt输出路径:dist/build/wgt/
四、使用方式
打包并自动提交版本
bash
npm run build:wgt-uat
等同于执行:
bash
node script/build-wgt.js --env uat --commit
自定义参数打包
bash
npm run build:wgt -- --env prod --commit
五、为何不用直接修改 manifest.json?
如果你的多个环境在不同分支中手动改动 manifest.json,会导致如下问题: 如果你的多个环境在不同分支中手动改动 manifest.json
,会导致如下问题:
dev
作为开发分支,版本迭代快,dev
版本1.0.10,prod
版本1.0.1,如果dev一直向上合代码,会导致prod版本快速增长- 容易出现提交遗漏、冲突、热更新错误版本等风险
通过版本号集中管理 + 构建脚本动态注入,我们能做到:
- 一套代码,多套版本号隔离
- 构建时注入,源码中始终保持一致
- 版本提交清晰、规范、可回溯
六、后续建议
这个脚本只实现了比较简单的功能,后续可以继续拓展,比如打包后自动上传,接入CI/CD等...