前端工具-eslint&prettier推荐最佳实践

1,879 阅读8分钟

前言

如今,前端工程都已全面推广代码规范化,代码格式便是代码规范化的内在命题之一,eslint提供了静态分析帮助开发者快速发现代码的一些规范问题,并可以借助内置的规则配置集自动地修复不符合最佳实践的代码(当然,这部分能力是有限的)。prettier则重点关注代码格式,不关心代码编写逻辑。

eslint和prettier是功能互补的工具,eslint关注代码编写是否符合最佳实践规范,prettier则专注于代码文件格式化

因此,大多数前端项目都会同时集成eslintprettier,他们两者规则集不同,部分规则存在冲突,于是在实际开发中经常出现使用prettier格式化后的代码因为eslint静态检查不通过而无法提交(前提是配置了eslint+prettier+pre-commit执行提交检查)。
如何利用好两者是一个比较能影响开发体验的问题。本文整理了笔者参与项目中的工程实践,给出eslintprettier两者的配置和使用场景,希望能给大家一些指引。

使用场景关键字:VSCodeReactTypescript

目标:基于快捷键灵活进行代码格式化和自动修复非规范代码。

prettier

prettier特性:

  • 一个内置规则集的代码格式化工具
  • 支持多种语言
  • 与大多数编辑器集成
  • 简单的配置项

安装

根据官方指引,用户可以本地安装或全局安装 prettier 依赖,这样就能通过npx指令调用prettier脚本执行代码格式化。

// 步骤1
// 局部安装
npm install --save-dev --save-exact prettier
// 全局安装
npm install prettier -g

// 步骤2.【可选】在项目根目录生成prettier配置文件
echo {}> .prettierrc.json

// 步骤3.【可选】添加忽略项(对于一些依赖和固定文件,无需格式化)
touch .prettierignore
// 在.prettierignore中添加任意文件or目录名
dist
node_modules
...

// 步骤4.格式化所有文件
npx prettier --write .

嗯,看起来不错,一行命令就可以格式文件,但是...我们需要每次运行命令来格式化文件吗?有没有更灵活快捷的方式呢?
我们有VSCode这个IDE啊,prettier提供了VSCode插件,可以通过安装prettier插件,让VSCode具备格式化的能力(应该没人会拒绝,因为快捷键使用起来真的很香!)。所以,推荐的实践是:

image.png

配置

安装完插件后可以添加自定义配置,这样可以更加灵活地使用prettier

  1. 在项目根目录生成prettier配置文件
echo {}> .prettierrc.json

添加配置项,配置项值可根据习惯自己设置,下面是笔者常用的配置:

{
  "trailingComma": "all",
  "tabWidth": 2,
  "semi": true,
  "bracketSpacing": true,
  "singleQuote": true,
  "printWidth": 120,
  "useTabs": false,
  "vueIndentScriptAndStyle": true,
  "quoteProps": "as-needed",
  "jsxBracketSameLine": false,
  "jsxSingleQuote": false,
  "arrowParens": "avoid",
  "insertPragma": false,
  "requirePragma": false,
  "proseWrap": "never",
  "htmlWhitespaceSensitivity": "strict",
  "endOfLine": "lf"
}
  1. 在项目根目录添加.prettierignore文件,忽略指定文件or目录
touch .prettierignore

.prettierignore文件内容示例:

node_modules/
build
dist

使用

安装和配置结束后就可以正常使用了,为了能够通过快捷键驱动prettier对代码进行格式化,需要先将prettier设置为VSCode的默认格式化插件:

  • 打开 Settings: image.png
  • 找到Format选项,选择Prettier作为格式化拓展插件:

image.png

当然也可以手动修改settings.json配置文件:

{
    ...
    // 新增如下配置
    "editor.defaultFormatter": "esbenp.prettier-vscode",
    "[javascript]": {
        "editor.defaultFormatter": "esbenp.prettier-vscode"
      },
      "[typescript]": {
        "editor.defaultFormatter": "esbenp.prettier-vscode"
      },
      "[typescriptreact]": {
        "editor.defaultFormatter": "esbenp.prettier-vscode"
      },
      "[html]": {
        "editor.defaultFormatter": "esbenp.prettier-vscode"
      },
      "[json]": {
        "editor.defaultFormatter": "esbenp.prettier-vscode"
      },
      "[scss]": {
        "editor.defaultFormatter": "esbenp.prettier-vscode"
      },
      "[jsonc]": {
        "editor.defaultFormatter": "esbenp.prettier-vscode"
      },
}

上面的配置项是精确匹配使用prettier进行格式化的文件类型,如果出现配置中不存在的文件类型,进行格式化时VSCode会让你进行选择默认的格式化扩展插件,届时选择prettier,VSCode会自动将设置写入到settings.json中。
完成上面所有步骤后就可以使用VSCode的Format快捷键进行格式化了,当然,开发者也可以自定义快捷键,比如我就就修改成了shift+option+F作为格式化的快捷键:

image.png

eslint

ESLint是一个用于识别和报告在ECMAScript/JavaScript代码中发现的代码模式的工具,其目标是使代码更加一致并避免bug。在许多方面,它类似于JSLintJSHint,但有几个独特特征:

  • ESLint使用Espree进行JavaScript解析。
  • ESLint使用AST来评估代码中的模式。
  • ESLint是完全可插入的,每一个规则都是一个插件,你可以在运行时添加更多的规则。

安装

同样的,可以安装eslint依赖使得拥有命令行脚本,然后通过npx驱动eslint命令对指定文件or目录进行代码检查和修复。

// 项目中安装
npm install eslint --save-dev
// or
yarn add eslint --dev

// 全局安装
npm install eslint -g
// or
yarn add eslint -g

当然,跟安装prettier一样,拥有eslint命令脚本不是我们的目的,我们的最终目标是通过VSCode快捷键完成代码静态检查和自动修复!所以,如何做到这一点呢?
通过eslint的VSCode插件,在VSCode中安装ESLint插件

image.png

安装后的VSCode设置请往下看。

配置

安装成功后,在项目目录执行下面命令生成配置文件:

npx eslint --init
// or
yarn run eslint --init

注意:执行init需要项目已经拥有package.json文件。如果项目中没有,可以在项目根目录下先执行npm inityarn init生成一个package.json文件

npx eslint --init执行过程(根据提示选择配置选项就行):

image.png

生成配置文件后可以添加自定义配置项,这里贴出笔者实际项目中用到的部分配置(项目技术栈React17.0.1+Typescript4.3.2):

{
  "env": {
    "browser": true,
    "node": true,
    "es6": true
  },
  "extends": [
    "react-app",
    "airbnb",
    "eslint:recommended",
    "plugin:import/recommended",
    "plugin:import/typescript",
    "plugin:react/recommended",
    "plugin:react-hooks/recommended",
    "plugin:@typescript-eslint/eslint-recommended",
    "plugin:@typescript-eslint/recommended",
    "eslint:recommended"
  ],
  "globals": {
    "Atomics": "readonly",
    "SharedArrayBuffer": "readonly"
  },
  "settings": {
    //自动发现React的版本,从而进行规范react代码
    "react": {
      "pragma": "React",
      "version": "detect"
    },
    "import/extensions": [".js", ".jsx", ".ts", ".tsx"],
    "import/parsers": {
      "@typescript-eslint/parser": [".ts", ".tsx"]
    },
    "import/resolver": {
      "typescript": {
        "alwaysTryTypes": true,
        "project": [
          "./tsconfig.json",
        ]
      },
      "node": {
        "extensions": [".js", ".jsx", ".ts", ".tsx"]
      }
    }
  },
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "ecmaVersion": 2019,
    "sourceType": "module",
    "ecmaFeatures": {
      "jsx": true
    }
  },
  "plugins": [
    "import",
    "react",
    "@typescript-eslint"
  ],
  "rules": {
    "prettier/prettier": 0,
    "complexity": ["warn", { "max": 10 }],
    "max-len": ["off", { "code": 120 }],
    "no-trailing-spaces": "warn",
    "quotes": ["warn", "single"],
    "react/jsx-indent": "warn",
    "react/jsx-indent-props": "warn",
    "@typescript-eslint/indent": ["warn", 2, { "SwitchCase": 1 }],
    "@typescript-eslint/camelcase": "off",
    "@typescript-eslint/member-ordering": "warn",
    "@typescript-eslint/prefer-optional-chain": "warn",
    "@typescript-eslint/naming-convention": [
      "error",
      {
        "selector": "variable",
        "format": ["camelCase", "PascalCase", "snake_case", "UPPER_CASE"],
        "leadingUnderscore": "allow",
        "trailingUnderscore": "allow"
      }
    ],
    "@typescript-eslint/consistent-type-assertions": "warn",
    "@typescript-eslint/typedef": "warn",
    "@typescript-eslint/no-require-imports": "warn",
    "@typescript-eslint/no-empty-function": "off",
    "react-hooks/exhaustive-deps": "warn",
    "react/prop-types": "off",
    "react/jsx-filename-extension": "off",
    "react/jsx-one-expression-per-line": "warn",
    "react/jsx-curly-newline": "warn",
    "react/jsx-wrap-multilines": "warn",
    "react/destructuring-assignment": "off",
    "react/display-name": "off",
    "react/jsx-props-no-spreading": "off",
    "react/jsx-closing-bracket-location": "warn",
    "react/no-access-state-in-setstate": "warn",
    "object-curly-newline": "off",
    "jsx-a11y/no-static-element-interactions": "off",
    "jsx-a11y/label-has-associated-control": "warn",
    "jsx-a11y/click-events-have-key-events": "off",
    "react/no-array-index-key": "warn",
    "import/prefer-default-export": "warn",
    "react/state-in-constructor": "warn",
    "react/static-property-placement": "warn",
    "react/no-unused-state": "warn",
    "@typescript-eslint/explicit-function-return-type": "off",
    "@typescript-eslint/no-explicit-any": "off",
    "no-debugger": 1,
    "@typescript-eslint/ban-ts-ignore": "off",
    "import/extensions": "off",
    "@typescript-eslint/ban-ts-comment": "off",
    "import/no-extraneous-dependencies": "off",
    "import/no-unresolved": "error",
    "camelcase": "off",
    "react/no-unused-prop-types": "off",
    "react/require-default-props": "off",
    "@typescript-eslint/ban-types": "off",
    "no-use-before-define": "off",
    "react-hooks/rules-of-hooks": "warn",
    "no-shadow": "off",
    "curly": "off",
    "quote-props": "warn",
    "function-paren-newline": "warn",
    "@typescript-eslint/no-shadow": ["warn"],
    "arrow-parens": ["warn", "as-needed", { "requireForBlockBody": true }],
    "implicit-arrow-linebreak": "warn",
    "import/no-duplicates": "warn",
    "no-confusing-arrow": "warn",
    "nonblock-statement-body-position": "warn",
    "operator-linebreak": ["warn", "before"]
  },
  "overrides": [
    {
      "files": ["*.ts", "*.tsx"],
      "rules": {
        "react/prop-types": "off"
      }
    }
  ]
}

