三个人写代码会有四种格式,讨论”要不要加分号”浪费的是全团队的时间。工程化的答案是把风格问题交给工具:Prettier 管格式、ESLint 管代码质量、Stylelint 管 scss,本地用 husky + lint-staged 在提交前强制执行,CI 里再兜底一次。本文给出 v22 项目从零到完整的配置过程。
三件套的分工
| 工具 | 职责 | 回答的问题 |
|---|---|---|
| Prettier | 代码格式 | 缩进几格、引号单双、要不要分号 |
| ESLint | 代码质量与 Angular 约定 | 未使用变量、选择器不合规范、模板里的隐患 |
| Stylelint | 样式质量 | 选择器命名、无效属性、scss 规则 |
原则:格式问题归 Prettier,其他工具关闭与之重叠的规则,否则两边打架,开发者无所适从。
Prettier:五分钟统一格式
npm install -D prettier
配置文件 .prettierrc.json:
{
"printWidth": 100,
"singleQuote": true,
"semi": true,
"trailingComma": "all",
"overrides": [
{
"files": "*.html",
"options": { "printWidth": 120 }
}
]
}
.prettierignore 排除生成物:
dist/
node_modules/
*.d.ts
package-lock.json
package.json 里加脚本:
{
"scripts": {
"format": "prettier --write \"src/**/*.{ts,html,scss,json,md}\"",
"format:check": "prettier --check \"src/**/*.{ts,html,scss,json,md}\""
}
}
两个 Angular 特有的注意点:
- 内联模板不会被格式化为 HTML。组件里
template反引号内的内容对 Prettier 只是一个字符串。长模板用外部xxx.component.html,才能享受格式化——这也是第 31 篇推荐长模板外置的原因之一 - angular.json 不在默认范围。它是 JSON with comments,如需格式化需在 Prettier 2.x 以上直接支持(会保留注释),加入 glob 即可
编辑器侧装好 Prettier 插件并设置”保存时格式化”,格式问题在日常编辑中就消化掉了。
ESLint:angular-eslint 与 flat config
ESLint 9 起 flat config(eslint.config.mjs)是标准形态,angular-eslint 官方生成器也基于它:
ng add @angular-eslint/eslint
生成器会创建 eslint.config.mjs 并在 angular.json 里配置 ng lint 的 builder。手写等价配置如下:
// eslint.config.mjs
// @ts-check
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
import angular from 'angular-eslint';
export default tseslint.config(
{
files: ['**/*.ts'],
extends: [
eslint.configs.recommended,
...tseslint.configs.recommended,
...angular.configs.tsRecommended,
],
processor: angular.processInlineTemplates,
languageOptions: {
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
rules: {
'@angular-eslint/component-selector': [
'error',
{ type: 'element', prefix: 'app', style: 'kebab-case' },
],
'@angular-eslint/directive-selector': [
'error',
{ type: 'attribute', prefix: 'app', style: 'camelCase' },
],
'@angular-eslint/no-input-rename': 'error',
'@angular-eslint/no-output-rename': 'error',
'@angular-eslint/use-lifecycle-interface': 'error',
'@typescript-eslint/consistent-type-imports': 'error',
'@typescript-eslint/no-unused-vars': [
'error',
{ argsIgnorePattern: '^_' },
],
},
},
{
files: ['**/*.html'],
extends: [
...angular.configs.templateRecommended,
...angular.configs.templateAccessibility,
],
rules: {
'@angular-eslint/template/no-positive-tabindex': 'error',
'@angular-eslint/template/eqeqeq': 'error',
},
},
{
files: ['**/*.spec.ts'],
rules: {
'@typescript-eslint/no-explicit-any': 'off',
},
},
);
几个关键点:
processor: angular.processInlineTemplates是 angular-eslint 的招牌能力:把内联模板抽出来按 HTML lint,弥补了 Prettier 不管内联模板的空缺- 组件/指令选择器规则把第 31 篇的命名约定变成硬约束,
prefix按团队实际情况改 - templateAccessibility 预设包含
alt-text、click-events-have-key-events等无障碍规则,对 v22 项目几乎零成本(无障碍话题见第 45 篇) - 测试文件单独放宽,避免
any报警淹没真正的断言
package.json 脚本:
{
"scripts": {
"lint": "ng lint",
"lint:fix": "ng lint --fix"
}
}
与 Prettier 的边界交给配置而非人:安装 eslint-config-prettier 并放在 extends 最后,关掉所有与格式相关的 ESLint 规则:
npm install -D eslint-config-prettier
import prettier from 'eslint-config-prettier';
// extends 数组末尾追加
// prettier
Stylelint:管住 scss
npm install -D stylelint stylelint-config-standard-scss
stylelint.config.mjs:
export default {
extends: ['stylelint-config-standard-scss'],
rules: {
'scss/at-rule-no-unknown': [
true,
{
ignoreAtRules: ['use', 'forward', 'include', 'mixin', 'function'],
},
],
'selector-class-pattern': [
'^[a-z][a-z0-9-]*$',
{
message: '类名使用 kebab-case(Angular 组件类与 Material 前缀已对齐)',
},
],
'scss/dollar-variable-empty-line-before': 'always',
'declaration-block-no-redundant-longhand-properties': null,
},
ignoreFiles: ['**/dist/**', '**/node_modules/**'],
};
selector-class-pattern 与 Angular 生态天然契合:组件 host 类、Material 的 mat-* 类都是 kebab-case。跑一遍:
{
"scripts": {
"stylelint": "stylelint \"src/**/*.scss\"",
"stylelint:fix": "stylelint \"src/**/*.scss\" --fix"
}
}
Material 主题项目(第 36、37 篇)的 scss 里有大量 @use 与 token 覆盖,at-rule-no-unknown 的 ignore 清单务必保留,否则满屏误报。
husky + lint-staged:提交前最后一道闸
全库 lint 太慢,只 lint 改动的文件才现实:
npm install -D husky lint-staged
npx husky init
.husky/pre-commit 只留一行:
npx lint-staged
package.json 中配置 lint-staged:
{
"lint-staged": {
"*.{ts,html}": ["eslint --fix", "prettier --write"],
"*.scss": ["stylelint --fix", "prettier --write"],
"*.{json,md}": ["prettier --write"]
}
}
效果:git commit 时对暂存文件依次执行修复与格式化,失败则提交被拒。注意 ng lint 走 Angular builder 不好按文件调用,所以 lint-staged 里直接用 eslint 命令行(flat config 会被自动读取)。
CI 兜底
本地钩子可以被 --no-verify 绕过,CI 必须独立设防。GitHub Actions 示例:
name: lint
on: [pull_request, push]
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm run format:check
- run: npm run lint
- run: npm run stylelint
format:check 用 --check 只报告不改写,保证主分支永远是格式化的。
收尾清单
一个 v22 新项目的工程化基线:
| 环节 | 工具 | 触发时机 |
|---|---|---|
| 保存时 | 编辑器 Prettier 插件 | 日常 |
| 提交时 | husky + lint-staged | git commit |
| 手动 | npm run lint / stylelint / format | 本地任意时刻 |
| CI | 同上三个命令的 check 形态 | 每次推送与 PR |
从第一天就配好这套流水线的成本是半小时,事后补救的成本是无数 code review 里的”请把缩进改成……”。