Skip to content

开发指南

本指南将帮助您了解如何在本地开发和贡献 Lerna Web UI 组件库。无论您是想添加新组件、修复 Bug 还是改进文档,这里都会提供详细的步骤说明。

🎯 开始之前

在开始开发之前,请确保您已经完成了环境搭建。如果您还没有安装和配置开发环境,请先参考 安装指南

快速检查清单

  • ✅ Node.js >= 18.0.0 已安装
  • ✅ pnpm >= 8.0.0 已安装
  • ✅ Git 已配置
  • ✅ 已克隆项目并安装依赖
  • ✅ 已成功运行 pnpm run bootstrap

🏗️ 项目结构

lerna-web-ui/
├── packages/              # 核心包目录
│   ├── shared/           # 共享常量、枚举、类型定义
│   ├── utils/            # 工具函数库
│   ├── core/             # 核心能力(composables, directives, plugins)
│   └── ui/               # UI 组件实现
├── docs/                 # 文档项目
├── tooling/              # 工具配置
│   ├── configs/          # ESLint、TypeScript 配置
│   ├── shared/           # 共享工具
│   └── unplugin/         # Unplugin 插件
├── scripts/              # 自动化脚本
│   ├── create-component.js  # 组件生成器
│   └── generate-docs.js     # 文档生成器
├── .husky/               # Git Hooks
├── .vscode/              # VS Code 配置
└── package.json          # 项目配置

🔨 开发工作流

1. 启动开发服务器

bash
# 并行启动所有包的开发服务
pnpm run dev

这会同时监听以下包的源码变化:

  • @lerna-web-ui/utils - 工具函数
  • @lerna-web-ui/shared - 共享类型
  • @lerna-web-ui/core - 核心逻辑
  • @lerna-web-ui/ui - UI 组件

2. 开发单个模块

如果只需要开发特定模块:

bash
# 开发 UI 组件库
cd packages/ui
pnpm run dev

# 开发核心包
cd packages/core
pnpm run dev

3. 构建模块

bash
# 构建所有模块
pnpm run build

# 单独构建 UI 包
pnpm run build:ui

# 单独构建 core 包
pnpm run build:core

🧩 组件开发

创建新组件

使用内置的组件生成器快速创建组件模板:

bash
# 在项目根目录执行
pnpm run new:component modal

这将自动生成以下文件:

packages/ui/src/components/modal/
├── Modal.vue      # Vue 组件文件
├── index.ts       # 导出文件
└── style.scss     # 样式文件

组件开发规范

1. Vue 组件文件结构

vue
<template>
  <div class="lerna-component-name">
    <slot />
  </div>
</template>

<script setup lang="ts">
/**
 * 组件描述
 * @version 1.0.0
 */

// Props 接口定义
export interface ComponentNameProps {
  /** 属性描述 */
  prop1?: string;
  /** 属性描述 */
  prop2?: boolean;
}

// Props 默认值
const props = withDefaults(defineProps<ComponentNameProps>(), {
  prop1: 'default',
  prop2: false,
});

// Emits 定义
const emit = defineEmits<{
  /**
   * @event 事件描述
   */
  (e: 'change', value: string): void;
}>();

// Slots 定义
defineSlots<{
  /** 默认内容 */
  default(): any;
}>();

// 组件逻辑
const handleClick = () => {
  emit('change', 'new value');
};
</script>

<style scoped lang="scss">
.lerna-component-name {
  // 组件样式
}
</style>

2. JSDoc 注释规范

typescript
export interface ButtonProps {
  /** 
   * @description 按钮类型
   * @default 'default'
   */
  type?: 'primary' | 'default' | 'danger';

  /** 
   * @description 按钮尺寸
   * @default 'medium'
   */
  size?: 'small' | 'medium' | 'large';

  /** 
   * @description 是否禁用
   * @default false
   */
  disabled?: boolean;
}

/**
 * @event click 点击按钮时触发
 * @param {MouseEvent} event 鼠标事件对象
 */
const emit = defineEmits<{
  (e: 'click', event: MouseEvent): void;
}>();

/**
 * @slot default 默认插槽
 * @slot icon 图标插槽
 */
defineSlots<{
  default(): any;
  icon(): any;
}>();

3. 样式命名规范

使用 BEM 命名约定:

scss
// 块(Block)
.lerna-button {}

