React Japan light logo
article hero
Steven Sacks
スティーブン・サックス

ESLintの保存時の修正でワークフローを次のレベルに上げる方法

ESLintPrettierStylelintは素晴らしいツールです。これらを一緒に使用し、完全に構成すると、コードを書く際のスピードと効率が大幅に、そして、コードの全体的な品質が向上し、バグが減少し、コードレビューが迅速になることで、チーム全体が本当に重要なことに集中できるようになります。

一部のチームは、ESLintのセットアップをコミット時に実行するか、さらに良くない場合はCIアクションでのみ実行しています。しかし、その方法ではESLintの保存時の修正が提供する非常に便利な自動化のチャンスを見逃してしまいます。

この記事では

  • プロジェクトでESLintの保存時の修正を使用すべき理由
  • 実装すべきルールの種類
  • プロジェクトに追加する方法
  • 私が推奨するルールについて詳しく説明した追加記事へのリンク

Prettierの哲学を受け入れる

Prettierの哲学をまだ読んでいない場合は、ぜひ読んでみてください。

Prettierは保存時に修正します。ファイルを保存すると、そのフォーマットルールが自動的に適用されます。

Prettierを使用することの副次的な利点は、コードのどこかに構文エラーがあるかどうかを知る手段にもなることです。保存すると、期待されるフォーマットが行われない場合、余分な文字や閉じ括弧やタグが欠落しているなどの構文エラーがあることを意味します。また、コードが予期しない方法でフォーマットされた場合、閉じ括弧やタグが誤って配置されている可能性があります。

ESLintの保存時の修正とは?

多くのESLintルールは、可能な限りルールに従うようにコードを自動的にリファクタリングする--fix flag を受け入れます。

Prettierと同様に、コードはリアルタイムで自動的に修正されます。修正できないものについては、すぐに通知されます。

JetBrains IDE (WebStormなど)

  1. 設定を開く (⌘+,) または Windows では Ctrl + Alt + S
  2. 言語とフレームワーク > コード品質ツール > ESLint を選択
  3. “ファイルを保存時に eslint —fix を実行” の横にあるチェックボックスをオンにする

VS Code

VS Codeで ESLint拡張機能 を使用している場合は、settings.json ファイルを開きます:

  1. +Shift+P または Windows では Ctrl + Shift + P を押します。
  2. “ユーザー設定 (JSON) を開く” と入力します
  3. 次のコードを settings.json に追加します:
{
  "editor.codeActionsOnSave": {
    "source.fixAll": true
  }
}

Vim

Vimで ALE Vimプラグイン を使用している場合は、次のコードを .vimrc に追加します:

let g:ale_fix_on_save = 1

Stylelintとは?

Stylelintは、CSS用のPrettierやESLintと同様の機能を提供します。

ESLintの保存時の修正の利点

人生ある多くのことのように、ESLintの保存時の修正の利点は、それを実際に使ってみることですぐに明らかになります。ただし、以下はその中でも最も優れた利点のいくつかです:

  • 問題をすぐに防ぐ
  • 自動強制に高品質なコードを保つ
  • コーディング中の不要な意思決定を減らすことで集中力を向上させる
  • コードレビュー中のコードスタイルに関する議論を減らす、または排除する
  • 一貫したコードは、プロジェクトで一緒に作業する際に誰もが作業しやすくなる
  • フォーマットやルールを気にせずにコードを書ける
  • ファイルの保存は、書いた内容を「整理」する強力なツールになる
  • 時間の経過とともに、誰もがESLintに修正される必要なしにルールに準拠した高品質なコードを自然に書くようになる
  • 初心者の開発者は、より良いコードを書く方法を学ぶための組み込みの教師を得る

ルールの選択

Prettierの哲学に述べられているように:

人々はコードを書く特定の方法に非常に感情的になります... Prettierがすべてのコードを100%望むようにフォーマットしなかったとしても、それに対する「犠牲」に値する

Prettierのカスタマイズは意図的に制限されています。しかし、ESLintには多くのルールがあり、それに伴って、多くの意見を言われています。

プロジェクトのニーズやチームの好みに合わせてルールをカスタマイズする能力がある一方で、標準されたいくつかのルールがあることで、強固な基盤を形成することもます。

