Monorepo + uni-app 开发中监听 / 热更新机制差异与解决方案
1️ 问题背景
在完成 PC 项目 → Monorepo → uni-app 接入 后,项目结构大致如下:
root/
├─ apps/
│ ├─ web/ # PC Web(Vue3 + Vite)
│ └─ uni/ # uni-app(H5 / 小程序)
├─ packages/
│ └─ core/ # 共享业务逻辑
└─ pnpm-workspace.yaml共享逻辑通过 workspace 包的形式被两个应用引用:
import { xxx } from "@zkzq/core";2️ 问题现象描述(非常关键)
在开发过程中,出现了明显不一致的行为:
✅ Web 项目
- 修改
packages/core中的逻辑 - 页面 立即热更新 / 刷新
- 无需额外 Vite 配置
❌ uni-app(H5 & 小程序)
- 修改
packages/core中的逻辑 - 页面 无任何变化
- 控制台无报错
- 手动刷新 / 保存无效
3️ 第一反应:是不是 Vite 配置问题?
最初的直觉判断是:
- alias 没配?
- HMR 没打开?
- Vite server 配置不完整?
但经过验证发现:
- Web 项目 零额外配置即可热更新
- uni 加了 HMR 相关配置,才能解决问题
👉 这就奇怪了 web 不加可以,uni 加配置才可以”,这就存在了“底层机制差异”
4️ 关键验证过程(结论来自实践)
为了搞清楚问题本质,对 uni 的行为进行了验证:
4.1 uni H5 的监听行为
验证发现:
apps/uni/index.html修改 → 页面刷新 ✅apps/uni/src/**修改 → 页面刷新 ✅packages/core/**修改 → 页面无变化 ❌
说明:
uni H5 并非“什么都不监听”, 但 workspace 包并未被纳入其依赖编译体系
4.2 uni 小程序的监听行为(更严格)
运行到小程序后发现:
- **只监听
apps/uni/src/**** - workspace / node_modules 完全不参与
- 即使文件变化被操作系统感知,也不会触发重新编译
5️ 本质原因:三套完全不同的监听 / 编译模型
这是整件事的核心结论。
5.1 Web(原生 Vite)的行为
Web 项目使用的是 纯 Vite Dev Server:
- 支持 symlink
- 支持 workspace link
- 会追踪真实文件路径
- workspace 包中的源码会被当作「类源码」
👉 结果:
workspace 包默认参与 HMR 不需要任何额外配置
5.2 uni-app H5 的行为(最容易误判)
uni H5 表面上使用 Vite,但实际上:
- Vite 只是外壳
- 核心编译逻辑来自 uni 编译器
uni H5 的特点:
- 会监听入口文件(如
index.html) - 会监听
src/** - 但不会将 workspace 包视为“源码模块”
因此:
文件变化 ≠ 触发编译 被监听 ≠ 被纳入依赖图
5.3 uni-app 小程序的行为(最严格)
uni 小程序的编译链路是:
源码 → uni 编译 → 中间产物 → 同步到小程序 IDE为了稳定性:
- 监听范围被强制限制
- 仅允许
apps/uni/src/** - workspace / node_modules 一律忽略
👉 这是设计选择,不是 bug
6️ 一个非常重要的工程级结论
uni 是否响应文件变化, 取决于该文件是否被当作“源码模块”加入编译依赖图
而不是:
- 操作系统是否监听
- Vite 是否 watch 到
- 文件是否真的发生变化
7️ 为什么配置 resolve.alias 能解决问题?
在 uni 项目 vite.config.ts 中加入配置:
resolve: {
alias: {
"@zkzq/core": path.resolve(__dirname, "../../packages/core/src")
}
}完整 vite.config.ts 配置:
import { defineConfig } from "vite";
import uni from "@dcloudio/vite-plugin-uni";
import path from "path";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [uni()],
resolve: {
alias: {
"@zkzq/core": path.resolve(__dirname, "../../packages/core/src/"),
},
},
});这一步的本质是:
绕开 workspace 包机制, 强制把共享逻辑@zkzq/core 包“伪装成 uni 项目自己的源码”
结果:
uni 编译器将其纳入依赖图
包文件变更会触发:
- H5 → 页面刷新
- 小程序 → 重新编译
8️ 不同端在“正确配置后”的真实表现
这是非常现实的一点,需要提前接受。
| 运行端 | 实际效果 |
|---|---|
| Web | 模块级 HMR |
| uni H5 | 整页刷新 |
| uni 小程序 | 全量重新编译 |
👉 uni 并不存在真正意义上的模块级 HMR
9️ 推荐的工程实践(非常重要)
9.1 开发态(Dev)
| 项目 | 做法 |
|---|---|
| Web | 直接使用 workspace 包 |
| uni | alias 指向 packages 源码 |
9.2 构建态(Build)
- 移除 alias
- 统一通过 workspace 包引用
- 保证依赖关系规范、可发布
可以通过环境变量控制:
const isDev = process.env.NODE_ENV === "development";
resolve: {
alias: isDev
? { "@zkzq/core": path.resolve(__dirname, "../../packages/core/src") }
: {};
}疑问 1:为什么 web 子项目里面没有配不也是实时访问 打包的时候不也是访问最新的? 疑问 2:使用命令在根目录打包 uni 的时候是不是就把最新包的状态打包进去了呢?
相关文档:
10 常见误区总结(避坑)
❌ 误区 1:以为是 Vite 的问题
👉 实际是 uni 编译器行为差异
❌ 误区 2:期待 uni 像 Web 一样 HMR
👉 uni 的热更新本质是“重编译”
❌ 误区 3:在 packages 中写 uni / DOM 代码
👉 会放大监听 & 编译问题