第2章:项目启动 - 使用Vite脚手架初始化项目与工程化配置

第2章:项目启动 - 使用Vite脚手架初始化项目与工程化配置

2.1 包管理工具的选择

在开始项目之前,让我们先解决一个基础但关键的问题:包管理工具的选择

包管理工具性能基准对比

  • 核心特性对比
特性 npm pnpm yarn
首次发布时间 2010 2017 2016
依赖管理方式 扁平化node_modules 内容寻址存储+符号链接 扁平化node_modules
锁文件 package-lock.json pnpm-lock.yaml yarn.lock
安装算法 顺序安装 并行安装+依赖共享 并行安装
磁盘空间使用 项目独立存储 全局共享存储 项目独立存储
Monorepo支持 基础支持 原生优秀支持 Workspaces支持
默认安全策略 默认宽松 严格依赖隔离 默认中等
  • 安装性能对比
yaml 复制代码
# 性能基准测试对比(相同项目)
npm install:  45.6s
yarn install: 38.2s  
pnpm install: 12.3s  # 速度提升3-4倍!
​
# 磁盘空间占用对比
npm:  450MB
yarn: 350MB
pnpm: 180MB  # 空间节省60%!

pnpm的核心原理:内容寻址存储与硬链接

  • 传统npm/yarn的问题

传统工具如npmyarn采用平铺或嵌套的node_modules结构,导致同一个依赖包可能在多个地方重复安装,这种结构不仅占用大量磁盘空间,还可能导致依赖版本冲突和所谓的"幽灵依赖"问题。

perl 复制代码
# 传统node_modules结构(嵌套或平铺)
node_modules/
├── package-a/
│   └── node_modules/
│       ├── lodash@4.17.21/
├── package-b/
│   └── node_modules/
│       ├── lodash@4.17.21/  # 重复安装!
  • pnpm的创新解决方案

通过内容寻址存储和符号链接技术,pnpm实现了真正的依赖共享,这种架构不仅解决了磁盘空间问题,还保证了依赖树的准确性和一致性。

bash 复制代码
# pnpm的虚拟存储结构
node_modules/
├── .pnpm/  # 虚拟存储目录(所有依赖的硬链接)
│   ├── lodash@4.17.21/
│   │   └── node_modules/lodash -> ~/.pnpm-store/v3/files/00/...
│   └── react@18.2.0/
│       └── node_modules/react -> ~/.pnpm-store/v3/files/01/...
├── lodash -> .pnpm/lodash@4.17.21/node_modules/lodash
└── react -> .pnpm/react@18.2.0/node_modules/react
  • pnpm的工作原理
  1. 内容寻址存储:基于文件内容的哈希值存储,相同内容只存一份
  2. 硬链接技术 :在项目node_modules中创建指向全局存储的链接
  3. 符号链接:维护真实的依赖树结构,避免幽灵依赖

基于上述分析,pnpm在存储效率与安装性能方面具有显著优势,故本项目采用 pnpm 作为首选包管理工具。

安装与配置pnpm

bash 复制代码
# 全局安装pnpm
npm install -g pnpm
​
# 验证安装
pnpm --version
​
# 配置pnpm(可选)
pnpm config set store-dir ~/.pnpm-store  # 设置全局存储位置

2.2 Vite脚手架深度解析

Vite代表了现代前端构建工具从"打包驱动"到"原生ESM按需编译"的范式转移,其核心价值在于重构开发体验。

Vite vs Webpack

要深入理解Vite的价值,首先需要回顾传统构建工具的工作方式。以Webpack为例,其工作流程可以概括为以下几个步骤:

markdown 复制代码
// Webpack的典型构建流程
1. 从入口文件(如 index.js)开始解析
2. 递归构建整个项目的依赖图谱
3. 将所有模块打包成一个或多个bundle文件
4. 启动开发服务器,将打包结果提供给浏览器

使用Webpack, 随着项目规模扩大,依赖模块数量增多,启动时间会显著延长。 热更新(HMR)需要重新构建部分或全部模块,导致响应延迟。

Vite采用了截然不同的设计思路,其核心理念是利用现代浏览器原生支持的ES模块(ESM)系统,实现按需编译与加载。其工作流程可以概括为以下几个步骤:

markdown 复制代码
// Vite的按需编译机制
1. 启动开发服务器(几乎是瞬间完成)
2. 当浏览器发起对某个模块的请求时,Vite才会对该模块进行实时编译
3. 仅返回当前请求的模块,无需等待整个应用构建完成
4. 热更新时仅重新编译发生变化的模块,实现毫秒级响应

Vite的核心优势在于,它利用了浏览器的原生ESM,省去了打包环节。同时,按需编译机制从根本上解决了启动和更新的性能瓶颈。这种架构上的根本差异,为开发者带来了颠覆性的体验提升。即便项目包含数百个组件和复杂的依赖关系Vite依然能够保持秒级的启动速度和毫秒级的热更新响应,极大提升了开发效率。

项目初始化与结构解析

下面,我们将使用Vite来创建React+TypeScript的项目,从中深入理解其项目结构的设计理念。

  • 创建项目
sql 复制代码
# 使用Vite官方脚手架创建项目
pnpm create vite backend-management-system --template react-ts

执行初始化命令后,根据交互提示选择相应配置项,可自动完成依赖安装与开发服务器启动。

如果未选择安装并启动,可以进入项目目录手动安装依赖并启动:

bash 复制代码
cd backend-management-system
​
# 安装依赖
pnpm install
​
# 启动项目
pnpm dev

项目创建完成后,我们可以看到Vite生成的目录结构具有清晰的层次和明确的职责划分:

csharp 复制代码
backend-management-system/
├── node_modules/           # 项目依赖(pnpm采用独特的磁盘存储结构,节省空间)
├── public/                 # 纯静态资源目录(该目录下的文件不会被Vite处理)
│   └── vite.svg           # 静态资源,构建时会直接复制到输出目录
├── src/                    # 源代码目录
│   ├── assets/            # 需要经过构建处理的资源文件(如图片、样式等)
│   │   └── react.svg      # 资源文件,会被Vite优化和处理
│   ├── components/        # React组件目录
│   │   └── HelloWorld.tsx # 示例组件文件
│   ├── App.css           # 应用级别的样式文件
│   ├── App.tsx           # 应用的根组件
│   ├── index.css         # 全局样式文件
│   ├── main.tsx          # 应用的入口文件
│   └── vite-env.d.ts     # Vite客户端类型定义,提供类型支持
├── .gitignore            # Git版本控制的忽略规则配置
├── eslint.config.js      # ESLint代码检查配置(采用新的扁平化配置格式)
├── index.html            # Vite的HTML入口文件(位于根目录是其特色之一)
├── package.json          # 项目配置文件,包含依赖和脚本定义
├── pnpm-lock.yaml        # pnpm包管理器的依赖锁文件
├── tsconfig.app.json     # 前端应用的TypeScript配置
├── tsconfig.json         # 根TypeScript配置文件
├── tsconfig.node.json    # Node.js环境下的TypeScript配置(用于工具链)
└── vite.config.ts        # Vite构建工具的配置文件

