工具 – Prettier、ESLint、Stylelint
前言
以前在 Webpack 学习笔记 有稍微介绍过它们。这篇是单独整理版。
参考
简单介绍
Prettier 是一个 formatting 工具,目的是方便统一代码格式,比如使用 single quote 还是 double quote?
它支持许多语言,包括 JS、TS、CSS、Sass、HTML、JSON 等等。
ESLint 是 JS / TS 代码检查器。它用于保证代码质量,通过 2 个方式
-
统一格式 (formating),这部分的功能和 Prettier 是一样的。 只是 ESLint 比较 configable。Prettier 是出了名的“固执”,很多规范你只能跟随官方的格式,它不允许你做配置。
-
code quality,这点是 Prettier 没有的功能。
比如 function declare 了一个 parameter,但 function 内却完全没有调用到。(这通常是因为忘记移除)
这些都会被检测出来。在提交代码前我们就可以进行修改,这样就保证了代码质量。
Stylelint 也是这类检查器,它用于 CSS / Sass
Prettier vs ESLint & Stylelint
Prettier 的 formatting 功能, ESLint 和 Stylelint 都有。但是通常我们是用 Prettier 做 formatting 然后用 ESLint 和 Stylelint 做 code quality control。
而且 Stylelint v15.0 后,它自己阉割了 formatting 的功能,官方也叫你用 Prettier。
所以下面 ESLint 和 Stylelint 都是搭配 Prettier 使用的。
Prettier
Command-line
首先全局安装 prettier package (Prettier 基于 Node.js)
npm install prettier --global
创建项目
yarn init
创建一个 index.ts
const value = 'value';
Prettier 支持很多种语言的格式,这里只是随便拿 TS 做例子。
运行
prettier index.ts
效果
Prettier 不会告诉你,哪个地方格式错了,它只会输出一个格式化后的内容。
注意看, value 从原本的 single quote 变成了 double quote。(没错,Prettier 默认 TS 是用 double quote 的,幸好这个规则是可以配置的,下面会教)
prettier index.ts --write
加上 --write,它会直接修改 index.ts 的内容。
VS Code Prettier 插件
Command-line 的用法不方便,通常 Prettier 都是搭配 IDE 用的,在我们每一次保存文件时,自动跑一次 formatting。
首先安装插件
配置 VS Code
{ "[typescript]": { "editor.formatOnSave": true, "editor.defaultFormatter": "esbenp.prettier-vscode", }, }
针对 typescript 文件, 开启保存时自动 format,同时配置 format 时采用 Prettier 插件。(你也可以放全局,不需要针对 TypeScript,因为 Prettier 支持非常多语言)
Config Prettier
Prettier 能配置的东西不多,但还是有几个常用的。
创建 .prettierrc.json5 (注意:它开头有一个点,如果要支持 JSON with Comments 的话,extension 是 .json5,不需要 comment 就 .json 就够了)
.json5 VS Code 也需要配置
{ "files.associations": { "*.json": "jsonc", // 所有 .json 都是 JSON with Comments ".prettierrc.json5": "jsonc" // .prettierrc.json5 是 JSON with Comments }, }
然后 .prettierrc.json5 的内容是
{ "overrides": [ { "files": "*.(ts|js)", "options": { "singleQuote": true, // Prettier 默认是 double quote,我改成 single quote "printWidth": 100, // Prettier 默认最长的代码是 80px width (超出就会换行),我改成 100。 "arrowParens": "avoid", // (value) => value 去掉不必要的括弧 value => value "endOfLine": "auto" // 这个为了解决 LF,CRLF,\r, \r\n 的兼容问题。 } } ] }
针对 .ts 和 .js 配置。
Prettier plugin sort imports
prettier-plugin-sort-imports 插件可以排序 TypeScript 的 import,这样 import 就不会很乱了。
安装
yarn add prettier --dev
yarn add --dev @ianvs/prettier-plugin-sort-imports
提醒:
它有 2 个版本,@trivago 是正版,@ianvs 是 fork 的版本,主要的区别是 @trivago 的正版会排序 CSS import,
而我的项目 CSS 是有 override 概念的,不可以乱排序,所以我用了 @ianvs 的版本,这个版本就不会排序有 side-effect 的 imports。
然后添加配置到 .prettierrc.json5
{ "overrides": [ { "files": "*.(ts|js)", "options": { "singleQuote": true, // Prettier 默认是 double quote,我改成 single quote "printWidth": 100, // Prettier 默认最长的代码是 80px width (超出就会换行),我改成 100。 "arrowParens": "avoid", // (value) => value 去掉不必要的括弧 value => value "endOfLine": "auto", // 这个为了解决 LF,CRLF,\r, \r\n 的兼容问题。 // 排序 TypeScript 的 imports 相关配置 "importOrder": [ // 用正则表达式来匹配路径,然后按 array 的顺序排 "<THIRD_PARTY_MODULES>", // match 所有 node_modules 的 imports "src/module", // match 项目 module "Shared/Component/Stooges", "Shared/Component", "^[.]" ], "importOrderSortSpecifiers": true, // import { a, b } 内容要不要也排序 "plugins": ["@ianvs/prettier-plugin-sort-imports"] } } ] }
当遇到 side-effiect import,排序就会停止了
所以 Best Practice 是把有 side-effect 的 import 和没有的分开 2 个 group,有 side-effect 的全部在最下面。
另外,文档中有一个把 css 放到最下面的配置
这个配置支队 import styles from 'xx.css' 有效,对 import 'xx.css' 是无效的,因为后者属于有 side-effect。
还有一个小区别,importOrderSeparation options 没有了。原因是 @ianvs 版本更灵活。
ESLint
Command-line
全局安装 eslint package
npm install eslint --global
创建项目
yarn init
添加 TypeScript
yarn add typescript --dev
tsc --init
添加一个 index.ts
const a = ''; const b = ""; const yes = null == undefined;
我故意同时用了 single quote 和 double quote,而且还用了不安全的 ==(JS best practice 是用 === 而不是 ==)
待会儿我们看看 ESLint 能检查出问题吗。
添加 eslint
yarn add eslint --dev
init eslint
yarn create @eslint/config
注:eslint v9.0 有个大改版,v9 之前 init eslint command 是 eslint --init,我们这里用的是 v9 之后的新版本。
这时它会问一些相关的问题。
尝试检查
// 指定检查 index.ts yarn eslint index.ts // 指定检查所有 .ts 文件 yarn eslint *.ts
效果
error 就表示代码不过关,需要我们修改。
我们可以使用 --fix 自动修复功能
yarn eslint index.ts --fix
不过它的能力有限,只能修复一些初级的代码问题,复杂的依然需要我们自己修改。
VS Code ESLint 插件
安装 VS Code ESLint 插件(微软推出的哦)
现在 index.ts 会出现各种 error 和 warning。
通过 Quick Fix 我们可以查看报错的原因,也可以通过注释 disable 掉这行代码的检测。
通过配置 VS Code 的 codeActionsOnSave 还可以让 VS Code 在保存时自动替我们运行 yarn eslint index.ts --fix。
"[typescript]": { "editor.codeActionsOnSave": { "source.fixAll.eslint": "explicit", } }
Config ESLint & 结合 Prettier
上面我们使用的是 eslint recommended 的代码规范。
市场上还有其它的规范,比如 Google 的、Airbnb 的等等。当然我们也可以自己写规范,或者 override 它们的规范。(ESLint 可以 override 很多配置,不像 Prettier 就只有几个可以配置)
下面是我的规范 eslint.config.mjs
import pluginJs from '@eslint/js'; import globals from 'globals'; import tseslint from 'typescript-eslint'; export default [ { files: ['**/*.{js,mjs,cjs,ts}'] }, { languageOptions: { globals: globals.browser, // 为了开启下面的 recommendedTypeChecked,这里必须提供 tsconfig.json // tsconfig.json 通常也需要添加 files: ['eslint.config.mjs'] parserOptions: { project: 'tsconfig.json', }, } }, pluginJs.configs.recommended, ...tseslint.configs.strict, // 取代 tseslint.configs.recommended ...tseslint.configs.stylistic, // 取代 tseslint.configs.recommended ...tseslint.configs.recommendedTypeChecked, // 很多严格的检测 { rules: { '@typescript-eslint/no-non-null-assertion': 'off', // 允许使用 TypeScript 的 Non-null assertion operator '@typescript-eslint/no-empty-function': 'warn', // 当函数没有内容时会警示 // 当 unused 警惕时,可以用 underscore by pass '@typescript-eslint/no-unused-vars': [ 'warn', { argsIgnorePattern: '^_', varsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_', destructuredArrayIgnorePattern: '^_', }, ], '@typescript-eslint/no-unused-expressions': [ 'error', { allowTernary: true, // 允许 ternary expressions call function. e.g. true ? fn1() : fn2(); allowShortCircuit: true, // 允许 true && fn(); }, ], '@typescript-eslint/unified-signatures': 'off', // 允许 overload function // .subscribe(async () => {}) <-- Error: no-misused-promises // RxJS 的 subscribe 内部没有特别处理 async callback // 若我们传入 async callback,eslint 会认为不合理,不够安全 // 但这样很不方便,所以我就 by pass 它了 '@typescript-eslint/no-misused-promises': [ 'error', { checksVoidReturn: { arguments: false, }, }, ], // 允许在 constructor 里使用 static 方法, 比如: // constructor() { // this.formControl = new FormControl<string>('', { // validators: Validators.required, // }); // } // Validators.required 是 static,所以可以使用 '@typescript-eslint/unbound-method': [ 'error', { ignoreStatic: true }, ], // 允许 any assign: // private readonly hostElement: HTMLElement = inject(ElementRef).nativeElement; // 上面会 error,因为 nativeElement 是 any,eslint 不允许 any 拿来做 assignment // 要改成 // private readonly hostElement = inject(ElementRef).nativeElement as HTMLElement; // 我个人觉得 any 就是用来 bypass 的,所以应该要可以 assign to whatever,比较方便,当然 eslint 严格也是正确的,因人而异吧 '@typescript-eslint/no-unsafe-assignment': 'off', 'require-await': 'error', // async 函数内一定要有 await eqeqeq: ['error', 'always', { null: 'ignore' }], // 强制用 === 而不是 ==, 除了 == null 是允许的 'no-constant-condition': ['error', { checkLoops: false }], // 不允许 if(true),但是 white(true) 可以 'no-useless-rename': ['error'], // const { width: width } = dimension <-- 报错.对象解构时 rename 一定要真的换名字 width: width 等于没有丫 'object-shorthand': ['error'], // const { width: width } <-- 报错, variable name same as object key name is not necessary 'no-restricted-globals': [ // 不允许直接用 global,必须加上 prefix window.setTimeout 这样。 'error', 'localStorage', 'location', 'alert', 'setTimeout', 'clearTimeout', 'setInterval', 'clearInterval', 'requestAnimationFrame', 'cancelAnimationFrame', 'navigator', ], }, }, ];
另外,ESLint 还可以结合 Prettier 来使用。这个结合需要特别解释一下,不然会有点乱。
有 2 种结合方式:
-
分开用,但是配合执行
我们项目先添加 Prettier (依据上面我教的方式),然后再添加 ESLint。
需要特别注意 formatting 的规则一定要一致。比如说,Prettier format 时用 double quote,如果 ESLint 检测时要求 single quote,那肯定是报错的。
确保双方规则一致后,运行 2 个 commands 就可以了。
prettier index.ts --write yarn eslint index.ts --fix
配合 VS Code 插件,它会在 save 的时候跑 eslint --fix 然后再跑 prettier --write。(注:它是先跑 eslint --fix 哦)
-
把 Prettier 内置到 ESLint 里
这样的好处是我们只需要运行 1 个 command 就可以了。
安装额外的插件
yarn add prettier --dev yarn add eslint-config-prettier --dev yarn add eslint-plugin-prettier --dev
然后配置
接着再加上我们自定义的 prettier rules
当内置 Prettier 之后,其实我们就不需要原本的 Prettier 了。
可以把 on save prettier formatting 关掉。
"[typescript]": { "editor.formatOnSave": false, // off prettier formatting "editor.codeActionsOnSave": { "source.fixAll.eslint": "explicit", // 它里面包含 prettier formatting 了 } }
有几个点要注意一下
-
error message 没有那么清楚。
本来 ESLint 的 formatting error 是很清楚的。
改成 Prettier 的 formatting 后 error message 被弱化了。
这是因为 ESLint 有检测的概念,它的 formatting 是先检测哪里有问题,并且给予 error 然后才 --fix。
Prettier 它没有检测的功能,它是直接 format 输出 formatted 的内容。所有 ESLint 内部是拿 format 前和 format 后的内容做对比,然后返回一个简单的 erorr message。
-
现在运行 yarn eslint --fix,它内部会执行 prettier --write。
-
ESLint 的 Prettier 需要通过在 eslint.config.mjs 里做配置。它可不理 .prettierrc.json 哦。
Stylelint
Stylelint 和 ESLint 大同小异,它是负责检测 CSS / Sass 的。
Command-line
首先全局安装 stylelint package
npm install stylelint --global
创建项目
yarn init
添加一个 index.scss
body { background-color: #ff0000; display: flex; }
添加 stylelint
yarn add stylelint --dev
init stylelint
yarn create stylelint
它会创建一个 .stylelintrc.json 的配置文件。此时已经可以检测 CSS 了,但还不能检测 Sass。我们需要再添加一些配置。
yarn add stylelint-config-standard-scss --dev
它依赖 postcss。
安装 postcss
yarn add postcss@^8.4.19 --dev
然后换掉 extends
运行检测
stylelint index.scss
效果
成功报错了。它同样支持用 --fix 自动修复。
VSCode Stylelint 插件
比起 ESLint 它需要配置比较多东西,
{ "css.validate": false, "scss.validate": false, "stylelint.validate": ["css", "scss"], "[scss]": { "editor.codeActionsOnSave": { "source.fixAll.stylelint": "explicit" } } }
先关闭 VS Code 原生对 CSS 和 Sass 的检测,改用 Stylelint
同时添加多一个 fixAll.stylelint 到 codeActionsOnSave
这时就可以看到 error 了
save and --fix 的效果
结合 prettier
yarn add prettier --dev
yarn add stylelint-prettier --dev
修改 .stylelintrc.json(注:它不支持 JSON with Comments)
{ "extends": [ "stylelint-config-standard-scss", "stylelint-prettier/recommended" ], "rules": { "declaration-empty-line-before": null, "prettier/prettier": [ true, { "singleQuote": true, "printWidth": 100, "endOfLine": "auto" } ] } }
关闭 VS Code setting [scss] formatOnSave
"[scss]": { "editor.formatOnSave": false, // off prettier formatting "editor.codeActionsOnSave": { "source.fixAll.stylelint": "explicit" // 已经包含 prettier formatting 了 } }
效果
CSS Properties Ordering
有时候 CSS 属性的顺序很烦人,如果我们想要统一规范可以借助 stylelint-order 插件
yarn add stylelint-order --dev
修改 .stylelintrc.json
properties-order 我们可以放我们规范的顺序。
当然我们也可以跟随市场的规范,比如 Bootstrap 的顺序插件是 stylelint-config-recess-order
yarn add stylelint-config-recess-order
配置
添加 extends : stylelint-config-recess-order 就可以了。stylelint-order plugin 可以移除。
我的 .stylelintrc.json
{ "extends": [ "stylelint-config-standard-scss", "stylelint-prettier/recommended" ], "rules": { // by default class 只能是 kebab-case,要支持 BEM 就用下面这个正则 "selector-class-pattern": [ "^[a-z]+(-?[a-z0-9]+)*(__[a-z0-9]+(-?[a-z0-9]+)?)*(--[a-z0-9]+(-?[a-z0-9]+)?)*$", { "resolveNestedSelectors": true } ], "scss/dollar-variable-pattern": "^_?[a-zA-Z0-9\\-]+$", // kebab-case 或 _kebab-case // 因为 Prettier format 会导致这个 error. 相关 Issue: // https://github.com/prettier/prettier-eslint/issues/186 // https://github.com/prettier/prettier/issues/3806 "scss/operator-no-newline-after": null, // 空行可以帮助将代码分组,提高阅读体验 "declaration-empty-line-before": null, // 允许重复 selector,因为有时候我是按功能 group properties 的。 "no-duplicate-selectors": null, // 我的规范,组件一定是 .html, .css, .ts 三剑客的,方便以后内部扩展功能,不用修改外部接口. "no-empty-source": null, "custom-property-pattern": "^_?[a-zA-Z0-9\\-]+$|^[a-zA-Z0-9\\-]+_[a-zA-Z0-9\\-]+$", // kebab-case 或 _kebab-case 或 kebab-case_kebab-case (underscore 前面可以放 namespace 做管理) // 它老是乱报错。干脆关了吧。它经常搞错的原因如下 // https://stylelint.io/user-guide/rules/no-descending-specificity/#:~:text=However%2C%20since%20the%20linter%20does%20not%20have%20access%20to%20the%20DOM%20it%20can%20not%20evaluate%20this "no-descending-specificity": null, "prettier/prettier": [ true, { "singleQuote": true, "printWidth": 100, "endOfLine": "auto" } ] } }
Work with Web Bundlers (e.g. Webpack, Vite, Rollup)
上面例子中,我们都是以 Command-line 或 VS Code Plugin 的形式去使用 Pretter, ESLint, Stylelint。
还有一种方式是配合 Web Bundlers (e.g. Webpack),当 bundler 打包文件的时候,它会自动跑 eslint --fix 和 stylelint --fix。
对 Webpack 配置 ESLint 和 StyleLint 感兴趣的朋友可以看这篇 Webpack 学习笔记。
如果 bundler 不支持 (比如 Angular 的 CLI 对 Stylelint 支持不友好),那就只能在打包之前自己跑 Command-line 了。
分享我的做法:
VS Code Plugin 是一定要用的,因为它可以在 on save 的时候就做 prettier --write,eslint --fix 和 stylelint --fix,立竿见影。
VS Code setting:
所有文件都用 Prettier做 formatting
"editor.formatOnSave": true, "editor.defaultFormatter": "esbenp.prettier-vscode",
有例外的话就特别声明
"[cshtml]": { "editor.formatOnSave": false, },
Scss 就用 Stylelint 就好,因为 Stylelint 已经包含了 Prettier。
"[scss]": { "editor.formatOnSave": false, "editor.codeActionsOnSave": { "source.fixAll.stylelint": "explicit" } },
TypeScript 就用 ESLint 就好,因为 ESLint 已经包含了 Prettier。
"[typescript]": { "editor.formatOnSave": false, "editor.codeActionsOnSave": { "source.fixAll.eslint": "explicit", } },
VS Code 只能在 on save 的时候做 formatting 和检测,这是不够的,我们依然需要一个可以对整个项目做检测的方案。
最好是使用 Command-line,在 git commit 之前可以做个检测:
-
ESLint
在 eslint.config.mjs 添加 ignores 属性,表示这些路径不要检测
然后运行
yarn eslint **/*.ts **/*.js --fix
-
StyleLint
创建一个 .stylelintignore 文件
node_modules/** .yarn/** wwwroot/assets/** dist/**
然后运行
stylelint **/*.scss **/*.css --fix
-
或者在 package.json
"scripts": { "lint": "eslint **/*.ts --fix && stylelint **/*.scss --fix" // 如果上面这句报错 spawn ENAMETOOLONG,可以是试试下面这句 // "lint": "eslint '**/*.ts' --fix && stylelint '**/*.scss' --fix" },
运行
yarn run lint
或者如果项目使用 Web Bundler 而且它支持 ESLint 和 StyleLint 也可以交由 Bundler 做。这样在发布或本地运行前可以做个检测,试试确保代码整齐干净。
使用 Yarn PnP
上面的例子都是用 Yarn Classic。但其实 Prettier、ESLint、Stylelint 都支持 Yarn PnP。只有 2 点要注意
-
Command 要前面要加 yarn
eslint index.ts // 改成 yarn eslint index.ts
-
Prettier 需要 node_modules folder,当我们运行 command
yarn prettier index.ts
它会自动创建 folder,但只是空 folder 哦。
总结
要控制 TS、JS、Sass、CSS 的代码质量就必须使用 Prettier、ESLint、Stylelint。