create-react-appをベースにeslint(airbnb)とprettierを導入

[create-react-app](https://create-react-app.dev/)をベースに作っているWebアプリに、途中から[eslint-config-airbnb](https://www.npmjs.com/package/eslint-config-airbnb)の設定およびコード整形ツールの[prettier](https://prettier.io/)を入れたくなることがあり、想定以上に苦戦したのでメモ。

なお、便宜上0からセットアップするような形で書いている(検証している)が、ある程度動いているものに導入して移行するようなケースを想定している。

## 0. 前提条件など

検証環境:

- Linux(Ubuntu20.04LTS)
- node 14.16.0
    * npm6.14.11

nodejsまわりのパッケージインストール・管理はyarnではなくnpmを使って行っている。

## 1. 構築手順

### 1-1. create-react-app

最新版(執筆時: 4.0.3)ではグローバルインストールが非推奨になった([参考: 公式README](https://github.com/facebook/create-react-app#quick-overview))ので、以下のように適当なディレクトリ上にcreate-react-appをインストールする:

```sh
mkdir tmp
cd tmp
npm init -y
npm i create-react-app
```

上記の例では`tmp/`にcreate-react-appがインストールされる。

実際にアプリ(gitプロジェクト)を作る場所をtmpの下に作る場合は`npx create-react-app hoge`などのようにすれば良いが、今回は外側に作りたかったので例えば以下のようにする

```sh
# create-react-appをインストールしてあるtmpの上のディレクトリへ移動
cd ..

# tmp下の実行ファイルの絶対パスを指定してcreate-react-appを実行
tmp/node_modules/.bin/create-react-app path/to/test-app
```

上記のようにすると指定したパスに`test-app`という名前でreactアプリが作成される:

```sh
cd path/to/test-app; tree -a -L 1
# .
# ├── .git
# ├── .gitignore
# ├── README.md
# ├── node_modules
# ├── package-lock.json
# ├── package.json
# ├── public
# └── src
```

### 1-2. eslint

上記で作った`test-app`に移動し、eslintの設定を行う。

eslintはデフォルトでcreate-react-appで生成されたアプリ内に含まれているため、そのまま`npx`で実行出来る。

オススメ設定などは`npx eslint --init`で自動的に設定することができ、今回は人気の高そうなairbnb設定を入れてみる

```sh
npx eslint --init

# 対話形式で色々聞かれるので、今回は以下のように設定
? How would you like to use ESLint? …
  ▸ To check syntax, find problems, and enforce code style
? What type of modules does your project use? …
  ▸ JavaScript modules (import/export)
? Which framework does your project use? …
  ▸ React
? Does your project use TypeScript?
  ▸ No
? Where does your code run? … (Press <space> to select, <a> to toggle all, <i> to invert selection)
  ✔ Browser
? How would you like to define a style for your project? …
  ▸ Use a popular style guide
? Which style guide do you want to follow? …
  ▸ Airbnb: https://github.com/airbnb/javascript
? What format do you want your config file to be in? …
  ▸ JavaScript
? Would you like to install them now with npm? 
  ▸ Yes # yarnを使う人はNo
```

これにより、開発用パッケージ(`devDependencies`)にeslint関係のものがいくつかインストールされ、`.eslintrc.js`が生成される。しかし、そのままだと不都合な部分もあるので[ここ](https://stackoverflow.com/questions/42640636/react-must-be-in-scope-when-using-jsx-react-react-in-jsx-scope)などを参考に次のように編集しておく:

```javascript:.eslintrc.js
module.exports = {
  env: {
    browser: true,
    es2021: true,
  },
  extends: [
    'plugin:react/recommended',
    'airbnb',
    'react-app', // 手動で追加
    'react-app/jest', // 手動で追加
  ],
  parserOptions: {
    ecmaFeatures: {
      jsx: true,
    },
    ecmaVersion: 12,
    sourceType: 'module',
  },
  plugins: [
    'react',
  ],
  rules: {
    'react/react-in-jsx-scope': 'off', // 手動で追加: suppress errors for missing 'import React' in files
    'react/jsx-filename-extension': [1, { extensions: ['.js', '.jsx'] }],     // 手動で追加: allow jsx syntax in js files (for next.js project), should add ".ts" if typescript project
  },
};
```

↑で"手動で追加"と入れている部分がデフォルトで生成されているものに手を加えた箇所。

`extends`部分について、実はcreate-react-appにより生成された`package.json`内にデフォルトでeslintの設定があるため、その部分を移動させたもの。

```json:package.json(抜粋)
...
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
...
```

上記部分を`.eslintrc.js`に移し、`package.json`からは不要(優先順位的に読み込まれない、[参考](https://eslint.org/docs/2.0.0/user-guide/configuring#configuration-file-formats))ので消しておく。

`rules`部分について、

- `'react/react-in-jsx-scope': 'off'`
    * 例えば分割されて呼び出されるソースコードなどでは必ずしも明示的に`import React from 'react'`を行わなくても良い
- `'react/jsx-filename-extension': [1, { extensions: ['.js', '.jsx'] }]`
    * デフォルトでは拡張子が`.js`のファイルでjsxを使うとエラーになるため、それを抑制する
    * create-react-appがデフォルトで作る`App.js`が上記エラーに対象になり、不便なため

ここまでである程度は設定出来ているが、eslintでエラーが出る状況だと`npm run start`や`npm run build`が通らない:

```sh
npm run start

# 以下のようなエラーが出て実行(コンパイル)出来ない

Failed to compile.

src/App.js
  Line 10:16:  `code` must be placed on a new line                          react/jsx-one-expression-per-line
  Line 10:39:  ` and save to reload.        ` must be placed on a new line  react/jsx-one-expression-per-line

src/index.js
  Line 11:34:  Missing trailing comma  comma-dangle

src/reportWebVitals.js
  Line 1:25:  Expected parentheses around arrow function argument  arrow-parens
  Line 3:32:  Expected a line break after this opening brace       object-curly-newline
  Line 3:74:  Expected a line break before this closing brace      object-curly-newline

Search for the keywords to learn more about each error.

```

厳格にやる場合はこれが正しい挙動だが、既存プロジェクトに途中から導入する、移行するといったケースでは正直かなり不便である。（更にeslintの設定に過不足があるケースも考えられる。）そこで、アプリの実行やビルドは許容するようにしたい。

上記の問題に対しては、developmentおよびproduction用の環境変数を設定して対処する:

- [公式ドキュメント](https://create-react-app.dev/)より:
    - https://create-react-app.dev/docs/adding-custom-environment-variables/#adding-development-environment-variables-in-env
    - https://create-react-app.dev/docs/advanced-configuration/

今回は`npm run start`と`npm run build`両方を実行出来るようにするため、以下の2つのファイルを作成してプロジェクトのルートディレクトリに配置する:

```sh:.env.development
SKIP_PREFLIGHT_CHECK=true
ESLINT_NO_DEV_ERRORS=true
EXTEND_ESLINT=true
```
(↑開発時のみの設定、`EXTEND_ESLINT=true`は不要かも?)

```sh:.env.production
DISABLE_ESLINT_PLUGIN=true
```
(プロダクション用ビルド時のみの設定、eslintのエラーを全無視するので本来良くないが、移行時などでは暫定的にOKとする)


とりあえず以上の設定でeslintの導入をしつつ、実行やビルドは出来るようになった。

### 1-3. prettier

実際に動かしているアプリなどだと上記まででおびただしい数のerrorが出てくる。いちいち手作業で直すのも面倒だし、コード整形自体はeslintよりもprettierの方が得意そうなので簡単に設定してみる。

まずはprettierと、eslintとの連携ツール(eslint-config-prettier)のインストール:

```sh
npm i -D prettier eslint-config-prettier
```

なお、eslint-plugin-prettierは最近非推奨になったとの話なので入れないでおく。

次に、

- https://prettier.io/docs/en/configuration.html
- https://prettier.io/docs/en/options.html

などを参考にして必要に応じて`.prettierrc.js`などを用意し、prettier用の設定を作成:

```javascript:.prettierrc.js
module.exports = {
  printWidth: 80,
  tabWidth: 2,
  singleQuote: true,
  semi: true,
  trailingComma: 'all',
};
```

次に、prettierを実行しやすいように`package.json`の`scripts`部分に`"format": "prettier --write ./src/**/*.{js,jsx}"`を入れておく:

```json:package.json(抜粋)
...
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject",
    "format": "prettier --write ./src/**/*.{js,jsx}" // 追加
  },
...
```

これで、

```sh
npm run prettier
```
のように実行することで`src/`以下の`*.js`, `*.jsx`の拡張子のファイルがprettierで整形されるようになる。

また、prettierで整形する部分についてはeslintの設定を切るようにすることが出来る（というか、eslintとpretterで競合しないようにするためにあった方が良いらしい）。

これは先程インストールした`eslint-config-prettier`を使って行うものであり、今回であれば`.eslintrc.js`の`extends`の**最後**に`prettier`を追加すれば良い([※参考](https://github.com/prettier/eslint-config-prettier#installation)):

```javascript:.eslintrc.js(変更後)
module.exports = {
  env: {
    browser: true,
    es2021: true,
  },
  extends: [
    'plugin:react/recommended',
    'airbnb',
    'react-app',
    'react-app/jest',
    'prettier', // 追加
  ],
  parserOptions: {
    ecmaFeatures: {
      jsx: true,
    },
    ecmaVersion: 12,
    sourceType: 'module',
  },
  plugins: [
    'react',
  ],
  rules: {
    'react/react-in-jsx-scope': 'off',
    'react/jsx-filename-extension': [1, { extensions: ['.js', '.jsx'] }],
  },
};
```
これによってeslintの出すエラー・警告からprettierで整形する部分が除外される。

## 2. まとめ

create-react-appで生成したものに対して、手作業でファイルの編集や追加をいくつかやったので最後に簡単にまとめておく。

上記の手順を実行後のプロジェクトのファイルは以下のような感じ:

```sh
tree -a -L 1
.
├── .env.development # 追加
├── .env.production # 追加
├── .eslintrc.js # 追加、変更
├── .git
├── .gitignore
├── .prettierrc.js # 追加
├── README.md
├── node_modules
├── package-lock.json # 変更(但し、自動的に編集されるので意識しない)
├── package.json # 変更
├── public
└── src
```
各ファイルの変更・追加内容は基本的にこれまでの記述の通り。
但し、`package.json`についてはいきなり最終形のものを置いて`npm i`などを実行すればこれまでの手順の`npm i -D ***`などを省くことが出来る。そこで、以下に最終的な`package.json`を掲載する:

```json:package.json(最終形)
{
  "name": "test-app",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@testing-library/jest-dom": "^5.11.9",
    "@testing-library/react": "^11.2.5",
    "@testing-library/user-event": "^12.8.3",
    "react": "^17.0.1",
    "react-dom": "^17.0.1",
    "react-scripts": "4.0.3",
    "web-vitals": "^1.1.1"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject",
    "format": "prettier --write ./src/**/*.{js,jsx}"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
  "devDependencies": {
    "eslint": "^7.22.0",
    "eslint-config-airbnb": "^18.2.1",
    "eslint-config-prettier": "^8.1.0",
    "eslint-plugin-import": "^2.22.1",
    "eslint-plugin-jsx-a11y": "^6.4.1",
    "eslint-plugin-react": "^7.22.0",
    "eslint-plugin-react-hooks": "^4.2.0",
    "prettier": "^2.2.1"
  }
}
```


## 3. 今後の課題など

- [`node-sass`によりsassを導入](https://create-react-app.dev/docs/adding-a-sass-stylesheet/)したり[css-moduleを使い](https://create-react-app.dev/docs/adding-a-css-modules-stylesheet/)つつ、prettierによる整形をcssやscssにも適用したい([`stylelint`](https://stylelint.io/)など?)
    * →[この記事](https://qiita.com/junkor-1011/items/148347d693118f91ec25)を参照
- jsdocの導入
    * ドキュメントのビルド
    * jsdoc部分のフォーマットチェックもlinterで行う
    * [eslint-plugin-jsdoc](https://github.com/gajus/eslint-plugin-jsdoc) などが使えそう
        * typescriptを使う場合、[eslint-plugin-tsdoc](https://github.com/microsoft/tsdoc/tree/master/eslint-plugin#eslint-plugin-tsdoc) などが使えそう
- `git commit`するときに自動で`eslint --fix`と`prettier --write`を対象ファイルにかける
    * https://prettier.io/docs/en/precommit.html のようにいくつか選択肢がありそう
- typescriptを使った場合(`create-react-app --template typescript`)で同様の設定を出来るようにする
- `create-react-app`だけでなく、nextjsやgatsbyなど他のフレームワークでも同様のことが出来ると嬉しい

など

## (追記)

### `eslint --fix`

[eslint自体にもフォーマット機能があり](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems)、例えば`package.json`の`scripts`に

```:package.json(抜粋)
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject",
    "eslintfmt": "eslint --fix ./src/**/*.{js,jsx}" // 追記
  },
```
のように追記しておき、

```sh
npm run eslintfmt
```

のようにして実行する事ができる。
修正内容はeslintの設定次第だが、varやletを適切にconstなどに変える、などprettierでは出来ないような修正もやってくれたりする。

ただ、筆者の環境だと行末に余分な空白が入ったり、みたいなことも起こったりする。
設定が悪いだけかもしれないが、単純な整形自体はprettierの方が得意な気がするので、個人的には

- eslintで修正 → prettierで再整形

のような感じで両方使って整形している。

### `.prettierignore`, `.eslintignore`

上記の設定だと、`src/`の下にあるファイル全てがeslint, prettierをかける対象となるが、
自動生成されたjsファイルなど、編集したくないものが対象内になってしまうことがある。
そこで、

- https://prettier.io/docs/en/ignore.html
- https://eslint.org/docs/user-guide/configuring/ignoring-code#the-eslintignore-file

を参考に、
`.gitignore`のような感覚で`.prettierignore`および`.eslintignore`ファイルをプロジェクトのルートディレクトリに配置すれば対象から除外することが出来る。

------------

## 参考

- https://stackoverflow.com/questions/42640636/react-must-be-in-scope-when-using-jsx-react-react-in-jsx-scope
- https://qiita.com/h-yoshikawa44/items/14a114da903fb0eff886#%E5%8F%82%E8%80%83
- https://qiita.com/jonakp/items/7d9f47c613c16cbf95aa
- https://qiita.com/sho-t/items/c9fe6d382636bd3402f8
- https://www.codebu.org/posts/react-linter-starter/
- https://dev.classmethod.jp/articles/introduce-prettier/
- https://qiita.com/takeshisakuma/items/bbb2cd2f1c65de70e363
- https://ics.media/entry/17030/
- https://qiita.com/soarflat/items/06377f3b96964964a65d
- https://zenn.dev/tatsurom/articles/7afc52e771ed08d156ef
- https://blog.ojisan.io/eslint-prettier

- eslint比較(airbnb, google, standard)
  * https://www.npmtrends.com/eslint-config-airbnb-vs-eslint-config-google-vs-eslint-config-standard
  * https://npmcompare.com/compare/eslint-config-airbnb,eslint-config-google,standard
  * https://betterprogramming.pub/comparing-the-top-three-style-guides-and-setting-them-up-with-eslint-98ea0d2fc5b7