全面梳理代码规范化:EditorConfig + Prettier + ESLint

前言

在项目中常常会搭配 EditorConfig、Prettier、ESLint 统一代码规范,但对为何需要搭配使用、三者的作用范围和工作原理只有模糊的认识。为了解答心中的疑惑,我开始着手探索最终整理出这篇文章。通过这篇文章,你能:

  • 理清三种规范化工具为何而生、如何工作、如何配置和集成、为何需要搭配使用
  • 了解 Git Hooks、Husky、lint-staged 的作用、原理和使用方式

EditorConfig:统一代码编辑器编码风格(Editor Coding Style)

EditorConfig 应对的问题

代码编辑器自身维护一份配置,往往可通过 Preferences Settings 修改。配置中包含编码风格设置,比如「设定缩进使用 Tab 还是 Space」,「一个 Tab 占用多少列」,「是否在文件末尾显示空行」等等。

编辑器设置界面

代码编辑器的配置信息独立维护不对外共享。使用不同编辑器打开同一份文件,如果编辑器配置不统一,显示效果和输入内容很有可能不一致。

比如,一个 Tab 占用两列的 Sublime,一个 Tab 占用四列的 VSCode ,打开同一份文件显示效果不同

img

比如,一个缩进为 Tab 的 Sublime,一个缩进为 2 个 Space 的 VSCode,编辑同一份文件,输入内容不同

img

单个编辑器独立操作只是存在隐患,在多编辑器或跨编辑器的情况下,隐患就容易升级为问题,最终导致代码处于混杂状态。

EditorConfig 如何工作

我们在项目中共享 .editorconfig 文件,该文件可被 EditorConfig 解析,由 EditorConfig 告知编辑器覆盖默认配置。

一些编辑器内置支持 EditorConfig,比如 WebStorm、Github;而一些编辑器需要安装 EditorConfig 插件,比如 VSCode、Sublime。

对主流代码编辑器的高覆盖支撑 EditorConfig 成为被广泛应用的解决方案。

如何使用 EditorConfig

1. 确保编辑器中已支持 EditorConfig

各编辑器的支持方式请查看 官方文档 「No Plugin Necessary」 和 「Download a Plugin」 两部分

2. 创建并配置 .editorconfig

在项目目录下创建 .editorconfig 文件,EditorConfig 可配置的编码风格条目包括:

indent_style    设置缩进为 tab 或 space

tab_width       设置 tab 所占列数。默认是indent_size

indent_size     设置缩进所占列数,如果 indent_style 为 tab,则以 tab_width 值作为缩进宽度

end_of_line     设置换行符,值为lf、cr和crlf

charset         设置编码,值为latin1、utf-8、utf-8-bom、utf-16be和utf-16le,不建议使用utf-8-bom

trim_trailing_whitespace  设为 true 表示会去除行尾的空白字符

insert_final_newline      设为 true 表示使文件以一个空白行结尾

root           表示是最顶层的配置文件,设为 true 时,停止向上查找
复制代码

EditorConfig 的作用对象是编辑器,因此配置可作用于所有类型的可编辑文件。同时 EditorConfig 支持为特定的文件或特定类型的文件进行差异化设置。

可参照下述配置,更细致的设置方法可见 官方文档 File Format Details 部分。

# https://editorconfig.org

# 已经是顶层配置文件,不必继续向上搜索
root = true

[*]
# 编码字符集
charset = utf-8
# 缩进风格是空格
indent_style = space
# 一个缩进占用两个空格,因没有设置tab_with,一个Tab占用2列
indent_size = 2
# 换行符 lf
end_of_line = lf
# 文件以一个空白行结尾
insert_final_newline = true
# 去除行首的任意空白字符
trim_trailing_whitespace = true

[*.md]
insert_final_newline = false
trim_trailing_whitespace = false

复制代码

总结

EditorConfig 解决了编辑器配置层面的编码风格一致性问题。

然而关于代码风格的部分并未涉及,比如是否「需要在语句末尾添加分号」,「字符串使用单引号还是双引号包裹」,「多行对象的书写规范」等等。

Prettier:统一代码风格(Code Formatter)

Prettier 解决的问题

