需求背景
在企业级项目开发中,一般都会分为开发、测试、预发布、生产等多个环境,在工程化中使用不同的打包命令改变环境变量解决不同环境各种变量需要手动修改的问题,比如接口请求地址,不同环境的请求路径前缀都是不同的。在使用
uni-app开发项目时,一般都是选择使用HbuilderX可视化创建项目,也不建议使用cli工程化方式创建uni-app项目。在HbuilderX中,默认只支持开发和生产两个环境,点击“运行”编译出来的代码是开发环境(development),点击“发行”编译出来的代码是生产环境(production),可以通过process.env.NODE_ENV获取当前环境。但在很多企业中,可能就2个环境并不能满足实际场景,同时在开发微信小程序时,测试和生产都是不同的appid,每次部署都要手动修改切换很容易出现问题。为了解决上述问题,通过在package.json中增加uni-app扩展节点,实现自定义条件编译平台,让每种编译具有不同环境标识。再扩展vite.config.js配置文件,用环境标识判断重写文件中的appid。
H5或者WEB怎么区分是在测试环境还是在生产环境?**
- 大家都会想到:可以通过process.env.NODE_ENV==='development’区分
但是怎么区分打包部署在测试环境还是生产环境呢?**
- 自定义环境变量呗
- 还好,uniapp可以设置环境变量,我在vue.config.js里面配置个环境变量参数不就解决了。但是HBuilderX创建的项目只能在vue.config.js里添加环境变量,配置.env文件不可以。
- H5的环境变量应该是解决了。。。但是,每次打包之前都确认和修改一下环境变量参数,是个很烦人的操作。
- 而且大部分开发者用uni-app,意图绝对不是在写h5页面或者web页面(如果是vue、react不香吗?)。微信小程序差不多和H5使用同样的方法解决,(其他小程序我没有试)。
- 然而uniapp运行app,发行app的process.env.NODE_ENV 是’product’,根本没有development一说,那APP怎么区分这是在本地开发还是生产上线了呢?
解决思路
一、增加扩展节点
官方文档上是这样说的:
- 在开发web时,有时需要一套代码编译发布到不同的站点,比如主站和微信h5站。(注意不是一套代码内部自适应不同浏览器,是真的分离部署了不同的网站)
- 在开发小程序时,经常有扩展小程序平台,比如基于阿里小程序的钉钉小程序、淘宝小程序。
那是不是也可以专门针对测试环境和生产环境扩展一个平台,然后在扩展参数里写一下我的自定义环境变量。
于是乎,我就照着官方例子,扩展了其他平台,参数如下
"uni-app": {
"scripts": {
"h5-dev": {
"title": "H5 开发环境",
"browser": "chrome",
"env": {
"UNI_PLATFORM": "h5",
"NAME": "development"
}
},
"h5-test": {
"title": "H5 测试环境",
"browser": "chrome",
"env": {
"UNI_PLATFORM": "h5",
"NAME": "test"
}
},
"h5-prod": {
"title": "H5 生产环境",
"browser": "chrome",
"env": {
"UNI_PLATFORM": "h5",
"NAME": "production"
}
},
"wx-dev": {
"title": "微信小程序 开发环境",
"env": {
"UNI_PLATFORM": "mp-weixin",
"NAME": "development"
}
},
"wx-test": {
"title": "微信小程序 测试环境",
"env": {
"UNI_PLATFORM": "mp-weixin",
"NAME": "test"
}
},
"wx-prod": {
"title": "微信小程序 生产环境",
"env": {
"UNI_PLATFORM": "mp-weixin",
"NAME": "production"
}
}
}
}
上面代码片段只以微信小程序和H5两个端为例,其中只增加了常量NAME用于区分当前环境,类似于process.env.NODE_ENV获取环境变量的作用。一般小程序也就测试和生产两个环境,环境太多都要重新申请账号也麻烦。H5按照常规的配置本地开发、测试、生产三个环境。配置好后我们点击顶部菜单栏的“运行”和“发行”即可看到效果。
当在业务里需要使用添加的NAME变量时,直接通过process.env.NAME即可获取。
判断AppId
官方文档上是这样说的:
注意只能扩展web和小程序平台,不能扩展app打包。并且扩展小程序平台时只能基于指定的基准平台扩展子平台,不能扩展基准平台
APP自定义环境变量得另想方法了,翻阅了以下参考文档 🌝
基础配置
DCloud appid 用途/作用/使用说明
APP专题 HTML5PULS
HTML5+ API Reference–runtime
想到了一个方法,测试环境和生产环境的appid不能一样,而且appid是可以使用代码获取到的,那是不是可以判断appid,确定是在哪个环境,然后使用不同的环境变量。有了下面的代码
const PRO_APPID = "开发环境appid", // 开发环境appid
DEV_APPID = "生产环境appid", // 生产环境appid
HB_APPID = "HBuilder"; // HBuilder调试基座
// puls是app才有的变量
const APP_ID = plus.runtime.appid;
// 测试环境的配置
let config = {
BASE_URL: 'http://dev/url',
CURRENT_MODE: 'dev',
MODE_CN: '测试环境APP'
}
// HBuilder调试基座
if (APP_ID === HB_APPID) {
config.ALERT_MSG = "您正在使用HBuilder调试基座,请切换至本系统开发环境调试基座,如有技术问题,请咨询工号124546";
config.CONFIG_PLATFORM= 'HBuilder_APP'
}
// 开发环境
if (APP_ID === DEV_APPID) {
config.ALERT_MSG = "您正在使用本系统开发环境调试基座,所有数据和业务仅为测试使用,不会产生实际业务影响,如要办理实际业务,请在应用市场下载本app。"
config.CONFIG_PLATFORM = 'DEV_APP'
}
if (APP_ID === PRO_APPID) {
// 生产环境的配置
config = {
BASE_URL: 'https://product/url',
CURRENT_MODE: 'product',
CONFIG_PLATFORM: 'APP',
MODE_CN: '生产环境APP'
}
}
export const envConfig = {
APP_ID,
...config
}
注:自定义调试基座的appid为制作自定义调试基座的时候mainfest.json中配置的appid。
附:自定义调试基座制作步骤
运行>运行到手机或模拟器>制作自定义调试基座
弹出如下提示框:
注意选择【传统打包】,或者按照提示选择【普通打包】
打包成功后控制台提示
再次运行>运行到手机或模拟器>会多出来一个选择【运行基座选择】,其中自定义调试基座就是刚刚生成的。
二、配置其他变量
根目录下新建config目录,用于放一些业务配置项,再新建环境相关变量配置文件env.js,代码如下:
// 不同的环境变量配置
const development = {
requestBaseUrl: 'http://development',
appid: '',
}
const test = {
requestBaseUrl: 'http://test',
appid: 'wxd5xxxxee0fce1c81',
}
const production = {
requestBaseUrl: 'http://production',
appid: 'wx3xxxx1ce403cab3',
}
export default {
development,
test,
production
}
其中变量对象名称development、test、production要和package.json文件中定义的NAME保持一致,方便后续通过对象方式直接取值。变量对象中添加的是需要根据不同环境配置的变量,比如后端服务请求地址,小程序appid和一些别的插件key。配置后我们就可以配合环境NAME获取到不同环境的其他变量了,简单使用方式如下:
import ENV_CONFIG from '@/config/env.js'
console.log(ENV_CONFIG[process.env.NAME].requestBaseUrl) // 运行H5 开发环境结果 http://development
每次引入获取方式肯定不够友好,在uni-app中还可以通过修改vite配置添加全局变量,更方便在全局使用。
首先根目录下新建vite.config.js文件,内容如下:
import { defineConfig } from 'vite'
import uni from '@dcloudio/vite-plugin-uni'
import ENV_CONFIG from './env/index.js'
export default defineConfig({
plugins: [uni()],
define: {
'process.env.config': ENV_CONFIG,
},
});
通过定义一个全局变量process.env.config,赋值为ENV_CONFIG,process.env.config可以改为任何一个字符串,这里主要是为了保持和默认通过process.env获取环境变量的语义一致性。此时使用时就无需在业务中单独引入,从全局对象process.env.config上取值即可:
console.log(process.env.config[process.env.NAME].requestBaseUrl) // 运行H5 开发环境结果 http://development
三、动态修改小程序appid
appid是在根目录下的manifest.json文件中,点击后最下面有源码视图。修改的方式就是运用node的fs模块,先读取manifest.json文件,然后根据当前环境,动态替换掉appid或者其他参数,最后重新写入到当前目录下。具体也是在vite.config.js中处理:
import { defineConfig } from 'vite';
import uni from '@dcloudio/vite-plugin-uni';
import ENV_CONFIG from './config/env.js'
// 引入fs模块
import fs from 'fs'
// 读取 manifest.json ,修改后重新写入
const manifestPath = `${__dirname}/manifest.json`;
let Manifest = fs.readFileSync(manifestPath, { encoding: 'utf-8' });
function replaceManifest(path, value) {
const arr = path.split('.');
const len = arr.length;
const lastItem = arr[len - 1];
let i = 0;
let ManifestArr = Manifest.split(/\n/);
for (let index = 0; index < ManifestArr.length; index++) {
const item = ManifestArr[index];
if (new RegExp(`"${arr[i]}"`).test(item)) ++i;
if (i === len) {
const hasComma = /,/.test(item);
ManifestArr[index] = item.replace(
new RegExp(`"${lastItem}"[\s\S]*:[\s\S]*`),
`"${lastItem}": ${typeof value === 'string'? '"'+value+'"' : value}${hasComma ? ',' : ''}`
);
break;
}
}
Manifest = ManifestArr.join('\n');
}
// 具体使用,找到对应key值替换为新的值
// replaceManifest('app-plus.usingComponents', false);
const appid = ENV_CONFIG[JSON.parse(process.env.UNI_CUSTOM_DEFINE).NAME].appid
replaceManifest('mp-weixin.appid', appid);
fs.writeFileSync(manifestPath, Manifest, { flag: 'w' });
export default defineConfig({
plugins: [uni()],
define: {
'process.env.config': ENV_CONFIG,
},
});
这个修改方案是根据官方提供的代码片段修改,其中进行了一些变动:
manifest.json文件路径由相对路径改为__dirname获取的绝对路径replaceManifest方法中的value进行了typeof类型判断,如果是字符串加上双引号。因为测试时传个字符串会替换成没引号的变量或者数字
同时关于当前环境变量的获取,此时通过process.env.NAME是获取不到package.json配置的NAME的,通过打印process.env可以发现此时它是个包含很多变量的json,在UNI_CUSTOM_DEFINE下面是可以找到NAME,这样就能根据不同环境获取不同的appid。
然后也可以用下面这种更容易理解的方式修改manifest.json,但是修改后内容排在一行,想要美观还需要手动格式化。
// appid获取只做参考,这里只是说明简单的只有两个环境,可以直接取process.env.NODE_ENV判断
let appid = process.env.NODE_ENV == "production" ? '生产的appid' : "开发的appid"
// manifest.json 路径
let manifestFileUrl = `${__dirname}/manifest.json`
// 读取文件数据
let manifestFileData = fs.readFileSync(manifestFileUrl, { encoding: 'utf8' });
// 移除// 和 /* */注释
manifestFileData = manifestFileData.replace(/\"|"(?:\"|[^"])*"|(//.*|/*[\s\S]*?*/)/g, (m, g) => g ? "" : m)
// // 将txt转成obj
let manifestFileDataObj = JSON.parse(manifestFileData)
// 修改指定key对应的value
manifestFileDataObj['mp-weixin']['appid'] = appid
// 把修改后的对象以json写入文件
fs.writeFileSync(manifestFileUrl, JSON.stringify(manifestFileDataObj), { encoding: 'utf8' })
四、使用方式
- 需要本地调试时,点击工具栏“运行”,选择自定义的对应开发或测试环境;
- 业务中通过
process.env.config[process.env.NAME]获取配置的变量对象; - 上线时,点击工具栏“发行”,选择自定义的对应测试或生产环境;
五、运行环境
以上代码是运行在Hbuilderx 3.7.9版本,项目为vue3版本,基于vite构建。如在vue2版本上使用,请按照vite.config.js逻辑自行探索配置vue.config.js文件。
👋🏻 Respect!欢迎一键三连 ~