使用(重点)

eslint主要进行代码静态检查和问题修复(基于内置的规则集),所以eslint能够处理的文件一般是逻辑代码文件:js,ts,jsx,tsx...

处理scss,less,css,json等文件不是它的特长!

所以,对于js,ts,jsx,tsx等文件推荐使用eslint进行代码格式化,对于json,css,less,scss文件则使用prettier进行格式化。为此,需要进行以下配置:

  1. 取消VSCode的codeActionsOnSave(可选)
// settings.json文件中增加 or 修改配置,设置source.fixAll.eslint = false
 "editor.codeActionsOnSave": {
     "source.fixAll.eslint": false
  },

取消自动修复的目的是防止代码IDE频繁进行静态代码检查和修复,对于体量巨大的项目尤其需要如此,否则整个IDE会非常卡顿,很影响开发体验。

  1. 保持prettier为默认的代码格式化工具
// settings.json文件
"editor.defaultFormatter": "esbenp.prettier-vscode",
    "[javascript]": {
        "editor.defaultFormatter": "esbenp.prettier-vscode"
      },
      "[javascriptreact]": {
        "editor.defaultFormatter": "dbaeumer.vscode-eslint"
      },
      "[typescript]": {
        "editor.defaultFormatter": "esbenp.prettier-vscode"
      },
      "[typescriptreact]": {
        "editor.defaultFormatter": "esbenp.prettier-vscode"
      },
      "diffEditor.ignoreTrimWhitespace": false,
      "[html]": {
        "editor.defaultFormatter": "esbenp.prettier-vscode"
      },
      "[json]": {
        "editor.defaultFormatter": "esbenp.prettier-vscode"
      },
      "[scss]": {
        "editor.defaultFormatter": "esbenp.prettier-vscode"
      },
      "[jsonc]": {
        "editor.defaultFormatter": "esbenp.prettier-vscode"
      },
  1. 设置自定义快捷键执行eslint代码检查与修复

打开配置:Code -> Preferences -> KeyBoard Shortcuts

image.png

找到ESLint: Fix all auto-fixable Problems配置项,设置快捷键(作者设置是option+F):

image.png

设置后就能灵活地使用option+F驱动prettier进行格式化;使用shift+option+F驱动eslint进行格式化了!

总结

  1. eslintprettier功能互补而不是互相替代,前者倾向于代码静态检查,适合对js,ts,jsx,tsx等文件进行“格式化”;后者则专注于代码格式化,可适用于任意脚本文件。
  2. eslint规则和prettier规则会有冲突的地方,为了避免规则冲突给开发带来困扰,应该分场景使用,对于js,ts,jsx,tsx等文件用eslint,对于json,css,less,scss文件使用prettier
  3. codeActionsOnSave开启会消耗IDE较多资源,当代码量庞大时会出现卡顿(作者之前参与过的百万行级项目就遇到过该问题),可根据喜好是否启动;关闭保存自动格式化后可以配置VSCode快捷键弥补无法自动格式化的缺憾。

参考

ESLint
Prettier
prettier extension
eslint extension