前端工程化-系列
Webpack
Rollup
Monorepo 单仓库多应用
集成 lint 代码规范工具
Git lint 相关

前言

书接上回,为 Monorepo 单仓库多应用 项目集成代码规范工具,提高代码质量。

示例仓库:qxchuckle/monorepo-test

参考:
【从 0 到 1 搭建 Vue 组件库框架】3. 集成 lint 代码规范工具
【前端工程化】项目搭建篇-项目初始化&prettier、eslint、stylelint、lint-staged、husky
前端工程:ESLint - eslint.config.js 配置详解
一文搞懂eslint、prettier以及vscode插件之间的弯弯绕绕
stylelint 规范你的 css
你不能再说你不会配置ESLint和prettier了
3分钟入门 StyleLint
使用 ESLint、Prettier 和 Stylelint 来规范代码
2024年|ESlint9+Prettier从0开始配置教程
通过引入eslint-config包的方式解决项目代码风格和规范

ESLint

ESLint 是在 ECMAScript/JavaScript 代码中识别和报告模式匹配的工具,它的目标是保证代码的一致性和避免错误。

安装

仅需一行代码即可安装 ESLint:

1
npm init @eslint/config@latest

但在 Monorepo 环境下,很可能会在最后安装依赖时出现如下错误:

1
2
3
√ Which package manager do you want to use? · pnpm
☕️Installing...
 ERR_PNPM_ADDING_TO_ROOT  Running this command will add the dependency to the workspace root, which might not be what you want - if you really meant it, make it explicit by running this command again with the -w flag (or --workspace-root). If you don't want to see this warning anymore, you may set the ignore-workspace-root-check setting to true.

需要在 .npmrc 添加 ignore-workspace-root-check=true 配置。

默认情况下,如果你在工作区根目录下运行 npm i 命令,npm 会显示一个警告,提示你这可能不是你想要的,因为这会将依赖添加到所有的工作区项目中。如果你想在工作区根目录下添加依赖,你需要使用-w或—workspace-root标志来明确指定。如果设置了ignore-workspace-root-check=true,那么 npm 将不会显示这个警告,而是默认在工作区根目录下添加依赖。

再次运行,cli 为我们添加了五个开发依赖。

  1. @eslint/js 插件的 recommended 配置包含了一组被 ESLint 社区推荐的规则。
  2. eslint 是 ESLint 的核心库。
  3. eslint-plugin-vue 提供了一些针对 Vue 的 ESLint 规则。
  4. globals 提供了一个包含各种 JS 环境全局变量的对象,在 ESLint 中,使用 globals 来配置哪些全局变量是允许在你的代码中使用的。
  5. typescript-eslint 使 ESLint 和 Prettier 支持 TypeScript 的工具。
1
2
3
4
5
6
devDependencies:
+ @eslint/js ^9.10.0
+ eslint ^9.10.0
+ eslint-plugin-vue ^9.28.0
+ globals ^15.9.0
+ typescript-eslint ^8.4.0

还在根目录输出了 eslint.config.mjs 文件。

eslint.config.mjs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import globals from "globals";
import pluginJs from "@eslint/js";
import tseslint from "typescript-eslint";
import pluginVue from "eslint-plugin-vue";

export default [
{ files: ["**/*.{js,mjs,cjs,ts,vue}"] },
{ languageOptions: { globals: globals.browser } },
pluginJs.configs.recommended,
...tseslint.configs.recommended,
...pluginVue.configs["flat/essential"],
{
files: ["**/*.vue"],
languageOptions: { parserOptions: { parser: tseslint.parser } },
},
];

运行

在 package.json 中添加 lint 命令:
--fix 参数表示自动修复一些错误。

package.json
1
2
3
"scripts": {
"lint": "eslint --fix ./",
},

首次运行,会出现非常多的报错,但不必担心,这些只是 ESLint 对代码的检查,并不是代码出现了不可运行的错误。

1
2
3
4
5
6
7
8
9
10
C:\chuckle\qx\study_demo\Monorepo\pnpmWorkspaceTest\demo\src\views\List.vue
1:1 error Component name "List" should always be multi-word vue/multi-word-component-names
6:11 error Custom elements in iteration require 'v-bind:key' directives vue/valid-v-for

