VS Code で ESLint × Prettier のベストかも知れないプラクティス【2025年1月】

## 大事な前提

:::note warn
この記事は **2025 年 1 月時点** の状況に基づいて記述しています。
Node.js, ESLint, Prettier いずれもバージョンアップが頻繁に行われているため、1 年後あるいは数ヶ月後にはこの記事の内容は不正確になっている可能性があります。
:::

## お急ぎの方

以下、最低限の手順です。
Node.js v18.18 以上、v20.9 以上、v21.1 以上が必要です。
`pnpm init` などで `package.json` が出来上がっている状態から始めます。
ES モジュール形式で進めていくことを決めているのであればこの時点で `"type": "module"` を追記しておくと良いです。

### 1. ESLint の設定を初期化

```bash
npm init @eslint/config@latest
```

対話形式でセットアップを進める形になります。

### 2. 然るべき内容を選択

プロジェクトに応じて選択を変更してください。
以下はごく基本的な選択の例です。

```bash
? How would you like to use ESLint? …
  To check syntax only
❯ To check syntax and find problems

? What type of modules does your project use? …
❯ JavaScript modules (import/export)
  CommonJS (require/exports)
  None of these

? Which framework does your project use? …
  React
  Vue.js
❯ None of these

? Does your project use TypeScript? …
❯ No
  Yes

? Where does your code run? …  (Press <space> to select, <a> to toggle all, <i> to invert selection)
✔ Browser
✔ Node

eslint, globals, @eslint/js
? Would you like to install them now? › No / Yes

? Which package manager do you want to use? …
  npm
  yarn
❯ pnpm
  bun
```

### 3. Prettier と eslint-config-prettier をインストール

```bash
pnpm install -D prettier eslint-config-prettier
```

### 4. Prettier 設定ファイルを作成

内容は各自調整してください。

```javascript:prettier.config.js
export default {
  tabWidth: 2,
  singleQuote: true,
  semi: false,
};
```

### 5. ESLint 設定ファイルを修正

```diff:eslint.config.js
import globals from 'globals'
import pluginJs from '@eslint/js'
+ import eslintConfigPrettier from 'eslint-config-prettier'

/** @type {import('eslint').Linter.Config[]} */
export default [
  { languageOptions: { globals: { ...globals.browser, ...globals.node } } },
  pluginJs.configs.recommended,
+   eslintConfigPrettier,
]
```

自動作成されたファイルの内容により異なりますが、

- `import eslintConfigPrettier from 'eslint-config-prettier'` を追加
- `eslintConfigPrettier` を配列の末尾に追加

と言う内容になります。

以上。

## お急ぎでない方への前置き

