基于 React Native/Expo 项目的持续集成(CI)最佳实践配置指南

目录


概述

本项目的 CI 配置包含以下核心功能:

🔍 检查项

检查项 触发时机 用途
TypeScript 类型检查 Pre-commit 确保类型安全
ESLint 代码规范 Pre-commit 代码风格和质量
Prettier 代码格式化 Pre-commit 统一代码格式
提交消息规范 Commit-msg 规范化 Git 提交
分支保护 Pre-commit 防止直接提交到主分支
依赖自动安装 Post-merge 保持依赖同步
单元测试 手动/CI 确保代码质量

📦 依赖包

json 复制代码
{
  "husky": "^9.1.5",
  "lint-staged": "^15.2.9",
  "@commitlint/cli": "^19.2.2",
  "@commitlint/config-conventional": "^19.2.2",
  "eslint": "^9.28.0",
  "prettier": "^3.3.3",
  "typescript": "^5.8.3",
  "jest": "^29.7.0"
}

Git Hooks 配置

1. Husky 设置

安装和初始化

bash 复制代码
# 安装依赖
pnpm add -D husky

# 初始化 husky
pnpm exec husky init

目录结构

bash 复制代码
.husky/
├── _/                  # Husky 内部文件
├── common.sh          # 共享脚本
├── pre-commit         # 提交前检查
├── commit-msg         # 提交消息检查
└── post-merge         # 合并后处理

2. Pre-commit Hook

文件:.husky/pre-commit

bash 复制代码
. "$(dirname "$0")/common.sh"

echo "===\n>> Checking branch name..."

# 分支保护
if [[ -z $SKIP_BRANCH_PROTECTION ]]; then
    BRANCH=$(git rev-parse --abbrev-ref HEAD)
    PROTECTED_BRANCHES="^(main|master)"

    if [[ $BRANCH =~ $PROTECTED_BRANCHES ]]; then
        echo ">> Direct commits to the $BRANCH branch are not allowed."
        exit 1
    fi
fi

echo ">> Linting your files and fixing them if needed..."

# TypeScript 类型检查
pnpm type-check

# 代码规范检查和自动修复
pnpm lint-staged

功能:

  • ✅ 阻止直接提交到 main/master 分支
  • ✅ 运行 TypeScript 类型检查
  • ✅ 对暂存文件运行 ESLint 和 Prettier

3. Commit-msg Hook

文件:.husky/commit-msg

bash 复制代码
pnpm commitlint --edit $1

功能:

  • ✅ 验证提交消息格式符合 Conventional Commits 规范

4. Post-merge Hook

文件:.husky/post-merge

bash 复制代码
function changed {
    git diff --name-only HEAD@{1} HEAD | grep "^$1" >/dev/null 2>&1
}

echo 'Checking for changes in pnpm-lock.yaml...'

if changed 'pnpm-lock.yaml'; then
    echo "📦 pnpm-lock.yaml changed. Installing dependencies..."
    pnpm install
fi

echo 'You are up to date :)'

功能:

  • ✅ 检测依赖变化,自动运行 pnpm install

5. Common Shell 脚本

文件:.husky/common.sh

bash 复制代码
command_exists() {
    command -v "$1" >/dev/null 2>&1
}

# Windows Git Bash 兼容性
if command_exists winpty && test -t 1; then
    exec </dev/tty
fi

代码质量检查

1. Lint-staged 配置

文件:lint-staged.config.js

javascript 复制代码
module.exports = {
  // TypeScript/JavaScript 文件
  '**/*.{js,jsx,ts,tsx}': (filenames) => [
    `npx eslint --fix ${filenames
      .map((filename) => `"${filename}"`)
      .join(' ')}`,
  ],

  // Markdown 和 JSON 文件
  '**/*.(md|json)': (filenames) =>
    `npx prettier --write ${filenames
      .map((filename) => `"${filename}"`)
      .join(' ')}`,

  // 翻译文件特殊处理
  'src/translations/*.(json)': (filenames) => [
    `npx eslint --fix ${filenames
      .map((filename) => `"${filename}"`)
      .join(' ')}`,
  ],
};

功能:

  • 只检查暂存的文件(提高性能)
  • 自动修复可修复的问题
  • 支持多种文件类型

2. Commitlint 配置

文件:commitlint.config.js

javascript 复制代码
module.exports = {
  extends: ['@commitlint/config-conventional'],
};

提交消息格式:

arduino 复制代码
<type>: <description>

[optional body]

[optional footer]

允许的 type 类型:

Type 说明 示例
feat 新功能 feat: 添加用户登录功能
fix Bug 修复 fix: 修复登录页面闪退问题
docs 文档更新 docs: 更新 API 文档
style 代码格式(不影响功能) style: 格式化代码缩进
refactor 重构 refactor: 重构用户服务模块
perf 性能优化 perf: 优化列表渲染性能
test 测试相关 test: 添加登录组件单元测试
chore 构建/工具相关 chore: 更新依赖版本
ci CI 配置 ci: 添加 GitHub Actions 配置
build 构建系统 build: 优化打包配置
revert 回滚提交 revert: 回滚登录功能