C:\chuckle\qx\study_demo\Monorepo\pnpmWorkspaceTest\demo\src\views\list\Estimated.vue
1:1 error Component name "Estimated" should always be multi-word vue/multi-word-component-names
31:36 error '_' is defined but never used @typescript-eslint/no-unused-vars
31:39 error 'index' is defined but never used @typescript-eslint/no-unused-vars

# ...

配置文件变化

ESLint v8.x 的生命周期终止日期为 2024-10-05,此后将不再维护。
在最新的 v9.x 版本中,使用了新的 eslint.config.js Flat config(扁平化配置),而不再使用传统的 .eslintrc 配置。迁移至 v9.x

关于 eslint 作出这一改变的原因,可以参阅 前端工程:ESLint - 扁平化配置系统【译】

可以使用如下命令,自动转换传统配置文件,但不保证在不进行进一步修改的情况下立即生效。

1
npx @eslint/migrate-config .eslintrc.json

还可以安装 @eslint/eslintrc 包,导入 FlatCompat() 并创建一个新实例来转换现有的 eslintrc 配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { FlatCompat } from "@eslint/eslintrc";
import path from "path";
import { fileURLToPath } from "url";
// mimic CommonJS variables -- not needed if using CommonJS
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const compat = new FlatCompat({
baseDirectory: __dirname
});

export default [
// mimic ESLintRC-style extends
...compat.extends("eslint-config-my-config"),
];

配置提示

安装 eslint-define-config 插件,在编辑配置时提供代码提示。

1
pnpm add -wD eslint eslint-define-config

该插件提供了 defineConfigdefineFlatConfig 函数,分别用于传统配置和平面配置。

1
2
3
4
5
import { defineFlatConfig } from "eslint-define-config";
export default defineFlatConfig([
{ files: ["**/*.{js,mjs,cjs,ts,vue}"] },
// ...
]);

配置对象简介

Flat config(扁平化配置) 导出一个包含若干配置对象的数组。官方文档:配置 ESLint

配置对象由以下属性组成:

  1. files:glob 数组,表示配置适用的文件,未指定时适用于所有与其他配置对象匹配的文件
  2. ignores:glob 数组,表示忽略检查的文件,
  3. languageOptions:配置如何检查 js 代码
    • ecmaVersion:strIng,表示支持的 ES 语法版本(只是语法,不包括 ES 的全局变量,全局变量要在 globals 中指定),可以是年份(如 2022)或者版本号(如 5),默认为 latest,表示最新版本
    • sourceType:string,表示js 源码类型,”script”表示传统 script 标签引入,”module”表示 esm,”commonjs”表示CommonJS 文件。默认情况下,.js 和 .mjs 文件使用 “module”;.cjs 文件使用 “commonjs”
    • globals:对象,指定添加到全局作用域中的其他对象,避免从第三方库中引入的全局变量不能被识别
    • parser:默认为 espree,指定解析器(包含 parse() 方法或 parseForESLint() 方法的对象)
    • parserOptions:对象,指定 parser 所需的额外配置选项,
  4. linterOptions:对象,包含与 linting 过程相关的设置
    • noInlineConfig :boolean,表示是否允许内联配置
    • reportUnusedDisableDirectives:boolean,表示是否应该跟踪和报告未用的禁用指令
  5. processor:包含 preprocess() 和 postprocess() 方法的对象,或者指示插件中处理器的名称的字符串(例如 “pluginName/processorName”)。
  6. plugins:包含插件名称与对应的插件对象的名值对对象。如果指定了 files,则只适用于与之匹配的文件。
  7. rules:包含具体的规则配置,当指定了 files 或 ignores 时,这些规则配置仅对匹配的文件可用。内置规则。规则值: warn(警告) | error(报错) | off
  8. settings:包含名称值对的对象,用于所有规则都可以访问的信息。

级联配置

当多个配置对象同时匹配一个给定的 glob 时,配置对象会被合并,如果有冲突时,后面的对象会覆盖前面的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
export default [
{
files: ["**/*.js"],
languageOptions: {
globals: {
MY_CUSTOM_GLOBAL: "readonly",
},
},
},
{
files: ["tests/**/*.js"],
languageOptions: {
globals: {
it: "readonly",
describe: "readonly",
},
},
},
];