每位开发者的代码书写风格或多或少都有不同,比如一些作者喜欢加分号,一些作者认为基于 JS 引擎识别能力根本没有必要写分号。在 Prettier 之前,对于代码风格的争论似乎永无定论。统一规范便能终止争论,人们不用再纠结于如何设计代码样式,代码修改将集中于逻辑而非代码样式。

Prettier 如何工作

Prettier 通过语法分析将代码解析为 AST 树,在 AST 树上应用代码风格规范重新生成代码。(可在 Playground 查看三者转换过程)

Prettier 根据文件路径自动推断解析器(.js 文件使用 babel 解析器、.css 文件使用 css 解析器),对代码进行解析。对不同类型文件的识别和解析受限于 Prettier 的内部支持,因此 Prettier 并非覆盖所有类型的文件,如下图所示,支持前端主流开发语言,足够应对日常开发。

img

配置 Prettier

Prettier 是一种严格规范的代码风格工具,主张尽量降低可配置性,严格规定组织代码的方式。

Prettier 只提供少量配置项,这么做的原因很直观:既然为停止争论而生,那么为何还需要设置更多选项让人们继续纠结于如何配置呢?

Prettier 处理的范围包含:

  1. 字符串引号风格
  2. 空行处理
  3. 多行对象格式
  4. 分号处理
  5. 打印宽度:控制换行
  6. 通过换行控制评论影响范围

配置项

Prettier 提供如下配置项,可按需查询选项自行配置,一般只按需少量配置。

{
    printWidth: 80,
    // 打印宽度,默认是 80 列

    tabWidth: 2,
    // 缩进所占列数,默认是 2 列

    useTabs: false,
    // 缩进风格是否是Tab,默认是 false ,使用空格缩进

    semi: true,
    // 在语句末尾添加分号,默认是 true

    singleQuote: false,
    // 使用单引号,默认是 false

    quoteProps: "as-needed",
    // 对象中的属性使用引号,
    // "as-needed" 只对需要的属性加引号,
    // "consistent" 同一对象中属性引号保持统一,有福同享,有难同当
    // "preserve" 强制使用引号。
    // 默认为 as-needed

    jsxSingleQuotes: false, // JSX中使用单引号,默认是 false

    trailingComma: "es5",
    // 多行时是否结尾添加逗号
    // "es5" ES5中允许逗号的容器中添加逗号,比如 objects/arrays
    // "all" 尽可能添加逗号
    // "none" 不允许添加逗
    // 默认值是 "es5"

    bracketSpacing: true,
    // 是否保留对象内侧两端的空格,比如 { foo: bar } 和 {foo:bar} 的区别

    jsxBracketSameLine: false,
    // 多行 JSX 的元素是否能和属性同行,默认是 false

    arrowParens: "always",
    // 箭头函数参数使用圆括号包裹 比如 (x) => x 和 x => x 的区别
    // "always" 总是包裹
    // "avoid" 尽可能避免包裹

    rangeStart: 0,
    // 只格式化文件中的一部分,范围开始于第几行

    rangeEnd: Infinity,
    // 只格式化文件中的一部分,范围结束于第几行

    parser: "none"
    // 指定解析器,Prettier会根据文件路径推断解析器
    // 比如 .js 文件使用 babel 解析,.scss 文件使用 post-scss 解析

    filepath: "none"
    // 指定用于推断使用那个解析器的文件名

    requirePragma: false
    // 限制只格式化在文件顶部做了需格式化标识的文件
    // 适用于在大型未格式化项目中,先指定少量文件格式化

    insertPragma: false

    proseWrap: "preserve"

    htmlWhitespaceSensitivity: "css"
    // HTML 文件的空格敏感度
    // "css" 和 css 的 display 属性保持一致
    // "strict" 空格敏感
    // "ignore" 空格不敏感

    vueIndentScriptAndStyle: false
    // 是否对 Vue 文件中 <script> 和 <style> 标签内的代码应用缩进

    endOfLine: "lf"
    // 换行符

    embeddedLanguageFormatting: "auto"
    // 是否格式化嵌入引用代码,比如 markdown 文件中嵌入的代码块
    // "auto" Prettier 自动识别并格式化
    // "off" 关闭自动格式化

}
复制代码

