fe-cli-web — Web SPA Scaffolding
Handles project scaffolding for standard Web Single Page Applications.
Workflow
Step 1: Gather Options
Ask these questions. Skip questions whose answer is implied by the user's initial request.
Quick mode: If user provides all options in one message (e.g. "React + Tailwind + Zustand + i18n,叫 my-site"), skip ALL questions and proceed directly to Step 2.
- Framework: React 19 / Vue 3
- Styling/UI: Tailwind CSS / shadcn/ui + Lucide (React) / shadcn-vue + Vue Bits (Vue) / Ant Design (React) / Element Plus (Vue) / MUI (React) / Chakra UI (React) / React Bits (React) / 纯 CSS (SCSS)
- CSS Preprocessor: Sass / Less / None (default: Sass if using SCSS approach)
- State Management: Zustand / Redux Toolkit / Pinia (for Vue) / None
- Router: React Router / Vue Router / None
- Charts: Recharts (lightweight) / ECharts (heavy) / None
- i18n: react-i18next / vue-i18n / None
- Testing: Vitest / None
- Pre-commit hooks: husky + lint-staged + commitlint? (default: No)
- Project name: string (required)
Step 2: Scaffold Project
Use bun create vite (or pnpm create vite if bun unavailable) as the base:
bun create vite <project-name> --template react-ts (for React)
bun create vite <project-name> --template vue-ts (for Vue)
Then cd <project-name> and install additional dependencies based on selections.
Step 3: Install Dependencies
| Selection | Packages |
|---|---|
| Tailwind CSS | tailwindcss @tailwindcss/vite |
| shadcn/ui (React) | npx shadcn@latest init → adds components on demand. Requires Tailwind CSS. Include lucide-react |
| shadcn-vue (Vue) | npx shadcn-vue@latest init → adds components on demand. Requires Tailwind CSS. Include lucide-vue-next |
| Ant Design | antd @ant-design/icons @ant-design/v5-patch-for-react-19 |
| Element Plus | element-plus @element-plus/icons-vue |
| MUI | @mui/material @emotion/react @emotion/styled @mui/icons-material |
| Chakra UI | @chakra-ui/react @emotion/react @emotion/styled framer-motion react-icons |
| React Bits | react-bits (animated UI components) |
| Vue Bits | vue-bits (animated UI components for Vue) |
| Zustand | zustand |
| Redux Toolkit | @reduxjs/toolkit react-redux |
| React Router | react-router-dom |
| Vue Router | vue-router |
| Recharts | recharts |
| ECharts | echarts echarts-for-react (React) / vue-echarts (Vue) |
| i18n (React) | react-i18next i18next i18next-browser-languagedetector |
| i18n (Vue) | vue-i18n |
| Vitest | vitest @testing-library/react @testing-library/jest-dom jsdom (React) |
| Sass | sass (built-in Vite support) |
| Less | less |
| pre-commit | husky lint-staged @commitlint/cli @commitlint/config-conventional |
Step 4: Configure Vite
Generate vite.config.ts based on selections. Use the template from ../references/shared-config.md.
Add Tailwind plugin if selected: import tailwindcss from '@tailwindcss/vite' → plugins: [react(), tailwindcss()]
Step 5: Generate Type-Specific Files
React projects:
src/
├── components/
│ ├── ErrorBoundary.tsx # React error boundary with fallback UI
│ ├── PageLoading.tsx # Full-page loading spinner
│ ├── EmptyState.tsx # Empty state placeholder
│ └── ErrorState.tsx # Error state with retry button
├── pages/
│ ├── Home/
│ │ └── index.tsx # Home page
│ └── NotFound.tsx # 404 page
├── hooks/ # Common hooks (generated by shared layer)
├── layouts/ # Layout components (generated by shared infrastructure)
├── store/ # State management (generated by shared infrastructure)
├── theme/ # Theme system (generated by shared infrastructure)
├── config/ # App constants + routes (generated by shared infrastructure)
├── locales/ # i18n (generated by shared infrastructure, if selected)
├── App.tsx # App with router setup + layout
└── main.tsx # Entry point
App.tsx template:
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import Home from '@/pages/Home';
import NotFound from '@/pages/NotFound';
export default function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
);
}
components/ErrorBoundary.tsx:
import { Component, type ReactNode } from 'react';
import ErrorState from './ErrorState';
interface Props { children: ReactNode; }
interface State { hasError: boolean; error?: Error; }
export default class ErrorBoundary extends Component<Props, State> {
state: State = { hasError: false };
static getDerivedStateFromError(error: Error) { return { hasError: true, error }; }
render() {
if (this.state.hasError) return <ErrorState error={this.state.error} />;
return this.props.children;
}
}
components/PageLoading.tsx:
Note: Inline styles below are for quick scaffolding. Production environments should replace them with SCSS modules.
// Pure CSS spinner — works regardless of UI library choice
export default function PageLoading() {
return (
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh' }}>
<div style={{
width: 32, height: 32, border: '3px solid #e8e8e8', borderTopColor: '#1677ff',
borderRadius: '50%', animation: 'spin 0.8s linear infinite',
}} />
<style>{`@keyframes spin { to { transform: rotate(360deg) } }`}</style>
</div>
);
}
components/EmptyState.tsx:
Note: Inline styles below are for quick scaffolding. Production environments should replace them with SCSS modules.
interface Props { description?: string; }
export default function EmptyState({ description = '暂无数据' }: Props) {
return <div style={{ textAlign: 'center', padding: '48px 0', color: '#999' }}>{description}</div>;
}
components/ErrorState.tsx:
Note: Inline styles below are for quick scaffolding. Production environments should replace them with SCSS modules.
interface Props { error?: Error; onRetry?: () => void; }
export default function ErrorState({ error, onRetry }: Props) {
return (
<div style={{ textAlign: 'center', padding: '48px 0' }}>
<p style={{ color: '#ff4d4f', marginBottom: 16 }}>{error?.message || '页面出错了'}</p>
{onRetry && <button
</div>
);
}
Note:
hooks/useRequest.tsand other common hooks are generated by the shared infrastructure layer (Step 6). See../references/shared-base.mdforuseRequesttemplate and../references/shared-infrastructure.mdfor all other hooks.
Vue projects: Adapt same patterns to Vue 3 Composition API (defineComponent, ref, reactive, etc.)
Step 6: Generate Shared Layer
After type-specific files, read the following reference files and generate all shared code:
../references/shared-base.md— services, utils, styles, types, env files../references/shared-config.md— vite, tsconfig, eslint, prettier configs../references/shared-infrastructure.md— store, theme, i18n, hooks, layouts, auth guard, config/constants
Conditional generation (only if user selected the corresponding option):
- Selected Zustand/Redux Toolkit/Pinia → generate
src/store/ - Selected i18n → generate
src/locales/ - Always generate (regardless of options):
src/hooks/(common hooks),src/components/AppProvider.tsx,src/components/AuthGuard.tsx,src/components/GlobalLoading.tsx,src/config/,src/theme/
Step 7: Final Setup
cd <project-name>
bun install # or: pnpm install
bun dev # or: pnpm dev
Announce completion: project name, framework, key dependencies, dev server URL (http://localhost:5173).
Vercel deployment (recommended):
bun add -g vercel
vercel
Or connect GitHub repo at vercel.com for auto-deploy.