SSF0SSF0
首页
前端
  • Node
  • Go
  • Python
  • MySql
  • Bash
  • Git
  • Docker
  • VuePress
  • CI/CD
  • 服务器
  • 网站
  • 学习资料
  • 软件
  • 开源项目
Timeline
Github
标签
分类
首页
前端
  • Node
  • Go
  • Python
  • MySql
  • Bash
  • Git
  • Docker
  • VuePress
  • CI/CD
  • 服务器
  • 网站
  • 学习资料
  • 软件
  • 开源项目
Timeline
Github
标签
分类
  • HTML

    • html1
    • html2
  • CSS

    • Flex 布局常见问题与解决方案
  • JavaScript

    • 数据类型及引用问题
    • 处理 Blob 类型文件下载问题总结
    • localStorage 与 sessionStorage 区别
    • JavaScript 中的 script 标签问题详解
    • JavaScript 中的 this 指向问题详解
    • SessionStorage踩坑记录
    • 动态加载 JS 脚本方法对比
    • 浏览器页面关闭场景下的数据上报
    • HTTP流式接口详解与封装实践
    • JavaScript 中 this 绑定与解构:为什么 Pinia Store 可以解构,但 Class 实例方法不行?
  • es6

    • Promise
    • es6 模块导出方式全解析
    • reduce
    • forEach 中使用 async/await 的异步陷阱
  • Vue2

    • created VS mounted 发起异步请求
    • vue2-2
  • Vue3

    • Vite + Vue3 修改 element-plus 源码
    • Vue v-if 与 v-show
    • Vue3 ref 获取组件
    • Vue3 路由传参
    • 父子组件与组件里 Hooks 加载顺序
    • 第三方组件传入参数TS提示
    • Vue3 Props 在 Hooks 中的响应性处理
    • Vue Router 的两种历史模式及部署配置详解
    • 在Vue 3项目中顺利集成Tailwind CSS的完整指南
    • Vue Scoped CSS 深度解析
    • Vue 3 Teleport 组件完全指南:从原理到实战
    • Vant List 组件中双向绑定 loading 的注意事项
    • Vue 3 + Vite 中的路径别名与静态资源处理深度解析
    • 获取图片高度
    • 深入理解 Pinia:storeToRefs vs toRefs - 避开 TypeScript 类型推断的坑
  • webSocket

    • WebSocket 鉴权入门(实战版):握手票据 / 首帧鉴权 + 连接级 cid
  • Electron

    • 快速构建 electron + vue3 项目
  • TS

    • TS 泛型
    • 记录模板使用断言的问题
    • type 与 interface
  • WebPack

    • Webpack 介绍
  • Vite

    • Vite CLI 常见命令
    • vite 与 webpack 比较
    • Vite base 配置与 public 静态资源路径问题
  • 客户端数据库

    • IndexedDB 索引陷阱:字段重命名后索引返回旧数据
    • IndexedDB 事务陷阱与最佳实践:TransactionInactiveError 完全解析
  • UniApp

    • uni-app + Monorepo + 微信小程序热更新失效问题排查与结论总结
    • Monorepo + uni-app 开发中监听 / 热更新机制差异与解决方案
    • uni-app 运行与编译原理梳理
    • 为什么 uni-app 在 Monorepo 中必须做「环境区分」,而 Web 项目却不用?
  • 项目工程

    • 前端代码风格
    • Vue3 项目规范
    • npm 镜像问题
    • 包管理工具
    • 使用 engines 限制开发环境依赖
    • 打包与shell交互指定模式
    • Monorepo示例
    • PC 项目兼容 uni-app:基于 pnpm Monorepo 的工程化重构实践
    • Vue3 + Vite + pnpm Monorepo 迁移问题指南
    • 前端工程化:深入理解 npm 依赖管理机制
    • pnpm 修改依赖源码打包报错
    • 代码片段
  • 移动端

    • 浏览器跳转外部应用时请求状态码问题
  • 记录一下小程序
  • 控制浏览器正确保存网站账号密码的技巧
  • 信息泄露
  • 使用Gulp实现前端项目哈希化压缩打包
  • 前端缓存策略与文件哈希化的关联解析
  • 浏览器刷新方式与 Vue 生命周期、缓存行为解析
  • 浏览器消息铃声实现记录

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 包
unialias 指向 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 的时候是不是就把最新包的状态打包进去了呢?

解答文档

相关文档:

项目工程架构设计

深入理解 npm 依赖管理机制


10 常见误区总结(避坑)

❌ 误区 1:以为是 Vite 的问题

👉 实际是 uni 编译器行为差异

❌ 误区 2:期待 uni 像 Web 一样 HMR

👉 uni 的热更新本质是“重编译”

❌ 误区 3:在 packages 中写 uni / DOM 代码

👉 会放大监听 & 编译问题

最后更新时间:
贡献者: 何风顺
上一页
uni-app + Monorepo + 微信小程序热更新失效问题排查与结论总结
下一页
uni-app 运行与编译原理梳理