3. ESLint 配置

文件:eslint.config.mjs

核心规则:

javascript 复制代码
export default defineConfig([
  // 全局忽略
  globalIgnores([
    'dist/*',
    'node_modules',
    'coverage',
    'android',
    'ios',
    '.expo',
  ]),

  // 核心规则
  {
    rules: {
      'max-params': ['error', 3], // 最多3个参数
      'max-lines-per-function': ['error', 300], // 函数最多300行
      'unicorn/filename-case': [
        'error',
        {
          case: 'kebabCase',
        },
      ], // kebab-case 命名
      'simple-import-sort/imports': 'error', // 自动排序 imports
      'unused-imports/no-unused-imports': 'error', // 禁止未使用的导入
      'import/no-cycle': ['error'], // 禁止循环依赖
      '@typescript-eslint/consistent-type-imports': [
        'warn',
        { prefer: 'type-imports' },
      ], // 强制使用 type imports
    },
  },

  // TypeScript 特定配置
  {
    files: ['**/*.ts', '**/*.tsx'],
    languageOptions: {
      parser: parser,
      parserOptions: {
        project: './tsconfig.json',
      },
    },
  },

  // 测试文件配置
  {
    files: ['**/__tests__/**/*', '**/*.test.*'],
    plugins: { 'testing-library': testingLibrary },
  },
]);

4. Prettier 配置

文件:.prettierrc.js

javascript 复制代码
module.exports = {
  singleQuote: true, // 使用单引号
  endOfLine: 'auto', // 自动行尾符
  trailingComma: 'es5', // ES5 尾随逗号
};

5. TypeScript 配置

文件:tsconfig.json

json 复制代码
{
  "extends": "expo/tsconfig.base",
  "compilerOptions": {
    "strict": true, // 严格模式
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"] // 路径别名
    },
    "esModuleInterop": true,
    "checkJs": true // 检查 JS 文件
  },
  "exclude": ["node_modules", "android", "ios"]
}

6. Jest 配置

文件:jest.config.js

javascript 复制代码
module.exports = {
  preset: 'jest-expo',
  setupFilesAfterEnv: ['<rootDir>/jest-setup.ts'],
  testMatch: ['**/?(*.)+(spec|test).ts?(x)'],

  // 代码覆盖率收集
  collectCoverageFrom: [
    'src/**/*.{ts,tsx}',
    '!**/coverage/**',
    '!**/node_modules/**',
  ],

  // 报告器
  reporters: [
    'default',
    ['github-actions', { silent: false }],
    'summary',
    [
      'jest-junit',
      {
        outputDirectory: 'coverage',
        outputName: 'jest-junit.xml',
      },
    ],
  ],

  // 路径映射
  moduleNameMapper: {
    '^@/(.*)$': '<rootDir>/src/$1',
  },
};

配置文件详解

Package.json Scripts

json 复制代码
{
  "scripts": {
    // 代码检查
    "lint": "eslint . --ext .js,.jsx,.ts,.tsx",
    "type-check": "tsc --noEmit",
    "lint:translations": "eslint ./src/translations/ --fix --ext .json",

    // 测试
    "test": "jest",
    "test:ci": "pnpm run test --coverage",
    "test:watch": "pnpm run test --watch",

    // 完整检查
    "check-all": "pnpm run lint && pnpm run type-check && pnpm run lint:translations && pnpm run test",

    // Git hooks
    "prepare": "husky"
  }
}

快速开始

在新项目中配置 CI

1. 安装依赖

bash 复制代码
pnpm add -D husky lint-staged @commitlint/cli @commitlint/config-conventional
pnpm add -D eslint prettier typescript jest
pnpm add -D @typescript-eslint/parser @typescript-eslint/eslint-plugin
pnpm add -D eslint-plugin-prettier eslint-config-prettier

2. 初始化 Husky

bash 复制代码
pnpm exec husky init

3. 创建配置文件

复制以下配置文件到项目根目录:

bash 复制代码
# 必需的配置文件
├── .husky/
│   ├── pre-commit
│   ├── commit-msg
│   ├── post-merge
│   └── common.sh
├── lint-staged.config.js
├── commitlint.config.js
├── eslint.config.mjs
├── .prettierrc.js
├── tsconfig.json
└── jest.config.js

4. 添加 NPM Scripts

package.json 中添加:

json 复制代码
{
  "scripts": {
    "prepare": "husky",
    "lint": "eslint . --ext .js,.jsx,.ts,.tsx",
    "type-check": "tsc --noEmit",
    "test": "jest",
    "check-all": "pnpm run lint && pnpm run type-check && pnpm run test"
  }
}

5. 设置 Git Hooks 可执行权限

bash 复制代码
chmod +x .husky/pre-commit
chmod +x .husky/commit-msg
chmod +x .husky/post-merge

6. 测试配置

bash 复制代码
# 测试 lint
pnpm lint

# 测试类型检查
pnpm type-check