配置文件

Prettier 使用 cosmiconfig 支持配置文件,cosmiconfig 是一种常用的配置文件读取工具,按照下述顺序沿文件树寻找配置文件,找到则停止:

  • package.json 中的 prettier 字段
  • .prettierrc 文件
  • .prettierrc.json 文件
  • .prettierrc.js 文件
  • .prettierrc.toml 文件

选择上述任一方式进行自定义配置 Prettier,如不存在配置文件,Prettier 将依照默认值处理。

共享配置

module.exports = {
  ...require("@company/prettier-config"),
  semi: false,
};
复制代码

有时需要在多项目之间共享配置文件,这时可将配置文件作为独立模块发布,通过引入模块实现共享;

有时需要在 monorepo 中共享配置文件,这时直接将配置文件放置在根目录,如果子项目需要自定义,引入根目录配置文件再进行扩展即可。

安装 Prettier 依赖

# 安装固定版本,确保不同环境中的表现一致(dev 和 ci)
yarn add prettier --dev --exact
复制代码

安装依赖之后,命令行调用 prettier --write [file/dir/glob] 即可对指定的文件、目录、匹配项进行代码格式化。

但通常我们不会选择手动调用,而是集成进 IDE 和 Git Hooks 使其成为自动化流程中的一部分。

IDE 集成

各个 IDE 的安装方式请查看 Prettier 首页的 Editor Support 部分。

Prettier 格式化工具内置 Prettier 内核并且拥有一份独立配置,但是当项目中已安装 Prettier 依赖时会使用项目依赖,项目依赖 > 全局依赖 > 内置依赖;当查找到项目中的 Prettier 配置文件时优先使用配置文件。

安装工具之后需要设置 IDE 才能成为利器,以 VSCode 为例:

  1. 设置为默认格式化工具:”Format Document With…” -> “Configure Default Formatter…”,调用 “Format Document” 对当前文件手动格式化检测设置效果;
  2. 设置保存文件时自动格式化:Preference Settings 中搜索 “Format On Save” 勾选;

上述设置可保证本地被修改的文件正确格式化,但无法保证外部导入的文件,也无法保证团队中其他成员的编辑。因此需要再设置一层更有保障的屏障:提交代码前走一遍自动格式化。

Git Hooks 集成

实现提交代码前自动格式化需要借助 Git Hooks,先来了解一下 Git Hooks、Husky、lint-staged

Git Hooks

Git 在特定的生命周期开放了一些钩子,使用者往钩子上挂自定义脚本,便能在合适的时机自动执行指定任务。比如在提交时执行 prettier --write 自动格式化代码。

Git 的所有钩子在 Git – Git 钩子 (git-scm.com) 中有具体详尽的介绍,有兴趣可以扩展阅读。

在这我们只会用到“提交工作流”钩子,提交工作流包含 4 个钩子:

  1. pre-commit 在提交信息 编辑前 运行,在这个阶段塞入 代码检查 流程,检查未通过返回非零值即可停止提交流程;
  2. prepare-commit-msg 在默认信息被创建之后运行,此时正是 启动编辑器前 ,可在这个阶段加载 commitizen 之类的辅助填写工具;
  3. commit-msg完成编辑后 运行,可在这个阶段借助 commitlint 进行提交信息规范性检查;
  4. post-commit提交完成后 运行,在这个阶段一般做一些通知操作。

使用 Git 钩子最直观的方式是操作 .git/hooks 下的示例文件,将对应钩子文件的 .sample 后缀名移除即可启用。然而这种操作方式存在弊端:

  1. 需要操作项目范围外的 .git 目录
  2. 无法同步 .git/hooks 到远程仓库

Husky

操作 .git/hooks 的两个弊端可以通过为 Git 指定 hooks 目录完美避过,如下命令

# 指定 Git hooksPath 为根目录下的 .githooks 目录
git config core.hooksPath .githooks
复制代码

Husky 便是基于此方案,将 hooks 目录指定为项目下的 .husky 目录,同时提供简单易用的命令行工具管理目录中的 hooks 文件,不妨跟着 Husky 的手动安装流程看看它做了什么

