# Nova 项目约定 (Project Conventions) > dumi + father 技术栈的 AI-Native 组件库工程规范。 > 所有配置在此预先约定,AI 生成代码时按此执行。 --- ## 一、技术栈确认 | 领域 | 方案 | 版本 | |------|------|------| | 包管理 | pnpm | >= 9 | | Monorepo | pnpm workspace | — | | 文档站点 | dumi | 2.x | | 组件构建 | father | 4.x | | 测试 | Vitest + @testing-library/react | — | | Lint | ESLint + Prettier | — | | Git Hooks | husky + lint-staged | — | | 版本管理 | Changesets | — | | 样式方案 | **CSS Modules** | — | | 语言 | TypeScript (strict) | 5.x | ### 为什么选 CSS Modules? | 方案 | dumi 兼容 | father 兼容 | AI 友好度 | 运行时开销 | |------|:---------:|:-----------:|:---------:|:---------:| | CSS Modules | ✅ 原生 | ✅ 原生 | ★★★ (import styles 清晰) | 0 | | vanilla-extract | ⚠️ 需插件 | ⚠️ 需插件 | ★★ (编译时,调试困难) | 0 | | styled-components | ✅ | ⚠️ 需配置 | ★ (模板字符串 AI 常出错) | 有 | | Tailwind | ✅ | ✅ | ★ (类名太长 AI 难控制) | 0 | **结论:CSS Modules 零配置、零运行时、AI 最可控。** --- ## 二、项目结构 ``` novaui/ ├── .husky/ │ ├── pre-commit # lint-staged │ └── commit-msg # commitlint ├── .github/ │ └── workflows/ │ ├── ci.yml # lint + test + build │ └── release.yml # 发布 npm ├── docs/ # dumi 站点文档 │ ├── index.md # 首页 │ ├── guide/ │ │ ├── getting-started.md │ │ ├── theme.md │ │ └── faq.md │ └── components/ # 组件概览页 │ └── index.md ├── src/ │ ├── components/ │ │ ├── Button/ │ │ │ ├── index.ts │ │ │ ├── Button.tsx │ │ │ ├── Button.module.css # ← CSS Modules │ │ │ ├── Button.test.tsx │ │ │ └── index.md # ← dumi 文档(关键) │ │ └── ... │ ├── hooks/ │ ├── theme/ │ │ ├── tokens.ts │ │ ├── dark.css │ │ └── global.css │ ├── charts/ # 可视化组件 │ │ ├── LineChart/ │ │ └── ... │ └── index.ts # 按需导出不做 barrel export ├── .dumirc.ts # dumi 配置 ├── .fatherrc.ts # father 配置 ├── vitest.config.ts ├── tsconfig.json ├── .eslintrc.cjs ├── .prettierrc ├── .commitlintrc.cjs ├── package.json ├── pnpm-workspace.yaml ├── .npmrc └── README.md ``` --- ## 三、dumi 配置约定 ### 3.1 `.dumirc.ts` ```ts import { defineConfig } from 'dumi' export default defineConfig({ themeConfig: { name: 'Nova', logo: '/logo.png', nav: [ { title: '指南', link: '/guide' }, { title: '组件', link: '/components' }, { title: '图表', link: '/charts' }, ], socialLinks: { github: 'https://github.com/user/novaui', }, }, // 自动 API 表格(必须,AI 生成组件时直接能看到类型) apiParser: {}, resolve: { // doc 目录下的 .md 作为文档路由 docDirs: ['docs'], // 组件目录下的 index.md 作为组件文档 atomDirs: [ { type: 'component', dir: 'src/components' }, { type: 'chart', dir: 'src/charts' }, ], }, // 约定输出 outputPath: 'dist-docs', }) ``` ### 3.2 组件文档 (`index.md`) 模板 ```md --- nav: 组件 group: 通用 title: Button 按钮 description: 用于触发操作的按钮组件。 --- ## 代码演示 ### 基础用法 ```tsx | pure /** * title: 基础按钮 * description: 使用 `variant` 切换按钮风格。 */ import { Button } from '@nova/ui/button' export default () => ( ) ``` ### 变体 ```tsx | pure ``` ## API ``` ### 3.3 dumi 文档规则 ``` 1. 每个组件目录必须包含 index.md 2. 每个 demo 必须用 ```tsx | pure 声明(禁止用 react,避免 dumi 额外包裹) 3. demo 必须有 title,可选 description 4. 必须用 生成 API 表格(依赖 apiParser 插件) 5. 组件分类:通用 / 录入 / 展示 / 反馈 / 导航 / 图表 ``` --- ## 四、father 配置约定 ### 4.1 `.fatherrc.ts` ```ts import { defineConfig } from 'father' export default defineConfig({ // Bundless 模式:保持源码目录结构,每个组件独立产物(Tree-shaking 最佳) cjs: { output: 'dist/cjs', platform: 'browser', }, esm: { output: 'dist/esm', platform: 'browser', // 保留 .module.css 作为 CSS Modules extraBabelPlugins: [ // 确保 .module.css 被正确处理 ], }, // 自动生成 .d.ts typescript: {}, // UMD 构建(可选) umd: { output: 'dist/umd', name: 'NovaUI', }, }) ``` ### 4.2 father 构建规则 ``` 1. 使用 Bundless 模式(非 Bundle),保持每个组件独立文件 → 用户可 import { Button } from '@nova/ui/button' 2. 产物格式:esm + cjs(双格式) 3. CSS Modules 文件 (.module.css) 自动保留,build 后生成对应的 .css 4. TypeScript 生成 .d.ts 类型定义 5. 外部依赖:react、react-dom 标记为 peerDependencies,不打包 ``` ### 4.3 package.json 导出配置 ```json { "name": "@nova/ui", "type": "module", "main": "dist/cjs/index.js", "module": "dist/esm/index.js", "types": "dist/esm/index.d.ts", "exports": { ".": { "import": "./dist/esm/index.js", "require": "./dist/cjs/index.js" }, "./*": { "import": "./dist/esm/*/index.js", "require": "./dist/cjs/*/index.js" } }, "sideEffects": [ "**/*.css" ], "files": [ "dist" ], "peerDependencies": { "react": ">=18", "react-dom": ">=18" } } ``` --- ## 五、CSS Modules 约定 ### 5.1 命名规则 ```css /* Button.module.css */ /* ✅ 用 camelCase,在 JS 中 import 后直接 styles.xxx */ .wrapper { } .iconLeft { } .iconRight { } /* ✅ 全局覆盖用 :global */ :global(.nv-theme-dark) .wrapper { background: #1a1a3e; } /* ✅ data-* 状态样式 */ .wrapper[data-variant='solid'] { } .wrapper[data-variant='outline'] { } .wrapper[data-size='lg'] { } .wrapper[data-loading] { } ``` ### 5.2 组件引用 ```tsx // ✅ AI 正确用法 import styles from './Button.module.css'
{icon}
// ❌ 禁止:styles['wrapper'](AI 容易拼错字符串) ``` ### 5.3 全局 CSS ```ts // theme/global.css — 引入 normalize + CSS 变量 // 在 dumi 的 .dumirc.ts 中通过 extraBabelPlugins 或手动 import // theme/dark.css — 暗色模式变量 // 通过 html[data-theme='dark'] 选择器作用 ``` --- ## 六、Monorepo 约定 ### 6.1 pnpm-workspace.yaml ```yaml packages: - 'packages/*' - 'apps/*' ``` ### 6.2 当前是单包,但结构保留扩展性 ``` novaui/ ├── packages/ │ ├── core/ # UI 组件(@nova/ui) │ ├── charts/ # 图表组件(@nova/charts) │ ├── theme/ # 主题包(@nova/theme) │ └── utils/ # 工具(@nova/utils) └── apps/ └── docs/ # dumi 文档站点 ``` > 初期可扁平,但目录名和 npm scope 预先约定好。 --- ## 七、代码质量工具约定 ### 7.1 ESLint + Prettier ```jsonc // .eslintrc.cjs module.exports = { extends: [ 'eslint:recommended', 'plugin:@typescript-eslint/recommended', 'plugin:react/recommended', 'plugin:react-hooks/recommended', 'prettier', // 关闭冲突规则 ], rules: { 'react/react-in-jsx-scope': 'off', // React 18+ 'react/prop-types': 'off', // TypeScript 已覆盖 '@typescript-eslint/no-explicit-any': 'error', '@typescript-eslint/consistent-type-definitions': ['error', 'interface'], }, } ``` ```jsonc // .prettierrc { "semi": false, "singleQuote": true, "trailingComma": "all", "printWidth": 100, "arrowParens": "avoid" } ``` ### 7.2 commitlint ``` // .commitlintrc.cjs module.exports = { extends: ['@commitlint/config-conventional'], rules: { 'type-enum': [2, 'always', [ 'feat', 'fix', 'docs', 'style', 'refactor', 'test', 'chore', 'ci', ]], }, } // 格式: (): // 示例: feat(Button): add glass variant ``` ### 7.3 lint-staged ```jsonc // package.json { "lint-staged": { "*.{ts,tsx}": ["eslint --fix", "prettier --write"], "*.{css,json,md}": ["prettier --write"] } } ``` --- ## 八、测试约定 ### 8.1 vitest.config.ts ```ts import { defineConfig } from 'vitest/config' export default defineConfig({ test: { environment: 'jsdom', globals: true, setupFiles: ['./test-setup.ts'], include: ['src/**/*.test.{ts,tsx}'], coverage: { provider: 'v8', include: ['src/components/**/*.tsx', 'src/charts/**/*.tsx'], thresholds: { functions: 80, }, }, }, }) ``` ### 8.2 测试规范 ```tsx // 每个组件至少覆盖: // 1. 默认渲染 import { render, screen } from '@testing-library/react' it('renders with default props', () => { render() expect(screen.getByRole('button')).toHaveTextContent('Click') }) // 2. Props 组合 it.each(['solid', 'outline', 'ghost'] as const)( 'renders variant="%s" correctly', variant => { const { container } = render(