该目录结构遵循职责单一原则,实现类型系统全链路覆盖与工具链合理分层。

完善项目结构

src目录下,添加componentspageslayoutscomponentsutilshooksservicesstorestypes,添加完后的目录如下:

csharp 复制代码
backend-management-system/
├── node_modules/           # 项目依赖(pnpm采用独特的磁盘存储结构,节省空间)
├── public/                 # 纯静态资源目录(该目录下的文件不会被Vite处理)
│   └── vite.svg           # 静态资源,构建时会直接复制到输出目录
├── src/                    # 源代码目录
│   ├── assets/            # 需要经过构建处理的资源文件(如图片、样式等)
│   │   └── react.svg      # 资源文件,会被Vite优化和处理
│   ├── components/        # React组件目录
│   │   └── HelloWorld.tsx # 示例组件文件
│   ├── hooks/              # Hooks目录
│   ├── layouts/            # 布局目录
│   ├── pages/              # 功能页面目录
│   ├── services/           # Api目录
│   ├── stores/             # 状态共享目录
│   ├── types/              # 类型约束目录
│   ├── utils/              # 工具方法目录
│   ├── App.css           # 应用级别的样式文件
│   ├── App.tsx           # 应用的根组件
│   ├── index.css         # 全局样式文件
│   ├── main.tsx          # 应用的入口文件
│   └── vite-env.d.ts     # Vite客户端类型定义,提供类型支持
├── .gitignore            # Git版本控制的忽略规则配置
├── eslint.config.js      # ESLint代码检查配置(采用新的扁平化配置格式)
├── index.html            # Vite的HTML入口文件(位于根目录是其特色之一)
├── package.json          # 项目配置文件,包含依赖和脚本定义
├── pnpm-lock.yaml        # pnpm包管理器的依赖锁文件
├── tsconfig.app.json     # 前端应用的TypeScript配置
├── tsconfig.json         # 根TypeScript配置文件
├── tsconfig.node.json    # Node.js环境下的TypeScript配置(用于工具链)
└── vite.config.ts        # Vite构建工具的配置文件

Vite核心配置文件解析

Vite的配置文件是项目构建行为的控制中心,通过合理的配置可以极大优化开发和构建体验。在 vite.config.ts 中配置路径别名、开发服务器行为及生产构建策略,完整配置如下:

javascript 复制代码
import { defineConfig, loadEnv } from 'vite';
import react from '@vitejs/plugin-react';
import { fileURLToPath } from 'url';
import { dirname, resolve } from 'path';
​
// 获取当前文件目录(兼容ESM)
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
​
export default defineConfig(({ mode }) => {
  // 加载环境变量(可访问 .env 文件)
  const env = loadEnv(mode, process.cwd(), '');
​
  return {
    // ========== 插件配置 ==========
    plugins: [
      react({
        // 仅在开发环境启用Fast Refresh
        include: '**/*.{tsx,ts}',
        // Babel配置(可选优化)
        babel: {
          plugins: [
            // 生产环境自动移除console
            ...(mode === 'production' ? [['transform-remove-console', { exclude: ['error', 'warn'] }]] : []),
          ],
        },
      }),
    ],
​
    // ========== 路径别名 ==========
    resolve: {
      alias: {
        '@': resolve(__dirname, './src'),
        '@components': resolve(__dirname, './src/components'),
        '@pages': resolve(__dirname, './src/pages'),
        '@utils': resolve(__dirname, './src/utils'),
        '@hooks': resolve(__dirname, './src/hooks'),
        '@services': resolve(__dirname, './src/services'),
        '@stores': resolve(__dirname, './src/stores'),
        '@types': resolve(__dirname, './src/types'),
        '@assets': resolve(__dirname, './src/assets'),
        '@layouts': resolve(__dirname, './src/layouts'),
      },
      // 自动解析扩展名
      extensions: ['.ts', '.tsx', '.js', '.jsx', '.json'],
    },
​
    // ========== 开发服务器优化 ==========
    server: {
      port: Number(env.VITE_APP_PORT) || 3000, // 支持环境变量配置
      open: true,
      host: true, // 监听所有地址
      cors: true,
      // 代理配置示例
      // proxy: {
      //   '/api': {
      //     target: env.VITE_API_BASE_URL,
      //     changeOrigin: true,
      //     rewrite: (path) => path.replace(/^/api/, ''),
      //   },
      // },
    },
​
    // ========== 构建优化 ==========
    build: {
      // 输出目录(默认dist,可省略)
      outDir: 'dist',
​
      // 生成 source map(生产环境可关闭)
      sourcemap: mode !== 'production',
​
      // 提升chunk大小警告限制
      chunkSizeWarningLimit: 1600,
​
      // 输出目标(现代浏览器)
      target: 'esnext',
​
      // Rollup打包策略(智能分割)
      rollupOptions: {
        output: {
          // 智能代码分割(优于手动配置)
          manualChunks(id) {
            // React核心库单独打包
            if (id.includes('node_modules/react') || id.includes('node_modules/react-dom')) {
              return 'react-vendor';
            }
​
            // UI库单独打包(按需启用)
            if (id.includes('node_modules/@mui')) {
              return 'mui-vendor';
            }
​
            // 工具库单独打包
            if (id.includes('node_modules/lodash') || id.includes('node_modules/axios')) {
              return 'utils-vendor';
            }
            return undefined;
          },
​
          // 输出文件命名规范
          chunkFileNames: 'assets/js/[name]-[hash].js',
          entryFileNames: 'assets/js/[name]-[hash].js',
          assetFileNames: 'assets/[ext]/[name]-[hash].[ext]',
​
          // 压缩选项(Vite 5+默认使用esbuild,无需额外配置)
        },
​
        // 优化依赖(预构建)
        // 通常无需手动配置,Vite会自动处理
      },
​
      // CSS代码分割
      cssCodeSplit: true,
​
      // 启用/禁用 brotli 压缩大小报告
      reportCompressedSize: true,
​
      // 清空输出目录
      emptyOutDir: true,
    },
​
    // ========== CSS 相关 ==========
    css: {
      // 开发环境启用CSS sourcemap
      devSourcemap: true,
    },
​
    // ========== ESBuild 配置 ==========
    esbuild: {
      // 支持 JSX 注入(无需手动import React)
      jsxInject: `import React from 'react'`,
    },
​
    // ========== 性能优化 ==========
    performance: {
      // 预构建中最大文件大小
      maxAssetSize: 500000, // 500kb
      // 预构建中最大入口文件大小
      maxEntrypointSize: 500000,
    },
  };
});

此配置兼顾开发体验优化与生产构建性能,通过智能代码分割与资源策略确保构建产物的高效性与可维护性。

TypeScript配置架构解析

项目创建完成后,可以看到我们的项目中有多个TypeScript配置文件,分别是tsconfig.jsontsconfig.app.jsontsconfig.node.json。其中,tsconfig.json作为基础配置,被其他两个继承。tsconfig.app.json用于应用代码(src目录)。tsconfig.node.json用于Vite配置文件等(vite.config.ts)。三者的关系如下:

bash 复制代码
tsconfig.json       # TypeScript基础 配置
├── tsconfig.app.json         #前端 TypeScript 配置
└── tsconfig.node.json        # Node.js TypeScript 配置

下面我们将依次对三个文件做配置。

修改tsconfig.json

json 复制代码
{
  "files": [],
  "references": [
    { "path": "./tsconfig.app.json" },
    { "path": "./tsconfig.node.json" }
  ]
}

修改tsconfig.app.json

perl 复制代码
{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    /* ========== 基础选项 ========== */
    "target": "ES2022",
    "lib": ["ES2022", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "moduleResolution": "bundler",
    
    /* ========== Vite 必需选项 ========== */
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "skipLibCheck": true,
    
    /* ========== JSX 支持 ========== */
    "jsx": "react-jsx",
    
    /* ========== 严格模式核心)========== */
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true,
    
    /* ========== 路径别名========== */
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"],
      "@components/*": ["./src/components/*"],
      "@pages/*": ["./src/pages/*"],
      "@utils/*": ["./src/utils/*"],
      "@hooks/*": ["./src/hooks/*"],
      "@services/*": ["./src/services/*"],
      "@stores/*": ["./src/stores/*"],
      "@types/*": ["./src/types/*"],
      "@assets/*": ["./src/assets/*"],
      "@layouts/*": ["./src/layouts/*"]
    },
    
    /* ========== Vite 客户端类型 ========== */
    "types": ["vite/client"]
  },
  
  /* ========== 包含应用代码 ========== */
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
}

修改tsconfig.node.json

json 复制代码
{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    /* ========== Node.js 环境 ========== */
    "target": "ES2022",
    "lib": ["ES2023"],
    "module": "ESNext",
    "moduleResolution": "bundler",
    
    /* ========== Node.js 全局类型 ========== */
    "types": ["node"],
    
    /* ========== 类型安全 ========== */
    "strict": true,
    "skipLibCheck": true,
    
    /* ========== 输出控制(工具链需要)========== */
    "noEmit": false,           // 允许输出(如类型声明)
    "declaration": true,       // 生成 .d.ts
    "declarationMap": true,    // 生成 .d.ts.map
    "incremental": true,       // 增量编译加速
    "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo"
  },
  
  /* ========== 包含 Node 脚本和配置 ========== */
  "include": [
    "vite.config.ts",
    "package.json",
    "*.config.{js,ts}"
  ],
  "exclude": ["node_modules", "src"]
}

这种分离配置的优势在于:

  • 关注点分离:前端代码和工具链代码有不同的编译要求
  • 构建性能:可以独立编译,避免不必要的类型检查
  • 维护性:配置更加清晰,易于理解和修改

2.3 ESLint + Prettier集成

在现代前端开发中,代码质量工具链已经从不被重视的辅助工具演变为项目成功的核心基础设施。一个精心设计的代码规范体系能够:

  • 降低维护成本:统一的代码风格使代码更易于理解和修改
  • 提高团队效率:减少代码审查中的风格争论,聚焦业务逻辑
  • 保障代码质量:自动捕获潜在错误和不良实践
  • 加速新人融入:提供清晰的编码标准和即时反馈

ESLint的核心作用与价值

ESLint主要专注于代码质量问题,它通过静态分析来识别代码中的:

  1. 潜在错误:未使用变量、未定义引用、可能的语法错误
  2. 不良实践 :使用已废弃的API、不安全的编码模式
  3. 代码规范:命名约定、代码组织、最佳实践遵循
  4. 类型安全 :在TypeScript环境中的类型相关错误

其核心的工作机制基于抽象语法树(AST) ,其处理流程如下:

scss 复制代码
// ESLint内部处理流程示意
源代码 → 词法分析(Tokenization) → 语法分析(Parsing) → AST生成 → 规则遍历 → 问题报告

这种AST驱动的架构使得ESLint能够深入理解代码结构,而不仅仅是表面文本模式。

AST分析示例:

css 复制代码
// 原始代码
function example() {
  const x = 1
  console.log(x)
}
​
// 对应的AST结构(简化)
{
  type: "Program",
  body: [{
    type: "FunctionDeclaration",
    id: { type: "Identifier", name: "example" },
    body: {
      type: "BlockStatement",
      body: [
        {
          type: "VariableDeclaration",
          declarations: [{
            type: "VariableDeclarator",
            id: { type: "Identifier", name: "x" },
            init: { type: "Literal", value: 1 }
          }]
        },
        {
          type: "ExpressionStatement",
          expression: {
            type: "CallExpression",
            callee: {
              type: "MemberExpression",
              object: { type: "Identifier", name: "console" },
              property: { type: "Identifier", name: "log" }
            },
            arguments: [{ type: "Identifier", name: "x" }]
          }
        }
      ]
    }
  }]
}

ESLint配置深度解析

在使用 Vite 创建项目后,我们可以在项目中看到 ESLint 的配置文件 eslint.config.js。在 ESLint 9.0 之前,采用的是传统配置方式,配置文件名通常为 .eslintrc.js。传统配置架构存在一些显著问题:

  1. 继承关系复杂 :多层 extends 导致规则来源难以追溯,调试困难。
  2. 文件匹配冗余overrides 配置分散,逻辑重叠,维护成本高。
  3. 性能开销大:项目规模扩大时,配置解析时间显著增加。
  4. 类型支持弱:配置对象缺乏强类型约束,易出现拼写或结构错误。

为了应对上述问题,ESLint 9.0 引入了全新的扁平化配置模式,其优势包括:

  1. 配置职责明确:每个配置块仅处理特定类型文件,逻辑更清晰。
  2. 匹配效率提升:支持并行处理配置,避免不必要的规则应用,加快校验速度。
  3. 类型安全保障:基于 ES 模块导入,支持类型推断与校验,减少配置错误。
  4. 模块化与复用性:配置块可独立封装、组合与复用,便于跨项目共享。

传统配置的局限性已不言而喻,下文将直接采用ESLint 9+扁平化配置方案。

