多端构建与发布
适用端:移动端 | 类型:操作
本页讲从源码到可交付产物这一段:56 个 dev / build 脚本各自的 mode 与产物落点、env/.env* 四层叠加如何决定产物里的常量、manifest.config.ts 如何生成三端清单、两个 app 端专属插件做什么、微信小程序的上传链路,以及实测的产物体积构成。
前置阅读:移动端环境准备与启动(怎么跑起来)、工程结构与约定式路由(pages.json / manifest.json 是谁生成的)。
它解决什么问题
一套代码要出三种形态的产物,而三者的构建入口、mode 语义、产物目录、发布动作全都不一样:
最容易踩的不是命令本身,是三处隐性耦合:
mode同时决定「加载哪个.env.[mode]」和「是否压缩 / 是否注入 eruda / 是否弹打包分析」,而不是只有前者;src/manifest.json由manifest.config.ts生成,但它自己另有一套 mode 推导(解析process.argv),不复用 Vite 的mode;- app 端产物还需要两个只在 app 平台生效的插件补资源,其中一个曾经把路径写死成
dist/dev/app(已修,见下文)。
代码在哪
| 要看什么 | 文件 |
|---|---|
全部脚本(73 条,其中 56 条是 dev / build) | package.json:24-98 |
| Vite 配置:base / 插件链 / server / build | vite.config.ts(base 在 :87,18 个插件在 :88-198,server 在 :226-241,build 在 :245-252) |
| 三端产物清单的真源 | manifest.config.ts(170 行) |
| 全局页面配置与 easycom | pages.config.ts |
| 环境变量四层 | env/.env(79 行,24 项公共基底)、.env.development、.env.test(各 15 行)、.env.production(23 行) |
| app 端原生插件资源复制 | vite-plugins/copy-native-resources.ts(199 行) |
| app 端 manifest plugins 同步 | vite-plugins/sync-manifest-plugins.ts(67 行) |
| 自动打开小程序开发者工具 | scripts/open-dev-tools.js(113 行) |
| 微信小程序上传 | scripts/upload-weixin.js(223 行) |
| 硬编码色守卫 | scripts/check-hardcoded-color.mjs(pnpm check:color) |
| 上游插件说明 | vite-plugins/README.md |
核心机制
1 mode 决定一切,而 56 个脚本只用了 3 个 mode
package.json 的 56 条 dev / build 脚本,mode 只有三种取值:
| 脚本后缀 | 实际命令 | Vite mode | 加载的 env 文件(后者覆盖前者) |
|---|---|---|---|
无后缀(dev、dev:h5、build:h5…) | uni / uni build | dev 命令 = development,build 命令 = production | .env + .env.development / .env + .env.production |
:test(dev:test、build:mp:test…) | 追加 --mode test | test | .env + .env.test |
:prod(dev:prod、build:h5:prod…) | 追加 --mode production | production | .env + .env.production |
非 H5 端的 dev 也是 build command
vite.config.ts:60-64 的注释写明了这一点:pnpm dev:app 得到的 command 是 build、mode 是 development。所以判断「是不是生产」不能看 command,只能看 mode / NODE_ENV——vite.config.ts:251 的 minify: mode === 'development' ? false : 'esbuild' 与 copy-native-resources.ts:100 的 process.env.NODE_ENV === 'production' ? 'build' : 'dev' 都是按这个口径写的。
用 Vite 自己的 loadEnv 逐 mode 实测(node -e "const {loadEnv}=require('vite');…"),三层叠加后的关键取值:
| 变量 | development | test | production |
|---|---|---|---|
VITE_SERVER_BASEURL | http://localhost:48080/admin-api | 同左 | https://demo.enlev.cn/admin-api |
VITE_STATIC_BASEURL | '' | '' | '' |
VITE_DELETE_CONSOLE | false | false | true |
VITE_APP_PUBLIC_BASE | / | / | / |
VITE_APP_PROXY_ENABLE | false | false | false |
VITE_COPY_NATIVE_RES_ENABLE | false | false | false |
VITE_APP_DEFAULT_LOGIN_USERNAME | admin | admin | undefined |
别把带口令的默认值写进 env/.env
env/.env 是所有 mode 的公共基底,production 也加载它,而 Vite 会把 import.meta.env.VITE_* 静态内联进产物——运行期没有任何开关能把它摘掉。本项目原先把 VITE_APP_DEFAULT_LOGIN_{TENANT_ID,USERNAME,PASSWORD} 放在 .env 里,结果 2026-08-30 那次 H5 构建的产物里能直接搜到明文:
dist/build/h5/assets/pages-core-auth-login.Dx0p7Pwd.js
… t=O({username:"admin",password:"admin123",captchaVerification:""}) …消费点是 src/pages-core/auth/login.vue:123-124(无条件填进 formData)与 src/pages-core/auth/components/tenant-picker.vue:50。现已把三项移到 .env.development 与 .env.test,.env 原位留了注释说明为什么不能挪回去;.env.production 不含这三项,实测 production mode 下三者均为 undefined,登录页变为空表单。
推论:任何以 VITE_ 开头的值都要当作「会出现在公开产物里」来对待。同理,env/.env:75-79 的 VITE_APP_API_ENCRYPT_* 把两把 AES 明文密钥直接写在仓库里,它们会被内联进每一端的产物。目前还没有造成实际泄露,是因为请求加密根本没被触发:VITE_APP_API_ENCRYPT_ENABLE = true 只是能力开关,真正的闸门是 src/http/interceptor.ts:83 的 if (options.isEncrypt),而全仓库没有任何调用方传 isEncrypt: true(该字段只出现在类型声明 src/http/types.ts:13)。响应侧的解密由服务端下发的加密头触发(src/http/http.ts:84-94),所以只要后端不加密就也不会解密。真要启用请求加密,必须先把密钥换成运行期下发的方案。
2 只有 3 个脚本挂了 init-baseFiles 前置钩子
src/pages.json 与 src/manifest.json 都被 gitignore(.gitignore:42-43),必须先由 scripts/create-base-files.js 造出桩文件,再由 UniPages / UniManifest 插件在启动时覆盖成真内容。
| 钩子 | 内容 | 覆盖到的脚本 |
|---|---|---|
preinstall | npx only-allow pnpm | 所有 pnpm install |
prepare | pnpm init-husky & pnpm init-baseFiles | pnpm install 之后 |
predev | pnpm init-baseFiles | 只有 pnpm dev |
predev:app | 同上 | 只有 pnpm dev:app |
predev:mp | 同上 | 只有 pnpm dev:mp |
npm/pnpm 的 pre<name> 钩子只对脚本名精确匹配生效,所以 dev:h5、dev:test、dev:prod、dev:h5:test、dev:mp-weixin、dev:mp:prod、以及全部 25 条 build:* 都没有前置钩子。目前不出问题,是因为 prepare 在 pnpm install 时已经跑过一次 init-baseFiles——也就是说这条链的兜底在安装期而不是构建期。手工删掉 src/pages.json 再直接 pnpm build:h5 就会踩空。
prepare 用的是单个 &
"prepare": "pnpm init-husky & pnpm init-baseFiles" 是单个 &:在 POSIX shell 下 init-husky 被丢到后台,它失败不会阻断安装,且两条命令并发跑;在 Windows 的 cmd 下 & 才是顺序分隔符。而 init-husky = git init && husky,在一个已经是 git 仓库的目录里再 git init 是多余的。这两处都属遗留(已记入待办),不影响构建产物。
3 build 段的四个开关,其中一个是死的
vite.config.ts:242-252:
esbuild: {
drop: VITE_DELETE_CONSOLE === 'true' ? ['console', 'debugger'] : [],
},
build: {
sourcemap: false,
// 方便非h5端调试
// sourcemap: VITE_SHOW_SOURCEMAP === 'true', // 默认是false
target: 'es6',
// 开发环境不用压缩
minify: mode === 'development' ? false : 'esbuild',
},| 开关 | 生效条件 | 实测状态 |
|---|---|---|
esbuild.drop | VITE_DELETE_CONSOLE === 'true' | production 为 true → console/debugger 全被摘掉;development/test 为 false |
build.minify | mode !== 'development' | uni -p app(dev)产物不压缩,uni build 系列压缩 |
build.target | 恒定 es6 | 三端一致 |
build.sourcemap | 恒定 false | 见下 |
VITE_SHOW_SOURCEMAP 是死变量
.env.development:6、.env.test:6、.env.production:6 都设了 VITE_SHOW_SOURCEMAP = false,但全仓库唯一读它的地方是 vite.config.ts:248 那行被注释掉的代码。改这个变量没有任何效果,三端产物一律无 sourcemap。要出带 sourcemap 的包,只能改 :246。
4 manifest.config.ts 自己另推一次 mode
src/manifest.json 的真源是根目录的 manifest.config.ts,它由 UniManifest()(vite.config.ts:92)驱动。关键在于它拿不到 Vite 的 mode,只能自己解析命令行(manifest.config.ts:8-14):
function getMode() {
const args = process.argv.slice(2)
const modeFlagIndex = args.findIndex(arg => arg === '--mode')
return modeFlagIndex !== -1
? args[modeFlagIndex + 1]
: args[0] === 'build' ? 'production' : 'development'
}
const env = loadEnv(getMode(), path.resolve(process.cwd(), 'env'))它从 env 里取 5 个值填进清单(manifest.config.ts:15-21):VITE_APP_TITLE → name、VITE_UNI_APPID → appid、VITE_WX_APPID → mp-weixin.appid、VITE_APP_PUBLIC_BASE → h5.router.base、VITE_FALLBACK_LOCALE → locale。
当前生成的 src/manifest.json 实测值:
| 字段 | 值 | 来源 |
|---|---|---|
name | 启凌智搭 | env/.env:1 VITE_APP_TITLE |
appid | __UNI__D1E5001 | env/.env VITE_UNI_APPID,仍是上游 DCloud appid |
mp-weixin.appid | wx63c280fe3248a3e7 | env/.env VITE_WX_APPID,仍是上游小程序 appid |
mp-weixin.setting.urlCheck | false | manifest.config.ts:128-133 写死 |
mp-weixin.optimization.subPackages | true | 同上,开启分包优化 |
h5.router.base | / | env/.env:16 VITE_APP_PUBLIC_BASE |
versionName / versionCode | 1.0.0 / 100 | manifest.config.ts:28-29 硬编码,从不递增 |
locale | 缺失 | VITE_FALLBACK_LOCALE 在任何 env 文件里都没定义 |
uniStatistics.enable | false | manifest.config.ts:166-168 写死 |
app-plus.distribute.android | minSdk 21 / targetSdk 30 / abiFilters: armeabi-v7a, arm64-v8a / 15 条权限 | manifest.config.ts:62-83 |
三个身份标识仍是上游的
VITE_UNI_APPID、VITE_WX_APPID、VITE_BAIDU_MAP_KEY 都还是上游演示值。VITE_WX_APPID 尤其要紧——scripts/upload-weixin.js:156-160 直接拿它当上传目标 appid,缺失时 throw,所以不能简单删掉,必须换成自己的。DCloud appid 要在 HBuilderX 里重新获取。这三项已记入待办,等账号信息。
locale 那行等于没写
manifest.config.ts:31 是 'locale': VITE_FALLBACK_LOCALE, // 'zh-Hans',注释暗示期望值,但变量从未定义,实测生成结果里 locale 字段整个不存在(undefined 被 JSON.stringify 丢掉)。uni-app 会退回它自己的默认语言。要固定语言,得先在 env/.env 里补 VITE_FALLBACK_LOCALE = 'zh-Hans'。
Android 权限清单(manifest.config.ts:66-82)整段继承自上游,含 READ_LOGS、WRITE_SETTINGS、READ_PHONE_STATE、GET_ACCOUNTS、MOUNT_UNMOUNT_FILESYSTEMS 这类宽权限。当前 App 端并未打包发布,所以还没有实际后果;真要上架前必须逐条核对,各大应用商店会因无关权限拒审。
5 两个只在 app 端生效的插件
| 插件 | 挂载点 | 生效条件 | 做什么 |
|---|---|---|---|
createCopyNativeResourcesPlugin | vite.config.ts:182-187 | UNI_PLATFORM === 'app' && VITE_COPY_NATIVE_RES_ENABLE === 'true' | 把根目录 nativeplugins/ 整个复制到 dist/{build|dev}/app/nativeplugins/(copy-native-resources.ts:39-45 的默认源/目标目录名,:100-102 按 NODE_ENV 与 UNI_PLATFORM 定位目标)。当前开关是 false,且仓库里没有 nativeplugins/ 目录 |
syncManifestPlugin | vite.config.ts:188 | apply: 'build',全平台挂载 | 把 src/manifest.json 的 app-plus.distribute.plugins 合并写进产物的 plus.distribute.plugins |
第二个插件原先有两处缺陷,本轮已修(提交 5f653c4b):
- 目标路径写死
./dist/dev/app/manifest.json,而uni build -p app的产物在dist/build/app——同步结果永远进不了生产产物; mkdirSync在「有没有 plugins 可同步」的判断之前无条件执行,于是连 H5 构建都会在dist下留一个空的dev/app/。仓库里那个 2026-08-30 造出来的空目录就是这么来的。
修法是:非 app 平台直接返回;build / dev 按 NODE_ENV 推导(与同目录 open-dev-tools.js:26,99 早就在用的口径一致);没有 plugins 时在建目录之前就返回。改完用 esbuild 转出插件、逐组合调用 writeBundle.handler 验过 7 例:h5 / mp-weixin 跳过、app 无插件跳过、app 生产写 dist/build、app 开发写 dist/dev、app-android 写 dist/build、已有 manifest 的 icons 等字段原样保留。
6 打包分析与开发者工具是「会自动弹出来」的
| 行为 | 触发条件 | 代码 |
|---|---|---|
visualizer 生成体积报告并自动打开浏览器 | UNI_PLATFORM === 'h5' && mode === 'production' | vite.config.ts:173-180,输出到 node_modules/.cache/visualizer/stats.html |
| 自动打开微信开发者工具 | SKIP_OPEN_DEVTOOLS !== 'true' 且 UNI_PLATFORM 含 mp 且是首次构建 | vite.config.ts:194-197 → scripts/open-dev-tools.js:97-113 |
| 注入 eruda 调试面板 | UNI_PLATFORM === 'h5' && mode === 'development' | vite.config.ts:189-191 |
html-transform 替换 %BUILD_TIME% / %VITE_APP_TITLE% | UNI_PLATFORM === 'h5' | vite.config.ts:166-171,模板见 index.html:2,17 |
两条对 CI 有直接影响:pnpm build:h5 会弹出一个浏览器标签(visualizer 的 open: true),pnpm build:mp 会尝试拉起微信开发者工具。前者在无人值守的构建机上只是多一个进程,后者要靠 SKIP_OPEN_DEVTOOLS=true 跳过——scripts/upload-weixin.js:142-150 就是这么做的。开发者工具的 CLI 路径通过 WECHAT_DEVTOOLS_CLI_PATH 配置(env/.env:9,11 有 macOS 与 Windows 两个注释掉的示例),未配置时退回 open-dev-tools.js:43(macOS)/ :53(Windows)的平台默认路径,找不到就打印提示后静默返回,不会让构建失败。
7 微信小程序上传链路
pnpm upload:mp-weixin → scripts/upload-weixin.js,全流程(:153-186):
三个参数的回退顺序:
| 参数 | 优先级 | 代码 |
|---|---|---|
version | --version= → WECHAT_UPLOAD_VERSION → readPackageVersion() | :162;readPackageVersion()(:62-69)取 pkg['yudao-version'] || pkg.version || '1.0.0' |
desc | --desc= → WECHAT_UPLOAD_DESC → readGitCommitMessage() → 上传于 <时间> | :163、:71-80 |
robot | --robot= → WECHAT_UPLOAD_ROBOT → 1;必须是 1–30 的整数,否则 throw | :164-165、assertRobot(:130-134) |
上传版本号读的是 yudao-version
readPackageVersion() 优先取 package.json:6 的 "yudao-version": "2026.07.0-snapshot",而不是 package.json:4 的 "version": "2026.07-snapshot"。这个键名是上游遗留,本项目刻意保留了它(它记录的是上游合并基线,见 docs/merge/)。后果是:不显式传 --version= 就会把 2026.07.0-snapshot 当小程序版本号上传。正式发布务必显式传 --version=1.0.0 这样的语义化版本,或者改 readPackageVersion() 的取值顺序。
私钥解析(resolvePrivateKeyPath,:112-126)按顺序找:--private-key= → 环境变量 WECHAT_UPLOAD_PRIVATE_KEY_PATH → 根目录 private.<appid>.key → 根目录 private.key;都没有就 throw 并提示前两种配法。私钥不进 git(.gitignore 覆盖),CI 上用 secret 注入环境变量。
loadEnvFile(:43-52)是手写的 .env 解析器(:27-41),只处理 key=value 单行、去掉首尾引号、跳过 # 开头行;不支持多行值、export 前缀、变量插值。它的合并顺序(.env 然后 .env.production)与 Vite 一致,所以拿到的 appid / 版本号跟构建时是同一套。
产物结构与体积
下面的数字来自 2026-08-30 的过期产物
仓库里的 dist/build/h5/ 构建于 2026-08-30 02:44,而当前 HEAD 是 2026-10-25——中间隔了改名(产物 index.html 的 <title> 还是旧品牌 X评云,现在应为 启凌智搭)、E1 的地址修复、以及默认口令的移除。这份产物不能部署,下面的体积数字只能当量级参考,不能作为当前基线。
dist/build/h5/ 的构成:
| 目录 | 大小 |
|---|---|
assets/ | 13,424 KB(1,242 个 .js + 584 KB 的 .css) |
static/ | 85 KB |
index.html | 4 KB |
最大的 8 个分块:
| 分块 | 大小 | 归属 |
|---|---|---|
index-JHpR1tCX.js | 1,056 KB | 主包(Vue + uni-app 运行时 + 公共依赖) |
yd-chart.vue_…js | 560 KB | echarts,仅被 pages-statistics 引用 |
pages-im-home-conversation-rtc-call-index.js | 512 KB | livekit-client,仅被 pages-im 引用 |
form-create.BbEIUtok.js | 172 KB | 在线表单渲染器(BPM + infra 共用) |
z-paging.CEwHvMdX.js | 128 KB | 分页列表 |
yd-markdown.DzNPV1Kk.js | 104 KB | Markdown 渲染 |
pages-im-home-conversation-message-index.js | 88 KB | pages-im |
conversationStore.Zt34oxru.js | 88 KB | pages-im 的 store |
按分包前缀汇总(pages-* 分块):
| 分包 | 文件数 | 大小 | 是否可达 |
|---|---|---|---|
pages-mes | 188 | 2,076 KB | ✗ 后端无此模块 |
pages-im | 44 | 968 KB | ✗ |
pages-mall | 87 | 772 KB | ✗ |
pages-erp | 61 | 704 KB | ✗ |
pages-iot | 43 | 476 KB | ✗ |
pages-crm | 40 | 412 KB | ✗ |
pages-wms | 29 | 252 KB | ✗ |
pages-statistics | 18 | 208 KB | ✗ |
pages-pay | 22 | 184 KB | ✗ |
pages-mp | 19 | 180 KB | ✗ |
pages-member | 18 | 176 KB | ✗ |
pages-system | 58 | 493 KB | ✓ |
pages-ai | 43 | 424 KB | ✓ |
pages-infra | 42 | 312 KB | ✓ |
pages-bpm | 28 | 244 KB | ✓ |
pages-core | 15 | 96 KB | ✓(登录 / 注册 / 我的 / 404) |
pages-index | 3 | 32 KB | ✓(工作台首页) |
11 个不可达分包的 pages-* 分块合计 6,408 KB,再加上只被 pages-statistics 引用的 yd-chart(560 KB),约 7.0 MB / 13.4 MB ≈ 52% 的 assets 是编译进了包但用户永远点不到的代码(不可达的判据见移动端总览:后端 ainb 已整体删除 crm/erp/im/iot/mall/member/mes/mp/pay/wms 等模块,导航树 system_nav_node 也不给它们建节点)。
想瘦身先动分包清单
vite.config.ts:104-121 的 UniPages({ subPackages: [...] }) 列了全部 16 个分包目录。去掉不可达的那些,产物能砍掉一半左右,src/pages.json 的路由数也会从 764 降到约 190。但这属于产品决策而非构建配置:这些目录还在,是为了将来后端补上对应模块时能直接接回来。真要动,先确认「不打算再要这些模块」,并且同步改 pages.config.ts、src/tabbar/config.ts 里的引用。
多端产物落点(uni-app 约定,dist 整个被 gitignore,.gitignore:12):
| 命令 | 产物目录 |
|---|---|
pnpm build:h5 / build:h5:prod | dist/build/h5/ |
pnpm dev:h5 | 不落盘(dev server,:9000) |
pnpm build:mp / build:mp:prod | dist/build/mp-weixin/ |
pnpm dev:mp / dev:mp-weixin | dist/dev/mp-weixin/ |
pnpm build:app / build:app:prod | dist/build/app/ |
pnpm dev:app | dist/dev/app/ |
发布流程
H5
pnpm build:h5(或build:h5:prod,二者 mode 相同)。- 把
dist/build/h5/整个上传到站点根。 VITE_APP_PUBLIC_BASE默认/(env/.env:16),部署到子路径时必须先改成/子路径/再构建——它同时喂给vite.config.ts:87的base和manifest.config.ts:34的h5.router.base,两处不一致会导致静态资源 404 或路由 base 错位。- 因为是 SPA + history 路由,nginx 要配
try_files $uri $uri/ /index.html;。 VITE_SERVER_BASEURL在 production 是https://demo.enlev.cn/admin-api(绝对地址)。若前后端同源部署,改成''走同源、由 nginx 反代/admin-api更省事,也省掉 CORS——.env.production:9-11的注释里写了这个选项。- 产物
index.html的<html build-time="…">属性由vite.config.ts:166-171注入,可以用它确认线上跑的是哪次构建。
微信小程序
- 确认
env/.env的VITE_WX_APPID是自己的小程序 appid(当前还是上游的)。 - 配好上传私钥:微信后台 → 开发管理 → 开发设置 → 生成「小程序代码上传密钥」,放到根目录
private.<appid>.key,或设WECHAT_UPLOAD_PRIVATE_KEY_PATH。同时把本机 IP 加进微信后台的 IP 白名单。 - 先跑
pnpm upload:mp-weixin --dry-run --version=1.0.0,它会构建 + 校验参数/私钥/产物但不上传(:183-186)。 - 正式上传:
pnpm upload:mp-weixin --version=1.0.0 --desc="…" --robot=2。 - 也可以走手工路:
pnpm build:mp→ 微信开发者工具导入dist/build/mp-weixin→ 点「上传」。 - 上传后到微信后台把该版本设为「体验版」或提交审核。
- 服务器域名白名单:小程序后台要把
https://demo.enlev.cn加进 request / uploadFile / downloadFile 合法域名,否则真机上所有请求被拦。开发阶段可在开发者工具里勾「不校验合法域名」(manifest.config.ts:129的setting.urlCheck: false就是这个开关的产物侧体现)。
App(Android / iOS)
pnpm build:app→dist/build/app/。- 用 HBuilderX 打开该目录,或直接在 HBuilderX 里对本仓库执行「发行 → 原生 App-云打包」。
- 云打包要用自己的 DCloud appid(
manifest.config.ts:26读的VITE_UNI_APPID,当前仍是上游值),并配好 Android 签名证书 / iOS 证书与描述文件。 - 若引入了本地原生插件:把插件放进根目录
nativeplugins/,在env/.env里把VITE_COPY_NATIVE_RES_ENABLE改成true,并在manifest.config.ts的app-plus.distribute下补plugins段——后者是syncManifestPlugin唯一会同步的字段。 - 上架前逐条核对
manifest.config.ts:66-82的 Android 权限,删掉用不到的(见上文 warning)。
App 与小程序两条链路本机从未实测
本仓库的开发验证只覆盖 H5(浏览器里能直接看)。HBuilderX 云打包、miniprogram-ci 真实上传、原生插件资源复制、以及各小程序平台(支付宝/百度/字节/QQ…,package.json 里有对应脚本但 manifest.config.ts 只给支付宝与百度写了配置段)都没有在这台机器上跑通过。上面的步骤是源码依据 + 官方文档推出来的,第一次执行时请按「未证实」一节的清单逐项确认。
怎么扩展
加一个新的构建目标平台(例如字节小程序):package.json 里 dev:mp-toutiao / build:mp-toutiao 已经有了(uni -p mp-toutiao),缺的是 manifest.config.ts 里的 mp-toutiao 配置段——目前只有一行 usingComponents: true(:163-165,mp-baidu 同样只有这一行,:160-162)。参照 mp-weixin(:126-143)或 mp-alipay(:144-159)补齐 appid、setting、optimization.subPackages。另外 vite.config.ts:127 的 UniOptimization({ enable: UNI_PLATFORM === 'mp-weixin' }) 只对微信开启,其他平台需要分包优化时要一并放开。
让构建产物带上 sourcemap:改 vite.config.ts:246 的 sourcemap: false,或把 :248 那行取消注释并给三个 env 文件的 VITE_SHOW_SOURCEMAP 赋真值。注意 sourcemap 会泄露未混淆源码,只建议对 test mode 开。
加一个只在某个平台生效的 Vite 插件:照 vite.config.ts:166-171(UNI_PLATFORM === 'h5' && { … })的写法用短路表达式,Vite 会忽略 falsy 项。但要注意 :133 的注释——改 pages.json 的插件必须排在 UniPages 之后、UniKuRoot 之前,插件数组的顺序是敏感的(:89 也标了 UniXXX 需要在 Uni 之前引入)。
在 CI 里跑构建:至少设 SKIP_OPEN_DEVTOOLS=true(否则 build:mp* 会尝试拉起微信开发者工具),并预期 build:h5* 会生成 node_modules/.cache/visualizer/stats.html 且尝试打开浏览器(vite.config.ts:173-180 的 open: true 没有环境变量开关,要在 CI 里彻底关掉只能改代码或让 mode 不等于 production)。
改产物里的品牌名 / 标题:只需改 env/.env:1 的 VITE_APP_TITLE,它同时喂给 manifest.config.ts:25(App 名)与 index.html:17 的 %VITE_APP_TITLE%(H5 页面标题)。
测试
本仓库没有单元测试,构建相关的可执行校验只有三条:
| 命令 | 校验什么 | 能不能挡住构建问题 |
|---|---|---|
pnpm type-check(vue-tsc --noEmit) | 全仓 TS / Vue 类型 | 能挡住类型错误,挡不住 env 配置错误 |
pnpm lint | ESLint(带 cache) | 只挡风格与被打开的规则 |
pnpm check:color | scripts/check-hardcoded-color.mjs,硬编码色守卫 | 只挡品牌色 |
env 与产物的一致性没有任何自动校验,本页用的手法可以直接复用:
# 逐 mode 打印实际生效的变量(用 Vite 自己的 loadEnv,与构建同源)
node -e "const {loadEnv}=require('vite'),path=require('path');
for (const m of ['development','test','production']) {
const e=loadEnv(m, path.resolve(process.cwd(),'env'));
console.log(m, JSON.stringify({
SERVER:e.VITE_SERVER_BASEURL, TITLE:e.VITE_APP_TITLE,
DROP:e.VITE_DELETE_CONSOLE, USER:e.VITE_APP_DEFAULT_LOGIN_USERNAME }))
}"
# 在产物里反查某个字符串是否被内联进去了
grep -rl "要查的值" dist/build/h5/assets/第二条是发现「默认口令进了生产包」的办法,也适合用来确认任何 VITE_ 值有没有意外落进产物。
坑与注意
dist/是 gitignore 的,仓库里那份是 2026-08-30 的过期产物,<title>还是改名前的旧品牌。别拿它部署,也别拿它的体积当基线。env/.env里的任何VITE_值都会进生产包,运行期开关摘不掉。口令、密钥、内网地址一律放.env.development/.env.test。mode不是development就没有 eruda、也不压缩以外的行为差异:dev:test/dev:prod名字里带dev,但 mode 是test/production,产物是压缩过、无 eruda 的。VITE_SHOW_SOURCEMAP改了没用,唯一的读取点被注释掉了。VITE_FALLBACK_LOCALE从未定义,manifest.config.ts:31那行等于空写。manifest.config.ts自己解析process.argv推 mode,跟 Vite 的 mode 是两条独立链路。如果将来加了新的 mode 名(比如--mode staging),要确认getMode()也能拿到,否则src/manifest.json会用错 env。versionName/versionCode硬编码在manifest.config.ts:28-29,App 端「关于」里显示的版本永远是 1.0.0;versionCode不递增会导致 Android 无法覆盖安装。- 小程序上传的默认版本号取自
pkg['yudao-version'],不是pkg.version,见上文 warning。 build:h5*会自动弹浏览器(visualizer),build:mp*会尝试拉起微信开发者工具;CI 里后者用SKIP_OPEN_DEVTOOLS=true跳过,前者没有开关。- 改
pages.config.ts/manifest.config.ts/env/*必须重启 dev server:它们生成的是src/pages.json与src/manifest.json,HMR 覆盖不到。vite.config.ts本身有ViteRestart(:161-164)会自动重启,其余不会。 base与h5.router.base必须同步改:两者都来自VITE_APP_PUBLIC_BASE,但一个进 Vite 的资源前缀、一个进 manifest 的路由 base,只改一边会得到「资源 404」或「路由 404」两种不同的坏法。server.proxy默认关闭(VITE_APP_PROXY_ENABLE = false),三端都是直连后端。把它打开只对 H5 dev server 生效(vite.config.ts:230的注释),小程序与 App 端根本没有 devServer。
→ 本节是贴着上下文的提醒;按症状查的移动端全域清单见移动端坑位,跨端清单见坑位索引。
未证实
- App 打包全链路:HBuilderX 云打包 / 本地离线打包、Android 签名、iOS 证书与描述文件、
nativeplugins/复制后的实际生效情况,本机都没有跑过。VITE_COPY_NATIVE_RES_ENABLE一直是false,copy-native-resources.ts的行为只有源码依据(不过它的目标目录推导与open-dev-tools.js:26同口径,后者是被真实构建验证过的)。 miniprogram-ci真实上传:只跑过阅读源码与参数校验路径,没有真的上传过(缺自己的 appid 与私钥)。robot1–30 的约束、setting.minifyWXML等选项的实际效果都取自官方文档。manifest.config.ts的getMode()在各种 argv 下的返回值:args[0] === 'build' ? 'production' : 'development'这一支依赖 uni CLI 实际传进来的 argv 形状,没有逐条脚本验证过。已验证的是结果:当前src/manifest.json的name是启凌智搭,说明loadEnv至少读到了env/.env。- 非微信的其他小程序平台(支付宝 / 百度 / 字节 / QQ / 快手 / 京东 / 小红书 / 快应用):
package.json里有全套dev:mp-*/build:mp-*,manifest.config.ts只给支付宝(:144-159)写了带styleIsolation/compileOptions的完整配置段,百度(:160-162)与字节(:163-165)各只有一行usingComponents: true,其余平台连配置段都没有、只有 uni-app 默认值。没有一个被构建验证过。 UniOptimization关闭时的分包行为:vite.config.ts:126-127的注释说「仅微信小程序开启,避免支付宝小程序运行报错」,这个「报错」没有复现过。
相关页面
- 移动端环境准备与启动:装依赖、三条 dev 路径、怎么确认真的跑通了;
- 工程结构与约定式路由:
src/职责、pages.json的生成机制、18 个插件的顺序; - 移动端总览:哪些分包不可达(本页体积分析的前提);
- 请求封装与登录态:
VITE_SERVER_BASEURL、VITE_AUTH_MODE、加解密开关在代码里怎么被消费; - 移动端页面开发范式:写页面时的组件与样式约定;
- 移动端坑位:按症状查;
- 仓库结构与改名约定:三仓的上游与改名口径;
- 构建与部署:后端与 Web 端的构建部署,可与本页对照三端差异。