上述配置中,对于所有的 js 文件,都只有一个自定义全局对象 MY_CUSTOM_GLOBAL,而对于 tests 目录下的 js 文件,还会有 it 和 describe 全局对象。

files

默认情况下,files 会匹配 **/*.js**/*.cjs**/*.mjs。 一般使用 files 和 ignores 的组合来决定配置对象的文件适用范围。

当一个配置对象没有 files 字段,则该配置对象为共享配置,供其他配置对象使用。

ignores

ignores 默认忽略 **/node_modules/**.git/**

对于仅有 ignores 的配置对象,则为全局忽略配置,会对默认忽略项进行扩展。

1
2
3
{
ignores: ["**/*.d.ts", "**/dist/*"],
},

指定了 ignore,最好也要指定 files:
如果有 ignores 而没有 files,但有任何其他配置项(如 rules),那么该配置对象适用于除了 ignores 指定的文件外的所有文件,相当于 files 为 **/*

通过 ! 取消忽略文件和目录,包括默认忽略项。

忽略 build 目录中除 build/test.js 之外的所有内容。
1
2
3
4
5
6
7
{
ignores: [
// 使用 build/** 会忽略整个目录及其内容,因此遍历将完全跳过目录,也就无法取消忽略其中的任何内容。
"build/**/*",
"!build/test.js"
]
}

安装 @eslint/compat,通过 includeIgnoreFile(),可以导入传统的 .gitignore 文件。

1
2
3
4
5
6
7
import { includeIgnoreFile } from "@eslint/compat";
// ...
const gitignorePath = path.resolve(__dirname, ".gitignore");

export default [
includeIgnoreFile(gitignorePath),
];

linterOptions

专门用来配置检查过程的选项,对文件源码的解析方式没有影响。它有两个选项:

  1. noInlineConfig:默认为 false,表示是否禁用内联配置,内联配置是通过 /*eslint*/ 注释实现的,例如 /*eslint semi: error*/。设置为 true,则会忽略所有内联配置。
  2. reportUnusedDisableDirectives/*eslint-disable*//*eslint-disable-next-line*/ 这样的禁用指令是用来禁用 ESLint 规则的,围绕代码的某些部分。随着代码的变化,这些指令有可能不再需要,因为代码的变化使规则不再被触发。通过设置该选项为 true,可以把未用的禁用指令被报告为警告。

languageOptions

languageOptions 是配置语言选项。

  1. ecmaVersion 指定支持的 ES 语法版本,确定语法和可用的全局变量,可以是年份(如 2022)或者版本号(如 5),默认为 latest,表示最新版本。
  2. sourceType 表示正在使用的 JavaScript 文件的模式。有以下三种值:
    • module ESM 模块(当ecmaVersion为3或5时无效)。代码具有模块范围并以严格模式运行。
    • commonjs CommonJS 模块。代码具有顶级函数作用域,并以非严格模式运行。
    • script 非模块。代码具有共享的全局范围并以非严格模式运行。
    • 默认情况下,.js 和 .mjs 文件的 sourceType 是 “module”,而 .cjs 文件则是 “commonjs”。
  3. parser 官方文档,自定义解析器将 JavaScript 代码转换为抽象语法树,以供 ESLint 检查。
  4. parserOptions 键值对对象,用于向自定义解析器传递选项。
  5. globals 配置可用的全局变量,键是全局变量名,值为 readonly、writable、off 之一,通常配合 globals 库使用。

plugins

plugins 插件是 eslint 最重要的配置,用于在 ESLint 项目中共享规则、处理器、配置、解析器等等。

可以通过 <插件名>/ 使用插件中特定的规则:

1
2
3
4
5
6
7
8
9
10
{
rules: {
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-unused-expressions": "off",
"@typescript-eslint/no-unsafe-function-type": "off",
"vue/multi-word-component-names": "off",
"vue/no-unused-vars": "off",
},
},

插件本质是一个向 ESLint 公开某些属性的 JavaScript 对象,创建插件详见官方文档-Create Plugins

processor

processor 用于将非 js 文件转化为 eslint 可以检查的代码片段。

处理 markdown 文件
1
2
3
4
5
6
7
8
9
10
import markdown from "@eslint/markdown";
export default [
{
files: ["**/*.md"],
plugins: {
markdown
},
processor: "markdown/markdown"
}
];

