工具 – 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, // 为了开启下面的 rules // -recommendedTypeChecked, // -@typescript-eslint/no-misused-promises, // -@typescript-eslint/unbound-method // 这里必须提供 tsconfig.json // 注:不是所有 rule 都需要 link tsconfig,有些需要而已 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', ], }, }, ];
如果有设置 TypeScript 相关的 rules,需要让 eslint.config.mjs 连接上 tsconfig.json。
连接上要确保,但凡有需要 eslint 的文件,tsconfig 也必须要 include,不然它就会报错,比如上面这样。
我设置的 eslint 是检查所有的文件
也包括了当前的 eslint.config.js。
但我的 tsconfig.json 却只 cover "src" forlder 而已,像这样就不行。
解决方向有两个
一个是扩大 tsconfig 范围,
另一个是缩小 eslint 范围。
推荐后者,因为 config 没有必要检查啦...
另外,要小心这个 ignores 坑 -- Github – Having issues with ignores in v9
ignores 必须单独一个对象,不可以和其它 (e.g. files) 属性放在一起,不然会失灵。
结合 Prettier
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。