TypeScriptプロジェクトにESLintとPrettierを導入して極上の安心を ~Git hooksとVSCodeを添えて~

TypeScript

はじめに

経緯

今回はTypeScriptプロジェクトにESLint・Prettierを導入し、TypeScript型システムにより得られる恩恵に加えてさらなる安心を手に入れようというものであります。まずは簡単にそれに踏み切った経緯について簡単に述べていきます。

私は現在開発しているプロジェクトにおいてReactやReact Nativeなどのフロントフレームワークを採用しています。現状はその全てはJavaScriptで記述しております。プロジェクト発足時、JSしか使えなかった私はTypeScriptなんて名前くらいしか知りませんでしたし、JSを採用する他なかったんですよね。ただ、ほとんど一人で開発しているもんでスケールアウトしていくうちに把握しきれない部分がでてきてリファクタリングをするのに恐怖心が伴うんです。そんなこともあり最近はTypeScript移行を考えていましてQiita記事: TypeScriptの型入門なんかを進めてCUに励んでおります。そんな中出会ったのが下記の動画です。

【日本一わかりやすいTypeScript入門】ESLintとPrettierでコードの品質を高めよう

TypeScriptの入門シリーズの一部なんですが、この回はESLint・Prettierに着目していました。現開発でもこれらは使っていなかったわけではないですが、正直メリットも分からず活用しきれていない部分があったのでこの機にこれらも合わせてCUしようとなったのが経緯です。

また、先日メンバーがプルリクを送ってくれたんですが、やっぱり他人ですしコーディングスタイルが自分のものとは違っていて、これを何かして合わせるべきか?と思ったのは正直なところ。コーディング規約を書いてプロジェクトをきっちり厳格化することも出来ますが、それを読む時間、理解する時間、正確性を鑑みた時にこれらで自動化した方が良いでしょとなったのも経緯としてあります。

ゴール

本稿のゴールと致しましては、まず、ESLint・Prettierをなぜ一緒に使うかの観点のもと概要を理解し、実際にTypeScriptプロジェクトに組み込みます。

具体的に、「npm run lint」で特定のディレクトリ内のtsファイルに構文解析・コードフォーマットをかけられるように。また、VSCodeの保存時に自動でコードフォーマットがかかるように。さらに、コミット直前に構文解析・コードフォーマットをかけ、エラーをキャッチした場合コミットを中止するようにします。これでプロジェクトメンバー全員が特に意識することなく、コードを綺麗に保つことが実現できるでしょう。

環境

node6.14.8
typescript4.1.3
eslint7.19.0
prettier2.2.1

ESLintとPrettier

まず、ESLintとPrettierの概要を見ていきます。そもそもESLintとPrettierとは何なのか、

ESLint … JavaScript のための静的検証ツール。つまり実行以前にシンタックスの観点からバグを検出する。
Prettier … コードフォーマッタ。JS、TSだけでなくHTMLやJSONやJSXなどの多岐にわたる言語やフレームワークに対応している。

これらはツールとしての役割は独立しているよう見受けられますが、実はESLintはコードフォーマットも兼ねる事が可能です。万能に思えるESLintですが通常Prettierと併用されることが多いみたいです。では、なぜESLintでコードフォーマットも可能であるのにわざわざPrettierを使うのでしょう。

以下の記事では「コードフォーマッタとしての優秀さや扱う際の手軽さ」を理由にESLintとPrettierの併用を勧められています。

Prettier 入門 ~ESLintとの違いを理解して併用する~ - Qiita
お知らせ(2021/05/26 追記)以前はeslint --fixなどで ESLint を実行時に Prettier でコードを整形し、整形したコードに対して構文チェックが実行されるようにするこ…

例えば、以下のような1lineで長いコードはESLintでは整形は行われません。

console.log(getName(), getAge(), getHeight(), getBirthplace(), getEducationalBackground());

対して、Prettierでは以下のように見やすく整形がなされます。

console.log(
  getName(),
  getAge(),
  getHeight(),
  getBirthplace(),
  getEducationalBackground()
);

ESLintとPrettierの導入