工作流程

ESLint 通过 parser 指定的解析器,如 @typescript-eslint/parser,将源码转换为抽象语法树(AST),供后续流程使用,而 parserOptions 可以对解析器的能力进行详细设置。

ESLint 提供了自定义规则的接口,开发者需要遵照接口,根据分析器的 AST 产物,实现规范检查逻辑,再将实现的多条规范聚合为 plugin 插件的形式,形成规则集

有了规则集,能够理解规范,还需要进一步在 rules 字段中对这些规则进行开启或者关闭的声明,只有开启的规则才会生效。

规则集中有大量的规则,完全在项目中一条条配置是不可取的,社区逐渐演进出了许多配置预设,从而减少绝大多数手动配置的工作量。大部分插件本身也自带推荐预设,如 eslint:recommended@typescript-eslint/recommended

最新的扁平化配置也使得我们很容易去自定义、覆盖一些独特的配置。

编辑配置

按目前项目需求编辑规则,关闭一些不需要的检查。

eslint.config.mjs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// @ts-check
import globals from "globals";
import pluginJs from "@eslint/js";
import tseslint from "typescript-eslint";
import pluginVue from "eslint-plugin-vue";
import { defineFlatConfig } from "eslint-define-config";

/// <reference types="@eslint-types/typescript-eslint" />

export default defineFlatConfig([
// 插件的预设配置放在最前面
pluginJs.configs.recommended,
...tseslint.configs.recommended,
...pluginVue.configs["flat/recommended"],
{
languageOptions: {
// 配置全局变量
globals: {
...globals.browser,
...globals.node,
InstanceType: "readonly",
NodeJS: "readonly",
},
},
},
{
// 因为demo使用了auto-imports插件,所以需要把自动导入的变量加到全局变量配置里
files: ["demo/src/**/*"],
languageOptions: {
// 配置全局变量
globals: {
ref: "readonly",
onMounted: "readonly",
nextTick: "readonly",
},
},
},
// 需要忽略的文件
{
ignores: ["**/*.d.ts", "**/dist/*"],
},
// 一些自定义规则
{
rules: {
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-unused-expressions": "off",
"@typescript-eslint/no-unsafe-function-type": "off",
"vue/multi-word-component-names": "off",
"vue/no-unused-vars": "off",
"no-unused-vars": "off",
semi: "error",
"prefer-const": "error",
},
},
// 匹配 vue、ts 文件,使用 tseslint.parser
{
files: ["**/*.ts", "**/*.vue"],
languageOptions: {
parserOptions: {
parser: tseslint.parser,
project: "./tsconfig.eslint.json",
extraFileExtensions: ["vue"],
},
},
},
]);

我们希望 ESLint 能覆盖所有源码文件,一般新创建一个 tsconfig.eslint.json,在其中包含所有希望被规范化的源码文件。

tsconfig.eslint.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
// eslint 检查专用,不要包含到 tsconfig.json 中
"extends": "./tsconfig.base.json",
"compilerOptions": {
"noEmit": true
},
// 只检查,不构建,因此要包含所有需要检查的文件
"include": [
"**/*",
// .xxx.js 文件需要单独声明,例如 .eslintrc.js
"**/.*.*"
],
"exclude": [
// 排除产物目录
"**/dist",
"**/node_modules"
]
}

注意,tsconfig 的文件集合范围总是需要 >= ESLint 的 files 范围,否则会出现该 TSConfig 不包括此文件的报错。

该 TSConfig 不包括此文件的报错
1
2
3
4
5
6
7
8
C:\chuckle\qx\study_demo\Monorepo\pnpmWorkspaceTest\scripts\utils.ts
0:0 error Parsing error: ESLint was configured to run on `<tsconfigRootDir>/scripts\utils.ts` using `parserOptions.project`: <tsconfigRootDir>/tsconfig.eslint.json
Found unexpected extension `vue` specified with the `parserOptions.extraFileExtensions` option. Did you mean `.vue`?
However, that TSConfig does not include this file. Either:
- Change ESLint's list of included files to not include this file
- Change that TSConfig to include this file
- Create a new TSConfig that includes this file and include it in your parserOptions.project
See the typescript-eslint docs for more info: https://typescript-eslint.io/troubleshooting/typed-linting#i-get-errors-telling-me-eslint-was-configured-to-run--however-that-tsconfig-does-not--none-of-those-tsconfigs-include-this-file