// 元素(Element)
.lerna-button__icon {}

// 修饰符(Modifier)
.lerna-button--primary {}
.lerna-button--disabled {}

4. 使用设计令牌

始终使用 CSS 变量而非硬编码值:

scss
.lerna-button {
  // ✅ 好的做法
  padding: var(--lerna-spacing-3) var(--lerna-spacing-4);
  background-color: var(--lerna-color-primary);
  border-radius: var(--lerna-radius-md);
  
  // ❌ 避免的做法
  padding: 12px 16px;
  background-color: #3b82f6;
  border-radius: 6px;
}

更新组件导出

创建组件后,确保在以下位置正确导出:

1. 组件入口文件 (components/button/index.ts)

typescript
import Button from './Button.vue';
import type { App } from 'vue';

Button.install = (app: App) => {
  app.component(Button.name || 'LernaButton', Button);
};

Button.name = 'LernaButton';

export default Button;
export { Button };
export type { ButtonProps } from './Button.vue';

2. 组件索引文件 (components/index.ts)

typescript
import Button from './button';
import Input from './input';

export { Button, Input };
export type { ButtonProps, InputProps } from './button';
export default [Button, Input];

3. 包入口文件 (index.ts)

typescript
import './styles/index.scss';
import { Button, Input } from './components';

export { Button, Input };
export type { ButtonProps, InputProps } from './components';

const components = [Button, Input];

const install = (app: App): void => {
  components.forEach(component => {
    app.component(component.name || component.__name || '', component);
  });
};

export default {
  install,
  version: '__VERSION__',
};

🎨 样式系统开发

添加设计令牌

1. 颜色令牌 (styles/tokens/_colors.scss)

scss
:root {
  // 新增主题色
  --lerna-color-brand: #ff6b35;
  --lerna-color-brand-light: #ff8c5a;
  --lerna-color-brand-dark: #e55a2b;
}

2. 间距令牌 (styles/tokens/_spacing.scss)

scss
:root {
  // 新增间距
  --lerna-spacing-11: 2.75rem;  // 44px
  --lerna-spacing-12: 3rem;     // 48px
}

3. 圆角令牌 (styles/tokens/_spacing.scss)

scss
:root {
  // 新增圆角
  --lerna-radius-2xl: 1rem;     // 16px
  --lerna-radius-3xl: 1.5rem;   // 24px
}

创建 SCSS Mixins

styles/mixins/ 目录下创建可复用的样式逻辑:

scss
// styles/mixins/_flex.scss
@mixin flex-center {
  display: flex;
  align-items: center;
  justify-content: center;
}