# 安装 husky
npm install husky --save-dev

# 激活 Git hooks,这一步创建 .husky 目录并设置 git hooksPath
npx husky install

# 将自动激活 Git hooks 挂载到 npm prepare 生命周期上
# prepare触发点:无参数执行 npm install 时,执行 npm publish/npm pack 时
npm set-script prepare "husky install"
复制代码

lint-staged

如果对项目中所有文件一次性格式化,大范围的修改很可能出现不可控的情况。

这时可以借助 lint-staged 将处理范围限制在 Git 暂存区内 (staged) 的文件。

lint-staged 同样使用 cosmiconfig 支持配置,由于需配置内容较少,一般会直接在 package.json 中进行设置。

lint-staged 按照文件匹配指定任务,比如:

{
  "lint-staged": {
    "*.{ts,js,json,yml,md}": ["prettier --write"]
  }
}
复制代码

如此一来,需执行的任务直接在 lint-staged 中指定即可。和 Git Hooks 配合使用时,只需在 hooks 上挂载 npx lint-staged 驱动任务执行。

Git Hooks 集成 Prettier

实现提交前进行代码格式化,不过是在 pre-commit 阶段执行 prettier --write,借助 lint-staged 限制操作范围。一行命令搞定:

# mrm 是配置文件生成工具
# mrm lint-staged 会进行下列操作:
# 1. 自动安装 lint-staged 和 husky 依赖
# 2. 根据项目中已存在的配置文件生成 lint-staged 配置
# 3. 通过 husky 往 pre-commit hooks 上挂载 npx lint-staged
npx mrm lint-staged
复制代码

和 EditorConfig 配合使用

进入总结之前,我们需要思考一个问题,有了 Prettier 还需要 EditorConfig 吗?两者相交且不包含,是否会有冲突?

我们需要重演一下两者的作用过程:EditorConfig 作用于预览和输入阶段,Prettier 在保存和提交阶段重新组织代码,Prettier 会成为代码形态的最终决定者。

实际上如 API · Prettier 所描述,Prettier 对 .editorconfig 文件做了采用。对于一条相交属性,优先级关系是:Prettier 配置 > editorconfig 配置 > Prettier 默认值。

考虑到 EditorConfig 覆盖所有类型的文件,我的方案是 EditorConfig 管理相交属性,其他属性则由 Prettier 控制。

总结

Prettier 用于规范代码风格,但并不涉及语法检查,由此引入下一个话题 —— ESLint

ESLint:JavaScript 代码检查工具(Code Linter)

Prettier 专注于统一代码样式,而 ESLint 专注于找到代码存在的问题避免错误。

JavaScript 本身是一门动态弱类型语言,原始状态下没有语法检查工具支持,开发者只能在运行代码时发现错误,不断“试错”。ESLint 填补了这个缺口,在开发者书写代码时提示代码存在的问题,甚至在能力范围以内加以修正。

简单概括 Lint 提供的功能:

1. 避免低级 bug,找出可能发生的语法错误

使用未声明变量、修改 const 变量……

2. 提示删除多余的代码

声明而未使用的变量、重复的 case ……

3. 确保代码遵循最佳实践

可参考 airbnb stylejavascript standard

4. 统一团队的代码风格

加不加分号?使用 tab 还是空格?

引用自 深入理解 ESLint – 知乎 (zhihu.com)

ESLint 的工作原理

ESLint 的底层要素是 AST 和 规则(Rules)。ESLint 的内部工作步骤可以概括为:

  1. ESLint 通过解析器(parser)将源代码解析成 AST
  2. 遍历 AST,遍历到节点和路径时触发特定的钩子
  3. Rule 在钩子上挂载检测逻辑;执行检测逻辑时发现当前语法不符合规范,直接向 ESLint 上报错误信息。

Rule

Rule 的本体是一个模块,包含 metacreate 两个属性。

  • meta 定义规则的元数据
  • create 返回对象,该对象在合适的钩子上定义检测逻辑

以 ESLint 内置的 no-void 规则为例:no-void 规则建议禁用 void

AST 结构