社区规则集

ESLint 拥有许多成熟的规则集,这些规则集都是成熟团队的实践。它们通过 plugin 实现了很多个性化的规则,又内置了海量的 rules 配置预设。

eslint-config-airbnb Airbnb 规则集
eslint-config-alloy 腾讯 AlloyTeam 的规则集
eslint-config-standard StandardJS 规则集

Stylelint

Stylelint 是 CSS 样式的 Lint 工具,用于在代码中发现并报告样式代码中的问题。

它与 ESLint 非常相似,包括配置文件、规则等,只是它专注于 CSS 样式的检查。

安装

1
pnpm i -wD stylelint

继续安装一些项目需要的插件、规则集。

  1. stylelint-config-standard-scss:集成完整的 sass 规则集。它本身继承了很多东西,包括 sass 规则实现的插件、css 标准规则集 stylelint-config-standard 等。
  2. stylelint-config-recommended-vue:使 Stylelint 支持对 .vue 文件的 style 标签部分进行检查。
  3. stylelint-config-recess-order:一种推荐的 css 属性排序的规则(先写定位,再写盒模型,再写内容区样式,最后写 CSS3 相关属性)。
  4. @stylistic/stylelint-plugin:Stylelint 升级到 15.0.0 大版本后,计划废弃风格相关的规则,这部分内容分离出来由社区维护,需要单独安装。(若使用 prettier 管理代码风格,则不必使用该插件)
1
pnpm i -wD stylelint-config-standard-scss stylelint-config-recommended-vue stylelint-config-recess-order @stylistic/stylelint-plugin

安装 stylelint-define-config 以获得配置提示。

1
pnpm i -wD stylelint-define-config

配置

Stylelint 的配置文件与 ESLint 的传统配置 .eslintrc 相似。

在根目录创建 stylelint.config.mjs 文件。具体的规则配置项,详见官方文档-rules

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// @ts-check
import defineConfig from "stylelint-define-config";

/// <reference types="@stylelint-types/stylelint-scss" />

export default defineConfig({
// 继承的预设,这些预设包含了规则集插件
extends: [
// 基本 scss 规则
"stylelint-config-standard-scss",
// scss vue 规则
"stylelint-config-recommended-vue/scss",
// 样式属性顺序规则
"stylelint-config-recess-order",
],
plugins: [
// 代码风格插件
"@stylistic/stylelint-plugin",
],
rules: {
// 自定义规则集的启用 / 禁用
"@stylistic/max-line-length": 100,
"no-empty-source": null,
},
// glob 匹配排除的文件
ignoreFiles: ["**/dist/*", "**/node_modules/*"],
overrides: [
{
files: ["*.html", "**/*.html"],
customSyntax: "postcss-html",
},
],
});

修复 html 报错

在对 html 检查时,可能有如下莫名其妙的报错:

1
2
demo/index.html
1:1 ✖ Unknown word CssSyntaxError

针对 html 格式文件采用自定义语法解析器:

1
pnpm i -wD postcss-html
stylelint.config.mjs
1
2
3
4
5
6
overrides: [
{
files: ["*.html", "**/*.html"],
customSyntax: "postcss-html",
},
],

运行

编辑 package.json,添加 lint 命令。

package.json
1
2
3
4
"scripts": {
"lint:style": "stylelint --fix ./**/*.{css,scss,vue,html}",
"build:ui": "pnpm run lint:fix && pnpm run lint:style && pnpm run type:src && pnpm --filter ./packages/** run build && pnpm run dts-mv && pnpm run css-mv"
},

Stylelint 将自动按规则格式化 CSS,并报告了需要手动修复的问题。

VSCode 插件

配合 VSCode 插件 ESLintStylelintPrettier 使用,可以在编辑器中实时检查代码规范。

如果插件没有实时提示,可以查看面板->输出,找到对应插件查看问题,多半是 VSCode 版本需要升级了。

.vscode\settings.json 对插件进行配置:

.vscode\settings.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
{
// 指定 typescript 的路径,使其能够找到项目中安装的 typescript。
"typescript.tsdk": "node_modules/typescript/lib",
// 关闭 IDE 自带的样式验证
"css.validate": false,
"less.validate": false,
"scss.validate": false,
// 指定 stylelint 生效的文件类型(尤其是 vue 文件)。
"stylelint.validate": [
"css",
"scss",
"postcss",
"vue"
],
// 启用 eslint 的格式化能力
"eslint.format.enable": true,
// eslint 会在检查出错误时,给出对应的文档链接地址
"eslint.codeAction.showDocumentation": {
"enable": true
},
// 指定 eslint 生效的文件类型(尤其是 vue 文件)。
"eslint.probe": [
"javascript",
"typescript",
"vue"
],
// 指定 eslint 的工作区
// "eslint.workingDirectories": [
// {
// "mode": "auto"
// }
// ],
// 指定 eslint 的 node_modules 路径,使其能够找到项目中安装的 eslint。
"eslint.nodePath": "node_modules",
// 在保存时,自动修复 lint 错误。
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.fixAll.stylelint": "explicit"
},
// 保存时自动格式化
"editor.formatOnSave": true,
}

代码风格

我们通过 Stylelint 管理了 CSS 的代码质量,且 ESLint 更多是用来管理代码质量。

ESLint 在 v8.53.0 版本弃用了代码格式化规则,deprecating-formatting-rules,但这些规则仍然能用,真正的移除预计会在 v10 版本。

仍要使用 ESLint 去管理代码风格,可以使用 @stylistic/eslint-plugin-js@stylistic/eslint-plugin-ts 插件,这些插件由 Anthony Fu 维护,包含了 ESLint 核心和 typescript-eslint 中废弃的格式化规则。

代码风格应该交给专业的插件去做,比如 Prettier

Prettier

Prettier 是一个代码格式化工具,它会自动格式化代码,使代码风格保持一致。

并且支持 vue、react 等框架的 SFC 文件,以及 markdown、json 等文件的格式化。它会删除了所有原始代码格式,并确保所有输出的代码符合一致的格式。

安装

1
pnpm i -wD prettier

在 .vscode/setting.json 中设置一些配置。

1
2
3
4
5
6
{
// 设置默认格式化程序
"editor.defaultFormatter": "esbenp.prettier-vscode",
// 保存时自动格式化
"editor.formatOnSave": true,
}

.prettierignore 为 Prettier 的忽略文件。

eslint 集成 prettier

eslint 及其相关插件目前仍然管理着一部分代码风格,我们需要将 prettier 的规则集成覆盖到 eslint 中,并解决 eslint 和 prettier 之间的冲突。

两个插件:

  1. eslint-plugin-prettier:基于 prettier 代码风格的 eslint 规则。即 eslint 使用 prettier 规则来格式化代码,并将差异报告为单个ESLint问题
  2. eslint-config-prettier:禁用所有与格式相关的 eslint 规则,解决 prettier 与 eslint 规则冲突。
1
pnpm install -wD eslint-config-prettier eslint-plugin-prettier

文档操作导入 eslint-plugin-prettier/recommended,该 recommended 已经集成了两者的配置。

注意:将其添加为文件中配置数组的最后一项,以便 eslint-config-prettier 有机会覆盖其他配置。

eslint.config.mjs
1
2
3
4
5
6
7
8
9
10
11
import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended";
export default defineFlatConfig([
// ....
eslintPluginPrettierRecommended,
{
rules: {
// prettier 配置为警告而不是报错
'prettier/prettier': ['warn'],
},
},
]);

与 stylelint 集成

从 Stylelint v15 开始,所有与样式相关的规则都已弃用。如果您使用的是 v15 或更高版本,并且未使用这些弃用的规则,则 stylelint-config-prettier 插件不再是必要的。

配置文件

创建 .prettierrc 文件,并设定一些规则。configurationoptions
我喜欢使用 prettier.config.mjs 配合 prettier-define-config 插件以获得配置提示。