この記事執筆時点から 4 年半ほど前、2020 年中ごろに [VS Code で ESLint × Prettier のベストかも知れないプラクティス](https://qiita.com/iShinkai/items/6f65b042618b76525659) と言う記事を書きました。

https://qiita.com/iShinkai/items/6f65b042618b76525659

低空飛行の多い筆者の記事の中では図抜けて評価をいただいてますが、この記事はとうの昔に陳腐化しており新しいプロジェクトで採用できる内容では既にありません。
なので 2025 年 1 月時点での最新の知見に基づいて改めてベストかも知れないプラクティスを探ってみたいと思います。

## その ESLint、ちゃんと動いてますか？

例えば、あなたが新しい Node.js 案件を任された PL だったとします。
メンバー数人がアサインされる予定になっており、チーム全体でコードの一貫性を保ち品質を担保するため、最初に諸々プロジェクトの準備を整えようとしています。
ゼロからプロジェクトを新規作成するために VS Code を立ち上げました。

リーダー「[pnpm](https://pnpm.io/ja/) で新しいプロジェクトを作ろうかね」

```bash
cd /path/to/my-awesome-project
pnpm init
```

リーダー「Node.js は [Volta](https://volta.sh/) で最新 LTS の v22 をインストールしようかね」

```bash
volta pin node@v22
```

リーダー「[ESLint](https://eslint.org/) と [Prettier](https://prettier.io/) もいるよな。[この記事](<(https://qiita.com/iShinkai/items/6f65b042618b76525659)>) が参考にできそうだね」

```bash
pnpm install -D eslint prettier eslint-config-prettier
```

リーダー「ESLint と Prettier の設定は過去のプロジェクトのやつをそのままコピーしてくりゃ良いやね」

<details><summary>ESLint（.eslintrc.js） と Prettier（.prettierrc） の設定</summary>

```javascript:.eslintrc.js
module.exports = {
  root: true,
  env: {
    browser: true,
    commonjs: true,
    es6: true,
    node: true,
  },
  parserOptions: {
    sourceType: "module",
    ecmaVersion: 2019,
    tsconfigRootDir: __dirname,
    project: ["./tsconfig.eslint.json"],
  },
  plugins: [],
  extends: ["eslint:recommended", "prettier"],
  rules: {
    "no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
    "no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
    "no-irregular-whitespace": "off",
    "no-constant-condition": ["error", { checkLoops: false }],
  },
  ignorePatterns: [".eslintrc.*", "vite.config.*"],
  overrides: [
    {
      files: [
        "**/__tests__/*.{j,t}s?(x)",
        "**/tests/unit/**/*.spec.{j,t}s?(x)",
      ],
      env: {
        jest: true,
      },
    },
  ],
};
```

```json:.prettierrc
{
  "semi": false,
  "singleQuote": true
}
```

</details>

リーダー「よし！これで準備 OK だ。メンバーたちよ、これで開発を始めてくれ！」
メンバーたち「「「はいす」」」

しばらくして・・・

メンバー A「あの・・・このプロジェクト、 ESLint 利いてなくないです？」
リーダー「えっ」
メンバー A「これ見てください」

```
[Info  - 13:22:47] ESLint server is starting.
[Info  - 13:22:47] ESLint server running in node v20.18.1
[Info  - 13:22:47] ESLint server is running.
[Info  - 13:26:50] ESLint library loaded from: /path/to/my-awesome-project/node_modules/.pnpm/eslint@9.18.0/node_modules/eslint/lib/api.js
[Error - 13:26:50] Calculating config file for file:///path/to/my-awesome-project/package.json) failed.
Error: Could not find config file.
    at assertConfigurationExists (/path/to/my-awesome-project/node_modules/.pnpm/eslint@9.18.0/node_modules/eslint/lib/config/config-loader.js:80:23)
    at LegacyConfigLoader.loadConfigArrayForFile (/path/to/my-awesome-project/node_modules/.pnpm/eslint@9.18.0/node_modules/eslint/lib/config/config-loader.js:335:9)
    at async ESLint.calculateConfigForFile (/path/to/my-awesome-project/node_modules/.pnpm/eslint@9.18.0/node_modules/eslint/lib/eslint/eslint.js:1057:25)
    at async ESLint.isPathIgnored (/path/to/my-awesome-project/node_modules/.pnpm/eslint@9.18.0/node_modules/eslint/lib/eslint/eslint.js:1096:24)
    at async /someone/.vscode/extensions/dbaeumer.vscode-eslint-3.0.10/server/out/eslintServer.js:1:24295
    at async Object.M [as withClass] (/someone/.vscode/extensions/dbaeumer.vscode-eslint-3.0.10/server/out/eslintServer.js:1:19807)
    at async t.workspace.getConfiguration.then.O.then.m.validate (/someone/.vscode/extensions/dbaeumer.vscode-eslint-3.0.10/server/out/eslintServer.js:1:24255)
    at async /someone/.vscode/extensions/dbaeumer.vscode-eslint-3.0.10/server/out/eslintServer.js:1:234417
    at async /someone/.vscode/extensions/dbaeumer.vscode-eslint-3.0.10/server/out/eslintServer.js:1:63886
```

リーダー「えっなにこれどうやって出したの？」
メンバー A「VS Code の『ターミナル』の並びの『出力』タブです。
　　　　　　`Ctrl` + `Shift` + `U` でも出ますよ。
　　　　　　その後ドロップダウンから『ESLint』を選択です」
リーダー「あっ出た・・・あぁ・・・私のとこでも同じエラー出てるな・・・」
メンバー A「ESLint の設定ファイルがないってエラーですけど」
リーダー「いやあるじゃん。`.eslintrc.js` 作ってあるし」
メンバー A「でも実際 ESLint 利いてませんよ」
リーダー「ぐぬぬ」

## 何が問題なのか？

この寸劇のなかで、このリーダーは何を間違えたんでしょう？
それはひとえに **最新の ESLint をインストールしたのに設定ファイルは昔のプロジェクトからまんまコピーしてきて設定を終えたつもりになっている** と言う点に集約されます。

冒頭の前提でもお話した通り、 Node.js、ESLint、Prettier、いずれもバージョンアップは頻繁で、少し前の常識はあっという間に過去のノウハウに成り下がってしまいます。
特にメジャーバージョンを跨ぐような更新があった場合、設定ファイルに対して破壊的な変更が入る事があり、過去のバージョンの設定ファイルはそのままでは使えないと言う事が頻発します。

技術系の記事を参考にする場合は使用しているライブラリのバージョンとか、そもそもその記事の公開日などを確認して、**「今の私の状況で有効なものか」** を常に意識しないといけません。（一般論）

上記の中で「出力」タブの「ESLint」を確かめたらエラーが出ていた、と言う点ですが、この辺もしっかり確認しておかないとプロジェクトの準備が整ったとは言えません。
意外に気にしてない人も多いんじゃないですかね？

この記事執筆時点で、ESLint の最新バージョンは `v9.18.0` です。
ESLint は v8 から v9 になった際に大きな変更があり、過去のバージョンの設定ファイルでは適切に動作しません。

## 対応方法

前置きが長くなりましたが、じゃあ一体どうすればいいのかと言う話をします。
前提として、 Node.js v18.18 以降、 Node.js v20.9 以降、 Node.21.1 以降である必要があります。
この記事では  **Node.js v18.20.5**、 **Node.js v20.18.1**、 **Node.js v22.13.0** で動作を確認してます。
また当然ですが VSCode 拡張機能である [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) と [Prettier](https://marketplace.visualstudio.com/items?itemName=SimonSiefke.prettier-vscode) がインストール済みである必要があります。
（VS Code の設定で `formatOnSave: true` も）

以下、作業手順を解説します。
いちばん上の [お急ぎの方](#お急ぎの方) の内容に解説を加えています。

### 0. package.json の設定

`pnpm init` などで `package.json` が出来上がっている状態から始めます。
ES モジュール形式で進めていくことを決めているのであれば事前に `package.json` に `"type": "module"` を追記しておきましょう。

### 1. ESLint の設定を初期化

```bash
npm init @eslint/config@latest
```

対話式で ESLint のセットアップが行える画面に遷移します。

### 2. 然るべき内容を選択

目的やプロジェクトの内容に応じて選択していきます。
1 ステップごとに簡単に解説していきます。

#### How would you like to use ESLint?

```bash
? How would you like to use ESLint? …
  To check syntax only
❯ To check syntax and find problems
```

ESLint をどう使うか。

**To check syntax only**
文法チェックだけ。

**To check syntax and find problems**
文法チェックに加え、問題を見つけるかどうか。
こちらを選択すると [@eslint/js](https://www.npmjs.com/package/@eslint/js) が追加でインストールされ、生成される設定ファイルに `import pluginJs from "@eslint/js";` や `pluginJs.configs.recommended` が追加されます。

#### What type of modules does your project use?

```bash
? What type of modules does your project use? …
❯ JavaScript modules (import/export)
CommonJS (require/exports)
None of these
```

JavaScript ファイルを ES モジュール形式で作るか CommonJS 形式で作るかそれ以外かの選択。
どのような形式でプロジェクトを作っていくかによって選択を変える必要があります。

**JavaScript modules (import/export)**

ES モジュール形式でプロジェクトを進める場合に選択。

**CommonJS (require/exports)**

CommonJS 形式でプロジェクトを進める場合に選択。
生成される設定ファイルに `{files: ["**/*.js"], languageOptions: {sourceType: "commonjs"}}` と言うオプションが追加されます。

**None of these**

モジュールを使わない従来の書き方をするプロジェクトで選択。
生成される設定ファイルに `{files: ["**/*.js"], languageOptions: {sourceType: "script"}}` と言うオプションが追加されます。

#### Which framework does your project use?

```bash
? Which framework does your project use? …
React
Vue.js
❯ None of these
```

[React](https://ja.react.dev/) や [Vue.js](https://ja.vuejs.org/) などのフレームワークを使うか、そうではなくそれ以外のフレームワークやバニラなプロジェクトかどうかの選択。
React も Vue.js もテンプレートから作成すれば ESLint の設定を組み込めるので、そちらで始めたほうが色々良いとは思います。

**React**

React を使う場合に選択。
[eslint-plugin-react](https://www.npmjs.com/package/eslint-plugin-react) が追加でインストールされ、生成される設定ファイルに `import pluginReact from "eslint-plugin-react";` や `{files: ["**/*.{js,mjs,cjs,jsx}"]}`、`pluginReact.configs.flat.recommended` が追加されます。

**Vue.js**

Vue.js を使う場合に選択。
[eslint-plugin-vue](https://www.npmjs.com/package/eslint-plugin-vue) が追加でインストールされ、生成される設定ファイルに `import pluginVue from "eslint-plugin-vue";` や `{files: ["**/*.{js,mjs,cjs,vue}"]}`、`...pluginVue.configs["flat/essential"]` が追加されます。

**None of these**

React や Vue.js を使わない場合に選択。

#### Does your project use TypeScript?

```bash
? Does your project use TypeScript? …
❯ No
Yes
```

プロジェクトで [TypeScript](https://www.typescriptlang.org/) を使うかどうかの選択。

**No**

JavaScript で開発をする場合に選択。

**Yes**

TypeScript で開発をする場合に選択。
[typescript-eslint](https://www.npmjs.com/package/typescript-eslint) が追加でインストールされ、生成される設定ファイルに `import tseslint from "typescript-eslint";` や `{files: ["**/*.{js,mjs,cjs,ts}"]}`、 `...tseslint.configs.recommended` が追加されます。

#### Where does your code run?

```bash
? Where does your code run? … (Press <space> to select, <a> to toggle all, <i> to invert selection)
✔ Browser
✔ Node
```

プロジェクトをどの環境で動かすか。
スペースで選択をオン・オフできます。
`Window` などグローバルオブジェクトを未定義と判定しないための選択ですね。
どちらか 1 つでも選択すると [globals](https://www.npmjs.com/package/globals) がインストールされます。

**Browser**

ブラウザで動かす前提のプロジェクトの場合に選択。
生成される設定ファイルに `{languageOptions: { globals: globals.browser }}` が追加されます。

**Node**

Node.js 環境で動かす前提のプロジェクトの場合に選択。
生成される設定ファイルに `{languageOptions: { globals: globals.node }}` が追加されます。

**Browser**, **Node** 双方にチェックを入れた場合は `{languageOptions: { globals: {...globals.browser, ...globals.node} }}` が追加されます。

#### Would you like to install them now?

```
eslint, globals, @eslint/js
? Would you like to install them now? › No / Yes
```

ここまでで設定してきたライブラリ類をインストールするかどうかの選択です。
（1 行目はここまでで選択してきた内容に応じて変わります）

**No**

パッケージをインストールせず `package.json` への追記と設定ファイルの生成のみ行いたい場合に選択。
他にも細々設定をしてから一気にインストールしたい場合やボイラープレート的にプロジェクトを作りたいときに選ぶ感じですかね。

**Yes**

最後に設定ファイルの生成とパッケージのインストールを行いたい場合に選択。
普通はこっちで良いでしょう。

#### Which package manager do you want to use?

```bash
? Which package manager do you want to use? …
npm
yarn
❯ pnpm
bun
```

パッケージマネージャーとして何を使いますかと言う選択。
init したときに使ったものを選べばいいでしょう。

ここまで選択を終えると `package.json` が更新され、`eslint.config.mjs` もしくは `eslint.config.js`（`package.json` に `"type": "module"` の記載がある場合）ファイルが生成されます。

### 3. Prettier と eslint-config-prettier をインストール

ESLint の基本設定ができましたので、次は Prettier の設定です。

```bash
pnpm install -D prettier eslint-config-prettier
```

### 4. Prettier 設定ファイルを作成

内容は各自調整してください。

```javascript:prettier.config.js
export default {
  tabWidth: 2,
  singleQuote: true,
  semi: false,
};
```

`packaje.json` に `"type": "module"` を追記しているかどうかでファイル名や記述内容が変わります。
上記は ES モジュール形式で書いていますが、従来の JSON 形式でも CommonJS 形式でも記述できます。
公式のドキュメントを見ると良いでしょう。
https://prettier.io/docs/en/configuration.html

### 5. ESLint 設定ファイルを修正

2.で作成された ESLint のファイルに対して eslint-config-prettier を有効にする記述を追記します。

```diff:eslint.config.js
import globals from 'globals'
import pluginJs from '@eslint/js'
+ import eslintConfigPrettier from 'eslint-config-prettier'

/** @type {import('eslint').Linter.Config[]} */
export default [
  { languageOptions: { globals: { ...globals.browser, ...globals.node } } },
  pluginJs.configs.recommended,
+   eslintConfigPrettier,
]
```

ここまでで最低限の設定は終了です。

### 6. カスタムルールやオプションの追加

ESLint の設定は見ての通り配列になっていて、設定の各項目は上から順に評価されていく形です。
（あとから設定されたもので上書きされる）
`eslintConfigPrettier`  は配列のいちばん最後に置くのが適切なので、カスタムルールの類はその 1 つ前に配置するのが良いでしょう。

例えば、

- `console`、`debugger` を警告する
- 等価演算子（`==`）を禁止する
- `const` を強制する

などのルールを追加したい場合、以下のようにします。
（解りやすくするため整形しています）

```diff:eslint.config.js
export default [
  {
    languageOptions: {
      globals: {
        ...globals.browser,
        ...globals.node,
      },
    },
  },
  pluginJs.configs.recommended,
+  {
+     rules: {
+       'no-console': 'warn',
+       'no-debugger': 'warn',
+       eqeqeq: 'error',
+       'prefer-const': 'error',
+     },
+   },
  eslintConfigPrettier,
];
```

`pluginJs.configs.recommended` のあと、`eslintConfigPrettier` の前に追加しているのが判るかと思います。

## まとめ

今回紹介した VS Code での ESLint と Prettier の基本的な初期設定ですが、実際のプロジェクトでは [TypeScript](https://www.typescriptlang.org/) を使ったり、 [React](https://ja.react.dev/) や [Vue.js](https://ja.vuejs.org/) などのフレームワークを使ったり、ビルドツールは [Vite](https://ja.vite.dev/) を使っていたりと、色々と条件が異なるものでしょう。
記事中でも述べた通り React や Vue.js の場合はそれぞれのテンプレートからプロジェクトを立ち上げることが多いでしょうから、その場合はフレームワークが用意する  ESLint の設定を採用したほうが適切です。
バニラなプロジェクトとして進める、テンプレートを使わずあとからフレームワークを組み込むと言った進め方をする場合はこの記事が役に立つかも知れません。
いずれにしても、バージョンが古い ESLint の設定ファイルをコピペしてくるみたいな横着はいけませんよ、「出力」タブの中身をきちんと確認してちゃんと動いているか確認しないといけませんよ、と言うお話でした。

と言うわけで、以上「VS Code で ESLint × Prettier のベストかも知れないプラクティス【2025年1月】」でした！
皆さま良きコーディングライフを！