Files
Claude-Code-Workflow/ccw/DUAL_FRONTEND_MIGRATION_PLAN.md
catlog22 35f9116cce feat: add support for dual frontend (JS and React) in the CCW application
- Updated CLI to include `--frontend` option for selecting frontend type (js, react, both).
- Modified serve command to start React frontend when specified.
- Implemented React frontend startup and shutdown logic.
- Enhanced server routing to handle requests for both JS and React frontends.
- Added workspace selector component with i18n support.
- Updated tests to reflect changes in header and A2UI components.
- Introduced new Radix UI components for improved UI consistency.
- Refactored A2UIButton and A2UIDateTimeInput components for better code clarity.
- Created migration plan for gradual transition from JS to React frontend.
2026-01-31 16:46:45 +08:00

7.5 KiB
Raw Blame History

CCW 双前端并存迁移方案

目标

  • 通过 ccw view 命令同时支持 JS 前端(旧版)和 React 前端(新版)
  • 实现渐进式迁移,逐步将功能迁移到 React
  • 用户可自由切换两个前端

架构设计

┌─────────────────┐     ┌──────────────────┐
│   ccw view      │────▶│   Node Server    │
│  (port 3456)    │     │   (3456)         │
└─────────────────┘     └────────┬─────────┘
                                   │
          ┌────────────────────────┼────────────────────────┐
          │                        │                        │
          ▼                        ▼                        ▼
┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│   JS Frontend   │    │  React Frontend │    │   /api/*        │
│   (/)           │    │   (/react/*)    │    │   REST API      │
│   dashboard-js  │    │   Vite dev/prod │    │                 │
└─────────────────┘    └─────────────────┘    └─────────────────┘

实现方案

Phase 1: 基础架构改造

1.1 修改 ccw/src/commands/serve.ts

添加 --frontend 参数支持:

interface ServeOptions {
  port?: number;
  path?: string;
  host?: string;
  browser?: boolean;
  frontend?: 'js' | 'react' | 'both';  // 新增
}

// 在 serveCommand 中处理
export async function serveCommand(options: ServeOptions): Promise<void> {
  const frontend = options.frontend || 'js';  // 默认 JS 前端
  
  if (frontend === 'react' || frontend === 'both') {
    // 启动 React 前端服务
    await startReactFrontend(port + 1);  // React 在 port+1
  }
  
  // 启动主服务器
  const server = await startServer({ 
    port, 
    host, 
    initialPath,
    frontend  // 传递给 server
  });
}

1.2 修改 ccw/src/core/server.ts

添加 React 前端路由支持:

// 在路由处理中添加
if (pathname === '/react' || pathname.startsWith('/react/')) {
  // 代理到 React 前端
  const reactUrl = `http://localhost:${options.reactPort || port + 1}${pathname.replace('/react', '')}`;
  // 使用 http-proxy 或 fetch 代理请求
  proxyToReact(req, res, reactUrl);
  return;
}

// 根路径根据配置决定默认前端
if (pathname === '/' || pathname === '/index.html') {
  if (options.frontend === 'react') {
    res.writeHead(302, { Location: '/react' });
    res.end();
    return;
  }
  // 默认 JS 前端
  const html = generateServerDashboard(initialPath);
  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
  res.end(html);
  return;
}

1.3 创建 ccw/src/utils/react-frontend.ts

import { spawn, type ChildProcess } from 'child_process';
import { join } from 'path';
import chalk from 'chalk';

let reactProcess: ChildProcess | null = null;

export async function startReactFrontend(port: number): Promise<void> {
  const frontendDir = join(process.cwd(), 'frontend');
  
  console.log(chalk.cyan(`  Starting React frontend on port ${port}...`));
  
  reactProcess = spawn('npm', ['run', 'dev', '--', '--port', port.toString()], {
    cwd: frontendDir,
    stdio: 'pipe',
    shell: true
  });
  
  // 等待服务启动
  return new Promise((resolve, reject) => {
    let output = '';
    
    const timeout = setTimeout(() => {
      reject(new Error('React frontend startup timeout'));
    }, 30000);
    
    reactProcess?.stdout?.on('data', (data) => {
      output += data.toString();
      if (output.includes('Local:') || output.includes('ready')) {
        clearTimeout(timeout);
        console.log(chalk.green(`  React frontend ready at http://localhost:${port}`));
        resolve();
      }
    });
    
    reactProcess?.stderr?.on('data', (data) => {
      console.error(chalk.yellow(`  React: ${data.toString().trim()}`));
    });
    
    reactProcess?.on('error', (err) => {
      clearTimeout(timeout);
      reject(err);
    });
  });
}

export function stopReactFrontend(): void {
  if (reactProcess) {
    reactProcess.kill('SIGTERM');
    reactProcess = null;
  }
}

Phase 2: React 前端适配

2.1 修改 ccw/frontend/vite.config.ts

添加基础路径配置:

export default defineConfig({
  plugins: [react()],
  base: '/react/',  // 添加基础路径
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
    },
  },
  server: {
    port: 5173,
    proxy: {
      '/api': {
        target: 'http://localhost:3456',
        changeOrigin: true,
      },
      '/ws': {
        target: 'ws://localhost:3456',
        ws: true,
      },
    },
  },
  // ...
})

2.2 创建前端切换组件

在 JS 前端添加切换按钮(ccw/src/templates/dashboard-js/components/react-switch.js

// 在导航栏添加切换按钮
function addReactSwitchButton() {
  const nav = document.querySelector('.navbar');
  if (!nav) return;
  
  const switchBtn = document.createElement('button');
  switchBtn.className = 'btn btn-sm btn-outline-primary ml-2';
  switchBtn.innerHTML = '<span class="icon">⚛️</span> React 版本';
  switchBtn.title = '切换到 React 版本';
  switchBtn.onclick = () => {
    window.location.href = '/react';
  };
  
  nav.appendChild(switchBtn);
}

// 初始化
document.addEventListener('DOMContentLoaded', addReactSwitchButton);

Phase 3: 命令行接口

3.1 修改 ccw/src/cli.ts

添加 --frontend 选项:

// View command
program
  .command('view')
  .description('Open workflow dashboard server with live path switching')
  .option('-p, --path <path>', 'Path to project directory', '.')
  .option('--port <port>', 'Server port', '3456')
  .option('--host <host>', 'Server host to bind', '127.0.0.1')
  .option('--no-browser', 'Start server without opening browser')
  .option('--frontend <type>', 'Frontend type: js, react, both', 'js')  // 新增
  .action(viewCommand);

使用方式

1. 默认 JS 前端(向后兼容)

ccw view
# 或明确指定
ccw view --frontend js

2. React 前端

ccw view --frontend react
# React 前端将在 http://localhost:3456/react 访问

3. 同时启动两个前端(开发调试)

ccw view --frontend both
# JS: http://localhost:3456
# React: http://localhost:3456/react (开发模式) 或 5173

迁移路线图

Phase 1: 基础架构 (1-2 周)
  ├── 添加 --frontend 参数支持
  ├── 实现 React 前端代理
  └── 基础切换功能

Phase 2: 功能迁移 (4-8 周)
  ├── 逐个迁移功能模块到 React
  ├── 保持 JS 前端稳定
  └── 添加功能开关

Phase 3: 默认切换 (2 周)
  ├── React 成为默认前端
  ├── JS 前端进入维护模式
  └── 发布迁移公告

Phase 4: 完全迁移 (可选)
  ├── 移除 JS 前端
  └── React 成为唯一前端

这个方案的优点:

  1. 向后兼容:默认行为不变,现有用户无感知
  2. 渐进迁移:可以逐个功能迁移到 React
  3. 灵活切换:用户和开发者可以随时切换前端
  4. 并行开发:两个前端可以同时开发调试