CHARLIE SAYS

查理如是说
DATE 2026-08-24
THEME
SERIES / ANGULAR / P-197 · Angular 高级教程

Angular 22+ 教程 32:工程化三件套——Prettier / ESLint / Stylelint

三个人写代码会有四种格式,讨论”要不要加分号”浪费的是全团队的时间。工程化的答案是把风格问题交给工具: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-textclick-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-stagedgit commit
手动npm run lint / stylelint / format本地任意时刻
CI同上三个命令的 check 形态每次推送与 PR

从第一天就配好这套流水线的成本是半小时,事后补救的成本是无数 code review 里的”请把缩进改成……”。

系列导航

← 算法 032:算法思想:回溯算法 目录 算法 033:算法思想:动态规划算法 →
← 返回文章列表