@mixin flex-between {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

// styles/mixins/_responsive.scss
@mixin responsive($breakpoint) {
  @if $breakpoint == 'sm' {
    @media (max-width: 640px) { @content; }
  }
  @else if $breakpoint == 'md' {
    @media (max-width: 768px) { @content; }
  }
  @else if $breakpoint == 'lg' {
    @media (min-width: 1024px) { @content; }
  }
}

🧪 测试

单元测试(待实现)

bash
# 运行测试
pnpm run test

目前测试框架尚未配置,计划集成 Vitest + Testing Library。

手动测试

1. 在示例项目中测试

bash
# 进入示例项目
cd examples/vite-vue3

# 安装依赖
pnpm install

# 启动开发服务器
pnpm run dev

2. 在 Storybook 中测试

bash
# 进入 UI 包
cd packages/ui

# 启动 Storybook
pnpm run storybook

📝 文档开发

生成文档

bash
# 生成所有文档
pnpm run docs:generate

# 仅生成 UI 组件文档
pnpm run docs:generate:ui

# 仅生成核心包文档
pnpm run docs:generate:core

预览文档

bash
# 进入文档项目
cd docs

# 安装依赖
pnpm install

# 启动开发服务器
pnpm run dev

🔍 代码质量

类型检查

bash
# 检查所有包
pnpm run type-check

# 检查单个包
cd packages/ui
pnpm run type-check

代码检查

bash
# 检查所有包
pnpm run lint

# 仅检查根目录
pnpm run lint:root

# 仅检查样式
pnpm run lint:style

格式化代码

bash
# 格式化所有文件
pnpm run format

# 格式化特定文件
prettier --write "packages/ui/src/**/*.vue"

提交前验证

bash
# 运行完整的验证流程
pnpm run validate

这将依次执行:

  1. TypeScript 类型检查
  2. ESLint 代码检查
  3. Prettier 格式化

🤝 贡献流程

1. Fork 项目

bash
# 在 GitHub 上 Fork 项目
# 然后克隆到本地
git clone https://github.com/YOUR_USERNAME/lerna-web-ui.git
cd lerna-web-ui

2. 创建特性分支

bash
# 基于 main 分支创建新分支
git checkout -b feature/add-new-component

3. 开发与提交

bash
# 开发完成后,添加文件
git add packages/ui/src/components/new-component/

# 提交更改(遵循 Conventional Commits 规范)
git commit -m "feat: add NewComponent"

# 推送分支
git push origin feature/add-new-component

4. 创建 Pull Request

  1. 在 GitHub 上创建 PR
  2. 填写详细的 PR 描述
  3. 等待 CI 检查通过
  4. 请求代码审查
  5. 根据反馈进行修改
  6. 合并到主分支

Commit 信息规范

遵循 Conventional Commits 规范:

<type>(<scope>): <subject>

<body>

<footer>

Type 类型

  • feat: 新功能
  • fix: Bug 修复
  • docs: 文档更新
  • style: 代码格式调整
  • refactor: 重构
  • perf: 性能优化
  • test: 测试相关
  • chore: 构建/工具链相关

示例

bash
# 新功能
git commit -m "feat(ui): add Modal component"

# Bug 修复
git commit -m "fix(button): fix hover state on mobile"

# 文档更新
git commit -m "docs: update installation guide"

# 重构
git commit -m "refactor(core): simplify useTheme logic"

🚀 发布流程

发布前准备

bash
# 1. 确保所有测试通过
pnpm run validate

# 2. 构建所有模块
pnpm run build

# 3. 更新版本号(使用 Lerna)
pnpm exec lerna version

发布到 NPM

bash
# 发布所有包
pnpm exec lerna publish from-package

# 或发布特定包
pnpm exec lerna publish --scope=@lerna-web-ui/ui

发布到 GitHub

bash
# 创建 Git 标签
git tag -a v0.1.0 -m "Release v0.1.0"

# 推送标签
git push origin v0.1.0

# 在 GitHub 上创建 Release

🛠️ 工具与技巧

VS Code 配置

推荐的 .vscode/settings.json

json
{
  "editor.formatOnSave": true,
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
  },
  "[vue]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[typescript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[scss]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  }
}

调试技巧

1. 使用 DevTools

在浏览器中使用 Vue DevTools 检查组件状态和 Props。

2. 添加调试日志

typescript
const props = withDefaults(defineProps<ButtonProps>(), {
  type: 'default',
});

// 开发环境下打印日志
if (import.meta.env.DEV) {
  console.log('Button props:', props);
}

3. 使用 debugger

typescript
const handleClick = (event: MouseEvent) => {
  debugger; // 断点
  emit('click', event);
};

性能优化

1. 按需导入

确保配置了自动导入插件,避免全量引入。

2. Tree Shaking

使用 ESM 格式导出,支持 Tree Shaking:

typescript
// ✅ 好的做法
export { Button };
export type { ButtonProps };

// ❌ 避免的做法
export default Button;

3. 懒加载组件

typescript
const AsyncModal = defineAsyncComponent(() => 
  import('./Modal.vue')
);

📚 相关资源

内部资源

外部资源

❓ 常见问题

Q: 如何调试构建问题?

A: 使用 verbose 模式查看详细构建日志:

bash
pnpm run build:ui -- --debug

Q: 为什么类型检查报错?

A: 检查以下几点:

  1. 确保 TypeScript 版本正确(>= 5.0.0)
  2. 检查 tsconfig.json 路径配置
  3. 清除缓存重新构建:rm -rf node_modules/.vite && pnpm install

Q: 如何添加新的设计令牌?

A: 在对应的 tokens 文件中添加 CSS 变量,然后在组件中使用。

Q: 组件样式不生效怎么办?

A: 检查:

  1. 是否正确引入样式文件
  2. 是否使用了正确的 CSS 变量
  3. 是否有样式冲突或覆盖

💡 提示: 开发过程中遇到任何问题,欢迎通过 GitHub Issues 提问或参与讨论。我们鼓励开发者积极参与社区建设!

Released under the MIT License.