TL;DR
完成形のソースコードはこちら↑
この記事の趣旨
TypeScript + Node.js プロジェクトのはじめかた2020 で作成したTypeScript + Node.jsのプロジェクトに ESLint
/ Pretiter
/ husky & lint-staged
を導入する手順を紹介します。
今回導入するツールとバージョンは以下になります。
項目 | バージョン |
---|---|
ESLint | 7.6.0 |
Prettier | 2.0.5 |
husky | 4.2.5 |
lint-staged | 10.2.11 |
動作環境
node と npm はインストール済みとします。
$ node -v
v12.18.3
$ npm -v
6.14.6
また、今回の記事はmacOSにて検証しております。
$ uname -v
Darwin Kernel Version 19.6.0: Sun Jul 5 00:43:10 PDT 2020; root:xnu-6153.141.1~9/RELEASE_X86_64
$ sw_vers
ProductName: Mac OS X
ProductVersion: 10.15.6
BuildVersion: 19G73
Linux環境だと手順はほぼ同じだと思いますが、Windows環境の場合は読み替えが必要になるかもしれません。
ESLint
ESLintについて
ESLintはJavaScriptコードのエラーチェックを行うLinterと呼ばれるツールの一つです。
JavaScriptは動的な言語なので、実行するまでエラーがあるのかどうかわからない部分があります。
ESLintを使うことで、JavaScriptのコードを実行することなくコード上の問題を発見するのに役立ちます。
また、ESLintは本来JavaScript用ですが、TypeScript用のプラグインを追加することにより、TypeScriptでもESLintが使えるようになります。
以下ではTypeScript (+ Node.js)なプロジェクトへのESLintの導入手順を紹介します。
ESLint (+ TypeScriptプラグイン)の導入
https://github.com/notakaos/typescript-node-base にESLintを導入します。
# typescript-node-base リポジトリをローカルで作成済みとします
cd typescript-node-base
Node.jsプロジェクトにESLintとESLintのTypeScript用プラグインを追加します。
npm install -D eslint typescript @typescript-eslint/parser @typescript-eslint/eslint-plugin
# ESLintのバージョン確認
./node_modules/.bin/eslint --version
#=> v7.6.0
次に、ESLint用のTypeScript設定ファイル tsconfig.eslint.json
を作成します。
# tsconfig.eslint.json ファイルの作成
touch tsconfig.eslint.json
# エディターで編集(お好みのエディターをお使いください)
vim tsconfig.eslint.json
{
"extends": "./tsconfig.json",
"include": [
"src/**/*.ts",
".eslintrc.js"
],
"exclude": [
"node_modules",
"dist"
]
}
そして、ESLint用の設定ファイル .eslintrc.js
をプロジェクトディレクトリ直下に作成します。
# .eslintrc.js ファイルの作成
touch .eslintrc.js
# エディターで編集(お好みのエディターをお使いください)
vim .eslintrc.js
module.exports = {
root: true,
env: {
es6: true,
node: true,
},
parser: '@typescript-eslint/parser',
parserOptions: {
sourceType: 'module',
ecmaVersion: 2019, // Node.js 12の場合は2019、他のバージョンのNode.jsを利用している場合は場合は適宜変更する
tsconfigRootDir: __dirname,
project: ['./tsconfig.eslint.json']
},
plugins: [
'@typescript-eslint',
],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:@typescript-eslint/recommended-requiring-type-checking',
],
rules: {
},
};
なお、extendsでいくつかの項目がありますが、extendsはあらかじめ用意されたESLintルールのセットになります。これを入れることによって、以下のルールを適用しています。
- eslint:recommended のルール一覧
- plugin:@typescript-eslint/recommended のルール一覧
- plugin:@typescript-eslint/recommended-requiring-type-checking のルール一覧
これで、ESLint導入完了です。
ESLintの実行
早速、ESLintを実行してみましょう。
npx eslint src/index.ts
何も表示されなければ成功です!
設定やソースコードに問題がない場合、何も表示されないのが正常です。
もしここでエラーが表示される場合は記述ミスが考えられますので、各種設定ファイルやソースコードを見直してください。
次に、ESLintでエラーを検知できるか確認してみましょう。
src/index.ts
を以下のように編集します。
function hello(name: string): string {
return `Hello, ${name}!`;
}
- console.log(hello('TypeScript'));
+ console.log(hello('TypeScript')
最後の行の閉じカッコを一つ削除して、文法エラーを意図的に発生させます。
ソースコードを書き換えたら保存し、ESLintを実行します。
npx eslint src/index.ts
すると以下のようなエラーが発生します。
/Users/notakaos/typescript-node-base/src/index.ts
6:0 error Parsing error: ')' expected
✖ 1 problem (1 error, 0 warnings)
「パースエラー: )
が期待されている(が存在しない)」というエラーがでましたね。
ESLintで文法チェックが正しく動いていることがわかります。
ちなみに、文法のチェックに関してはtscでも同様のチェックができます。
(--noEmitオプションをつけてjsファイルを生成しないようにします)
npx tsc --noEmit
src/index.ts:6:1 - error TS1005: ')' expected.
6
Found 1 error.
そのため、文法チェックだけだとtscのみでよいのでは? と思われるかもしれませんが、ESLintにはtscに含まれていないエラーチェック機能が入っているため、tscとESLintを併用することによりエラーの検知率をあげることができます。
ここで、tscではエラーにならず、ESLintでエラーになる例を見てみましょう。
src/index.ts
に以下の記述を追加します。
function hello(name: string): string {
return `Hello, ${name}!`;
}
- console.log(hello("TypeScript")
+ console.log(hello("TypeScript")); // 元に戻す
+ function example() {} // どこからも呼ばれない中身が空の関数を用意
まずはtscを実行してみます。
npx tsc --noEmit
特にエラーもなく正常にコマンドが終了しました。
次にESLintを実行します。
npx eslint src/index.ts
/Users/notakaos/typescript-node-base/src/index.ts
7:10 warning 'example' is defined but never used @typescript-eslint/no-unused-vars
7:20 error Unexpected empty function 'example' @typescript-eslint/no-empty-function
✖ 2 problems (1 error, 1 warning)
ESLintでは1つの警告と1つのエラーが発生しましたね。
警告・エラーの内容について、
- 「警告: example関数は定義されているものの使われていない」
- 「エラー: 空の関数は期待されていない」
といった内容になっています。
このように、tscでは通るもののESLintではエラーになるようなものもあり、
両者を適切に設定することでよりバグが少ないコードを書くことができるようになります。
最後に毎回 eslint
/ tsc
のコマンドを手打ちすると面倒なので、簡単にエラーチェックができるようにpackage.jsonのscriptsに設定を追加します。
{
...
"scripts": {
"dev": "ts-node src/index.ts",
"dev:watch": "ts-node-dev --respawn src/index.ts",
"clean": "rimraf dist/*",
"tsc": "tsc",
"build": "npm-run-all clean tsc",
- "start": "node ."
+ "start": "node .",
+ "check-types": "tsc --noEmit",
+ "eslint": "eslint src/**/*.ts",
+ "eslint:fix": "eslint src/**/*.ts --fix",
+ "lint": "npm-run-all eslint check-types"
},
...
}
エラーチェックを実行する時は以下のコマンドを実行します。
npm run lint
こうすることで、一度のコマンド実行で eslint
と tsc
の両方を実行することができます。
また、以下のように個別に実行することもできます。
# tsc --noEmit だけ実行
npm run check-types
# eslint だけ実行
npm run eslint
# eslint のエラーを(できるものだけ)自動修復
npm run eslint:fix
普段は npm run lint
を使い、必要に応じて個別のコマンドを使い分けることになります。
Prettier
Prettierについて
Prettierはソースコードの整形ツール(コードフォーマッター)の一つです。
JavaScript/TypeScript/JSON等の形式に対応しています。
Prettierを使うことで、スペースやインデント、文字列のクオートの統一、1行が長くなりすぎた場合の改行位置調整などを自動で行ってくれます。
Prettier等のコードフォーマッターを使わない場合、実装者によってコードの書き方がばらばらになり、コードレビュー時にインデント位置やスペース等の指摘が入ってしまうことがあります。それだと本質的なディスカッションができません。そのため、フォーマッターを設定しておくことで、コードの書き方が統一され、ロジックのレビューに集中できるようになる効果があります。
ちなみに、ESLintにもコードフォーマット機能があるため、ESLintと併せてPrettierを利用する場合はESLint側のフォーマットルールをOFFにする必要があります。幸い、PrettierとESLintのフォーマットルールがぶつからないようにするためのルールセット(config)が用意されていますので、そちらも併せて導入します。
Prettier (+ eslint-config-prettier) の導入
PrettierとESLintのPrettier configをインストールします。
# --save-exact でバージョンを固定してdevインストール
npm install -D --save-exact prettier eslint-config-prettier
# Prettierのバージョン確認
./node_modules/.bin/prettier --version
#=> 2.0.5
次に、Prettier用の設定ファイルをプロジェクトディレクトリ直下に作成します。
echo "{}"> .prettierrc.json
{}
※ .prettierrc.json
は必要に応じて書き換えてください
また、prettierの対象外となるファイルを指定する .prettierignore
をプロジェクトディレクトリ直下に作成します。
touch .prettierignore
# エディターで編集
vim .prettierignore
# Ignore artifacts:
/dist
node_modules
package.json
package-lock.json
tsconfig.json
tsconfig.eslint.json
このままだと、ESLintのコードフォーマット機能とPrettierのコードフォーマット機能がコンフリクトしてしまうので、.eslintrc.js
のextendsに prettier
と prettier/@typescript-eslint
の記述を追加します(追加する位置はextendsの最後の方になります)。
module.exports = {
// ...
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:@typescript-eslint/recommended-requiring-type-checking',
+ 'prettier',
+ 'prettier/@typescript-eslint',
],
// ...
}
これでPrettierの設定は完了です。
コラム: eslint-plugin-prettier について
インターネット上でESlintとPrettierの導入方法を調べると、 eslint-plugin-prettier
も一緒に入れる方法が紹介されていることがあります。
いつからかはわかりませんが、 公式ドキュメントによると、eslint-plugin-prettier が 一般的には推奨されなくなっている ようです。
なぜ非推奨なのかというと、以下のような理由が挙げられています。
- エディターに赤い波線がたくさん表示され、煩わしくなる
- Prettierを直接実行するよりも遅くなる
- 物事が壊れる可能性がある間接層の1つになる
そのため、この記事では eslint-plugin-prettier を導入せず、eslintとは別にpretiterを実行する形を採用しております。
Prettierの実行
では、Prettierを実行してみましょう。
まず src/index.ts
を以下のようにインデントなどがバラバラな状態に書き換えます。
function
hello(name:string):string{
return `Hello, ${name}!`
}
console.log(hello("TypeScript"
))
そして、以下のコマンドでPrettierを実行します。
npx prettier --write src/index.ts
すると、以下のようにコードが修正されます。
function hello(name: string): string {
return `Hello, ${name}!`;
}
console.log(hello("TypeScript"));
きれいに整形されましたね。
Prettierのnpm-scripts設定
あとは、prettierを簡単に実行できるようにpackage.jsonのscriptsに追記します。
Prettier単体で実行する format
と、 eslint
/ tsc
/ prettier
を1度に実行する lint:fix
を追加しています。
{
...
"scripts": {
"dev": "ts-node src/index.ts",
"dev:watch": "ts-node-dev --respawn src/index.ts",
"clean": "rimraf dist/*",
"tsc": "tsc",
"build": "npm-run-all clean tsc",
"start": "node .",
"check-types": "tsc --noEmit",
"eslint": "eslint src/**/*.ts",
"eslint:fix": "eslint src/**/*.ts --fix",
+ "format": "prettier --write 'src/**/*.{js,ts,json}'",
- "lint": "npm-run-all eslint check-types"
+ "lint": "npm-run-all eslint check-types",
+ "lint:fix": "npm-run-all eslint:fix check-types format"
},
...
}
Prettierのみを実行する場合は以下のコマンドを実行します。
npm run format
eslint
とtsc
も併せて実行する場合は、以下のコマンドを実行します。
npm run lint:fix
これでPrettierの導入完了です。
Git commit時に自動でlintを実行する(huskyとlint-staged)
これまでの手順でESLintとPrettierを導入しましたが、コードを修正するたびに lint:fix
コマンドを手動で実行する必要がありました。
そのため、lintをかけていないコードを間違ってコミットしてしまうことがあります。
これを防ぐため、git commit時に esLint
/ tsc
/ prettier
を自動的に実行するように husky
と lint-staged
を導入します。
huskyとは
gitのcommit前やpush前などに特定のコマンドを実行するためのGit hooksを簡単に作成するためのツールです。後述のlint-stagedと組み合わせて使います。
lint-stagedとは
git add
でステージングに追加されたファイルに対して、指定したコマンドを実行します。
huskyとlint-stagedの導入
huskyとlint-stagedは以下のコマンドを実行するとpackage.jsonに自動で設定が追加されます。
npx mrm lint-staged
{
...
"devDependencies": {
"@types/node": "^12.12.54",
"@typescript-eslint/eslint-plugin": "^3.9.0",
"@typescript-eslint/parser": "^3.9.0",
"eslint": "^7.6.0",
"eslint-config-prettier": "6.11.0",
+ "husky": "^4.2.5",
+ "lint-staged": "^10.2.11",
"npm-run-all": "^4.1.5",
"prettier": "2.0.5",
"rimraf": "^3.0.2",
"ts-node": "^8.10.2",
"ts-node-dev": "^1.0.0-pre.56",
"typescript": "^3.9.7"
- }
+ },
+ "husky": {
+ "hooks": {
+ "pre-commit": "lint-staged"
+ }
+ },
+ "lint-staged": {
+ "*.js": "eslint --cache --fix",
+ "*.{js,ts,json}": "prettier --write"
+ }
}
そのままではTypeScriptファイルにおいて、ESLintとtscが実行されないので、lint-stage
の部分を少し修正します。
{
...
"lint-staged": {
- "*.js": "eslint --cache --fix",
+ "*.{js,ts}": "eslint --cache --fix",
+ "*.ts": "tsc --noEmit",
"*.{js,ts,json}": "prettier --write"
}
}
これで設定完了です。
(2021/5/12 追記) lint-staged で tsconfig.json が効かない場合
こちらのコメントで情報をいただいたのですが、lint-stagedで tsc
コマンドを実行すると tsconfig.json
が読み込まれず、 tsc --noEmit
の結果で差異がでてしまうようです。
これは現状 tsc
コマンドがファイルを指定した場合に tsconfig.json を見ないという仕様になっていることが起因しています。
- 参考: https://github.com/okonet/lint-staged/issues/825
- 参考: https://github.com/microsoft/TypeScript/issues/27379
MacやLinux環境でbashが使用できる場合は、tscをbash経由で実行することでtsconfig.jsonを見るようになるとのことです。
{
...
"lint-staged": {
"*.{js,ts}": "eslint --cache --fix",
- "*.ts": "tsc --noEmit",
+ "*.ts": "bash -c tsc --noEmit",
"*.{js,ts,json}": "prettier --write"
}
}
もしくは、package.jsonにlint-stagedの設定を記述する代わりに lint-staged.config.js
ファイルを作成し、関数を渡すことでこの問題を回避できるようです(こちらは未検証)。
// lint-staged.config.js
module.exports = {
"*.{js,ts}": [
"eslint --cache --fix",
],
"*.ts": [
() => "tsc --noEmit",
"eslint --cache --fix",
],
"*.{js,ts,json}": [
"prettier --write"
],
}
husky & lint-staged を試す
それではhuskyとlint-stagedを試してみましょう。
まず src/index.ts
を以下のように書き換えます。
function
hello(name: string): string {
return `Hello, ${name}!`;
}
console.log(hello("TypeScript!!!!!")
);
そしてgit addを行い、その後git commitを実行します。
git add src/index.ts
git commit -m "Update src/index.ts"
husky > pre-commit (node v12.18.3)
✔ Preparing...
✔ Running tasks...
✔ Applying modifications...
✔ Cleaning up...
[feature/husky-lint-staged f69684a] Update src/index.ts
1 file changed, 1 insertion(+), 1 deletion(-)
eslint
/ tsc
/ prettier
の各コマンドが実行されたのがわかります。
そして src/index.ts
を確認してみると、prettierによって自動整形されているのがわかります。
function hello(name: string): string {
return `Hello, ${name}!`;
}
console.log(hello("TypeScript!!!!!"));
git log -p
で差分を確認してみましょう。
git log -p
...
--- a/src/index.ts
+++ b/src/index.ts
@@ -2,4 +2,4 @@ function hello(name: string): string {
return `Hello, ${name}!`;
}
-console.log(hello("TypeScript"));
+console.log(hello("TypeScript!!!!!"));
prettierでコードフォーマットされた後にcommitされていますね。
これでlintをかけずにcommitされることを防ぐことができます。
設定は以上です。
あとはお好みでカスタマイズしましょう!
成果物
以上の手順を実行した完成形のソースコードは以下のリポジトリをご参照ください。
参考
- typescript-eslint/README.md at master · typescript-eslint/typescript-eslint
- typescript-eslint/TYPED_LINTING.md at master · typescript-eslint/typescript-eslint
- TypeScript With Babel: A Beautiful Marriage
- VSCode + TypeScript + ESLint + Prettier + Husky + lint-staged (+ JavaScript Standard Style) でコードを防衛@2019秋(2019/12/30 更新) - Qiita
- "parserOptions.project" has been set for @typescript-eslint/parser - Stack Overflow
- Install · Prettier
- Integrating with Linters · Prettier
- Using ESLint and Prettier in a TypeScript Project - DEV
- VSCodeでESLint+typescript-eslint+Prettierを導入する v3.0.0 - Qiita
変更履歴
- 2020/8/12 記事公開