hookehuyr

feat: 配置代码质量工具链

添加 ESLint、Prettier、Husky 和 lint-staged 以自动化代码检查和格式化
新增 .prettierrc、.prettierignore、eslint.config.js 和 Husky pre-commit hook
在 package.json 中添加相关脚本和依赖,包括格式化、检查、测试覆盖率等命令
新增详细配置文档 HUSKY_LINT_STAGED.md 和 ESLINT_PRETTIER.md
#!/usr/bin/env sh
. "$(dirname "$0")/_/husky.sh"
npx lint-staged
# 依赖
node_modules/
dist/
dist.*/
build/
# 配置文件
package-lock.json
pnpm-lock.yaml
yarn.lock
# 自动生成
src/auto-imports.d.ts
src/components.d.ts
src/typings/env.d.ts
.eslintrc-auto-import.json
# 静态资源
public/
**/*.svg
**/*.png
**/*.jpg
**/*.jpeg
**/*.gif
**/*.ico
# 其他
.cursor/
coverage/
.nyc_output/
*.min.js
*.min.css
# 文档
CHANGELOG.md
LICENSE
VITE*.md
# 临时文件
*.log
*.tmp
*.temp
.DS_Store
{
"semi": false,
"singleQuote": true,
"quoteProps": "as-needed",
"trailingComma": "es5",
"bracketSpacing": true,
"bracketSameLine": false,
"arrowParens": "avoid",
"printWidth": 100,
"tabWidth": 2,
"useTabs": false,
"endOfLine": "lf",
"plugins": ["prettier-plugin-tailwindcss"],
"overrides": [
{
"files": "*.vue",
"options": {
"parser": "vue"
}
},
{
"files": "*.md",
"options": {
"proseWrap": "preserve"
}
}
]
}
# ESLint + Prettier 配置说明
## ✅ 已安装的包
```json
{
"devDependencies": {
"eslint": "^9.39.2",
"eslint-plugin-vue": "^10.7.0",
"eslint-config-prettier": "^10.1.8",
"@vue/eslint-config-prettier": "^10.2.0",
"prettier": "^3.8.1",
"prettier-plugin-tailwindcss": "^0.7.2"
}
}
```
## 📁 配置文件
### ESLint 配置
**文件**: `eslint.config.js` (ESLint 9 扁平配置格式)
**核心规则**:
- Vue 3 规则(组件命名、props 检查、模板规范)
- JavaScript 规则(no-var、prefer-const、箭头函数)
- 代码质量(===、错误处理、安全)
- 性能优化(避免循环中的函数、禁止 eval)
**忽略文件**:
```javascript
ignores: [
'node_modules/**',
'dist/**',
'dist.*',
'build/**',
'*.min.js',
'public/**',
'VITE*.md',
'.cursor/**',
'.history/**',
'coverage/**',
'.nyc_output/**',
'src/auto-imports.d.ts',
'src/components.d.ts',
'mlaj/**',
]
```
### Prettier 配置
**文件**: `.prettierrc`
**核心配置**:
```json
{
"semi": false, // 不使用分号
"singleQuote": true, // 使用单引号
"trailingComma": "es5", // ES5 尾部逗号
"printWidth": 100, // 每行最大 100 字符
"tabWidth": 2, // 2 空格缩进
"useTabs": false, // 使用空格而非 tab
"endOfLine": "lf" // LF 换行符
}
```
**忽略文件**:
- `node_modules/`
- `dist/`
- `build/`
- `public/`
- `coverage/`
- `*.min.js`
- `package-lock.json`
- `pnpm-lock.yaml`
### VS Code 配置
**文件**: `.vscode/settings.json`
**自动格式化**:
```json
{
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
```
**推荐扩展** (`.vscode/extensions.json`):
- `Vue.volar` - Vue 3 语言支持
- `dbaeumer.vscode-eslint` - ESLint 集成
- `esbenp.prettier-vscode` - Prettier 格式化
- `bradlc.vscode-tailwindcss` - TailwindCSS 智能提示
- `formulahendry.auto-rename-tag` - 自动重命名标签
- `christian-kohler.path-intellisense` - 路径智能提示
## 🚀 使用命令
### Lint(代码检查)
```bash
# 检查但不修复
pnpm lint:check
# 检查并自动修复
pnpm lint
```
### Format(代码格式化)
```bash
# 检查格式(不修改文件)
pnpm format:check
# 格式化代码
pnpm format
```
### 组合使用
```bash
# 先格式化,再 lint
pnpm format && pnpm lint
# 提交前检查
pnpm format:check && pnpm lint:check && pnpm test
```
## 📋 ESLint 核心规则
### Vue 3 规则
| 规则 | 级别 | 说明 |
| -------------------------------------- | ----- | ------------------ |
| `vue/multi-word-component-names` | off | 允许单词组件名 |
| `vue/no-v-html` | warn | 警告使用 v-html |
| `vue/require-prop-types` | error | props 必须有类型 |
| `vue/no-mutating-props` | error | 禁止直接修改 props |
| `vue/component-definition-name-casing` | error | 组件名 PascalCase |
### JavaScript 规则
| 规则 | 级别 | 说明 |
| ------------------ | ----- | ----------------------------------- |
| `no-console` | warn | 禁止 console.log(允许 warn/error) |
| `no-debugger` | error | 禁止 debugger |
| `no-var` | error | 禁止 var,使用 const/let |
| `prefer-const` | error | 优先使用 const |
| `eqeqeq` | error | 强制使用 === |
| `no-eval` | error | 禁止 eval |
| `no-throw-literal` | error | throw 必须是 Error 对象 |
## 🔧 常见问题
### 1. ESLint 报错但不显示具体规则
**解决**: 确保 `eslint.config.js` 配置正确
### 2. Prettier 与 ESLint 冲突
**解决**: `eslint-config-prettier` 已禁用所有与 Prettier 冲突的规则
### 3. VS Code 不自动格式化
**解决**:
1. 确保安装了 `Prettier - Code formatter` 扩展
2. 检查 `.vscode/settings.json` 中的配置
3. 重启 VS Code
### 4. 忽略特定文件
**ESLint 9**: 在 `eslint.config.js``ignores` 中添加
**Prettier**: 在 `.prettierignore` 中添加
## 📝 提交前检查
在提交代码前,请运行:
```bash
# 1. 格式化代码
pnpm format
# 2. 检查代码规范
pnpm lint:check
# 3. 运行测试
pnpm test
# 4. 检查测试覆盖率
pnpm test:coverage
```
## 🎯 最佳实践
### 1. 开发流程
```bash
# 1. 编写代码
# 2. 保存时自动格式化(VS Code 自动)
# 3. 手动运行 lint 检查
pnpm lint:check
# 4. 修复错误
pnpm lint
# 5. 提交代码
git add .
git commit -m "feat: xxx"
```
### 2. Git Hooks(可选)
使用 `husky``lint-staged` 自动化:
```bash
# 安装
pnpm add -D husky lint-staged
# 配置
npx husky install
npx husky add .husky/pre-commit "pnpm lint-staged"
```
**`package.json`**:
```json
{
"lint-staged": {
"*.{js,vue}": ["eslint --fix", "prettier --write"]
}
}
```
### 3. CI/CD 集成
在 CI 中运行:
```yaml
# .github/workflows/ci.yml
- name: Lint
run: pnpm lint:check
- name: Format check
run: pnpm format:check
- name: Test
run: pnpm test
```
## 📊 规则优先级
```
ESLint 规则 > Prettier 规则
1. Prettier 负责格式化(空格、引号、换行)
2. ESLint 负责代码质量(未使用变量、潜在错误)
3. eslint-config-prettier 禁用冲突的 ESLint 规则
```
## 🔄 迁移指南
### 从旧版 ESLint 迁移
如果你之前使用 `.eslintrc.js`
```bash
# 1. 删除旧配置
rm .eslintrc.js
# 2. 使用新的 eslint.config.js(已配置)
# 3. 删除 .eslintignore(ESLint 9 不再使用)
```
### 从旧版 Prettier 迁移
如果你之前使用 `.prettierrc.js`
```bash
# 1. 转换为 .prettierrc(JSON 格式)
# 2. 确保 prettier-plugin-tailwindcss 已安装
```
## 🚨 注意事项
1. **不要修改 `node_modules/` 中的文件** - ESLint 会忽略它们
2. **不要提交格式化后的构建产物** - `dist/``build/` 已被忽略
3. **团队统一配置** - 确保所有成员使用相同的配置文件
4. **定期更新依赖** - ESLint 和 Prettier 会定期更新
## 📚 参考资源
- [ESLint 9 文档](https://eslint.org/docs/latest/)
- [Vue 3 官方 ESLint 插件](https://eslint.vuejs.org/)
- [Prettier 文档](https://prettier.io/docs/en/)
- [Prettier TailwindCSS 插件](https://github.com/tailwindlabs/prettier-plugin-tailwindcss)
## 🎉 完成
你的项目现在已经配置了完整的 ESLint 和 Prettier!
**下一步**:
- [ ] 添加 Husky + lint-staged(Git Hooks)
- [ ] 添加 Vitest Coverage(测试覆盖率)
- [ ] 添加 Playwright(E2E 测试)
# Husky + lint-staged 配置说明
## ✅ 配置完成
你的项目现在已经配置了 **Husky****lint-staged**,在 Git 提交前自动运行代码检查和格式化。
## 📦 已安装的包
```json
{
"devDependencies": {
"husky": "^9.1.7",
"lint-staged": "^16.2.7"
}
}
```
## 🔧 配置文件
### 1. Husky 配置
**目录**: `.husky/`
**文件**:
- `pre-commit` - 提交前执行的 hook
- `_/husky.sh` - Husky 辅助脚本
**`pre-commit` 内容**:
```bash
#!/usr/bin/env sh
. "$(dirname "$0")/_/husky.sh"
npx lint-staged
```
### 2. lint-staged 配置
**位置**: `package.json` 中的 `lint-staged` 字段
**配置**:
```json
{
"lint-staged": {
"*.{js,vue}": ["eslint --fix", "prettier --write"],
"*.{css,less,scss}": ["prettier --write"],
"*.{json,md}": ["prettier --write"]
}
}
```
## 🚀 工作流程
### 自动化流程
```
1. git add .
2. git commit -m "xxx"
3. Husky 触发 pre-commit hook
4. lint-staged 检查暂存的文件
5. ESLint 自动修复问题
6. Prettier 自动格式化代码
7. 如果所有检查通过 → 提交成功 ✅
如果有错误 → 提交失败,显示错误信息 ❌
```
### 文件类型处理
| 文件类型 | 操作 |
| --------------------------- | ----------------------------------- |
| `*.js`, `*.vue` | ESLint 检查并修复 + Prettier 格式化 |
| `*.css`, `*.less`, `*.scss` | Prettier 格式化 |
| `*.json`, `*.md` | Prettier 格式化 |
## 📝 使用示例
### 正常提交
```bash
# 1. 修改文件
echo "console.log('test')" >> test.js
# 2. 添加到暂存区
git add test.js
# 3. 提交(自动运行 lint-staged)
git commit -m "test: add test file"
# 输出:
# ✔ prettier --write test.js
# ✔ eslint --fix test.js
# [main xxxxx] test: add test file
```
### 提交失败(有错误)
```bash
# 1. 创建有错误的文件
cat > bad.js << 'EOF'
function test() {
var x = 1 // 缺少分号,使用了 var
return x
}
EOF
# 2. 尝试提交
git add bad.js
git commit -m "test: bad code"
# 输出:
# ✖ eslint --fix bad.js
# ✖ 1:7 error Unexpected var, use let or const instead no-var
# ✖ 1:18 error Missing semicolon
# husky - pre-commit hook exited with code 1 (error)
```
### 跳过 Hook(不推荐)
```bash
# 使用 --no-verify 跳过 hook
git commit --no-verify -m "xxx"
```
## 🛠️ 调试
### 测试 lint-staged
```bash
# 不实际提交,只测试 lint-staged
npx lint-staged
```
### 查看已安装的 Hooks
```bash
# 查看所有 Git hooks
ls -la .husky/
# 查看 pre-commit 内容
cat .husky/pre-commit
```
### 手动运行 Hook
```bash
# 手动执行 pre-commit hook
.husky/pre-commit
```
## 📋 常见问题
### 1. Hook 不执行
**检查**:
```bash
# 1. 检查 .husky 目录是否存在
ls -la .husky/
# 2. 检查 pre-commit 是否可执行
ls -la .husky/pre-commit
# 3. 如果不可执行,添加权限
chmod +x .husky/pre-commit
```
### 2. lint-staged 找不到文件
**原因**: 文件未被 `git add` 到暂存区
**解决**:
```bash
# 确保文件已添加到暂存区
git add your-file.js
# 检查暂存区文件
git status
```
### 3. 提交速度慢
**原因**: lint-staged 检查的文件太多
**优化**:
- 缩小 `package.json``lint-staged` 的匹配范围
- 只检查 `src/` 目录
```json
{
"lint-staged": {
"src/**/*.{js,vue}": ["eslint --fix", "prettier --write"]
}
}
```
### 4. 想暂时禁用 Hook
**方法 1** - 跳过单次提交:
```bash
git commit --no-verify -m "xxx"
```
**方法 2** - 暂时删除 hook:
```bash
# 删除 hook
rm .husky/pre-commit
# 提交后恢复
git checkout .husky/pre-commit
```
## 🎯 最佳实践
### 1. 提交前检查
即使有 Husky,建议提交前也手动检查:
```bash
# 1. 格式化代码
pnpm format
# 2. Lint 检查
pnpm lint:check
# 3. 测试
pnpm test
# 4. 提交(Husky 会再次检查)
git add .
git commit -m "xxx"
```
### 2. 团队协作
**确保团队成员都安装了依赖**:
```bash
# 克隆项目后
pnpm install
# Husky 会自动安装(通过 prepare 脚本)
```
**`package.json` 中的 `prepare` 脚本**:
```json
{
"scripts": {
"prepare": "husky"
}
}
```
这确保了每次 `pnpm install` 后都会自动安装 Husky hooks。
### 3. CI/CD 集成
在 CI 中可以跳过 Husky(因为 CI 环境不使用 Git hooks):
```yaml
# .github/workflows/ci.yml
- name: Lint and format
run: |
pnpm lint:check
pnpm format:check
```
## 🔄 更新配置
### 添加新的 Hook
```bash
# 创建 commit-msg hook(检查提交信息)
cat > .husky/commit-msg << 'EOF'
#!/usr/bin/env sh
. "$(dirname "$0")/_/husky.sh"
npx commitlint --edit $1
EOF
chmod +x .husky/commit-msg
```
### 修改 lint-staged 配置
编辑 `package.json` 中的 `lint-staged` 字段:
```json
{
"lint-staged": {
"src/**/*.{js,vue}": ["eslint --fix", "prettier --write"],
"src/**/*.{css,less}": ["prettier --write"],
"*.md": ["prettier --write", "markdownlint --fix"]
}
}
```
### 添加测试到 Hook
编辑 `.husky/pre-commit`:
```bash
#!/usr/bin/env sh
. "$(dirname "$0")/_/husky.sh"
# 运行 lint-staged
npx lint-staged
# 运行测试(可选)
pnpm test
```
## 📚 参考资源
- [Husky 官方文档](https://typicode.github.io/husky/)
- [lint-staged 官方文档](https://github.com/okonet/lint-staged)
- [Git Hooks 文档](https://git-scm.com/docs/githooks)
## 🎉 总结
**优势**:
- ✅ 自动化代码质量检查
- ✅ 保持代码风格一致
- ✅ 防止低质量代码进入仓库
- ✅ 节省手动检查时间
- ✅ 团队协作更高效
**注意事项**:
- ⚠️ Hook 只在本地执行,不会影响已经推送的代码
- ⚠️ 可以使用 `--no-verify` 跳过(不推荐)
- ⚠️ CI/CD 中应该单独运行检查(不依赖 Hook)
**下一步**:
- [ ] 添加 commitlint(提交信息规范)
- [ ] 添加更多 Git hooks(commit-msg、pre-push)
- [ ] 配置 CI/CD 中的质量检查
享受自动化的 Git 工作流!🚀
/*
* @Date: 2026-01-28 20:45:00
* @Description: ESLint 配置 - 使用 ESLint 9 扁平配置格式
*/
import vue from 'eslint-plugin-vue'
import prettier from 'eslint-config-prettier'
export default [
{
// 忽略文件
ignores: [
'node_modules/**',
'dist/**',
'dist.*',
'build/**',
'*.min.js',
'public/**',
'VITE*.md',
'.cursor/**',
'.history/**',
'coverage/**',
'.nyc_output/**',
'src/auto-imports.d.ts',
'src/components.d.ts',
'mlaj/**',
],
},
// JavaScript / Vue 文件配置
{
files: ['**/*.{js,mjs,cjs,vue}'],
languageOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
globals: {
// 浏览器环境
window: 'readonly',
document: 'readonly',
navigator: 'readonly',
localStorage: 'readonly',
sessionStorage: 'readonly',
history: 'readonly',
location: 'readonly',
fetch: 'readonly',
URL: 'readonly',
XMLHttpRequest: 'readonly',
WebSocket: 'readonly',
HTMLElement: 'readonly',
customElements: 'readonly',
// 微信环境
wx: 'readonly',
WeixinJSBridge: 'readonly',
// Node.js 环境
process: 'readonly',
Buffer: 'readonly',
__dirname: 'readonly',
__filename: 'readonly',
module: 'readonly',
require: 'readonly',
exports: 'readonly',
// Vitest 测试环境
describe: 'readonly',
it: 'readonly',
test: 'readonly',
expect: 'readonly',
vi: 'readonly',
beforeEach: 'readonly',
afterEach: 'readonly',
beforeAll: 'readonly',
afterAll: 'readonly',
},
},
plugins: {
vue,
},
rules: {
// Vue 3 规则
'vue/multi-word-component-names': 'off', // 允许单词组件名
'vue/no-v-html': 'warn', // v-html 需谨慎使用
'vue/require-default-prop': 'off', // props 不强制默认值
'vue/require-prop-types': 'error', // props 必须有类型
'vue/no-mutating-props': 'error', // 禁止直接修改 props
'vue/component-definition-name-casing': ['error', 'PascalCase'], // 组件名 PascalCase
'vue/component-name-in-template-casing': ['error', 'PascalCase'], // 模板中组件名 PascalCase
'vue/no-unused-vars': 'error', // 禁止未使用的变量
'vue/padding-line-between-blocks': 'warn', // 块之间空行
// 通用 JavaScript 规则
'no-console': ['warn', { allow: ['warn', 'error'] }], // 禁止 console.log(允许 warn/error)
'no-debugger': 'error', // 禁止 debugger
'no-alert': 'warn', // 警告使用 alert
'no-var': 'error', // 禁止 var,使用 const/let
'prefer-const': 'error', // 优先使用 const
'no-duplicate-imports': 'error', // 禁止重复导入
'no-unused-vars': 'off', // Vue 已处理
'no-const-assign': 'error', // 禁止重新赋值 const
'prefer-template': 'warn', // 优先使用模板字符串
'template-curly-spacing': 'error', // 模板字符串空格
'object-shorthand': ['warn', 'always'], // 对象简写
'prefer-arrow-callback': 'warn', // 优先使用箭头函数
'arrow-spacing': 'error', // 箭头函数空格
'arrow-parens': ['warn', 'as-needed'], // 箭头函数括号
'arrow-body-style': ['warn', 'as-needed'], // 箭头函数体
// 代码质量
eqeqeq: ['error', 'always', { null: 'ignore' }], // 强制 ===
curly: ['error', 'all'], // 强制花括号
'brace-style': ['error', '1tbs', { allowSingleLine: true }], // 花括号风格
'no-else-return': 'warn', // if 中 return 后不要 else
'no-nested-ternary': 'warn', // 禁止嵌套三元
'no-unneeded-ternary': 'error', // 禁止不必要的三元
'prefer-destructuring': ['warn', { array: true, object: true }], // 优先解构
// 异步
'no-async-promise-executor': 'error', // 禁止 async promise executor
'no-await-in-loop': 'warn', // 循环中的 await 警告
'require-await': 'error', // async 函数必须有 await
'prefer-promise-reject-errors': 'error', // Promise reject 必须有 Error
// 错误处理
'no-throw-literal': 'error', // throw 必须是 Error 对象
'prefer-promise-reject-errors': 'error', // Promise reject 必须是 Error
// 性能
'no-loop-func': 'warn', // 循环中不要创建函数
'no-new-func': 'error', // 禁止 new Function
// 安全
'no-eval': 'error', // 禁止 eval
'no-implied-eval': 'error', // 禁止隐式 eval
'no-new-object': 'error', // 禁止 new Object()
'no-script-url': 'error', // 禁止 javascript:
},
},
// 测试文件配置
{
files: ['**/*.test.js', '**/*.spec.js', 'test/**'],
languageOptions: {
globals: {
describe: 'readonly',
it: 'readonly',
test: 'readonly',
expect: 'readonly',
vi: 'readonly',
beforeEach: 'readonly',
afterEach: 'readonly',
beforeAll: 'readonly',
afterAll: 'readonly',
},
},
},
// Prettier 配置(必须最后)
prettier,
]
......@@ -8,6 +8,12 @@
"build": ". ~/.nvm/nvm.sh && nvm use 18.19.1 && vite build",
"preview": "vite preview",
"test": "vitest run",
"test:ui": "vitest --ui",
"test:coverage": "vitest --coverage",
"lint": "eslint . --fix",
"lint:check": "eslint .",
"format": "prettier --write \"src/**/*.{js,vue,css,less,md,json}\"",
"format:check": "prettier --check \"src/**/*.{js,vue,css,less,md,json}\"",
"mcp:speckit": "node mcp/speckit_stdio_server.js",
"tar": "tar -czvpf dist.tar.gz mlaj",
"build_tar": "npm run build && npm run tar",
......@@ -20,7 +26,20 @@
"remove_tar": "rm -rf dist.tar.gz",
"dev_upload": "npm run build_tar && npm run scp-dev && npm run dec-dev && npm run remove_tar",
"behalo_upload": "npm run build_tar && npm run scp-behalo && npm run dec-behalo && npm run remove_tar",
"oa_upload": "npm run build_tar && npm run scp-oa && npm run dec-oa && npm run remove_tar"
"oa_upload": "npm run build_tar && npm run scp-oa && npm run dec-oa && npm run remove_tar",
"prepare": "husky"
},
"lint-staged": {
"*.{js,vue}": [
"eslint --fix",
"prettier --write"
],
"*.{css,less,scss}": [
"prettier --write"
],
"*.{json,md}": [
"prettier --write"
]
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^6.5.1",
......@@ -56,15 +75,24 @@
"weixin-js-sdk": "^1.6.5"
},
"devDependencies": {
"@playwright/test": "^1.58.0",
"@vitejs/plugin-vue": "^5.2.1",
"@vitejs/plugin-vue-jsx": "^4.1.2",
"@vue/eslint-config-prettier": "^10.2.0",
"@vue/test-utils": "^2.4.6",
"@vueuse/core": "^13.0.0",
"autoprefixer": "^10.4.19",
"axios": "^1.8.4",
"eslint": "^9.39.2",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-vue": "^10.7.0",
"husky": "^9.1.7",
"jsdom": "^24.1.3",
"less": "^4.2.2",
"lint-staged": "^16.2.7",
"postcss": "^8.4.35",
"prettier": "^3.8.1",
"prettier-plugin-tailwindcss": "^0.7.2",
"qs": "^6.14.0",
"rusha": "^0.8.14",
"tailwindcss": "^3.4.1",
......
This diff is collapsed. Click to expand it.