javascript 复制代码
import { defineConfig, globalIgnores } from "eslint/config"; // 官方API
import js from "@eslint/js";
import globals from "globals";
import reactHooks from "eslint-plugin-react-hooks";
import reactRefresh from "eslint-plugin-react-refresh";
import tseslint from "typescript-eslint";
​
export default defineConfig([
  // ========== 全局忽略配置 ==========
  // 使用官方 globalIgnores 函数(返回配置对象)
  globalIgnores([
    "dist/**",      // 构建输出
    "node_modules/**", // 依赖目录
    "**/*.config.{js,ts}", // 配置文件
    "coverage/**",  // 测试覆盖率
    "public/**",    // 静态资源
  ]),
​
  // ========== 核心 TypeScript/React 配置 ==========
  {
    name: "typescript-react-rules",
    files: ["**/*.{ts,tsx,js,jsx}"],
​
  // extends 在 Flat Config 中是数组合并
    extends: [
      js.configs.recommended,
      ...tseslint.configs.recommendedTypeChecked, // 推荐类型检查版本
      ...tseslint.configs.stylisticTypeChecked,   // 可选:风格建议
    ],
​
    languageOptions: {
      ecmaVersion: "latest",
      sourceType: "module",
      globals: {
        ...globals.browser,
        ...globals.es2022,
        React: "readonly",
        JSX: "readonly", // 补充JSX全局类型
      },
      parserOptions: {
        // ESLint 9+ 推荐:自动服务化,替代传统 project 字段
        projectService: {
          defaultProject: "./tsconfig.json", // 指定默认项目
          allowDefaultProject: ["*.config.*"], // 允许无TSConfig的文件
        },
        tsconfigRootDir: import.meta.dirname,
      },
    },
​
    // 插件注册
    plugins: {
      "react-hooks": reactHooks,
      "react-refresh": reactRefresh,
    },
​
    rules: {
      // ===== 命名规范 =====
      "@typescript-eslint/naming-convention": [
        "error",
        {
          selector: "variableLike",
          format: ["camelCase", "UPPER_CASE", "PascalCase"],
          leadingUnderscore: "allow",
        },
        {
          selector: "function",
          format: ["camelCase", "PascalCase"], // React组件允许PascalCase
        },
        {
          selector: "typeLike",
          format: ["PascalCase"],
        },
      ],
​
      // ===== TypeScript 核心规则 =====
      "@typescript-eslint/no-unused-vars": [
        "error",
        {
          argsIgnorePattern: "^_",
          varsIgnorePattern: "^_",
          caughtErrorsIgnorePattern: "^_",
        },
      ],
      "@typescript-eslint/no-explicit-any": "warn",
      "@typescript-eslint/no-unsafe-assignment": "warn", // 新增:禁止不安全的any赋值
      "prefer-const": "error",
​
      // ===== React Hooks 规则 =====
      "react-hooks/rules-of-hooks": "error",
      "react-hooks/exhaustive-deps": "warn",
​
      // ===== React Refresh 规则(修正版)=======
      "react-refresh/only-export-components": [
        "warn",
        {
          allowConstantExport: true,
          allowExportNames: ["meta", "config"], // Vite特殊导出
        },
      ],
​
      // ===== 通用 JavaScript 规则 =====
      "no-console": process.env.NODE_ENV === "production" ? "error" : "warn",
      "no-debugger": process.env.NODE_ENV === "production" ? "error" : "warn",
      "prefer-arrow-callback": "error",
      eqeqeq: ["error", "always"],
    },
  },
​
  // ========== 配置文件特殊处理 ==========
  {
    name: "config-files",
    files: ["vite.config.{ts,js}", "eslint.config.js"],
    languageOptions: {
      globals: {
        ...globals.node, //  Node.js全局变量
      },
    },
    rules: {
      "no-console": "off",
      "@typescript-eslint/no-require-imports": "off", // ESLint 9+ 新规则名
    },
  },
]);

上述配置为Vite React项目提供了:

  1. 命名规范校验-不同类型变量的命名规则
  2. 完整的TypeScript支持 - 类型检查和TS特定规则
  3. React最佳实践 - Hooks规则和组件规范
  4. 开发体验优化 - 开发环境宽松,生产环境严格
  5. 测试文件支持 - 针对测试文件的特殊处理
  6. 构建工具集成 - 与Vite构建流程完美配合

ESLint应用示例

  • 执行检查

在做完ESLint扁平化配置后,我们来测试看看ESLint的代码规范检查。因为Vite脚手架在创建项目时就集成了默认的ESLint配置,我们可以在package.json中看到ESLint的规范检查命令。在终端直接执行:

复制代码
pnpm lint

上面的命令是检查项目中的所有文件,如果想单独检查某个文件,则使用如下命令

css 复制代码
npx eslint src/**/*.{ts,tsx} 

不出意外,我们看到了很多的报错

  • 自动修复

接下来我们使用ESLint的自动修复命令来修复这些报错,在终端执行

css 复制代码
pnpm lint --fix

执行完后,可以看到报错的两个文件中的内容都被修复了。我们再次执行检查命令发现之前的报错消失了。说明我们的ESLint的修复功能是没问题的。

注意:ESLint的自动修复只支持一些可以通过简单的转换来解决的问题,比如格式问题(多余的空格,双引号,缺少分号等)

  • 编辑器配置

使用上面命令行的方式来检查和修复代码,是极其不方便的,有没有自动化的方式来实现这个功能呢,当然是有的!

我们在VS Code中安装ESLint插件

安装完成后,于项目根目录创建 .vscode/settings.json 并添加以下配置项:

json 复制代码
{
  "editor.formatOnSave": true,
  "[javascript][javascriptreact][typescript][typescriptreact]": {
    "editor.codeActionsOnSave": {
      "source.fixAll.eslint": "explicit"
    }
  },
  "eslint.useFlatConfig": true, // 明确启用Flat Config
  "eslint.validate": [
    "javascript",
    "javascriptreact",
    "typescript",
    "typescriptreact"
  ]
}

此时修改任意 .tsx 文件并执行保存操作,ESLint将自动应用修复。

*注意:此处需禁用VSCode中的代码格式化插件(Prettier等),否则会跟ESLint冲突!!! *,下面的Prettier配置中,我们将会解决这个问题。

Prettier的核心价值

Prettier专注于代码格式的统一性,其核心设计理念是:

  1. 有态度的格式化:提供有限的配置选项,强制团队统一风格
  2. 忽略原格式 :完全基于AST重新生成代码格式
  3. 一致性保证:确保项目中所有代码的格式完全一致
  4. 开发者友好:减少格式讨论,让开发者专注于逻辑

Prettier的格式化过程可以概括为:

复制代码
源代码 → 解析器 → AST → 中间表示 → 重新打印 → 格式化代码

关键特性:

  • 无损格式化:只改变格式,不改变代码逻辑
  • 语言感知:不同语言使用不同的格式化规则
  • 可配置性:在关键格式化决策上提供配置选项

Prettier的配置

  • 安装Prettier

在终端执行:

css 复制代码
pnpm i prettier -D
  • Prettier常用命令
scss 复制代码
//格式化所有文件
prettier --write .
​
//格式化整个目录
prettier --write src/
​
//格式化指定文件
prettier --write 文件路径
​
//格式化某一类型的文件
prettier --write src/**/*.js
  • Prettier配置

Prettier的配置文件支持jsonjsyaml等多种格式,我们使用js来进行配置。在项目根目录下新建.prettierrc.cjs文件,内容如下:

java 复制代码
module.exports = {
  // ========== 基础格式化配置 ==========
​
  // 每行最大字符数(超过会自动换行)
  "printWidth": 100,
​
  // 缩进使用的空格数
  "tabWidth": 2,
​
  // 使用空格而不是制表符
  "useTabs": false,
​
  // 在语句末尾添加分号
  "semi": true,
​
  // 使用单引号而不是双引号
  "singleQuote": true,
​
  // JSX 中使用单引号(false 表示使用双引号)
  "jsxSingleQuote": false,
​
  // 尾随逗号配置:es5 - 在 ES5 有效的的地方添加尾随逗号
  "trailingComma": "es5",
​
  // 对象字面量中括号之间的空格
  "bracketSpacing": true,
​
  // 将多行 HTML(HTML、JSX、Vue、Angular)元素的 > 放在最后一行的末尾
  "bracketSameLine": false,
​
  // 箭头函数参数括号:avoid - 尽可能省略括号
  "arrowParens": "avoid",
​
  // 文件格式配置
  "endOfLine": "lf",           // 换行符格式(lf - Unix/Mac, crlf - Windows)
  "quoteProps": "as-needed",   // 对象属性引号:按需添加
​
  // ========== 范围格式化配置 ==========
  "rangeStart": 0,             // 格式化范围的开始
  "rangeEnd": Infinity,        // 格式化范围的结束
​
  // ========== 解析器配置 ==========
  // Prettier 会自动根据文件类型选择解析器,通常不需要手动配置
  "parser": "",                // 显式指定解析器(留空自动检测)
​
  // ========== 文件类型特定配置 ==========
  "overrides": [
    {
      "files": "*.{css,scss,less}",
      "options": {
        "singleQuote": false   // CSS 中使用双引号
      }
    },
    {
      "files": "*.md",
      "options": {
        "printWidth": 80,      // Markdown 文件行宽较小
        "proseWrap": "always"  // 总是换行散文内容
      }
    },
    {
      "files": "*.json",
      "options": {
        "printWidth": 80       // JSON 文件行宽较小
      }
    },
    {
      "files": "*.yml",
      "options": {
        "singleQuote": false   // YAML 中使用双引号
      }
    }
  ]
}

然后,在根目录下创建Prettier忽略文件排除掉不想被格式化的文件,新建.prettierignore文件,内容如下:

bash 复制代码
# .prettierignore
# 忽略不需要格式化的文件和目录
​
# 构建输出
dist/
​
# 依赖目录
node_modules/
​
# 缓存文件
.cache/
.parcel-cache/
​
# 环境变量文件
.env
.env.development
.env.test
.env.production
​
# 日志文件
*.log
logs/
​
# 覆盖率目录
coverage/
.nyc_output/
​
# 依赖工具配置
.yarn/
.npm/
​
# IDE 配置
.vscode/
.idea/
*.swp
*.swo
​
# 操作系统文件
.DS_Store
Thumbs.db
​
# 测试快照
__snapshots__/
  • 编辑器配置

ESLint类似,手动执行Prettier命令效率低下,建议通过编辑器插件实现自动化。所以我们直接使用插件来自动完成格式化。在vscode中安装Prettier插件。

安装完成后,在.vscode目录中settings.json文件中修改配置:

json 复制代码
"editor.formatOnSave": true,
"[javascript][javascriptreact][typescript][typescriptreact]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode",
    "editor.codeActionsOnSave": {
        "source.fixAll.eslint": "explicit",
        "source.fixAll.stylelint": "explicit"
    }
},
"eslint.useFlatConfig": true, // 明确启用Flat Config
"eslint.validate": [
    "javascript",
    "javascriptreact",
    "typescript",
    "typescriptreact"
]

ESLint与Prettier协同配置策略

由于ESLintPrettier均涉及代码格式规则,直接集成易产生规则冲突,需通过以下策略实现协同:

  • 安装依赖

在终端执行如下命令安装相应的依赖

arduino 复制代码
pnpm i eslint-config-prettier eslint-plugin-prettier -D
  • 修改ESLint配置

完整配置如下

javascript 复制代码
import { defineConfig, globalIgnores } from "eslint/config"; // 官方API
import js from "@eslint/js";
import globals from "globals";
import reactHooks from "eslint-plugin-react-hooks";
import reactRefresh from "eslint-plugin-react-refresh";
import tseslint from "typescript-eslint";
import prettierConfig from "eslint-config-prettier";
​
export default defineConfig([
  // ========== 全局忽略配置 ==========
  // 使用官方 globalIgnores 函数(返回配置对象)
  globalIgnores([
    "dist/**",      // 构建输出
    "node_modules/**", // 依赖目录
    "**/*.config.{js,ts}", // 配置文件
    "coverage/**",  // 测试覆盖率
    "public/**",    // 静态资源
  ]),
​
  // ========== Prettier 集成(前置优先级)==========
  // 关闭所有与Prettier冲突的格式规则
  {
    name: "prettier-integration",
    rules: prettierConfig.rules,
  },
​
  // ========== 核心 TypeScript/React 配置 ==========
  {
    name: "typescript-react-rules",
    files: ["**/*.{ts,tsx,js,jsx}"],
​
  // extends 在 Flat Config 中是数组合并
    extends: [
      js.configs.recommended,
      ...tseslint.configs.recommendedTypeChecked, // 推荐类型检查版本
      ...tseslint.configs.stylisticTypeChecked,   // 可选:风格建议
    ],
​
    languageOptions: {
      ecmaVersion: "latest",
      sourceType: "module",
      globals: {
        ...globals.browser,
        ...globals.es2022,
        React: "readonly",
        JSX: "readonly", // 补充JSX全局类型
      },
      parserOptions: {
        // ESLint 9+ 推荐:自动服务化,替代传统 project 字段
        projectService: {
          defaultProject: "./tsconfig.json", // 指定默认项目
          allowDefaultProject: ["*.config.*"], // 允许无TSConfig的文件
        },
        tsconfigRootDir: import.meta.dirname,
      },
    },
​
    // 插件注册
    plugins: {
      "react-hooks": reactHooks,
      "react-refresh": reactRefresh,
    },
​
    rules: {
      // ===== 命名规范 =====
      "@typescript-eslint/naming-convention": [
        "error",
        {
          selector: "variableLike",
          format: ["camelCase", "UPPER_CASE", "PascalCase"],
          leadingUnderscore: "allow",
        },
        {
          selector: "function",
          format: ["camelCase", "PascalCase"], // React组件允许PascalCase
        },
        {
          selector: "typeLike",
          format: ["PascalCase"],
        },
      ],
​
      // ===== TypeScript 核心规则 =====
      "@typescript-eslint/no-unused-vars": [
        "error",
        {
          argsIgnorePattern: "^_",
          varsIgnorePattern: "^_",
          caughtErrorsIgnorePattern: "^_",
        },
      ],
      "@typescript-eslint/no-explicit-any": "warn",
      "@typescript-eslint/no-unsafe-assignment": "warn", // 新增:禁止不安全的any赋值
      "prefer-const": "error",
​
      // ===== React Hooks 规则 =====
      "react-hooks/rules-of-hooks": "error",
      "react-hooks/exhaustive-deps": "warn",
​
      // ===== React Refresh 规则)=======
      "react-refresh/only-export-components": [
        "warn",
        {
          allowConstantExport: true,
          allowExportNames: ["meta", "config"], // Vite特殊导出
        },
      ],
​
      // ===== 通用 JavaScript 规则 =====
      "no-console": process.env.NODE_ENV === "production" ? "error" : "warn",
      "no-debugger": process.env.NODE_ENV === "production" ? "error" : "warn",
      "prefer-arrow-callback": "error",
      eqeqeq: ["error", "always"],
    },
  },
​
  // ========== 配置文件特殊处理 ==========
  {
    name: "config-files",
    files: ["vite.config.{ts,js}", "eslint.config.js"],
    languageOptions: {
      globals: {
        ...globals.node, //  Node.js全局变量
      },
    },
    rules: {
      "no-console": "off",
      "@typescript-eslint/no-require-imports": "off", // ESLint 9+ 新规则名
    },
  },
]);