# 测试提交(会触发所有 hooks)
git add .
git commit -m "test: 测试 CI 配置"

提交代码流程

标准提交流程

bash 复制代码
# 1. 确保在功能分支上
git checkout -b feat/your-feature-name

# 2. 添加修改的文件
git add .

# 3. 提交(使用规范的提交消息)
git commit -m "feat: 添加新功能"

# 自动触发:
# ✓ 分支检查
# ✓ TypeScript 类型检查
# ✓ ESLint 自动修复
# ✓ Prettier 格式化
# ✓ 提交消息格式验证

绕过检查(仅紧急情况)

bash 复制代码
# 跳过分支保护
SKIP_BRANCH_PROTECTION=1 git commit -m "fix: 紧急修复"

# 跳过所有 pre-commit hooks
git commit --no-verify -m "fix: 紧急修复"

⚠️ 不推荐在正常开发中使用


常见问题

Q1: 提交时报错 "Direct commits to main branch are not allowed"

原因: 你在 mainmaster 分支上提交

解决方案:

bash 复制代码
# 创建新分支
git checkout -b feat/your-feature

# 或者跳过检查(不推荐)
SKIP_BRANCH_PROTECTION=1 git commit -m "your message"

Q2: TypeScript 类型检查失败

原因: 代码存在类型错误

解决方案:

bash 复制代码
# 查看具体错误
pnpm type-check

# 修复类型错误后重新提交

Q3: ESLint 错误无法自动修复

原因: 某些规则需要手动修复

解决方案:

bash 复制代码
# 查看详细错误
pnpm lint

# 尝试自动修复
pnpm lint --fix

# 手动修复剩余问题

Q4: 提交消息格式错误

错误示例:

bash 复制代码
git commit -m "add new feature"  # ❌ 缺少 type
git commit -m "Add new feature"  # ❌ 首字母大写

正确示例:

bash 复制代码
git commit -m "feat: add new feature"      # ✅
git commit -m "fix: resolve login issue"   # ✅

Q5: Husky hooks 不生效

解决方案:

bash 复制代码
# 重新安装 husky
rm -rf .husky
pnpm exec husky init

# 重新创建 hooks
# 复制配置文件到 .husky/

# 确保可执行权限
chmod +x .husky/*

Q6: 依赖没有自动安装

原因: post-merge hook 没有正确配置

解决方案:

bash 复制代码
# 手动安装
pnpm install

# 检查 .husky/post-merge 是否存在
ls -la .husky/post-merge

最佳实践

✅ 推荐做法

  1. 永远在功能分支上工作

    bash 复制代码
    git checkout -b feat/feature-name
  2. 提交前本地测试

    bash 复制代码
    pnpm check-all
  3. 编写清晰的提交消息

    bash 复制代码
    feat: 用户认证功能
    
    - 添加登录页面
    - 实现 JWT token 验证
    - 添加用户状态管理
  4. 小步提交,频繁提交

    • 每个提交只包含一个逻辑变更
    • 便于代码审查和回滚
  5. 定期运行完整检查

    bash 复制代码
    pnpm check-all

❌ 避免做法

  1. ❌ 频繁使用 --no-verify
  2. ❌ 在 main 分支直接提交
  3. ❌ 不遵循提交消息规范
  4. ❌ 提交未格式化的代码
  5. ❌ 跳过类型检查和测试

维护和更新

定期任务

bash 复制代码
# 每月更新依赖
pnpm update

# 检查过时的依赖
pnpm outdated

# 更新主要版本(谨慎)
pnpm update --latest

团队同步

bash 复制代码
# 拉取最新代码后
git pull
# post-merge hook 会自动安装依赖

# 如果配置文件更新
pnpm install

总结

通过这套 CI 配置,你的项目将获得:

  • 自动化代码质量检查
  • 统一的代码风格
  • 规范的提交历史
  • 早期发现问题
  • 更好的团队协作

这些配置可以直接应用于任何 React Native/Expo 项目,也可以根据项目需求进行调整。


参考资源

相关推荐
G_G#4 分钟前
纯前端js插件实现同一浏览器控制只允许打开一个标签,处理session变更问题
前端·javascript·浏览器标签页通信·只允许一个标签页
@大迁世界20 分钟前
TypeScript 的本质并非类型,而是信任
开发语言·前端·javascript·typescript·ecmascript
GIS之路28 分钟前
GDAL 实现矢量裁剪
前端·python·信息可视化
是一个Bug32 分钟前
后端开发者视角的前端开发面试题清单(50道)
前端
Amumu1213834 分钟前
React面向组件编程
开发语言·前端·javascript
持续升级打怪中1 小时前
Vue3 中虚拟滚动与分页加载的实现原理与实践
前端·性能优化
GIS之路1 小时前
GDAL 实现矢量合并
前端
hxjhnct1 小时前
React useContext的缺陷
前端·react.js·前端框架
前端 贾公子1 小时前
从入门到实践:前端 Monorepo 工程化实战(4)
前端
菩提小狗1 小时前
Sqlmap双击运行脚本,双击直接打开。
前端·笔记·安全·web安全