mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-04-28 11:41:04 +00:00
17 KiB
17 KiB
WebUI 组件库抽离计划
一、背景与目标
1.1 背景
packages/vscode-ide-companion 是一个 VSCode 插件,其核心内容是一个 WebView 页面,大量 UI 部分由 React 组件提供。随着产品线扩展,越来越多的场景需要构建包含 Web UI 的产品:
- Chrome 浏览器扩展 - 侧边栏聊天界面
- Web 端聊天页面 - 纯 Web 应用
- 对话分享页面 - 将对话渲染为静态 HTML
对于优秀的软件工程架构,我们需要让 UI 做到统一且可复用。
1.2 目标
- 将
vscode-ide-companion/src/webview/中的组件抽离到独立的@qwen-code/webui包 - 建立分层架构:纯 UI 组件 + 业务 UI 组件
- 使用 Vite + Storybook 进行开发和组件展示
- 通过 Platform Context 抽象平台能力,实现跨平台复用
- 提供 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 关键依赖分析
平台耦合点:
useVSCodehook - 调用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 设计
// @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)
// 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 预设方案
// @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 迁移原则
- 双向兼容:迁移期间,vscode-ide-companion 可以同时从 webui 和本地导入
- 逐个替换:每迁移一个组件,在 VSCode 插件中替换导入路径并验证
- 不破坏现有功能:确保每次迁移后插件可正常构建和运行
6.2 迁移流程
开发者 ──► @qwen-code/webui ──► vscode-ide-companion
│ │ │
│ 1. 复制组件到 webui │
│ 2. 添加 Story 验证 │
│ 3. 从 index.ts 导出 │
│ │ │
│ └──────────────────────┤
│ │
│ 4. 更新 import 路径
│ 5. 删除原组件文件
│ 6. 构建测试验证
6.3 示例:迁移 Icons
// 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 常见坑点
-
Tailwind 类名 Tree Shaking
- 问题:组件库打包后 Tailwind 类名可能被移除
- 解决:消费方的
content配置需要包含node_modules/@qwen-code/webui
-
CSS 变量作用域
- 问题:
var(--app-primary)等变量需要在消费方定义 - 解决:提供默认 CSS 变量文件,或在 Tailwind 预设中定义 fallback
- 问题:
-
React 版本兼容
- 当前 vscode-ide-companion 使用 React 19,webui 的 peerDependencies 是 React 18
- 需要更新 peerDependencies 为
"react": "^18.0.0 || ^19.0.0"
-
ESM/CJS 兼容
- VSCode 扩展可能需要 CJS 格式
- Vite 需要配置双格式输出
8.2 业界参考
- Radix UI: 纯 Headless 组件,样式完全由消费方控制
- shadcn/ui: 复制组件到项目中,而非作为依赖引入
- Ant Design: 完整的组件库,通过 ConfigProvider 进行定制
8.3 验收标准
每个迁移任务完成后需要:
- 组件有对应的 Storybook Story
- vscode-ide-companion 中的导入已更新
- 插件可正常构建 (
npm run build:vscode) - 插件功能正常(手动测试或已有测试通过)
九、预估时间
| 阶段 | 任务数 | 预估人天 | 可并行 |
|---|---|---|---|
| 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 调试命令
# 启动 Storybook 进行组件开发
cd packages/webui
npm run storybook
# 监听模式进行库开发
npm run dev
# 构建生产版本
npm run build
# 类型检查
npm run typecheck
10.3 核心原则
- 单一数据源: 所有 UI 组件都在
@qwen-code/webui中 - Storybook 优先: 在集成前先在 Storybook 中调试和验证组件
- 消费方不修改 UI 代码:
vscode-ide-companion只导入和使用组件 - 平台抽象: 使用
PlatformContext处理平台特定行为