AST explorer 所示,void 操作符在 AST 中被解析为一个 UnaryExpression 类型的节点;

同时查阅 estree 可知 UnaryExpression 涵盖 "-" | "+" | "!" | "~" | "typeof" | "void" | "delete" 操作符,并通过 operator 字段区分。因此 UnaryExpression[operator="void"] 便可匹配到 void,AST 遍历到 UnaryExpression[operator="void"] 节点时执行挂载到该钩子上的所有检测。

no-void 模块的代码如下所示

/**
 * @fileoverview Rule to disallow use of void operator.
 * @author Mike Sidorov
 */
"use strict";

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

module.exports = {
  meta: {
    // 规则类型:"suggestion" 表示不会导致错误,但建议修改
    type: "suggestion",
    // 文档描述: 在规则列表页显示的描述
    docs: {
      description: "disallow `void` operators",
      category: "Best Practices",
      recommended: false,
      url: "https://eslint.org/docs/rules/no-void",
    },
    // 上报信息中可用的字符串集
    messages: {
      noVoid: "Expected 'undefined' and instead saw 'void'.",
    },
    // 规定有效的规则选项
    schema: [
      {
        type: "object",
        properties: {
          allowAsStatement: {
            type: "boolean",
            default: false,
          },
        },
        additionalProperties: false,
      },
    ],
  },

  create(context) {
    // meta 中规定的有效选项可在运行上下文中被读取
    const allowAsStatement =
      context.options[0] && context.options[0].allowAsStatement;

    //--------------------------------------------------------------------------
    // Public
    //--------------------------------------------------------------------------

    return {
      'UnaryExpression[operator="void"]'(node) {
        // 遍历到 void 节点时执行检测逻辑
        if (
          allowAsStatement &&
          node.parent &&
          node.parent.type === "ExpressionStatement"
        ) {
          return;
        }
        // 上报错误信息
        context.report({
          node,
          // 从 meta 中设定的字符串集查找字符串
          messageId: "noVoid",
        });
      },
    };
  },
};
复制代码

fix 的实现

想必大家都有所耳闻,ESLint 除了检查问题还能 fix 可修复的问题,是否可修复又是如何确定的呢?

答案还是在 Rule 定义中,可修复的问题在上报错误时会夹带 fixer,告知 ESLint 如何修复。

context.report({
    node: node,
    message: "Missing semicolon".
    fix: function(fixer) {
        return fixer.insertTextAfter(node, ";");
    }
});
复制代码

配置 ESLint

在 ESLint 的架构中,需要解析器,需要规则;而在这种架构的支撑下,ESLint 支持可配置和可扩展,插件化也是 ESLint 的高光亮点。最终展现给开发者的是丰富的配置,接着来介绍 ESLint 的配置项。

parser

ESLint 默认使用 Espree 作为解析器,Espree 目前(2021.04.17)全面支持 ECMAScript 2020 同时部分支持 ECMAScript 2021(最新的提案支持情况可查阅 proposals)。

得益于 Espree 及时跟进语言更新,开发者在大部分环境中可直接使用 ESLint。但 ESLint 和其他工具同时使用时就需要配置合适的解析器了。

@babel/eslint-parser

面向客户端的项目往往要将高版本语言编译为 ES5,需要语言编译器比如 Babel,这时得确保 Compiler 和 Linter 解析出的 AST 一致。

@babel/eslint-parser 应对 Babel 和 ESLint 合用的场景,用 Babel 解析器生成并转换为 ESLint 可理解的 ESTree;

@typescript-eslint/parser

Espree 无法解析 TS 语法,@typescript-eslint/parser 将 TS 源码解析为 ESTree。

parserOptions

Espree 支持到 ES2020,同时支持 JSX,在解析器选项中可指定你想要支持的语言选项

{
  "parserOptions": {
    // 开发所用的 ECMAScript 版本 6 - 10 2015 - 2020,默认为 ES5
    "ecmaVersion": 6,
    // 代码类型:script 脚本, module 模块
    "sourceType": "module",
    // 额外的语言特性
    "ecmaFeatures": {
      // 启用 JSX
      "jsx": true,
      // 允许全局作用域下使用 return 语句
      "globalReturn": true,
      // 启用全局严格模式
      "impliedStrict": true
    }
  }
}
复制代码

