前言:本文基于真实的线上项目,介绍如何用一套代码支持多个地区、多种平台(微信/支付宝小程序、H5 等)、多种部署环境(开发/测试/生产)的构建与打包,并给出具体代码与配置说明。
开始之前,大家先想象这样一个场景:同一套业务,要同时给 A 省、B 省、C 省上线微信小程序和支付宝小程序,每个省的标题、首页内容、支付方式、后端网关地址都不一样,还要区分 dev / qa / prod 多套环境。如果每一套都复制一份代码维护,成本会很快失控。那么我这篇文章讲的,就是怎样通过「一套代码 + 一组脚本」把这些组合全部收拢起来,统一维护。
一、需求与目标
| 维度 | 说明 | 示例 |
|---|---|---|
| 地区(DISTRICT) | 不同省份/地区,不同应用标题、页面配置、manifest | 新疆、安徽、江苏、吉林… |
| 平台(UNI_PLATFORM) | 不同运行端 | 微信小程序 mp-weixin、支付宝 mp-alipay、H5 |
| 环境(DEPLOY) | 不同 API 与部署目标 | dev、qa02、release_anhui、xinjiang_prod… |
目标:通过一条 npm 脚本即可确定「地区 + 平台 + 环境」,打出对应产物,无需改代码。
二、整体思路
npm script (cross-env 注入环境变量)
↓
process.env.UNI_PLATFORM / DISTRICT / DEPLOY / NODE_ENV
↓
vue.config.js 加载时先执行 preBuild.js(类似于webpack自定义plugin的效果)
↓
preBuild.js 根据上述变量生成 CLIENT_TYPE、APP_TITLE、API_BASE_URL 等
↓
动态生成 pages.json、manifest.json(按地区合并)
↓
Webpack 通过 EnvironmentPlugin 将变量注入业务代码
↓
构建产物中 process.env.XXX 被替换为常量
核心有以下三点:
- 环境变量驱动:用
UNI_PLATFORM、DISTRICT、DEPLOY等控制整条构建链。 - 构建前预处理:在 Webpack 之前跑
preBuild.js,统一把「平台/地区/环境」转成业务需要的CLIENT_TYPE、API_BASE_URL、APP_TITLE等。 - 配置与代码注入:用 Webpack 官方
EnvironmentPlugin把process.env中的键注入到前端代码,保证运行时代码能拿到当前构建的「地区/平台/环境」。
三、具体实现步骤与代码说明
3.1 用 cross-env 在 npm script 中传参
不同操作系统下设置环境变量方式不同,使用 cross-env 可统一写法。
安装:
npm i -D cross-env
package.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:release_anhui": "cross-env NODE_ENV=development UNI_PLATFORM=mp-weixin DISTRICT=anhui DEPLOY=dev_anhui vue-cli-service uni-build --watch"
}
}
含义简述:
cross-env KEY=value会在当前 Node 进程中设置process.env.KEY = value。- 后面执行的
vue-cli-service uni-build与它加载的vue.config.js、preBuild.js都运行在同一进程,因此能直接读到这些环境变量。 - 命名约定:
dev/build+平台+地区+环境,便于一眼看出打的是哪一套。
关于 --watch:
- 开发脚本(如
dev:mp-weixin:...)末尾加上--watch,表示 开启监听模式:当代码或配置文件变更时,会自动重新编译对应平台/地区/环境的产物,无需每次手动重新执行命令,适合本地联调和反复修改。
常见变量:
| 变量 | 含义 | 示例 |
|---|---|---|
NODE_ENV | 开发/生产 | development / production |
UNI_PLATFORM | 平台 | mp-weixin、mp-alipay、h5 |
DISTRICT | 地区 | anhui、xinjiang、jiangsu |
DEPLOY | 部署环境 | dev、qa、release_anhui |
CUSTOM_TAB | 可选,自定义 tab 配置 | 如 pay、order_search |
3.2 统一配置:preBuild.config.js
把「平台 → 客户端类型」「地区 → 应用标题」「部署环境 → API 地址」等做成映射表,便于维护和扩展。
config/preBuild.config.js:
// 平台与客户端类型(用于接口等)
const CLIENT_TYPE_MAP = {
h5: 1,
'mp-weixin':2,
'mp-alipay':3,
'mp-baidu': 4,
'mp-toutiao': 5,
'mp-qq': 6
}
// 地区与应用标题等展示配置
const DISTRICT_CONFIG_MAP = {
jilin: { APP_TITLE: 'xxx' },
xinjiang: { APP_TITLE: 'xxx' },
// ... 其他地区
}
// 部署环境与 API 根地址(示例域名为占位)
const API_BASE_URL_MAP = {
dev: 'https://api-dev.example.com/mobile-app/api',
qa: 'https://api-qa02.example.com/mobile-app/api',
release_regionA: 'https://api-release-regionA.example.com/mobile-app/api',
// ... 其他环境
}
module.exports = {
CLIENT_TYPE_MAP,
DISTRICT_CONFIG_MAP,
API_BASE_URL_MAP
}
后续新增地区或环境时,只需在此处增补配置,无需改构建脚本逻辑。
3.3 构建前预处理:preBuild.js
在 Webpack 读取 vue.config.js 之前,需要把「平台/地区/环境」转成业务和配置生成器使用的变量。因此把 preBuild.js 放在 vue.config.js 最前面执行。
build/preBuild.js:
/**
* 设置自定义的 process.env.X 需同时在 vue.config.js 的
* configureWebpack.plugins 里加入 EnvironmentPlugin 对应 key
*/
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]
// 由「地区」得到应用标题(缺省用 jilin)
process.env.APP_TITLE = DISTRICT_CONFIG_MAP[process.env.DISTRICT || 'xuzhou'].APP_TITLE
// 由「部署环境」得到 API 根地址(缺省用 dev)
process.env.API_BASE_URL = API_BASE_URL_MAP[process.env.DEPLOY || 'dev']
// 可选:自定义 tab 等,未传则空字符串
process.env.CUSTOM_TAB = process.env.CUSTOM_TAB || ''
// 便于排查:构建时打印当前维度
console.log('------------------------------')
console.log('NODE_ENV:', process.env.NODE_ENV)
console.log('DEPLOY:', process.env.DEPLOY)
console.log('UNI_PLATFORM:', process.env.UNI_PLATFORM)
console.log('DISTRICT:', process.env.DISTRICT)
console.log('CLIENT_TYPE:', process.env.CLIENT_TYPE)
console.log('API_BASE_URL:', process.env.API_BASE_URL)
console.log('------------------------------')
// 根据 DISTRICT 等动态生成 pages.json、manifest.json
require('./pages.json.js')
require('./manifest.json.js')
要点:
- 只读不写:从
process.env读取由 cross-env 注入的UNI_PLATFORM、DISTRICT、DEPLOY。 - 派生变量:写入
CLIENT_TYPE、APP_TITLE、API_BASE_URL、CUSTOM_TAB等,供后续 Webpack 和动态配置使用。 - 执行顺序:本文件在
vue.config.js的顶部被require,因此先于 Webpack 配置执行;下面的pages.json.js、manifest.json.js会用到当前的process.env.DISTRICT等。
3.4 在 vue.config.js 中接入 preBuild 与 EnvironmentPlugin
vue.config.js:
const webpack = require('webpack')
const path = require('path')
// 必须最先执行:注入 CLIENT_TYPE、API_BASE_URL 等,并生成 pages.json / manifest.json
require('./build/preBuild.js')
module.exports = {
configureWebpack: {
resolve: {
alias: {
// 按地区做目录别名,业务里 import 来自 @district 即当前地区配置
'@district': path.join(__dirname, 'src/district', process.env.DISTRICT)
}
},
plugins: [
// 将 process.env 中列出的 key 在编译时注入到业务代码中
// 业务代码中 process.env.UNI_PLATFORM 等会被替换为构建时的常量
new webpack.EnvironmentPlugin([
'UNI_PLATFORM',
'CLIENT_TYPE',
'VERSION',
'DISTRICT',
'API_BASE_URL',
'CUSTOM_TAB'
])
]
}
}
说明:
require('./build/preBuild.js'):保证在任何 webpack 配置使用process.env.DISTRICT、process.env.API_BASE_URL等之前,这些变量就已经就绪。@district别名:指向src/district/${DISTRICT},便于按地区维护配置(如src/district/anhui/config.js),业务侧统一从@district引用。- EnvironmentPlugin:Webpack 内置插件,会把数组中列出的
process.env.XXX在编译时替换为当前构建时的值,因此业务里写process.env.UNI_PLATFORM、process.env.DISTRICT、process.env.API_BASE_URL等即可,无需再传参。
3.5 按地区动态生成 pages.json(build/pages.json.js)
不同地区可能需要不同的页面列表、tabBar 等,因此把「基础配置」与「地区配置」合并后再写入 src/pages.json。
build/pages.json.js:
const fs = require('fs')
const path = require('path')
const lodash = require('lodash')
const standardPages = require('../config/pages.js')
let districtPage = {}
let customPages = []
const district = process.env.DISTRICT
// 若存在该地区的 pages 配置则合并
try {
districtPage = require(`../config/districts/${district}/pages.js`)
} catch (e) {
console.log(`不存在【${district}】地区差异化 page.js,略过`)
}
// 若有 CUSTOM_TAB,可再合并 custom.xxx.pages.js
process.env.CUSTOM_TAB.split(',').forEach((type) => {
// ... 按 type 加载 config/custom.${type}.pages.js 与 config/districts/${district}/ 下同名文件
})
const pageJSON = lodash.mergeWith({}, standardPages, districtPage, ...customPages, (objValue, srcValue, key) => {
if (Array.isArray(objValue) && key === 'list') {
return srcValue // 如 tabBar.list 用地区配置覆盖,而不是数组合并
}
})
fs.writeFileSync(path.join(__dirname, '../src/pages.json'), JSON.stringify(pageJSON, null, 2), { encoding: 'utf8' })
console.log('page.json 构建完成')
思路:对基础 pages + 地区 pages + 自定义 tab 配置 做 merge,对 list 类数组采用覆盖策略,避免 tab 等被意外合并。
3.6 按地区动态生成 manifest.json(build/manifest.json.js)
小程序与 H5 的 appid、描述等可能按地区不同,同样采用「标准 manifest + 地区 manifest」合并。
build/manifest.json.js:
const fs = require('fs')
const path = require('path')
const lodash = require('lodash')
const standardManifest = {
name: 'app',
appid: 'xxx',
'mp-weixin': { /* ... */ },
'mp-alipay': { /* ... */ }
// ...
}
let districtManifest = {}
try {
districtManifest = require(`../config/districts/${process.env.DISTRICT}/manifest.json.js`)
} catch (e) {
console.log('该地区不存在差异化 manifest.json.js')
}
const merged = lodash.merge({}, standardManifest, districtManifest)
fs.writeFileSync(path.join(__dirname, '../src/manifest.json'), JSON.stringify(merged, null, 4), { encoding: 'utf8' })
console.log('manifest.json 生成完成')
地区目录示例:config/districts/anhui/manifest.json.js、config/districts/xinjiang/pages.js 等,按需添加。
3.7 业务代码中如何使用
构建时环境变量已被注入,业务中直接读 process.env 即可。
按平台分支:
// 例如仅微信小程序展示某模块
if (process.env.UNI_PLATFORM === 'mp-weixin') {
// 微信逻辑
}
// 计算属性中
moreModuleGroups: vm => process.env.UNI_PLATFORM === 'mp-weixin'
? xxxx
: xxxx
按地区使用配置:
// 使用别名 @district,实际指向 src/district/${DISTRICT}
import banner from '@district/banner.jpg'
// 或直接使用注入的常量
const district = process.env.DISTRICT
const apiBase = process.env.API_BASE_URL
请求 API:
// 封装请求时用 process.env.API_BASE_URL 作为 baseURL
axios.create({ baseURL: process.env.API_BASE_URL })
四、目录与脚本约定小结
| 角色 | 路径/命令 | 作用 |
|---|---|---|
| 环境变量注入 | cross-env UNI_PLATFORM=... DISTRICT=... DEPLOY=... | 在 npm script 中传入维度 |
| 映射配置 | config/preBuild.config.js | 平台/地区/环境 → 客户端类型、标题、API |
| 构建前逻辑 | build/preBuild.js | 设置 CLIENT_TYPE、API_BASE_URL 等并生成 pages/manifest |
| Webpack 入口 | vue.config.js | require preBuild、配置 @district、EnvironmentPlugin |
| 动态页面配置 | build/pages.json.js | 合并基础 + 地区 pages,写回 src/pages.json |
| 动态 manifest | build/manifest.json.js | 合并基础 + 地区 manifest,写回 src/manifest.json |
| 地区前端配置 | src/district/<DISTRICT>/config.js | 业务通过 @district 引用 |
| 地区构建配置 | config/districts/<DISTRICT>/pages.js、manifest.json.js | 仅该地区生效的页面与 manifest |
五、新增地区/环境/平台时的操作清单
-
新增地区
- 在
config/preBuild.config.js的DISTRICT_CONFIG_MAP中增加APP_TITLE等。 - 如需差异化页面:在
config/districts/<新地区>/下增加pages.js。 - 如需差异化 manifest:在
config/districts/<新地区>/下增加manifest.json.js。 - 在
src/district/下新增同名目录及config.js(可参考现有地区)。
- 在
-
新增部署环境
- 在
config/preBuild.config.js的API_BASE_URL_MAP中增加DEPLOY→ API 地址。 - 在
package.json的 scripts 中增加对应dev/build:平台:地区:环境命令。
- 在
-
新增平台
- 在
CLIENT_TYPE_MAP中增加平台与客户端类型。 - 若 uniapp 支持该平台,只需在 script 中增加
UNI_PLATFORM=新平台的 dev/build 脚本即可。
- 在
六、注意事项
- EnvironmentPlugin 与 preBuild 一致:凡在
preBuild.js里新加的process.env.XXX,若要在业务代码中使用,需在vue.config.js的EnvironmentPlugin数组中增加'XXX'。 - DISTRICT / DEPLOY 默认值:preBuild 中使用了
process.env.DISTRICT || 'xuzhou'、process.env.DEPLOY || 'dev',未传时会有默认地区与环境,可按需修改。 - 跨平台兼容:使用
cross-env可避免在 Windows 与 Mac/Linux 下环境变量写法不一致的问题。
按上述方案,即可用「地区 + 平台 + 环境」三维度通过一条命令完成打包,并实现配置集中、扩展清晰,欢迎大家学习指正!