最も重要なことは、あなたのルールセットが一般的な(そしてそうでないこともある)問題を回避し、開発者にとって簡単にするための可能な限り多く保存時の修正ルールを持っていることです。

基盤を整えた後、あるルールをどのようにでも構成できます。もしかしたら一部のルールはあくまで好みによるものになっていき、そしてもちろんどのように決定するかはそれぞれの企業文化次第ですが、これらのルールをスキップせず、個々の判断に委ねましょう。それは混乱を招くでしょう。特にルールが--fixをサポートする場合は、100%あなたの望むようなコードにならないかもしれないという「犠牲」がある決定をするほどの価値があります。

推奨事項

記事の最後で、推奨されるルールについて基盤になるルールと意見の分かれるルールを含めて、詳しく説明します。

"warning" を使用しない

あなたの ESLint ルールは "error" または "off" のいずれかであるべきです。

ルールは強制されない限り、ルールではありません。断固とした決断を下してください。

コミット時に実行

保存時の修正に加えて、コミット時にも ESLint を自動的に実行するべきです。これにより、明示的に開いていないファイルで変更が加えられたリファクタリングによる問題などがキャッチされます。

TypeScript を使用している場合は、tsc もコミット時に実行するようにしてください。なぜなら、これにより ESLint ではキャッチされなかった TypeScript の問題が検出されるからです。


ESLint や TypeScript のエラーがある場合、コミットが拒否されることが重要です。これにより、不注意なミスやバグが防がれ、不要なものが減少します。私たちは皆人間です。一つのコーディングセッション中でも何かを忘れることがあります。開発者がコミット時に ESLint や TypeScript の違反を修正するのは瞬時のことです。

何か問題のあるコードをコミットできないようにしないと、あなた以外のチームメンバーが問題を見つけて、修正をする負担がかかるか、最悪の場合は見逃されて本番環境に反映されてしまう可能性があります。

ESLint の --max-warnings=0 フラグがこの問題を自動的に処理します。


コミット時に実行する最も簡単で一般的な方法は、lint-stagedhusky を介して使用することです。

lint-staged

変更されたファイルのみで linters を実行することで時間を節約できます。

これは私たちが使用している .lintstagedrc ファイルです:

{
    "{src,.storybook}/**/*.{ts,tsx}": [
        "eslint --fix --max-warnings=0 --cache --cache-location ./node_modules/.cache/eslint .",
        "prettier --write"
    ],
    "src/**/*.css": [
        "stylelint --fix",
        "prettier --write"
    ],
    "src/**/*.json": [
        "eslint --cache --fix",
        "prettier --write"
    ]
}
 

Remix プロジェクトでは、srcapp に置き換えてください。

husky

husky を設定して、コードに変更があった場合のみ実行するようにすることができます。

また、テストハーネスもコミット時に実行しています。lint-staged とテストは変更されたファイルのみで実行できるように構成できますが、tsc はプロジェクト全体に対して実行されます。関連するファイルのみが変更されたコミットに時間を節約することで、コードが変更されないようなコミットに関して時間を省くことができます。(たとえばドキュメントの変更など)

以下は husky の pre-commit の設定です:

#!/usr/bin/env sh
 
declare HAS_SRC_CHANGED=$(git diff --cached --name-only --diff-filter=ACDM | grep 'src/')
declare HAS_TEST_CHANGED=$(git diff --cached --name-only --diff-filter=ACM | grep 'test/')
 
if [[ $HAS_SRC_CHANGED || $HAS_TEST_CHANGED ]]
then
  npm run typecheck
  npx lint-staged
  npm run test:lint-staged
else
    echo "\n✅  No changed files -- skipping lint-staged\n"
fi
 

Remix プロジェクトでは、src/app/ に置き換えてください。

package.json

以下は関連する package.json スクリプトの例です:

{
  ...
  "scripts": {
    "lint": "eslint --fix --max-warnings=0 --ext js,jsx,ts,tsx src --cache --cache-location ./node_modules/.cache/eslint .",
    "lint-all": "npm run typecheck && npm run lint && npm run format && npm run stylelint",
    "format": "prettier --ignore-path .prettierignore --write \"**/*.{ts,tsx,html,md,mdx,css,json}\"",
    "stylelint": "stylelint --fix src/**/*.css",
    "typecheck": "tsc",
    "test": "vitest",
    "test:ci": "vitest --run --passWithNoTests --coverage --bail 1",
    "test:lint-staged": "vitest --run --changed --passWithNoTests --bail 1"
  },
  ...
}