prettier.config.mjs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// @ts-check
import { defineConfig } from '@archoleat/prettier-define-config';
export default defineConfig({
arrowParens: 'always', // 箭头函数参数周围加上括号
bracketSameLine: true, // 大括号与代码在同一行
bracketSpacing: true, // 大括号内部加空格
semi: true, // 分号结尾
experimentalTernaries: false, // 不使用实验性三元表达式
singleQuote: true, // 使用单引号
jsxSingleQuote: true, // JSX属性值使用单引号
quoteProps: 'preserve', // 保留引号样式
trailingComma: 'all', // 尾随逗号保留
singleAttributePerLine: false, // 不强制单个属性换行
htmlWhitespaceSensitivity: 'css', // HTML空格敏感性为css
vueIndentScriptAndStyle: false, // Vue脚本和样式不缩进
proseWrap: 'never', // 文本不换行
insertPragma: false, // 不插入格式化标记
printWidth: 80, // 打印宽度为80个字符
requirePragma: false, // 不要求格式化标记
useTabs: false, // 不使用Tab缩进
embeddedLanguageFormatting: 'auto', // 嵌入语言格式自动
tabWidth: 2, // Tab宽度为2个空格
endOfLine: 'auto', // 行尾自动
});

VSCode 响应配置修改有时候非常慢,善用 reload。

.vscode\settings.json 中配置自动格式化等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
{
// 保存时自动格式化
"editor.formatOnSave": true,
// 指定格式化工具为 prettier
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[vue]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[html]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[markdown]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[css]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[less]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[scss]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
}

运行

除了在保存时自动格式化(这个也看个人喜好开启),每当 git 提交代码前,也应该格式化代码。

暂时不在这里展开,后续在 git hook 上集成 prettier 的操作。

https://prettier.nodejs.cn/docs/en/cli.html
1
2
# 暂时先使用命令行格式化吧
npx prettier . --write

Replace ··· with ↹↹

这是因为没有统一好项目的缩进风格。

.vscode\settings.json 中配置强制使用2个空格缩进:

1
2
3
4
5
{
"editor.tabSize": 2,
"editor.insertSpaces": true,
"editor.detectIndentation": false,
}

完整配置

qxchuckle/monorepo-test

eslint.config.mjs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
// @ts-check
import globals from 'globals';
import pluginJs from '@eslint/js';
import tseslint from 'typescript-eslint';
import pluginVue from 'eslint-plugin-vue';
import { defineFlatConfig } from 'eslint-define-config';
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';

/// <reference types="@eslint-types/typescript-eslint" />

export default defineFlatConfig([
// 插件的预设配置放在最前面
pluginJs.configs.recommended,
...tseslint.configs.recommended,
...pluginVue.configs['flat/recommended'],
{
languageOptions: {
// 配置全局变量
globals: {
...globals.browser,
...globals.node,
InstanceType: 'readonly',
NodeJS: 'readonly',
},
},
},
{
// 因为demo使用了auto-imports插件,所以需要把自动导入的变量加到全局变量配置里
files: ['demo/src/**/*'],
languageOptions: {
// 配置全局变量
globals: {
ref: 'readonly',
onMounted: 'readonly',
nextTick: 'readonly',
},
},
},
// 需要忽略的文件
{
ignores: ['**/*.d.ts', '**/dist/*'],
},
// 一些自定义规则
{
rules: {
'@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unused-expressions': 'off',
'@typescript-eslint/no-unsafe-function-type': 'off',
'vue/multi-word-component-names': 'off',
'vue/no-unused-vars': 'off',
'no-unused-vars': 'off',
semi: 'error',
'prefer-const': 'error',
},
},
// 匹配 vue、ts 文件,使用 tseslint.parser
{
files: ['**/*.ts', '**/*.vue'],
languageOptions: {
parserOptions: {
parser: tseslint.parser,
project: './tsconfig.eslint.json',
extraFileExtensions: ['vue'],
},
},
},
eslintPluginPrettierRecommended,
{
rules: {
// prettier 配置为警告而不是报错
'prettier/prettier': ['warn'],
},
},
]);
stylelint.config.mjs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// @ts-check
import defineConfig from 'stylelint-define-config';

/// <reference types="@stylelint-types/stylelint-scss" />

