Files
RustUI/CONVENTIONS.md
T
2026-05-28 14:18:56 +08:00

14 KiB
Raw Blame History

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

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) 模板

---
nav: 组件
group: 通用
title: Button 按钮
description: 用于触发操作的按钮组件。
---

## 代码演示

### 基础用法

```tsx | pure
/**
 * title: 基础按钮
 * description: 使用 `variant` 切换按钮风格。
 */
import { Button } from '@nova/ui/button'

export default () => (
  <Button variant="solid" size="md">
    确认
  </Button>
)

变体

<Button variant="solid">Solid</Button>
<Button variant="outline">Outline</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="glass">Glass</Button>

API

```

3.3 dumi 文档规则

1. 每个组件目录必须包含 index.md
2. 每个 demo 必须用 ```tsx | pure 声明(禁止用 react,避免 dumi 额外包裹)
3. demo 必须有 title,可选 description
4. 必须用 <API id="ComponentName" /> 生成 API 表格(依赖 apiParser 插件)
5. 组件分类:通用 / 录入 / 展示 / 反馈 / 导航 / 图表

四、father 配置约定

4.1 .fatherrc.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 导出配置

{
  "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 命名规则

/* 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 组件引用

// ✅ AI 正确用法
import styles from './Button.module.css'

<div className={styles.wrapper}>
  <span className={styles.iconLeft}>{icon}</span>
</div>

// ❌ 禁止:styles['wrapper']AI 容易拼错字符串)

5.3 全局 CSS

// theme/global.css — 引入 normalize + CSS 变量
// 在 dumi 的 .dumirc.ts 中通过 extraBabelPlugins 或手动 import

// theme/dark.css — 暗色模式变量
// 通过 html[data-theme='dark'] 选择器作用

六、Monorepo 约定

6.1 pnpm-workspace.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

// .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'],
  },
}
// .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',
    ]],
  },
}

// 格式: <type>(<scope>): <subject>
// 示例: feat(Button): add glass variant

7.3 lint-staged

// package.json
{
  "lint-staged": {
    "*.{ts,tsx}": ["eslint --fix", "prettier --write"],
    "*.{css,json,md}": ["prettier --write"]
  }
}

八、测试约定

8.1 vitest.config.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 测试规范

// 每个组件至少覆盖:
// 1. 默认渲染
import { render, screen } from '@testing-library/react'

it('renders with default props', () => {
  render(<Button>Click</Button>)
  expect(screen.getByRole('button')).toHaveTextContent('Click')
})

// 2. Props 组合
it.each(['solid', 'outline', 'ghost'] as const)(
  'renders variant="%s" correctly',
  variant => {
    const { container } = render(<Button variant={variant} />)
    expect(container.querySelector('button')).toHaveAttribute('data-variant', variant)
  }
)

// 3. 事件回调
it('fires onClick when clicked', () => {
  const onClick = vi.fn()
  render(<Button onClick={onClick} />)
  screen.getByRole('button').click()
  expect(onClick).toHaveBeenCalledTimes(1)
})

// 4. 边界(disabled、loading、空 children
it('does not fire onClick when disabled', () => {
  const onClick = vi.fn()
  render(<Button onClick={onClick} disabled />)
  screen.getByRole('button').click()
  expect(onClick).not.toHaveBeenCalled()
})

九、发布流程约定

9.1 Changesets

# 版本变更三步走

# 1. 开发完,记变更
pnpm changeset
# → 选择变更类型 (patch / minor / major)
# → 填写 changelog 描述

# 2. 统一版本
pnpm changeset version
# → 自动更新版本号 + 生成 CHANGELOG.md

# 3. 发布
pnpm changeset publish

9.2 GitHub Actions

# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
      - run: pnpm install
      - run: pnpm lint

  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
      - run: pnpm install
      - run: pnpm test -- --coverage

  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
      - run: pnpm install
      - run: pnpm build
      - run: pnpm docs:build
# .github/workflows/release.yml
name: Release
on:
  push:
    branches: [main]
concurrency: ${{ github.workflow }}-${{ github.ref }}
jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
      - run: pnpm install
      - run: pnpm build
      - name: Publish
        uses: changesets/action@v1
        with:
          publish: pnpm changeset publish
        env:
          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

十、Git 分支与工作流

分支 用途
main 稳定版,只接受 PR merge
feat/xxx 新组件/功能
fix/xxx Bug 修复
docs/xxx 文档改进
chore/xxx 工程配置
工作流:
1. 从 main 创建 feat/xxx
2. 开发 + 测试
3. PR → main(自动 CI 检查)
4. Merge → 触发 changesets → 发布

十一、提前准备清单(初始项目搭建步骤)

# 1. 初始化项目
mkdir novaui && cd novaui
pnpm init

# 2. 安装核心依赖
pnpm add -D dumi father vitest @testing-library/react \
  @testing-library/jest-dom jsdom \
  eslint prettier eslint-config-prettier \
  @typescript-eslint/eslint-plugin @typescript-eslint/parser \
  husky lint-staged @commitlint/cli @commitlint/config-conventional \
  @changesets/cli

# 3. 安装运行时依赖
pnpm add react react-dom
pnpm add -D @types/react @types/react-dom

# 4. 创建核心配置文件
# .dumirc.ts, .fatherrc.ts, vitest.config.ts,
# tsconfig.json, .eslintrc.cjs, .prettierrc,
# .commitlintrc.cjs, pnpm-workspace.yaml

# 5. 初始化 git + husky
git init
pnpm husky init

# 6. 初始化 changesets
pnpm changeset init

# 7. 创建目录骨架
mkdir -p src/components src/theme src/hooks src/shared docs/guide docs/components

十二、AI 生成检查清单

每次 AI 生成代码后,按此检查:

[ ] import 路径正确:@nova/ui/组件名(全小写 kebab-case
[ ] 样式引用正确:import styles from './Component.module.css'
[ ] 使用 data-* 属性标记状态,而非 class 拼接
[ ] 所有 prop 有类型定义(联合类型或 interface)
[ ] 所有 prop 有默认值(非必填的)
[ ] 测试覆盖渲染 + 事件 + 边界
[ ] 文档 index.md 有 demo + API 表格
[ ] 无 any 类型
[ ] 无 switch-case(代之以对象映射)
[ ] 无魔法字符串

提前约定越细,AI 犯错越少。此文档即 Nova 项目的"宪法",所有代码生成以此为据。