それでは、実際にESLintとPrettierをTypeScriptプロジェクトに導入してみます。

前提条件として既にTypeScriptが作成されているものとしtsc –initでtsconfig.jsonを作成済みであるとします。この時点でのpackage.jsonのdevDependenciesバリューはこちらです。

"devDependencies": {
   "ts-loader": "^8.0.15",
   "typescript": "^4.1.3",
   "webpack": "^5.21.2",
   "webpack-cli": "^4.5.0",
   "webpack-dev-server": "^3.11.2"
},

それでは、nodeモジュールをインストールします。–save-devオプションは忘れずに付けて下さい。開発時に利用するパッケージに指定するオプションです。

npm install --save-dev eslint eslint-config-prettier prettier @typescript-eslint/parser @typescript-eslint/eslint-plugin

プロジェクトのルートに.prettierrc.js.eslintrc.jsを作成します。今回は導入に焦点を当てるため、prettierとeslintの設定ファイルは特にこだわりなく記述します。Airbnbからこれらの設定ファイルが提供されているみたいで、Reactに導入する際は詳しく見ていきたいところです。

.prettierrc.js

module.exports = {
  trailingComma: "es5",
  tabWidth: 2,
  semi: true,
};

.eslintrc.js

prettier関連のextendsは配列の最後に記述します。

module.exports = {
  env: {
    browser: true,
    es6: true,
  },
  extends: [
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended",
    "prettier", // prettierのextendsは最後に記述
    "prettier/@typescript-eslint",
  ],
  plugins: ["@typescript-eslint"],
  parser: "@typescript-eslint/parser",
  parserOptions: {
    sourceType: "module",
    project: "./tsconfig.json",
  },
  root: true,
  rules: {},
};

それでは、ESLintの動作確認です。./src/index.tsに以下のコードを記述します。ちなみにhelloWorldですが、stringの型定義をするとリテラルを代入しているのに型定義をするのは冗長だと後で怒られるので、意識的に型定義をしておりません。また、わざとletを用いて変数宣言しています。

let helloWorld = 'Hello, world'
console.log(helloWorld);

npx eslintで構文解析を行います。渡しているのは、構文解析対象のディレクトリです。

npx eslint ./src/
  14:5  error  'helloWorld' is never reassigned. Use 'const' instead  prefer-const

✖ 1 problem (1 error, 0 warnings)
  1 error and 0 warnings potentially fixable with the `--fix` option.

正常に構文解析が行われていますね。再代入されてないから’helloWorld’をconstで宣言して!とエラーを吐いてくれています。

–fixオプションでそのエラーを修正してくれます。

npx eslint --fix ./src/
const helloWorld = 'Hello, world'
console.log(helloWorld);

次にPrettierの動作確認です。先程ESLintがfixしてくれた上記のコードをPrettierによってフォーマットします。–writeオプションでコードフォーマットした結果を上書きして更新してくれます。

npx prettier --write ./src/
const helloWorld = "Hello, world";
console.log(helloWorld);

更新されたindex.tsがシングルクォーテーションからダブルクォーテーションに、末尾にセミコロンが付与されているのがわかります。これは、先程設定した.prettierrc.jsをもとにPrettierがコードフォーマットしてくれてた結果になります。

.prettierrc.js.eslintrc.jsを変更しても設定が反映されない場合がございます。そういった場合はVSCodeを再起動して下さい。

最後に、この構文解析→コードフォーマットの一連の流れをnpm scriptsとして定義します。package.jsonのscriptsにlintコマンドを追記しましょう。

"scripts": {
  "lint": "npx eslint --fix ./src/ && npx prettier --write ./src/"
},

これにより、以下のコマンド一つで./srcディレクトリ内の全てのtsファイルを構文解析・コードフォーマットしてくれます。

npm run lint

Git hooksを用いたコミット直前の自動検証