全局变量和环境

ESLint 的 no-undef 规则会检测代码中未定义的变量,有一些变量是手动引入的第三方库声明的,使用前需要在 ESLint 配置文件中声明

{
  "globals": {
    // 声明 jQuery 对象为全局变量
    "$": false // true表示该变量为 writable,false 表示 readonly, "off" 表示禁用
  }
}
复制代码

一个环境定义了一组预定义的全局变量,根据代码执行环境设置

{
  "env": {
    "browser": true, // 项目中有代码在浏览器环境运行
    "node": true, // 项目中有代码在 Node.js 环境下运行
    "es6": true // 启动 ES6 全局变量和类型,同时会自动设置解析器为ES6
  }
}
复制代码

rules

{
  "rules": {
    "eqeqeq": "off",
    "curly": "error",
    "quotes": ["error", "double"]
  }
}
复制代码

ESLint 在内部维护规则定义,而在配置文件中,你可以使用注释或配置文件开启或关闭规则。

规则有三种状态:

  • "off"0 – 关闭规则
  • "warn"1 – 开启规则,使用警告级别的错误:warn (不会导致程序退出)
  • "error"2 – 开启规则,使用错误级别的错误:error (当被触发的时候,程序会退出)

规则定义中的 meta.schema 限定可接受的有效参数,配置时参数跟在规则状态后传入

// 打开规则 quotes,并且传入参数 "double"
/* eslint quotes: ["error", "double"] */

// 关闭规则 eqeqeq
/* eslint eqeqeq: "off" */
复制代码

plugins

ESLint 内置的核心规则只考虑 ECMAScript,对于 React、Vue、TypeScript 等非 ECMAScript 范围内的,ESLint 内置规则无法应对。这时需要通过插件来定制一些规则。

插件中定制规则,并提供可直接使用的一组或多组配置对象(ESLint 配置是一个对象模块,我将它简称为配置对象,每一个配置对象都可拥有 parserrulespluginsextends 等属性)

eslint-plugin-prettier 为例:

module.exports = {
  // 提供一组名为 recommended 的配置对象,可通过 extends 直接应用到配置中
  configs: {
    recommended: {
      extends: ["prettier"],
      plugins: ["prettier"],
      rules: {
        "prettier/prettier": "error",
        "arrow-body-style": "off",
        "prefer-arrow-callback": "off",
      },
    },
  },
  // 定制规则,[插件名]/[规则名],该规则为 prettier/prettier
  rules: {
    prettier: {
      meta: {
        docs: {
          url: "https://github.com/prettier/eslint-plugin-prettier#options",
        },
        type: "layout",
        fixable: "code",
        schema: [
          // Prettier options:
        ],
        messages: {
          // Prettier Messages:
        },
      },
      create(context) {
        return {
          Program() {},
        };
      },
    },
  },
};
复制代码

extends

{
  "extends": "eslint:recommended"
}
复制代码

ESLint 配置是一个对象模块,可以通过 extends 应用一组已存在的配置对象。

继承也就是在当前配置对象的基础上合并上另一些配置对象,继承的对象有几种情况:

  1. eslint: 开头的字符串,由官方提供
    1. eslint:recommended 启用 ESLint 推荐开启的规则
    2. eslint:all 开启 ESLint 中所有核心规则
  2. eslint-config- 开头的字符,由第三方库共享,常见场景:
    1. 直接使用代码最佳实践,如 eslint-config-airbnbeslint-config-standard
    2. 使用自定义的常用配置,以 eslint-config-xxx 形式发包到 npm,在多项目中共享使用
  3. plugin: 开头的字符串,使用 Plugin 中的配置
    • 调用方式为plugin:插件名/配置名,比如 plugin:react/recommended 意思是使用 eslint-plugin-react 中的 recommended 配置
  4. 相对路径,使用本地配置文件

配置文件