2.4 Stylelint配置

JavaScript/TypeScript代码质量通过ESLintPrettier得到有效管控后,样式代码的规范化成为工程化闭环中不可或缺的一环。Stylelint作为CSS领域的静态分析工具,其设计哲学与ESLint一脉相承,但在样式语言特性上具备深度适配能力。

Stylelint的核心定位与能力边界

Stylelint专注于样式表文件的结构性与一致性校验,其核心价值体现在:

  1. 语法错误拦截:识别无效的属性值、未闭合的选择器、错误的嵌套规则等解析期错误
  2. 编码规范落地 :统一团队内颜色表示法(hex/rgb/hsl)、简写属性使用、选择器命名范式等风格决策
  3. 反模式检测 :禁用性能不佳的选择器(如通配选择器*)、避免过度特异性的嵌套、识别冗余代码
  4. 跨预处理器支持 :原生支持Sass/SCSSLessStylus等预处理语言,通过语法插件扩展解析能力

ESLint互补关系ESLint负责脚本逻辑的正确性,Stylelint保障视觉层代码的可维护性,二者构成前端代码质量的双轨审查机制

Stylelint的配置

  • 依赖安装

在项目根目录执行以下命令,安装核心包及Sass支持(本项目采用Sass作为样式预处理器)

arduino 复制代码
pnpm i stylelint stylelint-config-standard-scss stylelint-config-clean-order -D

依赖说明:

  • stylelint:核心引擎
  • stylelint-config-standard-scssSCSS/Sass标准规则集,继承自stylelint-config-standard并扩展Sass语法支持
  • stylelint-config-clean-order:属性声明顺序规则,遵循从布局到动画的语义化分组原则
  • 配置文件架构

在项目根目录创建 stylelint.config.cjs,采用CommonJS格式与ESLint配置保持风格一致:

java 复制代码
// stylelint.config.cjs
module.exports = {
  extends: [
    'stylelint-config-standard-scss',
    'stylelint-config-clean-order',
    'stylelint-config-prettier-scss',
  ],
​
  // ===== 仅SCSS文件使用专用解析器 =====
  customSyntax: 'postcss-scss',
​
  // ========== 规则覆盖与自定义 ==========
  rules: {
    // ===== 命名规范 =====
    'selector-class-pattern': [
      '^[a-z]([a-z0-9-]+)?(__[a-z0-9]+)?(-{1,2}[a-z0-9]+)*$',
      {
        message: 'Expected class selector to be kebab-case (e.g., block__element--modifier)',
      },
    ],
​
    // ===== 颜色管理 =====
    //使用Prettier控制大小写,此处禁用冲突规则
    'color-hex-length': 'long',
    'color-function-notation': null, // 禁用现代语法强制
​
    // ===== 声明块规范 =====
    'declaration-block-no-duplicate-properties': [
      true,
      {
        ignore: ['consecutive-duplicates-with-different-values'],
      },
    ],
​
    'declaration-block-no-redundant-longhand-properties': [
      true,
      {
        ignoreShorthands: ['grid-template'],
      },
    ],
​
    // ===== 选择器限制 =====
    'max-nesting-depth': 3,
    'selector-max-type': 2,
​
    // ===== SCSS特定规则 =====
    'scss/dollar-variable-pattern': '^[a-z][a-z0-9-]*$',
    'scss/at-extend-no-missing-placeholder': true,
    'scss/load-no-partial-leading-underscore': true,
​
    // ===== 单位管理 =====
    'length-zero-no-unit': true,
    //所有合法CSS单位
    'unit-allowed-list': [
      'px', 'rem', 'em', '%', 'vw', 'vh', 'svw', 'svh', 'lvw', 'lvh', 'dvw', 'dvh',
      's', 'ms',
      'deg', 'rad', 'turn', 'grad',
      'dpi', 'dpcm', 'dppx',
      'fr', 'ch', 'ex', 'cm', 'mm', 'in', 'pt', 'pc',
    ],
​
    // ===== 现代CSS特性 =====
    'selector-pseudo-element-no-unknown': [
      true,
      {
        ignorePseudoElements: ['v-deep'],
      },
    ],
  },
};

配置设计原则

  • 约束大于配置:通过严格规则避免低质量代码入库
  • 可自动修复stylelint-config-clean-order支持属性排序的自动修复,降低开发者负担
  • 框架兼容 :保留Vue深度选择器v-deep等特殊语法支持
  • 双语法兼容 :同时支持SCSS大括号语法与Sass缩进语法,通过postcss-sass解析器自动适配

Stylelint与Prettier协同配置策略

ESLint类似,StylelintPrettier在样式格式上同样存在规则重叠(如缩进、引号),需通过专用配置实现协同

  • 安装冲突消解包
arduino 复制代码
pnpm i stylelint-prettier stylelint-config-prettier-scss -D
  • 修改Stylelint配置

extends数组末尾追加Prettier专用配置(顺序决定优先级),完整配置如下:

java 复制代码
// stylelint.config.cjs
module.exports = {
  extends: [
    'stylelint-config-standard-scss',
    'stylelint-config-clean-order',
    'stylelint-config-prettier-scss',
  ],
​
  plugins: ['stylelint-prettier'],
​
  // ===== 仅SCSS文件使用专用解析器 =====
  customSyntax: 'postcss-scss',
​
  // ========== 规则覆盖与自定义 ==========
  rules: {
​
    // 将Prettier错误作为Stylelint违规抛出
    'prettier/prettier': true,
​
    // ===== 命名规范 =====
    'selector-class-pattern': [
      '^[a-z]([a-z0-9-]+)?(__[a-z0-9]+)?(-{1,2}[a-z0-9]+)*$',
      {
        message: 'Expected class selector to be kebab-case (e.g., block__element--modifier)',
      },
    ],
​
    // ===== 颜色管理 =====
    //使用Prettier控制大小写,此处禁用冲突规则
    'color-hex-length': 'long',
    'color-function-notation': null, // 禁用现代语法强制
​
    // ===== 声明块规范 =====
    'declaration-block-no-duplicate-properties': [
      true,
      {
        ignore: ['consecutive-duplicates-with-different-values'],
      },
    ],
​
    'declaration-block-no-redundant-longhand-properties': [
      true,
      {
        ignoreShorthands: ['grid-template'],
      },
    ],
​
    // ===== 选择器限制 =====
    'max-nesting-depth': 3,
    'selector-max-type': 2,
​
    // ===== SCSS特定规则 =====
    'scss/dollar-variable-pattern': '^[a-z][a-z0-9-]*$',
    'scss/at-extend-no-missing-placeholder': true,
    'scss/load-no-partial-leading-underscore': true,
​
    // ===== 单位管理 =====
    'length-zero-no-unit': true,
    //所有合法CSS单位
    'unit-allowed-list': [
      'px', 'rem', 'em', '%', 'vw', 'vh', 'svw', 'svh', 'lvw', 'lvh', 'dvw', 'dvh',
      's', 'ms',
      'deg', 'rad', 'turn', 'grad',
      'dpi', 'dpcm', 'dppx',
      'fr', 'ch', 'ex', 'cm', 'mm', 'in', 'pt', 'pc',
    ],
​
    // ===== 现代CSS特性 =====
    'selector-pseudo-element-no-unknown': [
      true,
      {
        ignorePseudoElements: ['v-deep'],
      },
    ],
  },
};