npm run lintで構文解析・コードフォーマットを実行できますが、それをいつ実行すべきかが問題になります。プロジェクトメンバー全員がnpm run lintの存在を把握し、コードを書き換えるたびに実行してね。という約束は少しコストが重いように感じます。そこで、コミット直前に自動で構文解析・コードフォーマットを走らせ、スルーした場合のみコミットを実行する、という運用にすればGitHub上のコード全体が構文解析・コードフォーマットがスルーされていることを保証することが出来ます。

それでは、コミット直前に構文解析・コードフォーマットをかけ、エラーをキャッチした場合のコミット中止する運用を実現します。

Git hooksによりコミット直前に任意の処理をフックすることで実現します。使用するライブラリはHuskylint-stagedの2つになります。下記のコマンドを実行しインストールしましょう。

npm install --save-dev husky lint-staged

Huskyはコミット・プッシュ時に任意の処理をフックすることが可能になるツールで、lint-stagedはGitのステージ環境にあるファイルをlintしてくれるツールになります。package.jsonに以下の設定を追記します。

"husky": {
  "hooks": {
    "pre-commit": "lint-staged"
  }
},
"lint-staged": {
  "src/**/*.{ts,js}": [
    "npm run lint"
  ]
}

コミット直前、“src/**/*.{ts,js}”(src配下の全てのts・jsファイル)に対してnpm run lintを実効する設定になります。

コミットしても何も変わらない、反映されていないという方はこちらを参考にしてみてください。

これで、コミット直前に構文解析・コードフォーマットをかけ、エラーをキャッチした場合にコミット中止することが可能になります。

VSCodeによる保存時の自動整形

さて、中にはコミットの際だけでなく開発中に自分のコードをフォーマットしたいといった時もあるでしょう。もちろん、コードを編集する度にnpm run lintを走らせればよいのですが、それでは塵積でコストがかかってしまいます。

そこで、VSCodeのプラグインを使用して保存時に自動的にコードフォーマットが走るように設定しましょう。

それでは、まずVSCodeを開き以下の2つのプラグイン(ESLint・Prettier – Code formatter)をインストールします。

ESLint - Visual Studio Marketplace
Extension for Visual Studio Code - Integrates ESLint JavaScript into VS Code.
Prettier - Code formatter - Visual Studio Marketplace
Extension for Visual Studio Code - Code formatter using prettier

次に、settings.jsonに以下の設定を追記します。settings.jsonは、command + shift + P(Windows: Ctrl + Shift + P)でコマンドパレットを開いて”settings”と入力し「基本設定: 設定(JSON)を開く」をクリックすることでアクセスできます。

"[typescript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode" // フォーマッタをprettierに指定
},
"[javascript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
},
"editor.formatOnSave": true, // ファイル保存時にPrettierでフォーマット
"editor.codeActionsOnSave": {
    "source.fixAll.eslint": true // ファイル保存時にESLintでフォーマット
}

これで、保存時にESLint・Prettierによるコードフォーマットを走らせることが可能になります。

ちなみに”editor.formatOnSave”ではなく”editor.formatOnType”にすることでタイプ時にフォーマットをかけてくれるみたいで、よりリアルタイムな反映が実現できるっぽいです。

webpackとの共存

プロジェクトでモジュールバンドラのwebpackを採用している時、Git hooksのようにビルド前に構文解析を走らせる運用が理想的であると思われます。

実際、webpackのプログインとしてeslint-loader(2020年9月に非推奨になっており現状eslint-webpack-pluginが推奨されている)があり、ビルドプロセス内で入力されたファイルに対してESLintを走らせることができます。

しかし、以下の記事によると、ビルド時にeslint-loaderを挟むとビルド時間が伸びてしまうという問題があり、ビルドとLintの責務はわけるべきであると結論づけています。そのため、本稿でもwebpackによるビルド時に構文解析を走らせる運用は見送りたいと思います。

今後の課題

今回で、ESLint・Prettierの導入が一通り出来たので、実際に開発中であるReactやReact Nativeプロジェクトに導入していきたいと考えています。正直まだ本当の意味でESLint・Prettierの恩恵を実感できていないので、しばらくESLint・Prettierを導入した上でのプロジェクトで開発してみて、その利便性を実感したいと思います。

コメント