在当今多平台、多地区的移动应用开发场景中,如何高效管理不同环境下的构建和部署是一个常见痛点。想象一下,同一套业务需要同时为多个省份上线微信小程序和支付宝小程序,每个地区的标题、首页内容、支付方式和后端网关地址都不相同,还要区分开发、测试和生产环境。如果为每个组合都维护一套独立代码,开发成本将呈指数级增长。
本文分享的解决方案,正是为了解决这一痛点而生。通过一套代码+一组脚本的组合,我们实现了:
这个方案已经在多个线上项目中得到验证,显著降低了维护成本,提高了发布效率。下面我将详细介绍实现思路和具体步骤。
整个方案的核心是环境变量驱动。我们通过三个关键维度来控制构建过程:
这三个维度通过npm脚本注入,形成完整的构建上下文。例如:
bash复制cross-env NODE_ENV=production UNI_PLATFORM=mp-weixin DISTRICT=anhui DEPLOY=release_anhui vue-cli-service uni-build
完整的构建流程可以分为以下几个关键步骤:
这种分层设计确保了各环节职责清晰,扩展性强。新增地区或平台时,只需在对应环节添加配置,无需改动核心流程。
由于不同操作系统设置环境变量的方式不同,我们使用cross-env来统一写法。首先安装依赖:
bash复制npm i -D cross-env
然后在package.json中定义构建脚本:
json复制{
"scripts": {
"build:mp-weixin:anhui:release_anhui": "cross-env NODE_ENV=production UNI_PLATFORM=mp-weixin DISTRICT=anhui DEPLOY=release_anhui vue-cli-service uni-build",
"dev:mp-weixin:anhui:dev_anhui": "cross-env NODE_ENV=development UNI_PLATFORM=mp-weixin DISTRICT=anhui DEPLOY=dev_anhui vue-cli-service uni-build --watch"
}
}
我们采用一致的命名规范来提高可读性:
dev:开头build:开头这种命名方式让开发者一眼就能看出脚本的用途,例如build:mp-alipay:xinjiang:release表示构建新疆地区的支付宝小程序生产版本。
我们将所有映射关系集中管理在config/preBuild.config.js中:
javascript复制// 平台与客户端类型映射
const CLIENT_TYPE_MAP = {
h5: 1,
'mp-weixin': 2,
'mp-alipay': 3
// 其他平台...
}
// 地区配置映射
const DISTRICT_CONFIG_MAP = {
anhui: {
APP_TITLE: '安徽服务',
THEME_COLOR: '#1890ff'
},
xinjiang: {
APP_TITLE: '新疆服务',
THEME_COLOR: '#ff4d4f'
}
// 其他地区...
}
// 环境与API地址映射
const API_BASE_URL_MAP = {
dev: 'https://api-dev.example.com',
qa: 'https://api-qa.example.com',
release: 'https://api.example.com'
// 其他环境...
}
module.exports = {
CLIENT_TYPE_MAP,
DISTRICT_CONFIG_MAP,
API_BASE_URL_MAP
}
这种集中管理的方式使得维护和扩展变得非常简单。新增地区或环境时,只需在此文件中添加相应配置即可。
preBuild.js负责在Webpack构建前执行预处理逻辑:
javascript复制const { CLIENT_TYPE_MAP, DISTRICT_CONFIG_MAP, API_BASE_URL_MAP } = require('../config/preBuild.config')
// 设置版本号等固定值
process.env.VERSION = '1.0.0'
// 根据平台设置客户端类型
process.env.CLIENT_TYPE = CLIENT_TYPE_MAP[process.env.UNI_PLATFORM]
// 根据地区设置应用标题和主题色
const districtConfig = DISTRICT_CONFIG_MAP[process.env.DISTRICT] || {}
process.env.APP_TITLE = districtConfig.APP_TITLE
process.env.THEME_COLOR = districtConfig.THEME_COLOR
// 根据环境设置API地址
process.env.API_BASE_URL = API_BASE_URL_MAP[process.env.DEPLOY]
// 打印构建信息
console.log('构建配置:')
console.log('平台:', process.env.UNI_PLATFORM)
console.log('地区:', process.env.DISTRICT)
console.log('环境:', process.env.DEPLOY)
console.log('API地址:', process.env.API_BASE_URL)
// 生成动态配置
require('./pages.json.js')
require('./manifest.json.js')
这个脚本的核心作用是将原始的环境变量转换为业务可直接使用的配置项,并触发后续的动态配置生成。
不同地区可能需要不同的页面结构和tabBar配置。我们通过合并基础配置和地区特定配置来实现这一点:
javascript复制const fs = require('fs')
const path = require('path')
const merge = require('lodash/merge')
// 加载基础配置
const basePages = require('../config/base/pages.json')
// 尝试加载地区特定配置
let districtPages = {}
try {
districtPages = require(`../config/districts/${process.env.DISTRICT}/pages.json`)
} catch (e) {
console.log(`未找到${process.env.DISTRICT}地区的pages配置,使用默认配置`)
}
// 深度合并配置
const mergedPages = merge({}, basePages, districtPages)
// 写入最终pages.json
fs.writeFileSync(
path.join(__dirname, '../src/pages.json'),
JSON.stringify(mergedPages, null, 2)
)
类似地,manifest.json也需要根据不同平台和地区进行定制:
javascript复制const fs = require('fs')
const path = require('path')
const merge = require('lodash/merge')
// 基础manifest配置
const baseManifest = {
name: '基础应用',
appid: 'wx1234567890',
description: '基础描述',
// 其他公共配置...
}
// 加载地区特定配置
let districtManifest = {}
try {
districtManifest = require(`../config/districts/${process.env.DISTRICT}/manifest.json`)
} catch (e) {
console.log(`未找到${process.env.DISTRICT}地区的manifest配置`)
}
// 合并配置
const mergedManifest = merge({}, baseManifest, districtManifest)
// 写入最终manifest.json
fs.writeFileSync(
path.join(__dirname, '../src/manifest.json'),
JSON.stringify(mergedManifest, null, 2)
)
在vue.config.js中,我们需要确保预处理脚本先执行,并配置Webpack插件:
javascript复制const webpack = require('webpack')
const path = require('path')
// 先执行预处理
require('./build/preBuild.js')
module.exports = {
configureWebpack: {
resolve: {
alias: {
// 设置地区别名
'@district': path.join(__dirname, 'src/district', process.env.DISTRICT)
}
},
plugins: [
// 注入环境变量
new webpack.EnvironmentPlugin([
'UNI_PLATFORM',
'CLIENT_TYPE',
'VERSION',
'DISTRICT',
'API_BASE_URL',
'THEME_COLOR'
])
]
}
}
通过设置@district别名,我们可以方便地在代码中引用地区特定资源:
javascript复制// 引用地区特定图片
import banner from '@district/images/banner.jpg'
// 引用地区配置
import config from '@district/config'
在业务代码中,可以根据平台编写特定逻辑:
javascript复制// 平台判断
if (process.env.UNI_PLATFORM === 'mp-weixin') {
// 微信小程序特有逻辑
} else if (process.env.UNI_PLATFORM === 'mp-alipay') {
// 支付宝小程序特有逻辑
}
// 在Vue组件中使用
computed: {
showSpecialModule() {
return process.env.UNI_PLATFORM === 'mp-weixin'
}
}
在封装HTTP请求时,可以使用注入的API地址:
javascript复制import axios from 'axios'
const service = axios.create({
baseURL: process.env.API_BASE_URL,
timeout: 5000
})
export default service
可以根据地区变量应用不同的样式:
css复制/* 使用地区主题色 */
.header {
background-color: v-bind('process.env.THEME_COLOR');
}
规范的目录结构是方案可维护性的关键。以下是推荐的目录结构:
code复制project/
├── config/
│ ├── base/ # 基础配置
│ │ ├── pages.json # 基础页面配置
│ │ └── manifest.json # 基础manifest配置
│ ├── districts/ # 地区特定配置
│ │ ├── anhui/ # 安徽地区配置
│ │ │ ├── pages.json # 安徽特定页面配置
│ │ │ └── manifest.json # 安徽特定manifest
│ │ └── xinjiang/ # 新疆地区配置
│ │ ├── pages.json
│ │ └── manifest.json
│ └── preBuild.config.js # 映射配置
├── src/
│ ├── district/ # 地区前端资源
│ │ ├── anhui/ # 安徽地区资源
│ │ │ ├── config.js # 安徽业务配置
│ │ │ └── images/ # 安徽特定图片
│ │ └── xinjiang/ # 新疆地区资源
│ │ ├── config.js
│ │ └── images/
│ ├── pages.json # 动态生成的页面配置
│ └── manifest.json # 动态生成的manifest
├── build/
│ ├── preBuild.js # 预处理脚本
│ ├── pages.json.js # pages生成器
│ └── manifest.json.js # manifest生成器
└── vue.config.js # Webpack配置
config/districts/下创建地区目录(如jiangsu/)src/district/下创建对应目录,添加地区资源config/preBuild.config.js的DISTRICT_CONFIG_MAP中添加地区配置config/preBuild.config.js的API_BASE_URL_MAP中添加环境映射config/preBuild.config.js的CLIENT_TYPE_MAP中添加平台映射问题现象:业务代码中process.env.XXX为undefined
排查步骤:
问题现象:合并后的pages.json或manifest.json不符合预期
解决方案:
优化建议:
解决方案:
这套方案在实际项目中已经验证了其可行性和高效性,特别适合需要同时维护多个地区版本和平台的项目。通过合理的配置和脚本组织,可以显著提升开发效率和发布质量。