Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
235
Help us understand the problem. What is going on with this article?
@notakaos

TypeScript + Node.jsプロジェクトにESLint + Prettierを導入する手順2020

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用プラグインを追加します。

typescript-node-base
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 を作成します。

typescript-node-base
# tsconfig.eslint.json ファイルの作成
touch tsconfig.eslint.json

# エディターで編集(お好みのエディターをお使いください)
vim tsconfig.eslint.json
tsconfig.eslint.json
{
  "extends": "./tsconfig.json",
  "include": [
    "src/**/*.ts",
    ".eslintrc.js"
  ],
  "exclude": [
    "node_modules",
    "dist"
  ]
}

そして、ESLint用の設定ファイル .eslintrc.js をプロジェクトディレクトリ直下に作成します。

typescript-node-base
# .eslintrc.js ファイルの作成
touch .eslintrc.js

# エディターで編集(お好みのエディターをお使いください)
vim .eslintrc.js
~/typescript-node-base/.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導入完了です。

ESLintの実行

早速、ESLintを実行してみましょう。

typescript-node-base
npx eslint src/index.ts
実行結果

何も表示されなければ成功です!

設定やソースコードに問題がない場合、何も表示されないのが正常です。
もしここでエラーが表示される場合は記述ミスが考えられますので、各種設定ファイルやソースコードを見直してください。

次に、ESLintでエラーを検知できるか確認してみましょう。
src/index.ts を以下のように編集します。

src/index.ts
function hello(name: string): string {
  return `Hello, ${name}!`;
}

- console.log(hello('TypeScript'));
+ console.log(hello('TypeScript')

最後の行の閉じカッコを一つ削除して、文法エラーを意図的に発生させます。
ソースコードを書き換えたら保存し、ESLintを実行します。

typescript-node-base
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ファイルを生成しないようにします)

typescript-node-base
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 に以下の記述を追加します。

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
eslint実行結果
/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に設定を追加します。

package.json
{
  ...
  "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"
  },
  ...
}

エラーチェックを実行する時は以下のコマンドを実行します。

typescript-node-base
npm run lint

こうすることで、一度のコマンド実行で eslinttsc の両方を実行することができます。

また、以下のように個別に実行することもできます。

typescript-node-base
# 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をインストールします。

typescript-node-base
# --save-exact でバージョンを固定してdevインストール
npm install -D --save-exact prettier eslint-config-prettier

# Prettierのバージョン確認
./node_modules/.bin/prettier --version
#=> 2.0.5

次に、Prettier用の設定ファイルをプロジェクトディレクトリ直下に作成します。

typescript-node-base
echo "{}"> .prettierrc.json
.prettierrc.json
{}

.prettierrc.json は必要に応じて書き換えてください

また、prettierの対象外となるファイルを指定する .prettierignore をプロジェクトディレクトリ直下に作成します。

typescript-node-base
touch .prettierignore

# エディターで編集
vim .prettierignore
.prettierignore
# Ignore artifacts:
/dist
node_modules
package.json
package-lock.json
tsconfig.json
tsconfig.eslint.json

このままだと、ESLintのコードフォーマット機能とPrettierのコードフォーマット機能がコンフリクトしてしまうので、.eslintrc.jsのextendsに prettierprettier/@typescript-eslint の記述を追加します(追加する位置はextendsの最後の方になります)。

.eslintrc.js
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 を以下のようにインデントなどがバラバラな状態に書き換えます。

src/index.ts
function
hello(name:string):string{
return `Hello, ${name}!`
}

console.log(hello("TypeScript"
))

そして、以下のコマンドでPrettierを実行します。

typescript-node-base
npx prettier --write src/index.ts

すると、以下のようにコードが修正されます。

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 を追加しています。

package.json
{
  ...
  "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のみを実行する場合は以下のコマンドを実行します。

typescript-node-base
npm run format

eslinttscも併せて実行する場合は、以下のコマンドを実行します。

typescript-node-base
npm run lint:fix

これでPrettierの導入完了です。

Git commit時に自動でlintを実行する(huskyとlint-staged)

これまでの手順でESLintとPrettierを導入しましたが、コードを修正するたびに lint:fix コマンドを手動で実行する必要がありました。
そのため、lintをかけていないコードを間違ってコミットしてしまうことがあります。

これを防ぐため、git commit時に esLint/ tsc / prettier を自動的に実行するように huskylint-staged を導入します。

huskyとは

gitのcommit前やpush前などに特定のコマンドを実行するためのGit hooksを簡単に作成するためのツールです。後述のlint-stagedと組み合わせて使います。

lint-stagedとは

git add でステージングに追加されたファイルに対して、指定したコマンドを実行します。

huskyとlint-stagedの導入

huskyとlint-stagedは以下のコマンドを実行するとpackage.jsonに自動で設定が追加されます。

typescript-node-base
npx mrm lint-staged
package.json
{
  ...
  "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 の部分を少し修正します。

package.json
{
  ...
  "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 を見ないという仕様になっていることが起因しています。

MacやLinux環境でbashが使用できる場合は、tscをbash経由で実行することでtsconfig.jsonを見るようになるとのことです。

package.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
// 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 を以下のように書き換えます。

src/index.ts
function 
hello(name: string): string {
return `Hello, ${name}!`;
}

console.log(hello("TypeScript!!!!!")
);

そしてgit addを行い、その後git commitを実行します。

typescript-node-base
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によって自動整形されているのがわかります。

typescript-node-base
function hello(name: string): string {
  return `Hello, ${name}!`;
}

console.log(hello("TypeScript!!!!!"));

git log -p で差分を確認してみましょう。

typescript-node-base
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されることを防ぐことができます。

設定は以上です。

あとはお好みでカスタマイズしましょう!

成果物

以上の手順を実行した完成形のソースコードは以下のリポジトリをご参照ください。

参考

変更履歴

  • 2020/8/12 記事公開
235
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
235
Help us understand the problem. What is going on with this article?