export default defineConfig({
// 继承的预设,这些预设包含了规则集插件
extends: [
// 基本 scss 规则
'stylelint-config-standard-scss',
// scss vue 规则
'stylelint-config-recommended-vue/scss',
// 样式属性顺序规则
'stylelint-config-recess-order',
],
plugins: [
// 代码风格插件,应该使用 prettier 管理代码风格
// '@stylistic/stylelint-plugin',
],
rules: {
// 自定义规则集的启用 / 禁用
// '@stylistic/max-line-length': 100,
'no-empty-source': null,
},
// glob 匹配排除的文件
ignoreFiles: ['**/dist/*', '**/node_modules/*'],
overrides: [
{
files: ['*.html', '**/*.html'],
customSyntax: 'postcss-html',
},
],
});
prettier.config.mjs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// @ts-check
import { defineConfig } from '@archoleat/prettier-define-config';

export default defineConfig({
arrowParens: 'always', // 箭头函数参数周围加上括号
bracketSameLine: true, // 大括号与代码在同一行
bracketSpacing: true, // 大括号内部加空格
semi: true, // 分号结尾
experimentalTernaries: false, // 不使用实验性三元表达式
singleQuote: true, // 使用单引号
jsxSingleQuote: true, // JSX属性值使用单引号
quoteProps: 'preserve', // 保留引号样式
trailingComma: 'all', // 尾随逗号保留
singleAttributePerLine: false, // 不强制单个属性换行
htmlWhitespaceSensitivity: 'css', // HTML空格敏感性为css
vueIndentScriptAndStyle: false, // Vue脚本和样式不缩进
proseWrap: 'never', // 文本不换行
insertPragma: false, // 不插入格式化标记
printWidth: 80, // 打印宽度为80个字符
requirePragma: false, // 不要求格式化标记
useTabs: false, // 不使用Tab缩进
embeddedLanguageFormatting: 'auto', // 嵌入语言格式自动
tabWidth: 2, // Tab宽度为2个空格
endOfLine: 'auto', // 行尾自动
});
.vscode\settings.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
{
"editor.tabSize": 2, // 设置缩进的空格数
"editor.insertSpaces": true, // 使用空格而不是制表符进行缩进
"editor.detectIndentation": false, // 禁用自动检测文件的缩进方式
"files.exclude": {
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/CVS": true,
"**/.DS_Store": true,
"**/Thumbs.db": true,
"**/node_modules": false,
"**/.deploy_git": true,
"**/.history": true
},
// 指定哪些文件不参与搜索
"search.exclude": {
"**/node_modules": true,
"dist": true,
"build": true,
"yarn.lock": true
},
// 指定 typescript 的路径,使其能够找到项目中安装的 typescript。
"typescript.tsdk": "node_modules/typescript/lib",
// 关闭 IDE 自带的样式验证
"css.validate": false,
"less.validate": false,
"scss.validate": false,
// 指定 stylelint 生效的文件类型(尤其是 vue 文件)。
"stylelint.validate": [
"css",
"scss",
"postcss",
"vue"
],
// 启用 eslint 的格式化能力
"eslint.format.enable": true,
// eslint 会在检查出错误时,给出对应的文档链接地址
"eslint.codeAction.showDocumentation": {
"enable": true
},
// 指定 eslint 生效的文件类型(尤其是 vue 文件)。
"eslint.probe": [
"javascript",
"typescript",
"vue",
],
// 指定 eslint 的工作区
// "eslint.workingDirectories": [
// {
// "mode": "auto"
// }
// ],
// 指定 eslint 的 node_modules 路径,使其能够找到项目中安装的 eslint。
"eslint.nodePath": "node_modules",
// 在保存时,自动修复 lint 错误。
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.fixAll.stylelint": "explicit"
},
// 保存时自动格式化
"editor.formatOnSave": true,
// 指定格式化工具为 prettier
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[vue]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[html]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[markdown]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[css]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[less]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[scss]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
// 行尾默认为 LF 换行符而非 CRLF
"files.eol": "\n",
// 自动补充闭合的 HTML 标签
"html.autoClosingTags": true,
}

总结

我们在原先的 Monorepo 项目中集成了最新的 ESLint 和 Stylelint,以对代码质量进行检查。

然后,我们引入了 Prettier,使代码风格保持一致。

最后,我们解决了 ESLint 和 Prettier 之间的冲突,并配合 VSCode 设置了一系列自动化配置。

每次都进行全量 lint 检查是非常耗时的,在下一篇文章中进行解决。