uniapp 多地区、多平台、多环境打包方案

40 阅读7分钟

前言:本文基于真实的线上项目,介绍如何用一套代码支持多个地区多种平台(微信/支付宝小程序、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 被替换为常量

核心有以下三点:

  1. 环境变量驱动:用 UNI_PLATFORMDISTRICTDEPLOY 等控制整条构建链。
  2. 构建前预处理:在 Webpack 之前跑 preBuild.js,统一把「平台/地区/环境」转成业务需要的 CLIENT_TYPEAPI_BASE_URLAPP_TITLE 等。
  3. 配置与代码注入:用 Webpack 官方 EnvironmentPluginprocess.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.jspreBuild.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_PLATFORMDISTRICTDEPLOY
  • 派生变量:写入 CLIENT_TYPEAPP_TITLEAPI_BASE_URLCUSTOM_TAB 等,供后续 Webpack 和动态配置使用。
  • 执行顺序:本文件在 vue.config.js 的顶部被 require,因此先于 Webpack 配置执行;下面的 pages.json.jsmanifest.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.DISTRICTprocess.env.API_BASE_URL 等之前,这些变量就已经就绪。
  • @district 别名:指向 src/district/${DISTRICT},便于按地区维护配置(如 src/district/anhui/config.js),业务侧统一从 @district 引用。
  • EnvironmentPlugin:Webpack 内置插件,会把数组中列出的 process.env.XXX编译时替换为当前构建时的值,因此业务里写 process.env.UNI_PLATFORMprocess.env.DISTRICTprocess.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.jsconfig/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.jsrequire preBuild、配置 @district、EnvironmentPlugin
动态页面配置build/pages.json.js合并基础 + 地区 pages,写回 src/pages.json
动态 manifestbuild/manifest.json.js合并基础 + 地区 manifest,写回 src/manifest.json
地区前端配置src/district/<DISTRICT>/config.js业务通过 @district 引用
地区构建配置config/districts/<DISTRICT>/pages.jsmanifest.json.js仅该地区生效的页面与 manifest

五、新增地区/环境/平台时的操作清单

  1. 新增地区

    • config/preBuild.config.jsDISTRICT_CONFIG_MAP 中增加 APP_TITLE 等。
    • 如需差异化页面:在 config/districts/<新地区>/ 下增加 pages.js
    • 如需差异化 manifest:在 config/districts/<新地区>/ 下增加 manifest.json.js
    • src/district/ 下新增同名目录及 config.js(可参考现有地区)。
  2. 新增部署环境

    • config/preBuild.config.jsAPI_BASE_URL_MAP 中增加 DEPLOY → API 地址。
    • package.json 的 scripts 中增加对应 dev/build:平台:地区:环境 命令。
  3. 新增平台

    • CLIENT_TYPE_MAP 中增加平台与客户端类型。
    • 若 uniapp 支持该平台,只需在 script 中增加 UNI_PLATFORM=新平台 的 dev/build 脚本即可。

六、注意事项

  • EnvironmentPlugin 与 preBuild 一致:凡在 preBuild.js 里新加的 process.env.XXX,若要在业务代码中使用,需在 vue.config.jsEnvironmentPlugin 数组中增加 'XXX'
  • DISTRICT / DEPLOY 默认值:preBuild 中使用了 process.env.DISTRICT || 'xuzhou'process.env.DEPLOY || 'dev',未传时会有默认地区与环境,可按需修改。
  • 跨平台兼容:使用 cross-env 可避免在 Windows 与 Mac/Linux 下环境变量写法不一致的问题。

按上述方案,即可用「地区 + 平台 + 环境」三维度通过一条命令完成打包,并实现配置集中、扩展清晰,欢迎大家学习指正!