ESLint 并没有使用 cosmiconfig 维护配置文件,而是自成一派,以下述顺序排序优先级。在找到配置文件后会继续向上查找,找到 root: true 的文件为止,然后合并所有文件的配置对象。这种模式的优点在于自动实现按目录层级继承。

  1. .eslintrc.js
  2. .eslintrc.cjs
  3. .eslintrc.yaml
  4. .eslintrc.yml
  5. .eslintrc.json
  6. package.json

安装依赖

yarn add eslint -D
复制代码

依赖安装成功之后,便可通过命令行手动使用 ESLint

# eslint cli 引导创建配置文件
yarn eslint --init

# 尝试 lint 指定文件
yarn eslint xxx.js
# lint 所有文件
yarn eslint .
# lint 指定类型,指定目录
yarn eslint --ext .jsx,.js lib/

# 带缓存,只检查有改变的文件
yarn eslint --cache

# 尽量修复问题
yarn eslint --fix
复制代码

接下来将 lint 加入自动化工作流

IDE 集成

各 IDE 的插件安装方式可查看 Integrations – ESLint 中文

以 VSCode 为例,安装 ESLint – Visual Studio Marketplace 插件,该插件要求项目或全局环境安装 eslint 依赖。

插件提供智能提示。这里介绍几个常用的配置项:

{
  // 设置 lint 执行时机,onType 输入时,onSave 保存时
  "eslint.run": "onSave",
  // 设置保存时自动 fix
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
  }
}
复制代码

Git Hooks 集成

Git Hooks 在 Prettier 部分已着重介绍过,lint 集成进 commit 流程,同样是在 lint-staged 中进行配置

{
  "lint-staged": {
    "src/**/*.{js}": ["eslint --fix"]
  }
}
复制代码

和 Prettier 配合使用

ESLint 有两类规则:

ESLint 是否可替代 Prettier?

代码风格检查似乎和 Prettier 的功能相重叠,ESLint 是否可以完全替代 Prettier,答案是否定的。

首先,ESLint 中的 Formatting rules 并非都提供了fixer,上面列出几条规则示例里仅有 comma-style 提供了修复功能;

其次,ESLint 着重于 JS/TS,无法兼顾 CSS、Markdown 的代码风格;

术业有专攻,代码风格规范化还得交给 Prettier。

如何解决冲突?

如何解决 ESLint 和 Prettier 职责重叠部分的冲突?

方案一,[eslint-config-prettier](prettier/eslint-config-prettier: Turns off all rules that are unnecessary or might conflict with Prettier. (github.com)) 关闭 ESLint 中和 Prettier 可能冲突的所有 Rules,eslint 负责代码质量检查,prettier 做 formatter;

方案二,eslint-plugin-prettier 该插件增加了 prettier/prettier 规则, 该规则执行 prettier 并将错误信息上报 eslint。简而言之,将 prettier 融合到 eslint 中,担起代码风格检查的功能,同时需要搭配 eslint-config-prettier 关闭掉 ESLint 中代码风格检查相关的规则。

# 安装 config 关闭 ESLint 规则
yarn add eslint-config-prettier -D
复制代码
// .eslintrc.json 使用 prettier 插件提供的配置
// 一步加入全组配置:config/plugin/rules
{
  "extends": ["plugin:prettier/recommended"]
}
复制代码
// vscode 设置
{
  "editor.formatOnSave": true,
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
  }
}
复制代码

总结

EditorConfig、Prettier、ESLint 三者在代码规范化领域专注的点各不相同,各有专攻,组合使用才能达到全面覆盖。

Prettier 将 EditorConfig 考虑在内,ESLint 通过插件组合 Prettier,个人的推荐配置是自底向上分工

  • EditorConfig 专注于统一编辑器编码风格配置
  • Prettier 专注于检查并自动更正代码风格
  • ESLint 专注于 JavaScript 代码质量检查

最终的配置清单可见 code-canonicalization (github.com)

参考资源

Prettier 看这一篇就行了 – 知乎 (zhihu.com)

深入理解 ESLint – 知乎 (zhihu.com)

ESLint 工作原理探讨 – 知乎 (zhihu.com)

EditorConfig

Prettier 官方文档

Configuring ESLint – ESLint 中文

Git – Git 钩子 (git-scm.com)

husky/index.ts

okonet/lint-staged: ?? — Run linters on git staged files (github.com)

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享