@4399ywkf/cli
v2.0.33
Published
运维开发部脚手架
Readme
YWKF Framework
基于 Rspack 的企业级 React 前端框架,零配置开箱即用。
YWKF Framework 是一个类 Modern.js 的全栈前端框架,将 Rspack 构建、约定式路由、状态管理、请求层、微前端等能力封装为统一的开发体验。开发者只需关注业务代码,框架层面的配置和编排由 @4399ywkf/core 全权接管。
目录
- 快速开始
- 项目结构
- 框架配置
- 约定式路由
- 状态管理 (Zustand)
- 请求层 (Axios + React Query)
- 插件系统
- 微前端 (Garfish)
- 开发服务器
- 构建与部署
- 自定义 Rspack 配置
- 环境变量
- CLI 命令参考
- 发布到 npm
快速开始
创建新项目
npx @4399ywkf/cli@latest按照交互式提示完成项目配置:
◆ 请输入项目名称
│ my-app
◆ 选择包管理器
│ ● pnpm ○ npm ○ yarn
◆ 是否需要微前端 (Garfish) 支持?
│ ● 不需要 ○ 主应用(基座) ○ 子应用启动开发
cd my-app
pnpm install
pnpm run dev构建生产版本
pnpm run build项目结构
my-app/
├── config/env/ # 环境变量文件
│ ├── .env.development # 开发环境
│ ├── .env.production # 生产环境
│ └── .env.public # 公共变量(不区分环境)
├── public/ # 静态资源
│ ├── favicon.ico
│ └── index.html
├── src/
│ ├── index.css # 全局样式
│ ├── request.ts # 请求层配置(拦截器、错误处理)
│ ├── pages/ # 约定式路由目录
│ │ ├── page.tsx # 首页 → /
│ │ ├── layout.tsx # 根布局
│ │ ├── loading.tsx # 全局加载状态
│ │ ├── error.tsx # 全局错误边界
│ │ ├── $.tsx # 404 通配路由
│ │ ├── dashboard/
│ │ │ ├── page.tsx # → /dashboard
│ │ │ └── layout.tsx # dashboard 布局
│ │ └── user/
│ │ ├── page.tsx # → /user
│ │ └── [id]/
│ │ └── page.tsx # → /user/:id
│ └── store/ # Zustand 状态管理
│ ├── index.ts
│ ├── middleware/
│ └── app/
│ ├── store.ts
│ ├── index.tsx
│ ├── initialState.ts
│ └── slices/
├── .ywkf/ # 框架自动生成(勿手动修改)
├── ywkf.config.ts # 框架配置
├── tsconfig.json
└── package.json
.ywkf/目录由框架自动生成,包含路由、入口文件、请求实例等。请勿手动修改,所有自定义应通过ywkf.config.ts、src/request.ts或插件配置完成。
框架配置
在项目根目录创建 ywkf.config.ts:
import { defineConfig } from "@4399ywkf/core/config";
import {
tailwindPlugin,
reactQueryPlugin,
zustandPlugin,
} from "@4399ywkf/core";
export default defineConfig({
// ── 应用信息 ──
appName: "my-app",
appCName: "我的应用",
// ── 开发服务器 ──
dev: {
port: 3000,
host: "localhost",
proxy: {
"/api": {
target: "http://localhost:8080",
changeOrigin: true,
},
},
},
// ── 输出配置 ──
output: {
path: "dist",
publicPath: "/",
clean: true,
},
// ── HTML ──
html: {
title: "我的应用",
mountRoot: "root",
favicon: "public/favicon.ico",
},
// ── 路由 ──
router: {
basename: "/",
conventional: true,
pagesDir: "src/pages",
},
// ── 样式 ──
style: {
less: { enabled: true },
sass: { enabled: true },
cssModules: true,
tailwindcss: true,
},
// ── 性能 ──
performance: {
splitChunks: true,
dropConsole: process.env.NODE_ENV === "production",
rsdoctor: false,
},
// ── 路径别名 ──
alias: {
"@components": "src/components",
"@utils": "src/utils",
},
// ── 环境变量 ──
env: {
publicEnvFile: "config/env/.env.public",
envDir: "config/env",
},
// ── 插件 ──
plugins: [
reactQueryPlugin(),
zustandPlugin(),
tailwindPlugin(),
],
});完整配置项参考
| 配置项 | 类型 | 默认值 | 说明 |
|--------|------|--------|------|
| appName | string | "app" | 应用名称(用于 UMD 导出等) |
| appCName | string | "应用" | 应用中文名(用于页面标题等) |
| dev.port | number | 3000 | 开发服务器端口 |
| dev.host | string | "localhost" | 开发服务器主机 |
| dev.proxy | Record | {} | 代理配置,透传至 devServer |
| dev.https | boolean | false | 是否启用 HTTPS |
| output.path | string | "dist" | 构建输出目录 |
| output.publicPath | string | "/" | 静态资源公共路径 |
| output.clean | boolean | true | 构建前清理输出目录 |
| html.title | string | "应用" | HTML 页面标题 |
| html.template | string | "public/index.html" | HTML 模板路径 |
| html.favicon | string | "public/favicon.ico" | favicon 路径 |
| html.mountRoot | string | "root" | React 挂载根元素 ID |
| router.basename | string | "/" | 路由基路径 |
| router.conventional | boolean | false | 是否启用约定式路由 |
| router.pagesDir | string | "src/pages" | 约定式路由扫描目录 |
| style.cssModules | boolean | true | 启用 CSS Modules |
| style.less | { enabled: boolean } | { enabled: true } | Less 支持 |
| style.sass | { enabled: boolean } | { enabled: true } | Sass 支持 |
| style.tailwindcss | boolean | true | Tailwind CSS 支持 |
| performance.splitChunks | boolean | true | 代码分割 |
| performance.dropConsole | boolean | false | 生产环境移除 console |
| performance.rsdoctor | boolean | false | 启用 Rsdoctor 构建分析 |
| tools.rspack | (config, ctx) => config | — | 自定义 Rspack 配置 |
| plugins | PluginConfig[] | [] | 插件列表 |
约定式路由
启用 router.conventional: true 后,框架会根据 src/pages/ 目录结构自动生成路由,无需手动维护路由表。
路由文件约定
| 文件名 | 作用 | 说明 |
|--------|------|------|
| page.tsx | 页面组件 | 该目录下的路由叶子节点 |
| layout.tsx | 布局组件 | 使用 <Outlet /> 渲染子路由 |
| loading.tsx | 加载状态 | 路由懒加载时的 fallback 组件 |
| error.tsx | 错误边界 | 捕获子组件渲染错误 |
| $.tsx | 通配路由 | 匹配未命中的路径(404 页面) |
目录规则
| 目录形式 | 路由路径 | 说明 |
|----------|----------|------|
| user/page.tsx | /user | 普通路由 |
| user/[id]/page.tsx | /user/:id | 动态路由参数 |
| user/[id$]/page.tsx | /user/:id? | 可选动态参数 |
| [...slug]/page.tsx | /* | 全匹配(catch-all) |
| __auth/layout.tsx | — | 无路径布局(不产生 URL 片段) |
| user.profile/page.tsx | /user/profile | 扁平化路由(. 替代嵌套目录) |
自动排除目录
以下目录不参与路由扫描:components、hooks、utils、services、models、assets、types、constants、styles。
示例
src/pages/
├── page.tsx → /
├── layout.tsx → 根布局
├── loading.tsx → 全局 loading
├── error.tsx → 全局错误边界
├── $.tsx → 404
├── dashboard/
│ ├── page.tsx → /dashboard
│ ├── layout.tsx → dashboard 布局
│ └── analytics/
│ └── page.tsx → /dashboard/analytics
└── user/
├── page.tsx → /user
└── [id]/
└── page.tsx → /user/:id在页面中获取路由参数
import { useParams } from "react-router";
export default function UserDetail() {
const { id } = useParams<{ id: string }>();
return <div>用户 ID: {id}</div>;
}状态管理 (Zustand)
框架内置 zustandPlugin,基于 Agent / Slice 架构管理状态。
目录结构
src/store/
├── index.ts # 聚合导出
├── middleware/
│ └── index.ts # 中间件(devtools 等)
└── app/ # 业务域
├── index.tsx # 导出 useAppStore + Provider
├── store.ts # 创建 store(聚合所有 slice)
├── initialState.ts # 聚合初始状态
└── slices/
└── counter/
├── initialState.ts # 纯数据定义
└── actions.ts # 状态变更逻辑核心约束
| 原则 | 说明 |
|------|------|
| initialState 不含逻辑 | 纯数据对象,只定义初始值和类型 |
| actions 通过 set/get 修改状态 | 使用 StateCreator 类型约束 |
| slice 之间不直接修改彼此状态 | 需要跨 slice 协调时在 store 层处理 |
| 使用 selector | 避免无效重渲染,按需订阅 |
定义 Slice
store/app/slices/counter/initialState.ts
export interface CounterState {
count: number;
step: number;
}
export const counterInitialState: CounterState = {
count: 0,
step: 1,
};store/app/slices/counter/actions.ts
import type { StateCreator } from "zustand";
import type { AppStore } from "../../store";
export interface CounterActions {
increment: () => void;
decrement: () => void;
reset: () => void;
setStep: (step: number) => void;
incrementAsync: () => Promise<void>;
}
export const createCounterActions: StateCreator<
AppStore,
[["zustand/devtools", never]],
[],
CounterActions
> = (set, get) => ({
increment: () => set((s) => ({ count: s.count + s.step }), false, "counter/increment"),
decrement: () => set((s) => ({ count: s.count - s.step }), false, "counter/decrement"),
reset: () => set({ count: 0 }, false, "counter/reset"),
setStep: (step) => set({ step }, false, "counter/setStep"),
incrementAsync: async () => {
await new Promise((r) => setTimeout(r, 1000));
get().increment();
},
});聚合 Store
store/app/store.ts
import { create } from "zustand";
import { devtools, subscribeWithSelector } from "zustand/middleware";
import { counterInitialState, type CounterState } from "./slices/counter/initialState";
import { createCounterActions, type CounterActions } from "./slices/counter/actions";
export type AppStore = CounterState & CounterActions;
export const useAppStore = create<AppStore>()(
subscribeWithSelector(
devtools(
(...args) => ({
...counterInitialState,
...createCounterActions(...args),
}),
{ name: "AppStore" }
)
)
);在组件中使用
import { useAppStore } from "@/store/app";
export default function Counter() {
// 使用 selector,避免全量订阅
const count = useAppStore((s) => s.count);
const increment = useAppStore((s) => s.increment);
return (
<button onClick={increment}>
计数: {count}
</button>
);
}请求层 (Axios + React Query)
框架通过 reactQueryPlugin 提供开箱即用的 HTTP 请求层。
架构概览
ywkf.config.ts src/request.ts
reactQueryPlugin({ export default {
baseURL: "/api", 构建时 getToken() { ... },
timeout: 15000, ──────> responseInterceptor() { ... },
staleTime: 5min, errorHandler() { ... },
}) }
│ │
└──────────┬───────────────────────┘
│
▼
.ywkf/request.ts (自动生成)
├── 创建 Axios 实例
├── 加载用户配置
├── 注入 Token → Authorization header
├── 应用响应拦截器 → 业务码处理
└── 导出 request 实例- 构建时配置(
ywkf.config.ts中的reactQueryPlugin参数):控制baseURL、timeout、staleTime等静态值 - 运行时配置(
src/request.ts):控制拦截器、错误处理等需要浏览器 API 的逻辑
配置 src/request.ts
import type { RequestConfig, Result } from "@4399ywkf/core/runtime";
import { message } from "antd";
const requestConfig: RequestConfig = {
timeout: 15000,
getToken() {
return localStorage.getItem("token");
},
responseInterceptor(response: any) {
const { data } = response;
if (data && typeof data.code === "number") {
const { code, message: msg } = data as Result;
if (code === 0) return data.data;
if (code === 401 || code === 999) {
message.error("登录过期,请重新登录");
window.location.href = "/login";
return Promise.reject(data);
}
message.error(msg || `请求失败 (${code})`);
return Promise.reject(data);
}
return data;
},
errorHandler(error: any) {
const status = error?.response?.status;
switch (status) {
case 401: message.error("未授权,请重新登录"); break;
case 403: message.error("无访问权限"); break;
case 500: message.error("服务器内部错误"); break;
default:
if (!error?.response) {
message.error("网络异常,请检查网络连接");
}
}
return Promise.reject(error);
},
};
export default requestConfig;RequestConfig 完整字段
| 字段 | 类型 | 说明 |
|------|------|------|
| baseURL | string | API 基础路径 |
| timeout | number | 请求超时(毫秒) |
| headers | Record<string, string> | 自定义默认 headers |
| withCredentials | boolean | 是否携带 cookie |
| getToken | () => string \| null | Token 获取策略 |
| tokenPrefix | string | Token 前缀,默认 "Bearer" |
| requestInterceptor | (config) => config | 请求拦截器 |
| responseInterceptor | (response) => any | 响应拦截器 |
| errorHandler | (error) => any | 错误处理器 |
在业务代码中使用
import request from "@ywkf/request";
import type { Result } from "@4399ywkf/core/runtime";
interface User {
id: number;
name: string;
}
// 直接使用 Axios 实例
const users = await request.get<Result<User[]>>("/api/users");
// 配合 React Query
import { useQuery } from "@tanstack/react-query";
function useUsers() {
return useQuery({
queryKey: ["users"],
queryFn: () => request.get<User[]>("/api/users"),
});
}reactQueryPlugin 选项
| 选项 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| staleTime | number | 300000 (5min) | 数据过期时间 |
| gcTime | number | 600000 (10min) | 垃圾回收时间 |
| retry | number | 1 | 失败重试次数 |
| devtools | boolean | true | React Query DevTools |
| baseURL | string | "" | Axios baseURL |
| timeout | number | 10000 | Axios 超时时间 |
插件系统
框架基于三类 Hooks 实现插件机制:
- GeneratorHooks:代码生成阶段(生成
.ywkf/目录中的文件) - RuntimeHooks:运行时注入(Provider 包裹、入口代码修改)
- BuildHooks:构建阶段(修改 Rspack 配置)
内置插件一览
| 插件 | 说明 | 默认启用 |
|------|------|----------|
| reactQueryPlugin() | React Query + Axios 请求层 | 是 |
| zustandPlugin() | Zustand 状态管理脚手架 | 是 |
| tailwindPlugin() | Tailwind CSS 集成 | 是 |
| garfishPlugin() | Garfish 微前端 | 按需 |
| i18nPlugin() | i18next 国际化 | 按需 |
| themePlugin() | antd-style 主题管理 | 按需 |
| mockPlugin() | 开发环境 Mock 数据 | 按需 |
| analyticsPlugin() | 构建分析与统计 | 按需 |
使用方式
// ywkf.config.ts
import { defineConfig } from "@4399ywkf/core/config";
import {
reactQueryPlugin,
zustandPlugin,
tailwindPlugin,
i18nPlugin,
themePlugin,
mockPlugin,
garfishPlugin,
} from "@4399ywkf/core";
export default defineConfig({
plugins: [
reactQueryPlugin({
staleTime: 5 * 60 * 1000,
devtools: true,
}),
zustandPlugin({
scaffold: true,
storeDir: "store",
}),
tailwindPlugin({
darkModeSelector: ".dark",
}),
// 按需启用
i18nPlugin({
defaultLocale: "zh-CN",
locales: ["zh-CN", "en-US"],
}),
themePlugin({
darkMode: true,
primaryColor: "#1677ff",
}),
mockPlugin({
mockDir: "mock",
delay: 300,
}),
],
});插件详细参数
tailwindPlugin
| 选项 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| darkModeSelector | string | — | 暗色模式 CSS 选择器 |
| autoConfig | boolean | true | 自动生成 postcss 配置 |
| extraCSS | string | — | 额外注入的 CSS |
i18nPlugin
| 选项 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| defaultLocale | string | "zh-CN" | 默认语言 |
| locales | string[] | — | 支持的语言列表 |
| localesDir | string | "locales" | 翻译文件目录 |
| defaultNS | string | — | 默认命名空间 |
| autoScaffold | boolean | true | 自动生成翻译文件 |
themePlugin
| 选项 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| darkMode | boolean | false | 是否支持暗色模式 |
| defaultAppearance | "light" \| "dark" \| "auto" | "light" | 默认外观 |
| primaryColor | string | — | 主题主色 |
| prefixCls | string | — | antd 类名前缀 |
| cssVar | boolean | — | 是否使用 CSS 变量 |
| globalReset | boolean | true | 全局样式重置 |
mockPlugin
| 选项 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| mockDir | string | "mock" | Mock 文件目录 |
| enableInProd | boolean | false | 是否在生产环境启用 |
| delay | number | 0 | 模拟响应延迟(毫秒) |
| prefix | string | — | API 前缀 |
analyticsPlugin
| 选项 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| buildAnalysis | boolean | true | 启用构建分析 |
| timing | boolean | true | 输出构建耗时 |
| bundleSize | boolean | true | 分析 bundle 大小 |
| sizeWarningThreshold | number | 500 | 大文件警告阈值(KB) |
微前端 (Garfish)
框架通过 garfishPlugin 集成 Garfish 微前端方案,支持主应用和子应用两种模式。
主应用(基座)
// ywkf.config.ts
import { garfishPlugin } from "@4399ywkf/core";
export default defineConfig({
plugins: [
garfishPlugin({
master: true,
apps: [
{
name: "sub-app",
entry: "http://localhost:3001",
activeRule: "/sub",
},
{
name: "another-app",
entry: "http://localhost:3002",
activeRule: "/another",
basename: "/another",
},
],
sandbox: {
open: true,
strictStyleIsolation: true,
},
}),
],
});在页面中渲染子应用:
// src/pages/micro/layout.tsx
import { Outlet, useLocation } from "react-router";
export default function MicroLayout() {
const { pathname } = useLocation();
const isSubApp = pathname.startsWith("/sub");
return isSubApp
? <div id="subapp-container" style={{ minHeight: "100vh" }} />
: <Outlet />;
}子应用
// ywkf.config.ts
import { garfishPlugin } from "@4399ywkf/core";
export default defineConfig({
plugins: [
garfishPlugin({
appName: "sub-app",
}),
],
});子应用会自动:
- 输出 UMD 格式
- 添加 CORS 响应头
- 导出 Garfish
provider(render/destroy) - 独立运行时正常启动,被主应用加载时由 Garfish 接管
garfishPlugin 选项
| 选项 | 类型 | 说明 |
|------|------|------|
| appName | string | 应用名称 |
| master | boolean | 是否为主应用 |
| apps | Array | 子应用列表 |
| apps[].name | string | 子应用名称 |
| apps[].entry | string | 子应用入口 URL |
| apps[].activeRule | string | 激活路径 |
| apps[].basename | string | 子应用路由基路径 |
| sandbox.open | boolean | 启用沙箱 |
| sandbox.snapshot | boolean | 快照模式 |
| sandbox.strictStyleIsolation | boolean | 严格样式隔离 |
开发服务器
终端界面
启动 pnpm run dev 后,框架提供 Modern.js 风格的终端体验:
@4399ywkf/core Framework v5.0.4
client [━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━] (100%)
ready in 1.23s
➜ Local: http://localhost:3000/
➜ Network: http://192.168.1.100:3000/
plugins: reactQuery · zustand · tailwind
press h + enter to show shortcuts快捷键
| 按键 | 功能 |
|------|------|
| h + Enter | 显示快捷键帮助 |
| o + Enter | 在浏览器中打开 |
| c + Enter | 清空终端 |
| q + Enter | 退出开发服务器 |
自动端口检测
如果配置的端口被占用,框架会自动切换到下一个可用端口(最多尝试 20 个):
⚠ Port 3000 is in use, using 3001 instead.React Fast Refresh
开发模式下自动启用 React Fast Refresh,修改组件代码后浏览器即时更新,保留组件状态。
构建与部署
构建
pnpm run build产物输出到 dist/ 目录。构建过程自动:
- Tree Shaking
- 代码分割(
splitChunks) - 生产环境移除 console(如配置
dropConsole: true) - CSS 提取与压缩
- 资源哈希
构建分析
pnpm run build --analyze启用 Rsdoctor 构建分析,可视化查看 bundle 大小、构建耗时和依赖关系。
部署
构建产物为纯静态文件,可部署到任何静态服务器:
# Nginx
cp -r dist/* /usr/share/nginx/html/
# 或使用 Docker
FROM nginx:alpine
COPY dist/ /usr/share/nginx/html/如果使用了
router.basename或浏览器端路由,需要配置 Nginx 的try_files指向index.html。
自定义 Rspack 配置
通过 tools.rspack 可以修改底层 Rspack 配置:
// ywkf.config.ts
export default defineConfig({
tools: {
rspack(config, { isDev }) {
// 添加自定义 loader
config.module?.rules?.push({
test: /\.yaml$/,
type: "json",
});
// 添加别名
if (config.resolve) {
config.resolve.alias = {
...config.resolve.alias,
"@lib": "./src/lib",
};
}
// 仅开发环境
if (isDev) {
// ...
}
return config;
},
},
});环境变量
文件优先级
config/env/.env.public # 公共变量(最先加载)
config/env/.env.development # 开发环境
config/env/.env.production # 生产环境使用方式
# config/env/.env.development
APP_NAME=my-app
APP_PORT=3000
VITE_API_BASE_URL=http://localhost:8080/api// 在代码中使用
const apiUrl = import.meta.env.VITE_API_BASE_URL;
const appName = process.env.APP_NAME;以
VITE_前缀的变量会被注入到客户端代码中,其他变量仅在 Node.js 构建侧可用。
CLI 命令参考
ywkf dev
启动开发服务器。
ywkf dev [options]
Options:
-p, --port <port> 指定端口号
-h, --host <host> 指定主机地址ywkf build
构建生产版本。
ywkf build [options]
Options:
--analyze 启用 Rsdoctor 构建分析ywkf start
启动生产服务器(暂未实现,建议使用 Nginx 等静态服务器)。
npx @4399ywkf/cli
交互式创建新项目。
发布到 npm
项目提供一键发布脚本:
# 两个包一起 patch 发布
pnpm publish:all
# 只发布 @4399ywkf/core
pnpm publish:core
# 只发布 @4399ywkf/cli
pnpm publish:cli
# minor 版本升级
pnpm publish:minor
# major 版本升级
pnpm publish:major
# dry-run 预览(不实际发布)
pnpm publish:dry也可以直接调用脚本,组合参数:
./scripts/publish.sh -t core -b minor # core minor 升级
./scripts/publish.sh -b major -d # major 升级 dry-run技术栈
| 领域 | 技术 | |------|------| | 构建工具 | Rspack | | 前端框架 | React 18+ | | 类型系统 | TypeScript (strict mode) | | UI 组件库 | Ant Design | | 样式方案 | antd-style / Tailwind CSS | | 路由 | React Router v7 (约定式) | | 状态管理 | Zustand (Slice 架构) | | 请求层 | Axios + TanStack React Query | | 微前端 | Garfish | | 包管理 | pnpm |
License
ISC