Remix プロジェクトでは、srcapp に置き換えます。

必要に応じて手動で npm run できるスクリプトが含まれています。

  • lint はすべてのファイルに ESLint を実行します
  • lint-all はすべてのファイルに対してすべて(tsc、Prettier、ESLint、Stylelint)を実行します
  • format はすべてのファイルに対して Prettier を実行します - 必要に応じてファイルタイプをカスタマイズしてください
  • stylelint はすべてのファイルに対して Stylelint を実行します

既存のプロジェクトに追加

ルールセットに同意し、すべてを設定したら、多くのコード変更が必要になるでしょう。

良いニュースは、変更の大部分は自動的に行われ、手動で修正する必要があるのはわずかな部分だけです。

悪いニュースは、この種の変更をコミットすると、オープンブランチとのコードのコンフリクトが発生し、コンフリクト解消が面倒になることです。

これを処理する方法は2つあります:

  1. すべてのオープンブランチがマージされる時間をスケジュールし、ESLint の変更を単一の PR で行い、1日ですべてを完了することを目指します。最も経験豊富な開発者がリードを取り、他の開発者はスクリーン共有を通じて見学したり、計画立案、ハッカソン、または他のチームビルディングの演習などの非コーディングタスクを与えられます。
  2. ファイルごとに段階的に実装します。

段階的な実装

これを行うには:

  • eslint-plugin-eslint-comments がインストールされており、このルールが有効になっていることを確認してください: 'eslint-comments/no-unused-disable': 'error'
  • ESLint や lint-staged の保存時の修正を(まだ)有効にしないでください
  • Nodeスクリプトを使用して(下記を参照してください)、プロジェクト内のすべてのファイルの始まりに /* eslint-disable */ を挿入し、--no-verify でそれをコミットしてください。
  • npm run lint を実行して、(上記の no-unused-disable ルールに従い)ルールに違反していないファイルから eslint-disable を削除してください。
  • PR をメインブランチにマージします
  • すべての開発者がメインを自分のブランチにマージします

eslint-disable を追加すると、コンフリクトが発生する可能性はほとんどなくなり、今後、各開発者が好きなタイミングで、その行を削除し、保存時に修正を実行し、必要に応じてリファクタリングを行うことができます。

ファイルをより包括的にリファクタリングする必要がある場合、eslint-disable 行を削除してファイルを部分的に修正し、将来専用のリファクタリングブランチでファイルを再訪するために eslint-disable を再度追加できます。

時間の経過とともに、コードベースはすべての eslint-disable 行が削除された状態になります。できるだけ早く恩恵を受け入れるためにも、この作業を最優先事項にすることを強くお勧めします。

eslint-disable を注入する node スクリプト

プロジェクトに、このファイルをプロジェクトのルートに追加します(使用後に削除します)。

import fs from 'node:fs';
import path from 'node:path';
 
export const getAllFiles = (dirPath, arrayOfFiles = []) => {
  fs.readdirSync(dirPath).forEach(function (file) {
    if (fs.statSync(`${dirPath}/${file}`).isDirectory()) {
      arrayOfFiles = getAllFiles(`${dirPath}/${file}`, arrayOfFiles);
    } else {
      arrayOfFiles.push(path.join(dirPath, '/', file));
    }
  });
 
  return arrayOfFiles;
};
 
const INJECT = '/* eslint-disable */\n';
 
const injectEslintDisable = () => {
  const files = getAllFiles(path.resolve('src')).filter(
    (file) => ['.ts', '.js', '.tsx', '.jsx'].includes(path.extname(file))
  );
 
  const len = files.length;
 
  for (let i = 0; i < len; i++) {
    const readFile = fs.readFileSync(files[i]);
    if (readFile.includes(INJECT)) continue;
    const withInject = INJECT + readFile.toString();
    fs.writeFileSync(files[i], withInject);
  }
};
 
injectEslintDisable();

Remix を使用している場合は、srcapp に変更してください。

package.json のスクリプトにこれを追加し、次のように呼び出します: npm run inject

"inject": "node inject-disable.mjs"

inject-disable.mjs ファイルを削除できます。

推奨されるルール

次の記事では、私が推奨する ESLint ルールについて詳しく説明します。