qwen-code/packages/webui/docs/WEBUI_MIGRATION_PLAN_ZH.md

428 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# WebUI 组件库抽离计划
## 一、背景与目标
### 1.1 背景
`packages/vscode-ide-companion` 是一个 VSCode 插件,其核心内容是一个 WebView 页面,大量 UI 部分由 React 组件提供。随着产品线扩展,越来越多的场景需要构建包含 Web UI 的产品:
- **Chrome 浏览器扩展** - 侧边栏聊天界面
- **Web 端聊天页面** - 纯 Web 应用
- **对话分享页面** - 将对话渲染为静态 HTML
对于优秀的软件工程架构,我们需要让 UI 做到统一且可复用。
### 1.2 目标
1.`vscode-ide-companion/src/webview/` 中的组件抽离到独立的 `@qwen-code/webui`
2. 建立分层架构:纯 UI 组件 + 业务 UI 组件
3. 使用 Vite + Storybook 进行开发和组件展示
4. 通过 Platform Context 抽象平台能力,实现跨平台复用
5. 提供 Tailwind CSS 预设,保证多产品 UI 一致性
---
## 二、现状分析
### 2.1 当前代码结构
`packages/vscode-ide-companion/src/webview/` 包含 77 个文件:
```
webview/
├── App.tsx # 主入口
├── components/
│ ├── icons/ # 8 个图标组件
│ ├── layout/ # 8 个布局组件
│ │ ├── ChatHeader.tsx
│ │ ├── InputForm.tsx
│ │ ├── SessionSelector.tsx
│ │ ├── EmptyState.tsx
│ │ ├── Onboarding.tsx
│ │ └── ...
│ ├── messages/ # 消息展示组件
│ │ ├── UserMessage.tsx
│ │ ├── Assistant/
│ │ ├── MarkdownRenderer/
│ │ ├── ThinkingMessage.tsx
│ │ ├── Waiting/
│ │ └── toolcalls/ # 16 个工具调用组件
│ ├── PermissionDrawer/ # 权限请求抽屉
│ └── Tooltip.tsx
├── hooks/ # 自定义 hooks
├── handlers/ # 消息处理器
├── styles/ # CSS 样式
└── utils/ # 工具函数
```
### 2.2 关键依赖分析
**平台耦合点:**
- `useVSCode` hook - 调用 `acquireVsCodeApi()` 进行消息通信
- `handlers/` - 处理 VSCode 消息协议
- 部分类型定义来自 `../types/` 目录
```
┌─────────────────────────────────────────────────────────┐
│ App.tsx (入口) │
├─────────────────────────────────────────────────────────┤
│ hooks/ │ handlers/ │ components/ │
│ ├─useVSCode ◄───┼──────────────────┼──────────────────┤
│ ├─useSession │ ├─MessageRouter │ ├─icons/ │
│ ├─useFileContext│ ├─AuthHandler │ ├─layout/ │
│ └─... │ └─... │ ├─messages/ │
│ │ │ └─PermDrawer/ │
├─────────────────────────────────────────────────────────┤
│ VSCode API (acquireVsCodeApi) │
└─────────────────────────────────────────────────────────┘
```
---
## 三、目标架构
### 3.1 分层架构设计
```
┌─────────────────────────────────────────────────────────┐
│ Layer 3: Platform Adapters │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │VSCode Adapter│ │Chrome Adapter│ │ Web Adapter │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
├─────────┼────────────────┼────────────────┼────────────┤
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Platform Context Provider │ │
│ └─────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────┤
│ Layer 2: Chat Components │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ MessageList│ │ ChatHeader │ │ InputForm │ │
│ └────────────┘ └────────────┘ └────────────┘ │
├─────────────────────────────────────────────────────────┤
│ Layer 1: Primitives (纯 UI) │
│ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │ Button │ │ Input │ │ Icons │ │Tooltip │ │
│ └────────┘ └────────┘ └────────┘ └────────┘ │
└─────────────────────────────────────────────────────────┘
```
### 3.2 Platform Context 设计
```typescript
// @qwen-code/webui/src/context/PlatformContext.ts
interface PlatformContext {
// 消息通信
postMessage: (message: unknown) => void;
onMessage: (handler: (message: unknown) => void) => () => void;
// 文件操作
openFile?: (path: string) => void;
attachFile?: () => void;
// 认证
login?: () => void;
// 平台信息
platform: 'vscode' | 'chrome' | 'web' | 'share';
}
```
---
## 四、技术方案
### 4.1 构建配置Vite Library Mode
**输出格式:**
- ESM (`dist/index.js`) - 主要格式
- CJS (`dist/index.cjs`) - 兼容性
- TypeScript 声明 (`dist/index.d.ts`)
```javascript
// vite.config.ts
export default defineConfig({
build: {
lib: {
entry: resolve(__dirname, 'src/index.ts'),
formats: ['es', 'cjs'],
fileName: (format) => `index.${format === 'es' ? 'js' : 'cjs'}`,
},
rollupOptions: {
external: ['react', 'react-dom'],
},
},
});
```
### 4.2 Tailwind 预设方案
```javascript
// @qwen-code/webui/tailwind.preset.js
module.exports = {
theme: {
extend: {
colors: {
'app-primary': 'var(--app-primary)',
'app-background': 'var(--app-primary-background)',
'app-foreground': 'var(--app-primary-foreground)',
},
},
},
};
// 消费方 tailwind.config.js
module.exports = {
presets: [require('@qwen-code/webui/tailwind.preset')],
content: [
'./src/**/*.{ts,tsx}',
'./node_modules/@qwen-code/webui/dist/**/*.js',
],
};
```
### 4.3 Storybook 配置
```
packages/webui/
├── .storybook/
│ ├── main.ts # Storybook 配置
│ ├── preview.ts # 全局装饰器
│ └── manager.ts # UI 配置
└── src/
└── stories/ # Story 文件
```
---
## 五、组件迁移分类
### 5.1 第一批:无依赖组件(可立即迁移)
| 组件 | 来源路径 | 复杂度 | 说明 |
| ------------------ | ------------------------ | ------ | -------------------- |
| Icons | `components/icons/` | 低 | 8 个图标组件,纯 SVG |
| Tooltip | `components/Tooltip.tsx` | 低 | 纯 UI |
| WaitingMessage | `messages/Waiting/` | 低 | 加载状态展示 |
| InterruptedMessage | `messages/Waiting/` | 低 | 中断状态展示 |
### 5.2 第二批:轻度依赖组件(需要抽象 props
| 组件 | 来源路径 | 依赖 | 改造方式 |
| ---------------- | ------------------------------ | ----------- | --------------- |
| UserMessage | `messages/UserMessage.tsx` | onFileClick | 通过 props 注入 |
| AssistantMessage | `messages/Assistant/` | onFileClick | 通过 props 注入 |
| ThinkingMessage | `messages/ThinkingMessage.tsx` | onFileClick | 通过 props 注入 |
| MarkdownRenderer | `messages/MarkdownRenderer/` | 无 | 直接迁移 |
| EmptyState | `layout/EmptyState.tsx` | 无 | 直接迁移 |
| ChatHeader | `layout/ChatHeader.tsx` | callbacks | 通过 props 注入 |
### 5.3 第三批:中度依赖组件(需要 Context
| 组件 | 来源路径 | 依赖 | 改造方式 |
| ---------------- | ---------------------------- | -------------- | --------------- |
| InputForm | `layout/InputForm.tsx` | 多个 callbacks | Context + Props |
| SessionSelector | `layout/SessionSelector.tsx` | session 数据 | Props 注入 |
| CompletionMenu | `layout/CompletionMenu.tsx` | items 数据 | Props 注入 |
| PermissionDrawer | `PermissionDrawer/` | 回调函数 | Context + Props |
| ToolCall 组件 | `messages/toolcalls/` | 多种工具展示 | 分模块迁移 |
### 5.4 第四批:重度依赖(保留在平台包)
| 组件/模块 | 说明 |
| ---------- | ------------------------ |
| App.tsx | 总入口,包含业务编排逻辑 |
| hooks/ | 大部分需要平台适配 |
| handlers/ | VSCode 消息处理 |
| Onboarding | 认证相关,平台特定 |
---
## 六、渐进式迁移策略
### 6.1 迁移原则
1. **双向兼容**迁移期间vscode-ide-companion 可以同时从 webui 和本地导入
2. **逐个替换**:每迁移一个组件,在 VSCode 插件中替换导入路径并验证
3. **不破坏现有功能**:确保每次迁移后插件可正常构建和运行
### 6.2 迁移流程
```
开发者 ──► @qwen-code/webui ──► vscode-ide-companion
│ │ │
│ 1. 复制组件到 webui │
│ 2. 添加 Story 验证 │
│ 3. 从 index.ts 导出 │
│ │ │
│ └──────────────────────┤
│ │
│ 4. 更新 import 路径
│ 5. 删除原组件文件
│ 6. 构建测试验证
```
### 6.3 示例:迁移 Icons
```typescript
// Before: vscode-ide-companion/src/webview/components/icons/index.ts
export { FileIcon } from './FileIcons.js';
// After: 修改导入
import { FileIcon } from '@qwen-code/webui';
// 或 import { FileIcon } from '@qwen-code/webui/icons';
```
---
## 七、任务拆分
### Phase 0: 基础设施搭建(前置任务)
- [ ] **T0-1**: Vite 构建配置
- [ ] **T0-2**: Storybook 配置
- [ ] **T0-3**: Tailwind 预设创建
- [ ] **T0-4**: Platform Context 定义
- [ ] **T0-5**: 类型定义迁移(共享 types
### Phase 1: 纯 UI 组件迁移
- [ ] **T1-1**: Icons 组件迁移8 个文件)
- [ ] **T1-2**: Tooltip 组件迁移
- [ ] **T1-3**: WaitingMessage / InterruptedMessage 迁移
- [ ] **T1-4**: 基础 Button/Input 组件完善
### Phase 2: 消息组件迁移
- [ ] **T2-1**: MarkdownRenderer 迁移
- [ ] **T2-2**: UserMessage 迁移
- [ ] **T2-3**: AssistantMessage 迁移
- [ ] **T2-4**: ThinkingMessage 迁移
### Phase 3: 布局组件迁移
- [ ] **T3-1**: ChatHeader 迁移
- [ ] **T3-2**: EmptyState 迁移
- [ ] **T3-3**: InputForm 迁移(需要 Context
- [ ] **T3-4**: SessionSelector 迁移
- [ ] **T3-5**: CompletionMenu 迁移
### Phase 4: 复杂组件迁移
- [ ] **T4-1**: PermissionDrawer 迁移
- [ ] **T4-2**: ToolCall 系列组件迁移16 个文件)
### Phase 5: 平台适配器
- [ ] **T5-1**: VSCode Adapter 实现
- [ ] **T5-2**: Chrome Extension Adapter
- [ ] **T5-3**: Web/Share Page Adapter
---
## 八、风险与注意事项
### 8.1 常见坑点
1. **Tailwind 类名 Tree Shaking**
- 问题:组件库打包后 Tailwind 类名可能被移除
- 解决:消费方的 `content` 配置需要包含 `node_modules/@qwen-code/webui`
2. **CSS 变量作用域**
- 问题:`var(--app-primary)` 等变量需要在消费方定义
- 解决:提供默认 CSS 变量文件,或在 Tailwind 预设中定义 fallback
3. **React 版本兼容**
- 当前 vscode-ide-companion 使用 React 19webui 的 peerDependencies 是 React 18
- 需要更新 peerDependencies 为 `"react": "^18.0.0 || ^19.0.0"`
4. **ESM/CJS 兼容**
- VSCode 扩展可能需要 CJS 格式
- Vite 需要配置双格式输出
### 8.2 业界参考
- **Radix UI**: 纯 Headless 组件,样式完全由消费方控制
- **shadcn/ui**: 复制组件到项目中,而非作为依赖引入
- **Ant Design**: 完整的组件库,通过 ConfigProvider 进行定制
### 8.3 验收标准
每个迁移任务完成后需要:
1. 组件有对应的 Storybook Story
2. vscode-ide-companion 中的导入已更新
3. 插件可正常构建 (`npm run build:vscode`)
4. 插件功能正常(手动测试或已有测试通过)
---
## 九、预估时间
| 阶段 | 任务数 | 预估人天 | 可并行 |
| ------- | ------ | -------- | ---------- |
| Phase 0 | 5 | 2-3 天 | 部分可并行 |
| Phase 1 | 4 | 1-2 天 | 全部可并行 |
| Phase 2 | 4 | 2-3 天 | 全部可并行 |
| Phase 3 | 5 | 3-4 天 | 部分可并行 |
| Phase 4 | 2 | 3-4 天 | 可并行 |
| Phase 5 | 3 | 2-3 天 | 可并行 |
**总计**:约 13-19 人天(单人顺序执行),如果多人并行可缩短至 1-2 周
---
## 十、开发与调试流程
### 10.1 组件开发流程
```
┌─────────────────────────────────────────────────────────────────┐
│ 开发工作流程 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. 开发/修改组件 │
│ └── 在 @qwen-code/webui/src/ 中编辑文件 │
│ │
│ 2. 使用 Storybook 调试 │
│ └── npm run storybook (端口 6006) │
│ └── 独立查看组件 │
│ └── 测试不同的 props/状态 │
│ │
│ 3. 构建组件库 │
│ └── npm run build │
│ └── 输出: dist/index.js, dist/index.cjs, dist/index.d.ts │
│ │
│ 4. 在 VSCode 插件中使用 │
│ └── import { Component } from '@qwen-code/webui' │
│ └── vscode-ide-companion 中不再修改 UI 代码 │
│ │
└─────────────────────────────────────────────────────────────────┘
```
### 10.2 调试命令
```bash
# 启动 Storybook 进行组件开发
cd packages/webui
npm run storybook
# 监听模式进行库开发
npm run dev
# 构建生产版本
npm run build
# 类型检查
npm run typecheck
```
### 10.3 核心原则
1. **单一数据源**: 所有 UI 组件都在 `@qwen-code/webui`
2. **Storybook 优先**: 在集成前先在 Storybook 中调试和验证组件
3. **消费方不修改 UI 代码**: `vscode-ide-companion` 只导入和使用组件
4. **平台抽象**: 使用 `PlatformContext` 处理平台特定行为