# 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 19,webui 的 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` 处理平台特定行为