ESLintとPrettierのCI環境を構築

## この記事の対象者
- TypeScriptの環境設定が終わっている方
- Gitのコミット時にコードのフォーマットを行いたい方
- ESLintとPrettierのCI環境構築について学びたい方

## 開発環境
```
mac Ventura 13.3.1
Visual Studio Code
Typescript 5.0
Node.js version 18
```

## ESLintとは何か
ESLintは、JavaScript、TypeScriptを始めとするスクリプト言語でのコーディングエラーや不適切なパターンを発見し、それらを修正するためのツールです。コードのバグやスタイルミスを早期に見つけ出すことで、品質を向上させることが可能になります。

## Prettierとは何か
Prettierは、JavaScript、TypeScript、CSS、JSONなど多様な言語に対応したコードフォーマッターです。コードを一定のスタイルに整形することで、読みやすさを向上させ、開発者間でのコーディングスタイルの差異を減らすことができます。ESLintと違い、Prettierはコードの品質やパフォーマンスに関わる問題を検出するよりは、一貫したコードスタイルを維持することに重点を置いています。




## 実際の流れ

![diagram.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2401767/ca355067-93ad-6e72-8a03-c2fb537900a8.png)

1:  `.ts` ファイルにコードを書きます。

2: 開発者が作業を完了したら、`git commit`コマンドを使用してコードをコミットします。

3: `Husky`が`git commit`コマンドをフックして、定義されたタスクを実行します。このタスクは通常、コードの検証と整形を行うためのものです。

4: `Husky`は`pre-commit hook`を使用して`lint-staged`を呼び出します。`lint-staged`は、ステージングされたファイル（つまり、コミット対象となるファイル）のみを対象に`Prettier`と`ESLint`を実行します。

5: `Prettier`と`ESLint`が動作し、コードのチェックと整形を行います。`Prettier`はコードのスタイルを整形し、`ESLint`は構文エラーやバグを見つけ出します。

6: チェックと整形が完了したら、`lint-staged`はその結果を`Husky`に返します。もしエラーがあった場合、それが表示され、コミットは中断されます。

7: エラーがなければ、コミットが行われ、その後 GitHub にプッシュされます。

これが TypeScript ファイルから GitHub へのコミット時に ESLint と Prettier が動作する基本的なフローです。このように、ESLint と Prettier を用いて自動的にコードの検証と整形を行うことで、バグの早期発見とコードスタイルの一貫性が保たれます。

## ESLintとPretterの環境構築

パッケージのインストール
```
npm install --save-dev eslint eslint-config-prettier prettier @typescript-eslint/parser @typescript-eslint/eslint-plugin husky lint-staged
```



## ディレクトリ構造
```
.
├── dist
│   ├── bundle.js
│   └── index.html
├── node_modules
├── src
│   ├── index.ts
│   └── world.ts
├── .eslintrc.js
├── .prettierrc
├── package-lock.json
├── package.json
├── tsconfig.json
└── webpack.config.js
```


## 各ファイルを用意


#### index.html
```
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>TypeScript学習</title>
</head>
<body>
  <div id="root"></div>
  <script src="bundle.js"></script>
</body>
</html>
```

#### index.ts
```
export default class World {
  message: string

  constructor(message: string) {
    this.message = message
  }

  public sayHello(elem: HTMLElement | null): void {
    if (elem) {
      elem.innerHTML = this.message
    }
  }
}
```


#### package.json
```
{
  "name": "ts-basic",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "webpack --mode=production",
    "start": "webpack-cli serve --mode development",
    "lint-fix": "eslint --fix './src/**/*.{js,ts}' && prettier --write './src/**/*.{js,ts}'",
    "prepare": "husky install"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@typescript-eslint/eslint-plugin": "^5.59.6",
    "@typescript-eslint/parser": "^5.59.6",
    "eslint": "^8.41.0",
    "eslint-config-prettier": "^8.8.0",
    "husky": "^8.0.0",
    "lint-staged": "^13.2.2",
    "prettier": "^2.8.8",
    "ts-loader": "^9.4.2",
    "typescript": "^5.0.4",
    "webpack": "^5.83.1",
    "webpack-cli": "^5.1.1",
    "webpack-dev-server": "^4.15.0"
  },
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  },
  "lint-staged": {
    "src/**/*.{js,ts}": [
      "npm run lint-fix"
    ]
  }
}
```

#### .prettierrc
```
{
  "printWidth": 120,
  "singleQuote": true,
  "semi": false
}
```

#### .eslintrc.js
```module.exports = {
  env: {
    browser: true,
    es6: true,
  },
  extends: [
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended",
    "prettier"
  ],
  plugins: [
    "@typescript-eslint"
  ],
  parser: "@typescript-eslint/parser",
  parserOptions: {
    "sourceType": "module",
    project: "./tsconfig.json",
  },
  root: true,
  rules: {
    '@typescript-eslint/explicit-module-boundary-types': 'error',
  }
}
```

#### webpack.config.js
```
const path = require('path');

module.exports = {
  entry: {
    bundle: './src/index.ts'
  },
  output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name].js'
  },
  resolve: {
    extensions: ['.ts', '.js']
  },
  devServer: {
    static: path.join(__dirname, 'dist'),
  },
  module: {
    rules: [
      {
        loader: 'ts-loader',
        test: /\.ts$/
      }
    ]
  }
}
```

## huskyが動かない場合

1: もしHuskyが設定されていない場合、まずHuskyをアンインストールします。
```
npm uninstall husky
```

2: 次に、Huskyを再インストールします。

```
npm install --save-dev husky
```

3: Huskyのバージョンが7以降の場合、設定方法が少し異なります。まず、以下のコマンドでHuskyを初期化します。

```
npx husky-init && npm install
```

4: このコマンドにより、.huskyディレクトリがプロジェクトのルートに生成されます。このディレクトリ内にHuskyのフックが格納されます。

```
npx husky add .husky/pre-commit "npm run lint"
```

これにより、コミット時にESLintが実行されるようになります。"npm run lint"の部分は、プロジェクトに合わせて変更することができます。