HBuilderX多环境发行实战:uni-app环境变量配置与避坑指南
开头先讲个真实经历。去年我维护的一个 uni-app 项目测试阶段一切正常结果一发行到微信小程序就疯狂报错——接口全部 401页面白屏数据全丢了。查了半天最后定位到原因非常简单request里写死的 API 地址是测试环境的而我在 HBuilderX 里点了发行之后压根没切换成生产环境地址。这种发行换地址的原始操作我相信很多人经历过。HBuilderX 作为 uni-app 的主力开发工具官方提供了运行和发行两套流程但多环境发行这个需求说实话默认配置并不能直接满足。这篇文章我不会给你讲那些虚的概念直接从我实际配置的经验出发把 HBuilderX 多环境发行的完整思路、具体配置步骤、以及我踩过的坑全部摊开说清楚。不管你是用 vite 版 vue3 的新项目还是 webpack 版 vue2 的老项目都能在这篇文章里找到能直接抄的作业。1. 发版连错环境多环境发行的本质需求1.1 一次让我记忆深刻的翻车现场那次事故发生在一个周五下午。项目是公司的一个电商小程序我负责前端后端同学给了三个环境的接口文档开发环境、测试环境、生产环境。当时项目已经上线过一版API 地址是通过一个config.js全局配置文件管理的每次发版前手动改一下。那天我改完地址在 HBuilderX 里点了发行-小程序-微信生成dist/build/mp-weixin后用微信开发者工具打开直接提交上传。结果上线当天运营就反馈小程序登录失败。我第一反应是后端出问题了结果后端查了半天日志发现请求根本没打到生产服务器全打在测试服务器上。原因就是我在发行前确实改了config.js但改完之后手滑运行了一次npm run dev:mp-weixinHBuilderX 的运行机制会重新编译一次把代码里的地址又覆盖回了开发环境的配置。也就是说我发行用的是刚改过的文件但微信开发者工具里加载的却是运行编译产生的缓存产物。这种问题本质上就是多环境管理没做好。如果当初就把环境配置做成自动切换发行时带上环境参数编译产物里就是正确的环境根本不会有这回事。1.2 区分平台与环境两个维度很多人一提到多环境就头大是因为把两个完全不同的维度混在一起了。平台维度小程序微信、支付宝、百度、AppAndroid、iOS、H5、以及各种快应用。这是 uni-app 的天然优势一套代码编译到多端。环境维度开发环境、测试环境、预发布环境、生产环境。这是同一个平台在不同生命周期阶段的配置差异主要是 API 地址、appid、密钥、开关配置等。在 HBuilderX 里运行和发行是两个不同的动作很容易让人误以为运行对应开发环境、发行对应生产环境。但实际不是这样。运行只是启动本地开发服务器或编译发行是产出可发布的分发包两者和环境并没有绑定关系。所以做多环境配置的核心思路是把平台和环境两个维度解耦。代码里通过条件编译处理平台差异通过环境变量处理环境差异。这样才能做到一次编写到处切环境。1.3 多环境发行到底要解决哪些问题理清需求才能设计配置方案。我在项目里总结下来多环境发行至少要解决以下几个问题API 地址自动切换不同环境下接口的 baseURL 不同这个是最基本的。第三方平台 appid 切换小程序 appid、开放平台相关的 key在测试和生产往往不是同一个。功能开关差异化比如测试环境可能需要开启 vConsole 调试工具、关闭埋点上报生产环境则相反。特殊配置隔离比如支付回调地址、分享链接、客服链接等一旦配错就会出线上事故。产物可追溯拿到一个dist目录能快速识别它是哪个环境编译出来的而不是靠猜。这五类需求里前两类是刚需凡是开发过一段时间 uni-app 项目的人都会遇到。后三类则是项目复杂度上来之后自然暴露出来的。我见过不少团队前面两类硬编码解决后面三类完全靠人工记忆迟早出问题。2. 方案选型环境变量、条件编译与多套代码的取舍2.1 三种常见方案的横向对比在 HBuilderX 项目里实现多环境大致有三条路我逐一分析。方案一多套配置文件手切就像我开头说的在项目里放多个config.dev.js、config.prod.js发版前手动切换或者改config.js的内容。这个方案的优点是直观、简单缺点是极易出错——人不是机器总会忘记切或者切错。而且一旦多个开发人员同时操作很容易互相覆盖。方案二uni-app 条件编译uni-app 内置了一套以注释形式存在的条件编译语法可以按平台或自定义条件来区分代码块比如#ifdef MP-WEIXIN只在小程序端编译进去#ifndef H5表示非 H5 端。这套语法默认是按平台维度来处理的但也可以自定义条件。理论上你可以自己定义一个TEST条件在发行时带上这个条件就能做到多环境代码隔离。但实操上比较麻烦因为 HBuilderX 的可视化发行界面并不会让你填自定义条件。这个方案的优点是平台维度天然支持、性能无损耗缺点是环境维度支持很弱而且大量条件编译注释会让代码变得很难看。我的建议是条件编译用来处理平台差异不要用来处理环境差异。方案三构建工具的环境变量机制这是我现在用的方式。用 vite 开发的项目直接利用.env.development、.env.test、.env.production这套标准的环境文件机制用 webpackvue2 项目开发时则通过process.env.VUE_APP_*系列自定义变量 构建命令里指定--mode来实现。三种方案对比我用个表格直观呈现对比项多套配置手切条件编译环境变量机制实现成本最低中低出错概率高中低环境维度支持弱弱强平台维度支持人工控制强依赖配合条件编译可维护性差中好是否需要改 HBuilderX 默认流程否部分是2.2 为什么我最终选了 Vite mode 环境变量我现在的项目大多是 vue3 vite 技术栈HBuilderX 从 3.x 开始也对 vite 模式的项目做了完整的支持。选环境变量机制主要有几个原因。首先这是前端工程化的标准做法。Vite 和 Vue CLI 都原生支持模式和环境变量文件社区资料多、团队成员熟悉以后再接 CI/CD 做自动化打包可以直接复用这套机制。其次环境变量机制把环境从代码里彻底抽离了。代码中只出现import.meta.env.VITE_API_BASE_URL这样的引用具体值是什么由构建时选择的.env.xxx文件决定。这意味着同一个 Git 分支、同一份代码用不同的命令可以打出不同环境的包对 CI/CD 非常友好。再者环境变量的覆盖规则也很清晰通用的放.env各个环境特有的放.env.test、.env.productionVite 会自动按规则加载和覆盖不用你自己写合并逻辑。提示无论你选哪种方案建议一开始就约定好命名规则和目录结构不要等环境多了再重构那时候成本会成倍增加。2.3 HBuilderX 内置发行与 CLI 构建的边界这里有一个很多人没想清楚的问题HBuilderX 的发行按钮到底做了什么对于 HBuilderX 可视化创建的项目点击发行-小程序-微信时HBuilderX 会调用自己内置的 uni-app 编译器把源码编译成微信小程序代码输出到dist/build/mp-weixin目录。这个过程是集成好的不会再去看你 package.json 里的 scripts。但如果你是用 CLI 方式创建的 uni-app 项目通过npx degit dcloudio/uni-preset-vue#vite之类的命令初始化那么 HBuilderX 就只负责代码编辑和项目管理真正的编译流程是走 npm 脚本的。这时你可以在 package.json 的 scripts 里自定义各种构建命令通过--mode test指定环境。理解了边界之后你就明白多环境发行最顺的路径是用 CLI 项目结构把构建命令掌控在自己手里再用 HBuilderX 作为编辑器操作。如果你的项目已经是用 HBuilderX 可视化创建的也不用慌可以补齐 package.json 和 vite.config 相关内容逐步迁移过去。实测下来HBuilderX 对 CLI 项目的支持已经很完善了打开目录、运行、发行都能正常工作。3. 实操落地Vite mode 多环境发行的完整配置3.1 环境文件的组织与命名项目根目录下我通常这样组织环境文件项目根目录/ ├── .env # 所有环境共享的基础配置 ├── .env.development # 开发环境 ├── .env.test # 测试环境 ├── .env.production # 生产环境 ├── package.json ├── vite.config.js └── src/.env文件内容示例放所有环境通用的配置# 所有环境都会加载的基础配置 VITE_APP_NAME电商小程序 VITE_APP_VERSION1.0.0.env.test文件内容示例# 测试环境 NODE_ENVproduction VITE_API_BASE_URLhttps://test-api.example.com VITE_MINI_APPIDwx1234567890test VITE_ENABLE_DEBUGtrue.env.production文件内容示例# 生产环境 NODE_ENVproduction VITE_API_BASE_URLhttps://api.example.com VITE_MINI_APPIDwx0987654321prod VITE_ENABLE_DEBUGfalse这里有个细节需要注意.env.development里的NODE_ENV默认为development而.env.test和.env.production都需要显式写成production否则某些依赖环境的库比如 Vue 的错误提示、vconsole 插件会按开发模式处理带来额外的日志开销和性能问题。3.2 代码层面对环境变量的引用定义好了环境变量接着就是在代码里使用。在 vite 构建的 uni-app 项目里访问方式是通过import.meta.env对象// src/config/index.js export const getEnvConfig () { return { apiBaseUrl: import.meta.env.VITE_API_BASE_URL, appName: import.meta.env.VITE_APP_NAME, enableDebug: import.meta.env.VITE_ENABLE_DEBUG true } }然后在request.js里统一读取这个配置// src/utils/request.js import { getEnvConfig } from /config/index.js const { apiBaseUrl, enableDebug } getEnvConfig() if (enableDebug) { console.log(当前环境 API:, apiBaseUrl) } export const request (options) { return new Promise((resolve, reject) { uni.request({ url: apiBaseUrl options.url, method: options.method || GET, data: options.data || {}, success: (res) resolve(res.data), fail: (err) reject(err) }) }) }如果你的项目还是 vue2 webpack那么对应的是process.env.VUE_APP_API_BASE_URL这种写法。注意VUE_APP_前缀是 Vue CLI 强制要求的没有这个前缀的变量不会暴露给客户端代码。而在 vite 里对应前缀是VITE_。这个前缀限制是有意设计的可以避免把敏感的环境变量都暴露到前端代码里。提示在manifest.json里也可以通过import.meta.env或者process.env来动态设置小程序 appid 吗答案是不行。manifest.json的配置基本都是静态的不会经过编译处理。所以要切换小程序 appid需要单独处理下面会讲。3.3 在 HBuilderX 中执行自定义模式发行这是配置多环境发行最关键的一步。在你的项目package.json的scripts里定义好各环境的构建命令{ scripts: { dev:mp-weixin: uni -p mp-weixin, build:mp-weixin:test: uni build -p mp-weixin --mode test, build:mp-weixin:prod: uni build -p mp-weixin --mode production, build:h5:test: uni build -p h5 --mode test, build:h5:prod: uni build -p h5 --mode production, build:app:test: uni build -p app --mode test, build:app:prod: uni build -p app --mode production } }这里uni build -p mp-weixin是 uni-app 官方 CLI 提供的编译命令--mode test会告诉 vite 加载.env.test文件。然后在 HBuilderX 里怎么执行呢我推荐一种干净利落的方式在 HBuilderX 的终端中直接运行。打开 HBuilderX用文件-打开目录加载你的项目。菜单栏视图-显示终端打开内置终端。在终端里执行npm run build:mp-weixin:test。命令执行完成后产物会生成在dist/build/mp-weixin。直接用微信开发者工具打开这个目录就是测试环境的包。如果要发生产执行npm run build:mp-weixin:prod产物目录一致但内容加载的是生产环境变量。这种方式看似简单但解决了大问题编译命令和环境彻底绑定你不需要在代码里来回改地址不会出现改完忘了切的问题。我在实际项目中还会在命令里加上--outDir参数把不同环境的产物输出到不同目录比如{ build:mp-weixin:test: uni build -p mp-weixin --mode test --outDir dist/test/mp-weixin, build:mp-weixin:prod: uni build -p mp-weixin --mode production --outDir dist/prod/mp-weixin }这样两个环境的产物互不干扰排查问题的时候一看路径就知道是哪个环境打的包。3.4 manifest.json 与图标、appid 的联动处理环境变量解决了代码层面的切换但小程序发行还有一个绕不开的静态配置manifest.json里的mp-weixin.appid以及各平台的图标、名称配置。这个文件不会被 vite 做环境变量替换我一般建议的做法是开发测试阶段manifest.json里填测试小程序的 appid。发行生产包之前手动改一次 appid。更稳妥的做法是写一个 Node 脚本在执行构建前自动修改manifest.json中的 appid不同环境对应不同的值。下面是我用过的自动修改脚本思路放在scripts/set-appid.js里// scripts/set-appid.js const fs require(fs) const path require(path) const mode process.argv[2] || production const manifestPath path.resolve(__dirname, ../src/manifest.json) const manifest JSON.parse(fs.readFileSync(manifestPath, utf-8)) const appidMap { test: wx1234567890test, production: wx0987654321prod } if (appidMap[mode]) { manifest[mp-weixin] manifest[mp-weixin] || {} manifest[mp-weixin].appid appidMap[mode] fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2)) console.log(已设置 ${mode} 环境 appid: ${appidMap[mode]}) }然后把 scripts 调整成先执行脚本再构建{ build:mp-weixin:test: node scripts/set-appid.js test uni build -p mp-weixin --mode test, build:mp-weixin:prod: node scripts/set-appid.js production uni build -p mp-weixin --mode production }这样做的好处是CI/CD 环境下也能自动处理 appid不用担心有人手动改错。不过要注意改完manifest.json后记得不要把这个临时改动提交到 Git 仓库最好把manifest.json的变动排除出去或者在构建结束后恢复原始文件。4. 微信小程序发行全流程从编译到真机预览4.1 发行前必须过一遍的检查清单我在点击发行前一定会花两分钟过一遍检查清单。不要嫌麻烦这两分钟能避免大多数低级失误。检查 manifest.json 中的小程序 appid是否填对了测试包用测试 appid生产包用生产 appid。检查项目基本信息应用名称、版本号、小程序页面路径配置是否正确。检查环境配置确认当前要构建的目标环境在 npm scripts 里选对应命令而不是手动改代码。检查接口域名白名单微信小程序后台的 request 合法域名如果你生产环境的域名没配线上请求会被拦截。检查调试开关vConsole、日志上报、埋点配置生产环境该关的都关掉。检查产物目录上一次构建的产物是否残留避免加载到旧文件。这六项里第三项和第四项是最容易踩坑的。尤其是接口域名白名单很多时候本地开发和测试环境可以随便请求一到生产环境就必须在小程序后台配置合法域名否则请求直接失败而且控制台报错还不明显。4.2 HBuilderX 发行小程序的完整操作链以下是我目前最常用的发行操作链整个过程不依赖 HBuilderX 的图形化发行按钮而是用命令行方式保证环境切换可控。第一步在 HBuilderX 里打开项目确认项目能正常编译。可以先运行一次npm run dev:mp-weixin启动本地小程序开发模式用微信开发者工具看是否能正常打开。这一步的目的是提前发现代码层面的报错。第二步打开 HBuilderX 内置终端执行对应环境的构建命令。比如发测试环境npm run build:mp-weixin:test构建完成后终端会输出产物路径一般是dist/build/mp-weixin或你自定义的dist/test/mp-weixin。第三步打开微信开发者工具选择导入项目目录选择刚才的产物路径AppID 填入测试小程序对应的 AppID点击导入。第四步在微信开发者工具中预览和真机调试。确认接口请求正常后点击右上角上传按钮填入版本号和备注提交为体验版。整个流程看起来不复杂但很多人容易在前面第二步和第三步之间出错。比如在 HBuilderX 里点了图形化的发行按钮生成了代码却忘了自己有没有用--mode指定环境直接拿默认 production 模式打的包去测结果发现访问的是生产接口这是很典型的问题。4.3 在微信开发者工具中验证环境正确性怎么确认当前加载的包一定是目标环境我教大家一个最实用也最直观的方法。在代码里加一个环境标识让它在界面上或者控制台里能直接看到。比如在App.vue的onLaunch生命周期里打印环境信息// App.vue export default { onLaunch() { const env import.meta.env.VITE_API_BASE_URL console.log(当前构建环境 API:, env) } }然后在微信开发者工具的 Console 面板查看。如果看到的是测试环境的地址那这个包就是测试包。更直观一点的做法是在页面顶部用环境变量控制一个角落标签template view view v-ifisTestEnv classenv-tag测试环境/view !-- 页面内容 -- /view /template script setup import { computed } from vue const isTestEnv computed(() import.meta.env.VITE_ENABLE_DEBUG true) /script这个标签在开发测试阶段能清楚地提示你当前处于哪个环境防止测试人员在测试环境上测出了生产环境的问题反过来还搞不清楚状况。4.4 多环境思路在 App 云打包中的差异如果你发行的目标不是小程序而是 App那么大体的思路是一致的但有两点差异需要特别注意。第一App 云打包在 HBuilderX 里走的是发行-原生App-云打包这个入口它需要你在 manifest.json 里配置好 App 图标、启动图、证书等信息。云打包是在 DCloud 服务器上完成的本机不需要安装 Android SDK 和 iOS 证书iOS 需要 p12 证书文件。这里的环境切换逻辑和小程序大同小异关键是 manifest 里的相关配置要按生产环境准备。第二如果你用的是离线打包也就是用 Android Studio 自己打包 APK那你必须在本机准备好完整的 Android 开发环境。这个场景下环境变量机制依然生效但最终打包脚本里需要手动传入环境参数。离线打包时有一个问题我遇到过很多次就是java: 错误: 不支持发行版本 5这个我在后面第 5 部分专门讲。所以我的建议是能云打包就别离线打包能自动生成配置就别手动改配置。多环境方案在云打包的流程里只要控制好--mode基本没有额外的坑。5. 我踩过的坑与排查经验5.1 微信开发者工具打不开 HBuilderX 产物的终极排查热词里有一条微信开发者工具无法通过hbuilderx打开我太有感触了。这个问题的原因通常有三个按出现频率排序第一微信开发者工具的服务端口没有开启。在微信开发者工具里点击设置-安全设置把服务端口开关打开。如果这个开关是关闭的HBuilderX 就无法通过命令行唤起微信开发者工具更别说自动打开编译产物了。第二HBuilderX 找不到微信开发者工具的安装路径。HBuilderX 在运行到小程序模拟器时会自动探测安装路径但如果你的微信开发者工具装在非默认位置或者安装的是绿色版HBuilderX 就会识别不到。解决办法是在 HBuilderX 的运行-运行到小程序模拟器-运行设置里手动指定微信开发者工具的安装路径。第三项目路径包含中文或者特殊字符。微信开发者工具对中文路径的支持一直不完美笔者曾经把项目放在D:\工作\项目A\小程序这种路径下导致 HBuilderX 生成的文件路径和微信开发者工具的监听逻辑冲突一直无法正常打开。后来把项目迁移到纯英文路径问题就消失了。如果遇到打不开的情况按照端口开关、安装路径、项目路径这个顺序去排查绝大多数都能解决。5.2 环境变量没生效先查 mode 再查缓存有一次同事反馈说他执行npm run build:mp-weixin:test后打印出来的 API 地址还是生产环境的。我过去一看发现他把命令写成了npm run build:mp-weixin根本没带--mode test。这里要强调一下 Vite 的行为如果你执行vite build而不带--mode默认的 mode 是production所以会加载.env.production。也就是说你在 scripts 里定义了 test 命令但实际运行的是不带--mode的构建命令那么环境变量自然就是生产的了。这不是 bug是机制。排查看起来很简单但有一种情况容易被忽略环境文件缓存。Vite 在某些版本中如果你新增了.env.test文件而之前已经启动过 dev server 或者 build 过程部分缓存可能导致新环境变量没有被加载。遇到这种情况停掉进程删掉node_modules/.vite目录vite 的缓存目录再重新执行构建就好。另外还要注意变量的前缀。如果你的变量名没有以VITE_开头在 vite 里是拿不到的。很多人把API_BASE_URL当成普通变量写在.env.test里然后在代码里用import.meta.env.API_BASE_URL去取结果永远是 undefined。这个坑非常隐蔽因为编译不会报错。5.3 条件编译误伤业务代码的坑uni-app 的条件编译虽然好用但它有一个隐患它是在编译阶段根据预定义的条件来决定代码块是否保留如果你的自定义条件命名和 uni-app 保留条件冲突或者你根本没有在编译命令里指定这个条件那么代码里被注释包起来的块就会被整体剔除而且不会给你任何警告。比如我在一个老项目里见过这种写法// #ifdef TEST let apiBase https://test-api.example.com // #endif // #ifndef TEST let apiBase https://api.example.com // #endif这个写法的本意是TEST 条件存在时用测试地址否则用生产地址。问题在于TEST这个条件不是 uni-app 内置的平台条件你在 HBuilderX 的发行按钮里根本没法指定它。所以这段代码的最终效果是永远走#ifndef TEST那个分支测试地址永远不会被编译进去。我当时排查了很久才发现不是变量写错了而是条件编译把代码直接优化掉了。如果要处理环境维度的差异老老实实用环境变量机制不要自己发明条件编译条件。条件编译只用来处理平台维度比如#ifdef MP-WEIXIN和#ifndef APP-PLUS这种。5.4 离线打包 App 时的 JDK 版本冲突最后说一下热词里的java: 错误: 不支持发行版本 5这个报错在 HBuilderX 离线打包场景下非常典型。报错本身的意思是当前编译的 Java 源文件要求的语言级别是 5但当前 JDK 版本已经不支持这么老的语言级别了。出现这个报错通常是因为 Android Studio 里模块的build.gradle配置了很低的sourceCompatibility和targetCompatibility或者项目里某个依赖的 jar 包是用旧版本 JDK 编译的。接在 uni-app 离线打包工程里常见原因是下载了官方的离线打包 SDK里面的模板工程默认配置较老而你的本机 JDK 版本是 17 甚至更高。这时候需要统一配置 module 的编译级别android { compileOptions { sourceCompatibility JavaVersion.VERSION_11 targetCompatibility JavaVersion.VERSION_11 } }并且确认gradle-wrapper.properties里的 Gradle 版本和你本机 JDK 版本兼容。JDK 8 对应 Gradle 5.xJDK 11 对应 Gradle 7.xJDK 17 对应 Gradle 8.x这个对应关系不能乱。我见过有人用 JDK 17 跑一个老版本 Gradle报了一堆莫名的编译错误最后全部排查清楚后才发现是版本矩阵不匹配。在配置多环境发行的时候这个问题看起来不相关但它会卡在发行环节。建议做 App 离线打包前先把 JDK、Gradle、Android Gradle Plugin 三者版本对照表查一遍。最后再分享一个我的个人习惯。每次执行完构建命令后我会随手在 dist 产物目录下生成一个env-info.json文件自动记录当前构建时间、环境名称、Git 提交号。这样就算几个星期后翻出这个 dist 目录也能立刻知道这个包是用哪个环境的代码、哪次提交构建出来的。多环境发行不是一锤子买卖配置好了、验证过了、形成习惯之后你会明显感受到发版从心惊胆战变成例行公事。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →