@4399ywkf/cli 2.0.8 → 2.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +988 -0
  2. package/dist/index.js +13 -1
  3. package/package.json +1 -1
package/README.md ADDED
@@ -0,0 +1,988 @@
1
+ # YWKF Framework
2
+
3
+ > 基于 Rspack 的企业级 React 前端框架,零配置开箱即用。
4
+
5
+ YWKF Framework 是一个类 Modern.js 的全栈前端框架,将 Rspack 构建、约定式路由、状态管理、请求层、微前端等能力封装为统一的开发体验。开发者只需关注业务代码,框架层面的配置和编排由 `@4399ywkf/core` 全权接管。
6
+
7
+ ## 目录
8
+
9
+ - [快速开始](#快速开始)
10
+ - [项目结构](#项目结构)
11
+ - [框架配置](#框架配置)
12
+ - [约定式路由](#约定式路由)
13
+ - [状态管理 (Zustand)](#状态管理-zustand)
14
+ - [请求层 (Axios + React Query)](#请求层-axios--react-query)
15
+ - [插件系统](#插件系统)
16
+ - [微前端 (Garfish)](#微前端-garfish)
17
+ - [开发服务器](#开发服务器)
18
+ - [构建与部署](#构建与部署)
19
+ - [自定义 Rspack 配置](#自定义-rspack-配置)
20
+ - [环境变量](#环境变量)
21
+ - [CLI 命令参考](#cli-命令参考)
22
+ - [发布到 npm](#发布到-npm)
23
+
24
+ ---
25
+
26
+ ## 快速开始
27
+
28
+ ### 创建新项目
29
+
30
+ ```bash
31
+ npx @4399ywkf/cli@latest
32
+ ```
33
+
34
+ 按照交互式提示完成项目配置:
35
+
36
+ ```
37
+ ◆ 请输入项目名称
38
+ │ my-app
39
+ ◆ 选择包管理器
40
+ │ ● pnpm ○ npm ○ yarn
41
+ ◆ 是否需要微前端 (Garfish) 支持?
42
+ │ ● 不需要 ○ 主应用(基座) ○ 子应用
43
+ ```
44
+
45
+ ### 启动开发
46
+
47
+ ```bash
48
+ cd my-app
49
+ pnpm install
50
+ pnpm run dev
51
+ ```
52
+
53
+ ### 构建生产版本
54
+
55
+ ```bash
56
+ pnpm run build
57
+ ```
58
+
59
+ ---
60
+
61
+ ## 项目结构
62
+
63
+ ```
64
+ my-app/
65
+ ├── config/env/ # 环境变量文件
66
+ │ ├── .env.development # 开发环境
67
+ │ ├── .env.production # 生产环境
68
+ │ └── .env.public # 公共变量(不区分环境)
69
+ ├── public/ # 静态资源
70
+ │ ├── favicon.ico
71
+ │ └── index.html
72
+ ├── src/
73
+ │ ├── index.css # 全局样式
74
+ │ ├── request.ts # 请求层配置(拦截器、错误处理)
75
+ │ ├── pages/ # 约定式路由目录
76
+ │ │ ├── page.tsx # 首页 → /
77
+ │ │ ├── layout.tsx # 根布局
78
+ │ │ ├── loading.tsx # 全局加载状态
79
+ │ │ ├── error.tsx # 全局错误边界
80
+ │ │ ├── $.tsx # 404 通配路由
81
+ │ │ ├── dashboard/
82
+ │ │ │ ├── page.tsx # → /dashboard
83
+ │ │ │ └── layout.tsx # dashboard 布局
84
+ │ │ └── user/
85
+ │ │ ├── page.tsx # → /user
86
+ │ │ └── [id]/
87
+ │ │ └── page.tsx # → /user/:id
88
+ │ └── store/ # Zustand 状态管理
89
+ │ ├── index.ts
90
+ │ ├── middleware/
91
+ │ └── app/
92
+ │ ├── store.ts
93
+ │ ├── index.tsx
94
+ │ ├── initialState.ts
95
+ │ └── slices/
96
+ ├── .ywkf/ # 框架自动生成(勿手动修改)
97
+ ├── ywkf.config.ts # 框架配置
98
+ ├── tsconfig.json
99
+ └── package.json
100
+ ```
101
+
102
+ > `.ywkf/` 目录由框架自动生成,包含路由、入口文件、请求实例等。请勿手动修改,所有自定义应通过 `ywkf.config.ts`、`src/request.ts` 或插件配置完成。
103
+
104
+ ---
105
+
106
+ ## 框架配置
107
+
108
+ 在项目根目录创建 `ywkf.config.ts`:
109
+
110
+ ```typescript
111
+ import { defineConfig } from "@4399ywkf/core/config";
112
+ import {
113
+ tailwindPlugin,
114
+ reactQueryPlugin,
115
+ zustandPlugin,
116
+ } from "@4399ywkf/core";
117
+
118
+ export default defineConfig({
119
+ // ── 应用信息 ──
120
+ appName: "my-app",
121
+ appCName: "我的应用",
122
+
123
+ // ── 开发服务器 ──
124
+ dev: {
125
+ port: 3000,
126
+ host: "localhost",
127
+ proxy: {
128
+ "/api": {
129
+ target: "http://localhost:8080",
130
+ changeOrigin: true,
131
+ },
132
+ },
133
+ },
134
+
135
+ // ── 输出配置 ──
136
+ output: {
137
+ path: "dist",
138
+ publicPath: "/",
139
+ clean: true,
140
+ },
141
+
142
+ // ── HTML ──
143
+ html: {
144
+ title: "我的应用",
145
+ mountRoot: "root",
146
+ favicon: "public/favicon.ico",
147
+ },
148
+
149
+ // ── 路由 ──
150
+ router: {
151
+ basename: "/",
152
+ conventional: true,
153
+ pagesDir: "src/pages",
154
+ },
155
+
156
+ // ── 样式 ──
157
+ style: {
158
+ less: { enabled: true },
159
+ sass: { enabled: true },
160
+ cssModules: true,
161
+ tailwindcss: true,
162
+ },
163
+
164
+ // ── 性能 ──
165
+ performance: {
166
+ splitChunks: true,
167
+ dropConsole: process.env.NODE_ENV === "production",
168
+ rsdoctor: false,
169
+ },
170
+
171
+ // ── 路径别名 ──
172
+ alias: {
173
+ "@components": "src/components",
174
+ "@utils": "src/utils",
175
+ },
176
+
177
+ // ── 环境变量 ──
178
+ env: {
179
+ publicEnvFile: "config/env/.env.public",
180
+ envDir: "config/env",
181
+ },
182
+
183
+ // ── 插件 ──
184
+ plugins: [
185
+ reactQueryPlugin(),
186
+ zustandPlugin(),
187
+ tailwindPlugin(),
188
+ ],
189
+ });
190
+ ```
191
+
192
+ ### 完整配置项参考
193
+
194
+ | 配置项 | 类型 | 默认值 | 说明 |
195
+ |--------|------|--------|------|
196
+ | `appName` | `string` | `"app"` | 应用名称(用于 UMD 导出等) |
197
+ | `appCName` | `string` | `"应用"` | 应用中文名(用于页面标题等) |
198
+ | `dev.port` | `number` | `3000` | 开发服务器端口 |
199
+ | `dev.host` | `string` | `"localhost"` | 开发服务器主机 |
200
+ | `dev.proxy` | `Record` | `{}` | 代理配置,透传至 devServer |
201
+ | `dev.https` | `boolean` | `false` | 是否启用 HTTPS |
202
+ | `output.path` | `string` | `"dist"` | 构建输出目录 |
203
+ | `output.publicPath` | `string` | `"/"` | 静态资源公共路径 |
204
+ | `output.clean` | `boolean` | `true` | 构建前清理输出目录 |
205
+ | `html.title` | `string` | `"应用"` | HTML 页面标题 |
206
+ | `html.template` | `string` | `"public/index.html"` | HTML 模板路径 |
207
+ | `html.favicon` | `string` | `"public/favicon.ico"` | favicon 路径 |
208
+ | `html.mountRoot` | `string` | `"root"` | React 挂载根元素 ID |
209
+ | `router.basename` | `string` | `"/"` | 路由基路径 |
210
+ | `router.conventional` | `boolean` | `false` | 是否启用约定式路由 |
211
+ | `router.pagesDir` | `string` | `"src/pages"` | 约定式路由扫描目录 |
212
+ | `style.cssModules` | `boolean` | `true` | 启用 CSS Modules |
213
+ | `style.less` | `{ enabled: boolean }` | `{ enabled: true }` | Less 支持 |
214
+ | `style.sass` | `{ enabled: boolean }` | `{ enabled: true }` | Sass 支持 |
215
+ | `style.tailwindcss` | `boolean` | `true` | Tailwind CSS 支持 |
216
+ | `performance.splitChunks` | `boolean` | `true` | 代码分割 |
217
+ | `performance.dropConsole` | `boolean` | `false` | 生产环境移除 console |
218
+ | `performance.rsdoctor` | `boolean` | `false` | 启用 Rsdoctor 构建分析 |
219
+ | `tools.rspack` | `(config, ctx) => config` | — | 自定义 Rspack 配置 |
220
+ | `plugins` | `PluginConfig[]` | `[]` | 插件列表 |
221
+
222
+ ---
223
+
224
+ ## 约定式路由
225
+
226
+ 启用 `router.conventional: true` 后,框架会根据 `src/pages/` 目录结构自动生成路由,无需手动维护路由表。
227
+
228
+ ### 路由文件约定
229
+
230
+ | 文件名 | 作用 | 说明 |
231
+ |--------|------|------|
232
+ | `page.tsx` | 页面组件 | 该目录下的路由叶子节点 |
233
+ | `layout.tsx` | 布局组件 | 使用 `<Outlet />` 渲染子路由 |
234
+ | `loading.tsx` | 加载状态 | 路由懒加载时的 fallback 组件 |
235
+ | `error.tsx` | 错误边界 | 捕获子组件渲染错误 |
236
+ | `$.tsx` | 通配路由 | 匹配未命中的路径(404 页面) |
237
+
238
+ ### 目录规则
239
+
240
+ | 目录形式 | 路由路径 | 说明 |
241
+ |----------|----------|------|
242
+ | `user/page.tsx` | `/user` | 普通路由 |
243
+ | `user/[id]/page.tsx` | `/user/:id` | 动态路由参数 |
244
+ | `user/[id$]/page.tsx` | `/user/:id?` | 可选动态参数 |
245
+ | `[...slug]/page.tsx` | `/*` | 全匹配(catch-all) |
246
+ | `__auth/layout.tsx` | — | 无路径布局(不产生 URL 片段) |
247
+ | `user.profile/page.tsx` | `/user/profile` | 扁平化路由(`.` 替代嵌套目录) |
248
+
249
+ ### 自动排除目录
250
+
251
+ 以下目录不参与路由扫描:`components`、`hooks`、`utils`、`services`、`models`、`assets`、`types`、`constants`、`styles`。
252
+
253
+ ### 示例
254
+
255
+ ```
256
+ src/pages/
257
+ ├── page.tsx → /
258
+ ├── layout.tsx → 根布局
259
+ ├── loading.tsx → 全局 loading
260
+ ├── error.tsx → 全局错误边界
261
+ ├── $.tsx → 404
262
+ ├── dashboard/
263
+ │ ├── page.tsx → /dashboard
264
+ │ ├── layout.tsx → dashboard 布局
265
+ │ └── analytics/
266
+ │ └── page.tsx → /dashboard/analytics
267
+ └── user/
268
+ ├── page.tsx → /user
269
+ └── [id]/
270
+ └── page.tsx → /user/:id
271
+ ```
272
+
273
+ ### 在页面中获取路由参数
274
+
275
+ ```tsx
276
+ import { useParams } from "react-router";
277
+
278
+ export default function UserDetail() {
279
+ const { id } = useParams<{ id: string }>();
280
+ return <div>用户 ID: {id}</div>;
281
+ }
282
+ ```
283
+
284
+ ---
285
+
286
+ ## 状态管理 (Zustand)
287
+
288
+ 框架内置 `zustandPlugin`,基于 Agent / Slice 架构管理状态。
289
+
290
+ ### 目录结构
291
+
292
+ ```
293
+ src/store/
294
+ ├── index.ts # 聚合导出
295
+ ├── middleware/
296
+ │ └── index.ts # 中间件(devtools 等)
297
+ └── app/ # 业务域
298
+ ├── index.tsx # 导出 useAppStore + Provider
299
+ ├── store.ts # 创建 store(聚合所有 slice)
300
+ ├── initialState.ts # 聚合初始状态
301
+ └── slices/
302
+ └── counter/
303
+ ├── initialState.ts # 纯数据定义
304
+ └── actions.ts # 状态变更逻辑
305
+ ```
306
+
307
+ ### 核心约束
308
+
309
+ | 原则 | 说明 |
310
+ |------|------|
311
+ | **initialState 不含逻辑** | 纯数据对象,只定义初始值和类型 |
312
+ | **actions 通过 set/get 修改状态** | 使用 `StateCreator` 类型约束 |
313
+ | **slice 之间不直接修改彼此状态** | 需要跨 slice 协调时在 store 层处理 |
314
+ | **使用 selector** | 避免无效重渲染,按需订阅 |
315
+
316
+ ### 定义 Slice
317
+
318
+ **`store/app/slices/counter/initialState.ts`**
319
+
320
+ ```typescript
321
+ export interface CounterState {
322
+ count: number;
323
+ step: number;
324
+ }
325
+
326
+ export const counterInitialState: CounterState = {
327
+ count: 0,
328
+ step: 1,
329
+ };
330
+ ```
331
+
332
+ **`store/app/slices/counter/actions.ts`**
333
+
334
+ ```typescript
335
+ import type { StateCreator } from "zustand";
336
+ import type { AppStore } from "../../store";
337
+
338
+ export interface CounterActions {
339
+ increment: () => void;
340
+ decrement: () => void;
341
+ reset: () => void;
342
+ setStep: (step: number) => void;
343
+ incrementAsync: () => Promise<void>;
344
+ }
345
+
346
+ export const createCounterActions: StateCreator<
347
+ AppStore,
348
+ [["zustand/devtools", never]],
349
+ [],
350
+ CounterActions
351
+ > = (set, get) => ({
352
+ increment: () => set((s) => ({ count: s.count + s.step }), false, "counter/increment"),
353
+ decrement: () => set((s) => ({ count: s.count - s.step }), false, "counter/decrement"),
354
+ reset: () => set({ count: 0 }, false, "counter/reset"),
355
+ setStep: (step) => set({ step }, false, "counter/setStep"),
356
+ incrementAsync: async () => {
357
+ await new Promise((r) => setTimeout(r, 1000));
358
+ get().increment();
359
+ },
360
+ });
361
+ ```
362
+
363
+ ### 聚合 Store
364
+
365
+ **`store/app/store.ts`**
366
+
367
+ ```typescript
368
+ import { create } from "zustand";
369
+ import { devtools, subscribeWithSelector } from "zustand/middleware";
370
+ import { counterInitialState, type CounterState } from "./slices/counter/initialState";
371
+ import { createCounterActions, type CounterActions } from "./slices/counter/actions";
372
+
373
+ export type AppStore = CounterState & CounterActions;
374
+
375
+ export const useAppStore = create<AppStore>()(
376
+ subscribeWithSelector(
377
+ devtools(
378
+ (...args) => ({
379
+ ...counterInitialState,
380
+ ...createCounterActions(...args),
381
+ }),
382
+ { name: "AppStore" }
383
+ )
384
+ )
385
+ );
386
+ ```
387
+
388
+ ### 在组件中使用
389
+
390
+ ```tsx
391
+ import { useAppStore } from "@/store/app";
392
+
393
+ export default function Counter() {
394
+ // 使用 selector,避免全量订阅
395
+ const count = useAppStore((s) => s.count);
396
+ const increment = useAppStore((s) => s.increment);
397
+
398
+ return (
399
+ <button onClick={increment}>
400
+ 计数: {count}
401
+ </button>
402
+ );
403
+ }
404
+ ```
405
+
406
+ ---
407
+
408
+ ## 请求层 (Axios + React Query)
409
+
410
+ 框架通过 `reactQueryPlugin` 提供开箱即用的 HTTP 请求层。
411
+
412
+ ### 架构概览
413
+
414
+ ```
415
+ ywkf.config.ts src/request.ts
416
+ reactQueryPlugin({ export default {
417
+ baseURL: "/api", 构建时 getToken() { ... },
418
+ timeout: 15000, ──────> responseInterceptor() { ... },
419
+ staleTime: 5min, errorHandler() { ... },
420
+ }) }
421
+ │ │
422
+ └──────────┬───────────────────────┘
423
+
424
+
425
+ .ywkf/request.ts (自动生成)
426
+ ├── 创建 Axios 实例
427
+ ├── 加载用户配置
428
+ ├── 注入 Token → Authorization header
429
+ ├── 应用响应拦截器 → 业务码处理
430
+ └── 导出 request 实例
431
+ ```
432
+
433
+ - **构建时配置**(`ywkf.config.ts` 中的 `reactQueryPlugin` 参数):控制 `baseURL`、`timeout`、`staleTime` 等静态值
434
+ - **运行时配置**(`src/request.ts`):控制拦截器、错误处理等需要浏览器 API 的逻辑
435
+
436
+ ### 配置 `src/request.ts`
437
+
438
+ ```typescript
439
+ import type { RequestConfig, Result } from "@4399ywkf/core/runtime";
440
+ import { message } from "antd";
441
+
442
+ const requestConfig: RequestConfig = {
443
+ timeout: 15000,
444
+
445
+ getToken() {
446
+ return localStorage.getItem("token");
447
+ },
448
+
449
+ responseInterceptor(response: any) {
450
+ const { data } = response;
451
+
452
+ if (data && typeof data.code === "number") {
453
+ const { code, message: msg } = data as Result;
454
+
455
+ if (code === 0) return data.data;
456
+
457
+ if (code === 401 || code === 999) {
458
+ message.error("登录过期,请重新登录");
459
+ window.location.href = "/login";
460
+ return Promise.reject(data);
461
+ }
462
+
463
+ message.error(msg || `请求失败 (${code})`);
464
+ return Promise.reject(data);
465
+ }
466
+
467
+ return data;
468
+ },
469
+
470
+ errorHandler(error: any) {
471
+ const status = error?.response?.status;
472
+
473
+ switch (status) {
474
+ case 401: message.error("未授权,请重新登录"); break;
475
+ case 403: message.error("无访问权限"); break;
476
+ case 500: message.error("服务器内部错误"); break;
477
+ default:
478
+ if (!error?.response) {
479
+ message.error("网络异常,请检查网络连接");
480
+ }
481
+ }
482
+
483
+ return Promise.reject(error);
484
+ },
485
+ };
486
+
487
+ export default requestConfig;
488
+ ```
489
+
490
+ ### `RequestConfig` 完整字段
491
+
492
+ | 字段 | 类型 | 说明 |
493
+ |------|------|------|
494
+ | `baseURL` | `string` | API 基础路径 |
495
+ | `timeout` | `number` | 请求超时(毫秒) |
496
+ | `headers` | `Record<string, string>` | 自定义默认 headers |
497
+ | `withCredentials` | `boolean` | 是否携带 cookie |
498
+ | `getToken` | `() => string \| null` | Token 获取策略 |
499
+ | `tokenPrefix` | `string` | Token 前缀,默认 `"Bearer"` |
500
+ | `requestInterceptor` | `(config) => config` | 请求拦截器 |
501
+ | `responseInterceptor` | `(response) => any` | 响应拦截器 |
502
+ | `errorHandler` | `(error) => any` | 错误处理器 |
503
+
504
+ ### 在业务代码中使用
505
+
506
+ ```typescript
507
+ import request from "@ywkf/request";
508
+ import type { Result } from "@4399ywkf/core/runtime";
509
+
510
+ interface User {
511
+ id: number;
512
+ name: string;
513
+ }
514
+
515
+ // 直接使用 Axios 实例
516
+ const users = await request.get<Result<User[]>>("/api/users");
517
+
518
+ // 配合 React Query
519
+ import { useQuery } from "@tanstack/react-query";
520
+
521
+ function useUsers() {
522
+ return useQuery({
523
+ queryKey: ["users"],
524
+ queryFn: () => request.get<User[]>("/api/users"),
525
+ });
526
+ }
527
+ ```
528
+
529
+ ### `reactQueryPlugin` 选项
530
+
531
+ | 选项 | 类型 | 默认值 | 说明 |
532
+ |------|------|--------|------|
533
+ | `staleTime` | `number` | `300000` (5min) | 数据过期时间 |
534
+ | `gcTime` | `number` | `600000` (10min) | 垃圾回收时间 |
535
+ | `retry` | `number` | `1` | 失败重试次数 |
536
+ | `devtools` | `boolean` | `true` | React Query DevTools |
537
+ | `baseURL` | `string` | `""` | Axios baseURL |
538
+ | `timeout` | `number` | `10000` | Axios 超时时间 |
539
+
540
+ ---
541
+
542
+ ## 插件系统
543
+
544
+ 框架基于三类 Hooks 实现插件机制:
545
+
546
+ - **GeneratorHooks**:代码生成阶段(生成 `.ywkf/` 目录中的文件)
547
+ - **RuntimeHooks**:运行时注入(Provider 包裹、入口代码修改)
548
+ - **BuildHooks**:构建阶段(修改 Rspack 配置)
549
+
550
+ ### 内置插件一览
551
+
552
+ | 插件 | 说明 | 默认启用 |
553
+ |------|------|----------|
554
+ | `reactQueryPlugin()` | React Query + Axios 请求层 | 是 |
555
+ | `zustandPlugin()` | Zustand 状态管理脚手架 | 是 |
556
+ | `tailwindPlugin()` | Tailwind CSS 集成 | 是 |
557
+ | `garfishPlugin()` | Garfish 微前端 | 按需 |
558
+ | `i18nPlugin()` | i18next 国际化 | 按需 |
559
+ | `themePlugin()` | antd-style 主题管理 | 按需 |
560
+ | `mockPlugin()` | 开发环境 Mock 数据 | 按需 |
561
+ | `analyticsPlugin()` | 构建分析与统计 | 按需 |
562
+
563
+ ### 使用方式
564
+
565
+ ```typescript
566
+ // ywkf.config.ts
567
+ import { defineConfig } from "@4399ywkf/core/config";
568
+ import {
569
+ reactQueryPlugin,
570
+ zustandPlugin,
571
+ tailwindPlugin,
572
+ i18nPlugin,
573
+ themePlugin,
574
+ mockPlugin,
575
+ garfishPlugin,
576
+ } from "@4399ywkf/core";
577
+
578
+ export default defineConfig({
579
+ plugins: [
580
+ reactQueryPlugin({
581
+ staleTime: 5 * 60 * 1000,
582
+ devtools: true,
583
+ }),
584
+
585
+ zustandPlugin({
586
+ scaffold: true,
587
+ storeDir: "store",
588
+ }),
589
+
590
+ tailwindPlugin({
591
+ darkModeSelector: ".dark",
592
+ }),
593
+
594
+ // 按需启用
595
+ i18nPlugin({
596
+ defaultLocale: "zh-CN",
597
+ locales: ["zh-CN", "en-US"],
598
+ }),
599
+
600
+ themePlugin({
601
+ darkMode: true,
602
+ primaryColor: "#1677ff",
603
+ }),
604
+
605
+ mockPlugin({
606
+ mockDir: "mock",
607
+ delay: 300,
608
+ }),
609
+ ],
610
+ });
611
+ ```
612
+
613
+ ### 插件详细参数
614
+
615
+ #### `tailwindPlugin`
616
+
617
+ | 选项 | 类型 | 默认值 | 说明 |
618
+ |------|------|--------|------|
619
+ | `darkModeSelector` | `string` | — | 暗色模式 CSS 选择器 |
620
+ | `autoConfig` | `boolean` | `true` | 自动生成 postcss 配置 |
621
+ | `extraCSS` | `string` | — | 额外注入的 CSS |
622
+
623
+ #### `i18nPlugin`
624
+
625
+ | 选项 | 类型 | 默认值 | 说明 |
626
+ |------|------|--------|------|
627
+ | `defaultLocale` | `string` | `"zh-CN"` | 默认语言 |
628
+ | `locales` | `string[]` | — | 支持的语言列表 |
629
+ | `localesDir` | `string` | `"locales"` | 翻译文件目录 |
630
+ | `defaultNS` | `string` | — | 默认命名空间 |
631
+ | `autoScaffold` | `boolean` | `true` | 自动生成翻译文件 |
632
+
633
+ #### `themePlugin`
634
+
635
+ | 选项 | 类型 | 默认值 | 说明 |
636
+ |------|------|--------|------|
637
+ | `darkMode` | `boolean` | `false` | 是否支持暗色模式 |
638
+ | `defaultAppearance` | `"light" \| "dark" \| "auto"` | `"light"` | 默认外观 |
639
+ | `primaryColor` | `string` | — | 主题主色 |
640
+ | `prefixCls` | `string` | — | antd 类名前缀 |
641
+ | `cssVar` | `boolean` | — | 是否使用 CSS 变量 |
642
+ | `globalReset` | `boolean` | `true` | 全局样式重置 |
643
+
644
+ #### `mockPlugin`
645
+
646
+ | 选项 | 类型 | 默认值 | 说明 |
647
+ |------|------|--------|------|
648
+ | `mockDir` | `string` | `"mock"` | Mock 文件目录 |
649
+ | `enableInProd` | `boolean` | `false` | 是否在生产环境启用 |
650
+ | `delay` | `number` | `0` | 模拟响应延迟(毫秒) |
651
+ | `prefix` | `string` | — | API 前缀 |
652
+
653
+ #### `analyticsPlugin`
654
+
655
+ | 选项 | 类型 | 默认值 | 说明 |
656
+ |------|------|--------|------|
657
+ | `buildAnalysis` | `boolean` | `true` | 启用构建分析 |
658
+ | `timing` | `boolean` | `true` | 输出构建耗时 |
659
+ | `bundleSize` | `boolean` | `true` | 分析 bundle 大小 |
660
+ | `sizeWarningThreshold` | `number` | `500` | 大文件警告阈值(KB) |
661
+
662
+ ---
663
+
664
+ ## 微前端 (Garfish)
665
+
666
+ 框架通过 `garfishPlugin` 集成 [Garfish](https://github.com/web-infra-dev/garfish) 微前端方案,支持主应用和子应用两种模式。
667
+
668
+ ### 主应用(基座)
669
+
670
+ ```typescript
671
+ // ywkf.config.ts
672
+ import { garfishPlugin } from "@4399ywkf/core";
673
+
674
+ export default defineConfig({
675
+ plugins: [
676
+ garfishPlugin({
677
+ master: true,
678
+ apps: [
679
+ {
680
+ name: "sub-app",
681
+ entry: "http://localhost:3001",
682
+ activeRule: "/sub",
683
+ },
684
+ {
685
+ name: "another-app",
686
+ entry: "http://localhost:3002",
687
+ activeRule: "/another",
688
+ basename: "/another",
689
+ },
690
+ ],
691
+ sandbox: {
692
+ open: true,
693
+ strictStyleIsolation: true,
694
+ },
695
+ }),
696
+ ],
697
+ });
698
+ ```
699
+
700
+ 在页面中渲染子应用:
701
+
702
+ ```tsx
703
+ // src/pages/micro/layout.tsx
704
+ import { Outlet, useLocation } from "react-router";
705
+
706
+ export default function MicroLayout() {
707
+ const { pathname } = useLocation();
708
+ const isSubApp = pathname.startsWith("/sub");
709
+
710
+ return isSubApp
711
+ ? <div id="subapp-container" style={{ minHeight: "100vh" }} />
712
+ : <Outlet />;
713
+ }
714
+ ```
715
+
716
+ ### 子应用
717
+
718
+ ```typescript
719
+ // ywkf.config.ts
720
+ import { garfishPlugin } from "@4399ywkf/core";
721
+
722
+ export default defineConfig({
723
+ plugins: [
724
+ garfishPlugin({
725
+ appName: "sub-app",
726
+ }),
727
+ ],
728
+ });
729
+ ```
730
+
731
+ 子应用会自动:
732
+ - 输出 UMD 格式
733
+ - 添加 CORS 响应头
734
+ - 导出 Garfish `provider`(`render` / `destroy`)
735
+ - 独立运行时正常启动,被主应用加载时由 Garfish 接管
736
+
737
+ ### `garfishPlugin` 选项
738
+
739
+ | 选项 | 类型 | 说明 |
740
+ |------|------|------|
741
+ | `appName` | `string` | 应用名称 |
742
+ | `master` | `boolean` | 是否为主应用 |
743
+ | `apps` | `Array` | 子应用列表 |
744
+ | `apps[].name` | `string` | 子应用名称 |
745
+ | `apps[].entry` | `string` | 子应用入口 URL |
746
+ | `apps[].activeRule` | `string` | 激活路径 |
747
+ | `apps[].basename` | `string` | 子应用路由基路径 |
748
+ | `sandbox.open` | `boolean` | 启用沙箱 |
749
+ | `sandbox.snapshot` | `boolean` | 快照模式 |
750
+ | `sandbox.strictStyleIsolation` | `boolean` | 严格样式隔离 |
751
+
752
+ ---
753
+
754
+ ## 开发服务器
755
+
756
+ ### 终端界面
757
+
758
+ 启动 `pnpm run dev` 后,框架提供 Modern.js 风格的终端体验:
759
+
760
+ ```
761
+ @4399ywkf/core Framework v5.0.4
762
+
763
+ client [━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━] (100%)
764
+
765
+ ready in 1.23s
766
+
767
+ ➜ Local: http://localhost:3000/
768
+ ➜ Network: http://192.168.1.100:3000/
769
+
770
+ plugins: reactQuery · zustand · tailwind
771
+
772
+ press h + enter to show shortcuts
773
+ ```
774
+
775
+ ### 快捷键
776
+
777
+ | 按键 | 功能 |
778
+ |------|------|
779
+ | `h` + Enter | 显示快捷键帮助 |
780
+ | `o` + Enter | 在浏览器中打开 |
781
+ | `c` + Enter | 清空终端 |
782
+ | `q` + Enter | 退出开发服务器 |
783
+
784
+ ### 自动端口检测
785
+
786
+ 如果配置的端口被占用,框架会自动切换到下一个可用端口(最多尝试 20 个):
787
+
788
+ ```
789
+ ⚠ Port 3000 is in use, using 3001 instead.
790
+ ```
791
+
792
+ ### React Fast Refresh
793
+
794
+ 开发模式下自动启用 React Fast Refresh,修改组件代码后浏览器即时更新,保留组件状态。
795
+
796
+ ---
797
+
798
+ ## 构建与部署
799
+
800
+ ### 构建
801
+
802
+ ```bash
803
+ pnpm run build
804
+ ```
805
+
806
+ 产物输出到 `dist/` 目录。构建过程自动:
807
+ - Tree Shaking
808
+ - 代码分割(`splitChunks`)
809
+ - 生产环境移除 console(如配置 `dropConsole: true`)
810
+ - CSS 提取与压缩
811
+ - 资源哈希
812
+
813
+ ### 构建分析
814
+
815
+ ```bash
816
+ pnpm run build --analyze
817
+ ```
818
+
819
+ 启用 [Rsdoctor](https://rsdoctor.dev/) 构建分析,可视化查看 bundle 大小、构建耗时和依赖关系。
820
+
821
+ ### 部署
822
+
823
+ 构建产物为纯静态文件,可部署到任何静态服务器:
824
+
825
+ ```bash
826
+ # Nginx
827
+ cp -r dist/* /usr/share/nginx/html/
828
+
829
+ # 或使用 Docker
830
+ FROM nginx:alpine
831
+ COPY dist/ /usr/share/nginx/html/
832
+ ```
833
+
834
+ > 如果使用了 `router.basename` 或浏览器端路由,需要配置 Nginx 的 `try_files` 指向 `index.html`。
835
+
836
+ ---
837
+
838
+ ## 自定义 Rspack 配置
839
+
840
+ 通过 `tools.rspack` 可以修改底层 Rspack 配置:
841
+
842
+ ```typescript
843
+ // ywkf.config.ts
844
+ export default defineConfig({
845
+ tools: {
846
+ rspack(config, { isDev }) {
847
+ // 添加自定义 loader
848
+ config.module?.rules?.push({
849
+ test: /\.yaml$/,
850
+ type: "json",
851
+ });
852
+
853
+ // 添加别名
854
+ if (config.resolve) {
855
+ config.resolve.alias = {
856
+ ...config.resolve.alias,
857
+ "@lib": "./src/lib",
858
+ };
859
+ }
860
+
861
+ // 仅开发环境
862
+ if (isDev) {
863
+ // ...
864
+ }
865
+
866
+ return config;
867
+ },
868
+ },
869
+ });
870
+ ```
871
+
872
+ ---
873
+
874
+ ## 环境变量
875
+
876
+ ### 文件优先级
877
+
878
+ ```
879
+ config/env/.env.public # 公共变量(最先加载)
880
+ config/env/.env.development # 开发环境
881
+ config/env/.env.production # 生产环境
882
+ ```
883
+
884
+ ### 使用方式
885
+
886
+ ```bash
887
+ # config/env/.env.development
888
+ APP_NAME=my-app
889
+ APP_PORT=3000
890
+ VITE_API_BASE_URL=http://localhost:8080/api
891
+ ```
892
+
893
+ ```typescript
894
+ // 在代码中使用
895
+ const apiUrl = import.meta.env.VITE_API_BASE_URL;
896
+ const appName = process.env.APP_NAME;
897
+ ```
898
+
899
+ > 以 `VITE_` 前缀的变量会被注入到客户端代码中,其他变量仅在 Node.js 构建侧可用。
900
+
901
+ ---
902
+
903
+ ## CLI 命令参考
904
+
905
+ ### `ywkf dev`
906
+
907
+ 启动开发服务器。
908
+
909
+ ```bash
910
+ ywkf dev [options]
911
+
912
+ Options:
913
+ -p, --port <port> 指定端口号
914
+ -h, --host <host> 指定主机地址
915
+ ```
916
+
917
+ ### `ywkf build`
918
+
919
+ 构建生产版本。
920
+
921
+ ```bash
922
+ ywkf build [options]
923
+
924
+ Options:
925
+ --analyze 启用 Rsdoctor 构建分析
926
+ ```
927
+
928
+ ### `ywkf start`
929
+
930
+ 启动生产服务器(暂未实现,建议使用 Nginx 等静态服务器)。
931
+
932
+ ### `npx @4399ywkf/cli`
933
+
934
+ 交互式创建新项目。
935
+
936
+ ---
937
+
938
+ ## 发布到 npm
939
+
940
+ 项目提供一键发布脚本:
941
+
942
+ ```bash
943
+ # 两个包一起 patch 发布
944
+ pnpm publish:all
945
+
946
+ # 只发布 @4399ywkf/core
947
+ pnpm publish:core
948
+
949
+ # 只发布 @4399ywkf/cli
950
+ pnpm publish:cli
951
+
952
+ # minor 版本升级
953
+ pnpm publish:minor
954
+
955
+ # major 版本升级
956
+ pnpm publish:major
957
+
958
+ # dry-run 预览(不实际发布)
959
+ pnpm publish:dry
960
+ ```
961
+
962
+ 也可以直接调用脚本,组合参数:
963
+
964
+ ```bash
965
+ ./scripts/publish.sh -t core -b minor # core minor 升级
966
+ ./scripts/publish.sh -b major -d # major 升级 dry-run
967
+ ```
968
+
969
+ ---
970
+
971
+ ## 技术栈
972
+
973
+ | 领域 | 技术 |
974
+ |------|------|
975
+ | 构建工具 | [Rspack](https://rspack.dev/) |
976
+ | 前端框架 | [React 18+](https://react.dev/) |
977
+ | 类型系统 | [TypeScript](https://www.typescriptlang.org/) (strict mode) |
978
+ | UI 组件库 | [Ant Design](https://ant.design/) |
979
+ | 样式方案 | [antd-style](https://ant-design.github.io/antd-style/) / [Tailwind CSS](https://tailwindcss.com/) |
980
+ | 路由 | [React Router v7](https://reactrouter.com/) (约定式) |
981
+ | 状态管理 | [Zustand](https://zustand-demo.pmnd.rs/) (Slice 架构) |
982
+ | 请求层 | [Axios](https://axios-http.com/) + [TanStack React Query](https://tanstack.com/query/) |
983
+ | 微前端 | [Garfish](https://garfishjs.org/) |
984
+ | 包管理 | [pnpm](https://pnpm.io/) |
985
+
986
+ ## License
987
+
988
+ ISC
package/dist/index.js CHANGED
@@ -147,6 +147,7 @@ var index = async ({
147
147
  initialValue: ERegistry.npm
148
148
  });
149
149
  };
150
+ let shouldInitGitPrompt = false;
150
151
  const selectMicroFrontend = async () => {
151
152
  const enableMicro = await confirm({
152
153
  message: "\u662F\u5426\u542F\u7528\u5FAE\u524D\u7AEF\uFF08Garfish\uFF09\uFF1F",
@@ -171,6 +172,16 @@ var index = async ({
171
172
  exitPrompt();
172
173
  }
173
174
  };
175
+ const selectGitInit = async () => {
176
+ const initGitAnswer = await confirm({
177
+ message: "\u662F\u5426\u521D\u59CB\u5316 Git \u4ED3\u5E93\uFF1F",
178
+ initialValue: false
179
+ });
180
+ if (isCancel(initGitAnswer)) {
181
+ exitPrompt();
182
+ }
183
+ shouldInitGitPrompt = !!initGitAnswer;
184
+ };
174
185
  const selectGitTemplate = async () => {
175
186
  const gitTemplate2 = await text({
176
187
  message: "\u8BF7\u8F93\u5165 Git \u6A21\u677F\u540D\u79F0",
@@ -229,6 +240,7 @@ var index = async ({
229
240
  exitPrompt();
230
241
  }
231
242
  await selectMicroFrontend();
243
+ await selectGitInit();
232
244
  outro(utils.chalk.green(`\u2728 \u4E00\u5207\u5C31\u7EEA, \u5F00\u59CB\u521B\u5EFA\u9879\u76EE...`));
233
245
  };
234
246
  const useDefaultData = !!args.default;
@@ -295,7 +307,7 @@ Git\u6A21\u677F: ${gitTemplate}
295
307
  const monorepoRoot = await detectMonorepoRoot({ target });
296
308
  const inMonorepo = !!monorepoRoot;
297
309
  const projectRoot = inMonorepo ? monorepoRoot : target;
298
- const shouldInitGit = args.git !== false;
310
+ const shouldInitGit = args.git === true || shouldInitGitPrompt;
299
311
  const withHusky = shouldInitGit && !inMonorepo;
300
312
  let pnpmExtraNpmrc = "";
301
313
  const isPnpm = npmClient === "pnpm" /* pnpm */;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@4399ywkf/cli",
3
- "version": "2.0.8",
3
+ "version": "2.0.10",
4
4
  "description": "运维开发部脚手架",
5
5
  "main": "dist/index.js",
6
6
  "bin": {