Files
MrleeAdmin/frontend/scanapp/uni_modules/uview-pro/plugins/root/README.md
T

162 lines
5.4 KiB
Markdown
Raw Normal View History

2026-08-31 11:33:59 +08:00
# vite-plugin-uni-root
UniApp 虚拟根组件 Vite 插件,零额外依赖,纯字符串操作实现。
## 解决的问题
UniApp 不支持原生的根组件包裹机制,无法在全局统一注入 `u-config-provider`、`u-toast`、`u-modal` 等需要在每个页面根部存在的组件。传统方案需要在每个页面手动添加,维护成本高。
本插件通过 Vite 构建时注入,自动为每个页面包裹 `<global-root-view>` 根组件,无需修改任何页面代码本身。
## 技术方案
### 架构
```
App.root.vue(源码) → 在 main.ts 中导入并注册为全局组件 global-root-view
→ 页面模板被包裹 <global-root-view> 页面内容 </global-root-view>
→ 渲染时页面内容填入 <slot />
```
### 工作流程
```
┌─────────────┐ ┌──────────────┐ ┌─────────────────┐
│ main.ts │ → │ 注入 import │ → │ 全局注册组件 │
│ │ │ + 注册代码 │ │ global-root-view │
└─────────────┘ └──────────────┘ └─────────────────┘
│
┌─────────────┐ ┌──────────────┐ │
│ page.vue │ → │ 包裹模板 │ │
│ │ │ <global-...> │ ←─────────┘
└─────────────┘ └──────────────┘
│
┌─────────────┐ │
│ App.root.vue│ → <slot /> 接收页面内容
└─────────────┘
```
### 三步实现
1. **main.ts 注入**(`root.ts -> registerRootApp`)
- 在 main.ts 中注入 `import GlobalRootView from "./App.root.vue"`
- 注入 `app.component("global-root-view", GlobalRootView)` 注册为全局组件
- 注:`GlobalRootView` 仅为局部变量名,实际组件就是 `App.root.vue`
2. **页面模板包裹**(`page.ts -> transformPage`)
- 解析 SFC,定位 `<template>` 标签的起止位置
- 提取 PageMeta 节点(如有则放到包裹层外部)
- 将模板内容包裹在 `<global-root-view>...</global-root-view>` 中
3. **App.root.vue 加载**(`index.ts -> load`)
- 拦截 `App.root.vue` 的模块请求,从文件读取并返回
- 模板中的 `<slot />` 接收并渲染页面内容
### 跨端兼容
插件是纯 Vite 构建时插件,在 UniApp 编译之前完成源代码转换,不依赖任何平台特定 API,所有 UniApp 支持的平台均可正常工作:
| 平台 | 支持 |
|------|------|
| H5 | ✅ |
| 微信小程序 | ✅ |
| 支付宝小程序 | ✅ |
| 头条/抖音小程序 | ✅ |
| Android App | ✅ |
| iOS App | ✅ |
| 鸿蒙 App | ✅ |
## 使用方式
### 1. 创建 App.root.vue
在 `src/` 目录下创建 `App.root.vue` 文件:
```vue
<script setup lang="ts">
import { onLoad } from '@dcloudio/uni-app';
import { useTheme, useLocale } from 'uview-pro';
const { darkMode, themes, currentTheme } = useTheme();
const { currentLocale } = useLocale();
onLoad(() => {
console.log('darkMode->', darkMode.value);
console.log('theme->', currentTheme.value?.name);
console.log('locale->', currentLocale.value?.name);
});
</script>
<template>
<u-config-provider
:dark-mode="darkMode"
:themes="themes"
:current-theme="currentTheme?.name"
:current-locale="currentLocale?.name"
>
<slot />
<u-toast global></u-toast>
<u-modal global></u-modal>
</u-config-provider>
</template>
```
关键点:`<slot />` 是页面内容的渲染位置,必须保留。
### 2. 配置 vite.config.ts
```ts
// npm 安装
import { UniRoot } from 'uview-pro/plugins';
// uni_modules 安装
import { UniRoot } from './src/uni_modules/uview-pro/plugins';
export default defineConfig({
plugins: [
UniRoot(), // 放在其他插件之前
// ...其他插件
],
});
```
### 3. 运行
```bash
npm run dev
```
不需要修改任何页面代码,所有页面自动被 `<global-root-view>` 包裹。
## 文件结构
```
src/uni_modules/uview-pro/plugins/root/
├── index.ts # 插件入口,Vite 插件生命周期
├── root.ts # main.ts 注入 + App.root.vue 处理
├── page.ts # 页面模板包裹转换
└── utils.ts # 工具函数(SFC 解析、pages.json 加载、AST 查找等)
```
## 配置项
```ts
UniRoot({
rootFileName: 'App.root', // 根组件文件名(不含扩展名),默认 App.root
})
```
## HMR 支持
修改 `pages.json` 后自动重载页面列表,无需手动重启开发服务器。
## 注意事项
- Vue 版本需 >= 3.2.13(使用 `vue/compiler-sfc` 的 `parse` API,HBuilderX 环境下自动降级为正则匹配)
- 页面模板中如有 `<page-meta>` 组件,会被提取到包裹层外部,确保微信小程序兼容
- 页面模板中如有嵌套 `<template #slot>` 具名插槽,不影响根模板的正确识别
## 致谢
本插件的实现参考了 [@uni-ku/root](https://github.com/uni-ku/root)(MIT License,作者 skiyee),核心思路受其启发并进行了重新实现。