协同机制Prettier负责格式美化,Stylelint专注结构审查,二者通过stylelint-prettier插件实现错误统一上报,开发者仅需关注Stylelint的输出即可。

编译器自动化集成

  • 安装插件

在扩展市场搜索Stylelint并安装官方插件。

  • 修改编译器配置

.vscode/settings.json中添加以下配置,实现保存时自动修复:

json 复制代码
"[scss][sass][css]": {
    "editor.formatOnSave": true,
    "editor.codeActionsOnSave": {
        "source.fixAll.stylelint": "explicit"
    },
    "editor.defaultFormatter": "stylelint.vscode-stylelint"
},
// 确保Stylelint配置被识别
"stylelint.validate": ["css", "scss", "sass"],
"stylelint.snippet": ["css", "scss", "sass"],
"stylelint.configFile": "stylelint.config.cjs"
  • 执行验证

package.json中添加脚本,集成到CI流程

json 复制代码
{
  "scripts": {
    "lint:style": "stylelint "src/**/*.{css,scss,sass}"",
    "lint:style:fix": "stylelint "src/**/*.{css,scss,sass}" --fix"
  }
}

2.5 Git 工作流集成 - Husky 与 lint-staged 协同配置

当代码质量工具链在本地环境具备执行能力后,下一步是将其嵌入版本控制流程 ,形成不可绕过的质量门禁。传统 Git Hooks 配置分散于 .git/hooks 目录,存在跨平台兼容性差、团队协作难以同步、权限管理复杂等固有问题。Husky 通过项目化配置实现对 Git Hooks 的集中管理,而 lint-staged 则精准拦截暂存区文件,避免全量检查带来的性能损耗。二者协同构建提交前质量验证(Pre-commit Quality Gate) 机制。

Git Hooks 的现代化演进

原生 Git Hooks 脚本存储于 .git/hooks 目录,该目录未被版本跟踪,导致以下问题:

  1. 配置无法共享:新成员克隆仓库后需手动复制钩子脚本
  2. 跨平台脆弱:Shell 脚本在 Windows/macOS/Linux 上执行行为不一致
  3. 权限管理复杂:可执行权限需在每次更新后重新设置
  4. 缺乏灵活性:无法根据文件类型动态选择检查策略

Husky v9+ 采用项目级钩子目录 方案,将钩子脚本纳入版本控制,并通过 prepare 脚本实现自动化安装。lint-staged 基于 git diff --cached 提取暂存文件列表,实现增量式精准检查,其执行效率与项目规模解耦。

仓库初始化与首次提交

在项目根目录执行 Git 仓库初始化(若尚未执行):

csharp 复制代码
# 初始化 Git 仓库
git init
​
# 配置用户信息(全局或项目级)
git config user.name "your-name"
git config user.email "your-email@example.com"
​
# 创建初始提交(Husky v9+ 要求至少一次提交)
git add .
git commit -m "chore: initial commit"

关键约束:Husky安装需在至少一次提交后执行,否则钩子安装将失败。若项目已含 Git 历史,此步骤可跳过。

Husky 安装与钩子配置

  • 安装依赖
css 复制代码
pnpm i husky lint-staged -D
  • 初始化 Husky

Husky v9+提供声明式初始化命令,自动生成钩子目录结构:

bash 复制代码
# 执行初始化(自动生成 .husky/ 目录)
pnpm exec husky init

执行后项目结构新增:

perl 复制代码
backend-management-system/
├── .husky/                  # Husky 钩子目录(纳入版本控制)
│   └── _/                   # 内部工具目录
│   └── pre-commit           # pre-commit 钩子脚本
│   └── ...                  # 其他钩子模板
├── node_modules/
├── src/
└── package.json
  • 配置 pre-commit 钩子

编辑 .husky/pre-commit 文件,移除默认内容并添加 如下lint-staged调用:

bash 复制代码
#!/bin/sh
# .husky/pre-commit
​
# 执行 lint-staged 检查
pnpm exec lint-staged

权限说明:Husky init已自动为 pre-commit 添加可执行权限,无需手动 chmod +x

  • lint-staged 配置矩阵

在项目根目录创建 lint-staged.config.cjs,采用 CommonJS格式配置多文件类型检查任务:

java 复制代码
// lint-staged.config.cjs
module.exports = {
  // ========== TypeScript/JavaScript 文件检查 ==========
  '**/*.{ts,tsx,js,jsx}': [
    // ESLint 自动修复与检查
    'eslint --fix --max-warnings 0',
    // Prettier 格式化(二次校验)
    'prettier --write',
    // Git 添加修复后的文件
    'git add',
  ],
​
  // ========== SCSS/CSS 文件检查 ==========
  '**/*.{scss,css}': [
    // Stylelint 自动修复与检查
    'stylelint --fix --max-warnings 0',
    // Prettier 格式化
    'prettier --write',
    // Git 添加修复后的文件
    'git add',
  ],
​
  // ========== JSON 文件格式化 ==========
  '**/*.json': ['prettier --write', 'git add'],
​
  // ========== Markdown 文档格式化 ==========
  '**/*.md': ['prettier --write', 'git add'],
​
  // ========== 配置文件格式化 ==========
  '*.{yml,yaml}': ['prettier --write', 'git add'],
};

配置策略解读:

  1. 任务串行执行:数组内命令按序执行,前序失败则中断提交
  2. 自动修复优先--fix 自动处理可修复项,减少人工干预
  3. 零警告策略--max-warnings 0 将警告视为错误,确保质量红线
  4. Git 重添加:修复后文件需重新加入暂存区,否则修复不会进入提交
  • prepare 脚本自动化

package.json 中添加 prepare 脚本,确保团队成员安装依赖后自动启用 Husky:

json 复制代码
{
  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "lint": "eslint . --ext ts,tsx --report-unused-disable-directives",
    "lint:style": "stylelint "src/**/*.{css,scss}"",
    "lint:fix": "eslint . --fix && stylelint "src/**/*.{css,scss}" --fix",
    // ===== prepare 脚本实现 Husky 自动安装 =====
    "prepare": "husky"
  }
}

工作原理:pnpm install 执行后自动触发 prepare 脚本,husky检测 .husky/ 目录存在则完成钩子注册。

  • 流程演示

修改 src/App.tsxsrc/App.scss 并尝试提交。

csharp 复制代码
# 1. 修改文件(引入 lint 错误)
# App.tsx: 添加未使用变量 const unused = 1;
# App.scss: 使用错误单位 margin: 0px;
​
# 2. 添加至暂存区
git add src/App.tsx src/App.scss
​
# 3. 执行提交(触发 pre-commit 钩子)
git commit -m "feat: update app component"

执行检查过程,如果检查通过,则有如下信息:

sql 复制代码
✔ Backed up original state in git stash (f0bda7f)
✔ Running tasks for staged files...
✔ Applying modifications from tasks...
✔ Cleaning up temporary files...
[master fbeac15] feat: update app component
 5 files changed, 1899 insertions(+), 551 deletions(-)
 create mode 100644 .husky/pre-commit
 create mode 100644 lint-staged.config.cjs

如果lint检查不通过,则会报错:

perl 复制代码
✔ Backed up original state in git stash (e4d8bad)
⚠ Running tasks for staged files...
  ❯ lint-staged.config.cjs --- 1 file
    ❯ **/*.{ts,tsx,js,jsx} --- 1 file
      ✖ eslint --fix --max-warnings 0 [FAILED]
      ◼ prettier --write
      ◼ git add
    ↓ **/*.{scss,css} --- no files
    ↓ **/*.json --- no files
    ↓ **/*.md --- no files
    ↓ *.{yml,yaml} --- no files
↓ Skipped because of errors from tasks.
✔ Reverting to original state because of errors...
✔ Cleaning up temporary files...
​
✖ eslint --fix --max-warnings 0:
​
E:\ReactProject\backend-management-system\src\App.tsx
  9:9  error  'a' is assigned a value but never used. Allowed unused vars must match /^_/u  @typescript-eslint/no-unused-vars
​
✖ 1 problem (1 error, 0 warnings)
​
​
✖ eslint --fix --max-warnings 0 failed to spawn:
Command failed with exit code 1: eslint --fix --max-warnings 0 'E:/ReactProject/backend-management-system/src/App.tsx'
undefined
husky - pre-commit script failed (code 1)

紧急修复时可使用--no-verify跳过检查

sql 复制代码
git commit -m "hotfix: critical bug fix" --no-verify
  • CI一致性保障

在 GitHub Actions 或 GitLab CI 中运行相同检查,确保本地与远端行为一致。在项目中创建.github/workflows/quality.yml

yaml 复制代码
# .github/workflows/quality.yml
name: Code Quality
​
on: [push, pull_request]
​
jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v2
      - name: Run lint
        run: |
          pnpm install
          pnpm lint
          pnpm lint:style
  • commitlint 集成

代码风格的统一不应止步于文件内容,提交信息(Commit Message) 作为代码历史的元数据,同样需要规范化管理。commitlintESLint 的静态分析思想应用于提交信息校验,确保团队遵循一致的提交范式,为自动化生成 CHANGELOG、语义化版本(Semantic Versioning)及代码回溯奠定基础。

Angular Commit Convention 作为业界广泛采纳的规范,定义了 featfixdocsstylerefactortestchore 等类型标签,配合作用域(Scope)与描述(Description)形成三段式结构:

xml 复制代码
<type>(<scope>): <subject>
​
<body>
​
<footer>

安装commitlint

bash 复制代码
pnpm i @commitlint/cli @commitlint/config-conventional -D

在项目根目录创建 commitlint.config.cjs,继承 Conventional 规范并扩展团队特定约束:

java 复制代码
// commitlint.config.cjs
module.exports = {
  // 继承 Conventional Commits 规范
  extends: ['@commitlint/config-conventional'],
​
  // ========== 自定义规则覆盖 ==========
  rules: {
    // 描述最小长度
    'subject-min-length': [2, 'always', 10],
​
    // ===== 正文与脚注 =====
    // 正文每行最大长度
    'body-max-line-length': [2, 'always', 100],
  },
​
  // ========== 辅助配置 ==========
  // 解析器选项
  parserPreset: {
    parserOpts: {
      // 允许脚注关联 Issue(如 Close #123, Fix #456)
      issuePrefixes: ['#', 'https://github.com/your-org/your-repo/issues/'],
    },
  },
};

创建 .husky/commit-msg 文件,调用 commitlint 校验提交信息:

bash 复制代码
#!/bin/sh
# .husky/commit-msg
​
# 通过 commitlint 校验提交信息格式
pnpm exec commitlint --edit $1

与 lint-staged 的协同机制:在 lint-staged.config.cjs 中补充提交信息格式检查,虽然 lint-staged 不直接处理 commit message,但可确保提交前所有代码检查通过:

java 复制代码
// lint-staged.config.cjs
module.exports = {
  // ...原有配置
  
  // ========== 提交信息文件处理(如有)==========
  // 若项目包含提交模板,可格式化模板文件
  '.gitcommitmessage': ['prettier --write', 'git add'],
};
  • 演示

违反类型约束

bash 复制代码
git commit -m "feature: add new button"
​
#输出
⧗   input: feature: add new button
✖   type must be one of [build, chore, ci, docs, feat, fix, perf, refactor, revert, style, test] [type-enum]
​
✖   found 1 problems, 0 warnings
ⓘ   Get help: https://github.com/conventional-changelog/commitlint/#what-is-commitlint
​
husky - commit-msg script failed (code 1)

描述长度不足

python 复制代码
git commit -m "feat: fix"
​
# 输出:
→ lint-staged could not find any staged files matching configured tasks.
⧗   input: feat: fix
✖   subject must not be shorter than 10 characters [subject-min-length]
​
✖   found 1 problems, 0 warnings
ⓘ   Get help: https://github.com/conventional-changelog/commitlint/#what-is-commitlint
​
husky - commit-msg script failed (code 1)

符合规范的提交

csharp 复制代码
git commit -m "fix: add new commitlit rules"
​
#输出
→ lint-staged could not find any staged files matching configured tasks.
[master a19b912] fix: add new commitlit rules
 1 file changed, 52 deletions(-)
  • 自动生成changelog
bash 复制代码
# 基于规范提交自动生成 CHANGELOG.md
pnpm i -D conventional-changelog-cli
​
# package.json 脚本
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0"

至此,从代码内容到提交信息的全链路质量门禁已构建完成,团队可基于结构化提交历史实现自动化版本管理与发布,极大提升工程化成熟度与协作效率。

相关推荐
Mh1 小时前
如何优雅的消除“if...else...”
前端·javascript
火鸟21 小时前
给予虚拟成像台尝鲜版十之二,完善支持 HTML 原型模式
前端·html·原型模式·通用代码生成器·给予虚拟成像台·快速原型·rust语言
逍遥江湖2 小时前
Vue3 + TypeScript 项目框架搭建指南
前端
lapiii3582 小时前
[前端-React] Hook
前端·javascript·react.js
白龙马云行技术团队2 小时前
前端自适应动态架构图演进
前端
一枚前端小能手2 小时前
🎬 使用 Web 动画 API - 关键帧与交互控制实战指南
前端·javascript·api
西西学代码2 小时前
Flutter---异步编程
开发语言·前端·javascript
拉不动的猪2 小时前
CSS 像素≠物理像素:0.5px 效果的核心密码是什么?
前端·css·面试
前端市界3 小时前
Copilot新模型GPT-5.1太强了!自动生成完美Axios封装,同事都看傻了
前端·前端框架·github