webpack + typescript + react + prettier + eslint + airbnb

## TL;DR

* webpack, typescript, react, prettier, eslint, airbnbを使ってreactの最新開発環境を構築する。

## 基本知識

下記のポストと文書を参考にしてこの記事を作成しました。

1. https://ics.media/entry/16329/#webpack-ts-react
2. https://www.typescriptlang.org/docs/handbook/react-&-webpack.html
3. https://github.com/typescript-eslint/typescript-eslint#how-do-i-configure-my-project-to-use-typescript-eslint
4. https://prettier.io/docs/en/integrating-with-linters.html#use-both
5. https://medium.com/dubizzletechblog/setting-up-prettier-and-eslint-for-js-and-react-apps-bbc779d29062
6. https://medium.com/@joshuacrass/javascript-linting-and-formatting-with-eslint-prettier-and-airbnb-30eb746db862

他のものはともかく1と2は目を通してください。
1と2のポストでは最小構成で説明しているので、自分なりに必要だと思った構成を3~6まで参考にして追加しました。
step by stepでコマンドを実行する形になっています。

## プロジェクトの生成

Reactだけではないですが、プロジェクト単位でフォルダを作ってその中で必要なライブラリを設置し開発作業（コーディング）を行うのは基本です。

```sh
mkdir webpack-ts-react
cd webpack-ts-react
npm init -y
mkdir dist
mkdir src
```

srcはソースフォルダで、distはソースファイルをcompileしたファイルが保存されるフォルダになります。
dist以外にbuildやpublicなどの名前でフォルダを作る場合もあります。

## webpackのインストール

webpackはデファクトスタンダードとして使われているモジュール（ライブラリ）バンドラーです。

```sh
npm i -D webpack webpack-cli webpack-dev-server
```

* webpack
  - core library
* webpack-cli
  - webpackのcommand line interface
* webpack-dev-server
  - webpackのdevelopment server
  - ソースコードの変更事項をブラウザにすぐ反映してくれる。

## typescriptのインストール

```sh
npm i -D typescript ts-loader
```

* typescript
  - core library
* ts-loader
  - webpackでtypescriptをcompileするために必要

## reactのインストール

```sh
npm i -S react react-dom @types/react @types/react-dom
```

reactとreact-domでtypescriptの型定義[^1]を使いたいので、@types/reactと@types/react-domも一緒にインストールします。

[^1]: typescriptを使う場合もjavascriptのライブラリを使います。しかし、javascriptにはtypescriptのように型定義が存在しないので、そのままだとcompileできない場合があります。そこで、使うのが型定義ファイルです。

## prettierのインストール

prettierはcode formatting（コード整形）を行ってくれるライブラリーです。
eslintも「eslint --fix」でcode formattingができますが、prettierの方がeslintより優れていて、eslintとprettierを併用して使うケースが多いです。
lintingはeslintを、code formattingはprettierを使うイメージです。

```sh
npm i -D prettier eslint-config-prettier eslint-plugin-prettier pretty-quick
```

* prettier
  - core library

* eslint-config-prettier
  - prettierと競合するeslintのルールを無視する。

* eslint-plugin-prettier
  - eslintのルールでprettierを実行する。

* pretty-quick
  - 変更されたファイルだけprettierを実行する。

.eslintrc.jsonに以下の内容を追記します。
eslintとprettierを一緒に使うための設定です。

```json
{
  "plugins": ["prettier"],
  "extends": ["plugin:prettier/recommended"],
  "rules": {
    "prettier/prettier": "error"
  }
}
```

## eslint + airbnb

eslintのルールを拡張したairbnbのルール（コードコンベンション）を使います。
eslint-config-airbnbの最新バージョンの依存性パーケージを確認してください。

```sh
npm info "eslint-config-airbnb@latest" peerDependencies

{
  eslint: '^4.19.1 || ^5.3.0',
  'eslint-plugin-import': '^2.18.0',
  'eslint-plugin-jsx-a11y': '^6.2.3',
  'eslint-plugin-react': '^7.14.2'
}
```

eslint-config-airbnbとその依存性パーケージをインストールします。

```sh
npm i -D eslint-config-airbnb eslint@5.3.0 eslint-plugin-import eslint-plugin-jsx-a11y@6.2.3 eslint-plugin-react
```

npm 5+以上を使っている場合は下記のshortcutも使えます。

```sh
npx install-peerdeps --dev eslint-config-airbnb
```

.eslintrc.jsonに以下の内容を追記します。

```json
{
  "extends": ["airbnb"]
}
```

reactを使わない場合は、airbnbの代わりにairbnb-baseを使います。

## eslint + typescript

typescriptではlintingのためにtslintというものがあります。
eslintの方が幅広いルールを提供しているので、tslintではないeslintを使います。
typescriptとeslintを一緒に使うためには下記のパーケージをインストールする必要があります。

```sh
npm i -D @typescript-eslint/parser @typescript-eslint/eslint-plugin
```

.eslintrc.jsonに以下の内容を追記します。
eslintとtypescriptを一緒に使うための設定です。

```json
{
  "parser": "@typescript-eslint/parser",
  "plugins": ["@typescript-eslint"],
  "extends": ["plugin:@typescript-eslint/recommended"]
}
```

typescriptを使わない場合は、parserとしてbabel-eslintを使います。

## tsconfig.json

typescriptの設定ファイルです。

```json
{
  "compilerOptions": {
    "outDir": "./dist/",
    "sourceMap": true,
    "target": "es5",
    "module": "es2015",
    "jsx": "react",
    "moduleResolution": "node",
    "lib": [
      "es2019",
      "dom"
    ]
  },
  "include": [
    "src/**/*"
  ]
}
```

compileされたソースコードの出力場所はdistで、
sourceMapをtrueにしたので、complieされたソースコードは圧縮されずに保存されます。
targetをes5にしたので、es5でcomplieされます。
includeにはソースフォルダを指定します。これで自分が作成したソースコードも補完に利用されます。
excludeは設定しない場合、デフォルトでnode_modulesが設定されます。

tsconfig.jsonの詳細が知りたい場合は下記の文書を参照してください。

https://www.typescriptlang.org/docs/handbook/tsconfig-json.html

## .eslintrc.json

ここまでして完成された.eslintrc.jsonのファイルです。

```json
{
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "ecmaFeatures": {
      "jsx": true
    },
    "project": "./tsconfig.json",
    "useJSXTextNode": true
  },
  "plugins": ["@typescript-eslint", "prettier"],
  "extends": [
    "airbnb",
    "plugin:@typescript-eslint/recommended",
    "prettier/@typescript-eslint",
    "plugin:prettier/recommended"
  ],
  "env": {
    "browser": true,
    "node": true,
    "es6": true
  },
  "rules": {
    "prettier/prettier": "error",
    "max-len": ["error", {"code": 100}],
    "prefer-promise-reject-errors": ["off"],
    "react/jsx-filename-extension": ["off"],
    "react/prop-types": ["off"],
    "no-return-assign": ["off"]
  }
}
```

| ルール（コードコンベンション）          | ライブラリー                                                                                    |
| -----                                   | -----                                                                                           |
| "airbnb"                                | eslint-config-airbnb, eslint, eslint-plugin-import, eslint-plugin-jsx-a11y, eslint-plugin-react |
| "plugin:@typescript-eslint/recommended" | @typescript-eslint/eslint-plugin                                                                |
| "prettier/@typescript-eslint"           | eslint-config-prettier                                                                          |
| "plugin:prettier/recommended"           | eslint-plugin-prettier                                                                          |

rulesのところはご自分の設定に合わせてください。

## .prettierrc

prettierの設定ファイルです。
option名を見れば大体わかると思うので、optionの説明は割愛します。

```
{
 “printWidth”: 100,
 “trailingComma”: “all”,
 “tabWidth”: 2,
 “semi”: true,
 “singleQuote”: true
}
```

## package.json

ここまでして完成されたpackage.jsonのファイルです。

```json
{
  "name": "webpack-ts-react",
  "version": "1.0.0",
  "description": "webpack + typescript + reactの最小構成",
  "main": "index.js",
  "scripts": {
    "start": "webpack-dev-server",
    "build": "webpack",
    "watch": "webpack -w",
    "lint": "eslint src -c .eslintrc.json --ext ts,tsx; exit 0",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@typescript-eslint/eslint-plugin": "^1.13.0",
    "@typescript-eslint/parser": "^1.13.0",
    "babel-eslint": "^10.0.2",
    "eslint": "^5.3.0",
    "eslint-config-airbnb": "^17.1.1",
    "eslint-config-prettier": "^6.0.0",
    "eslint-plugin-import": "^2.18.2",
    "eslint-plugin-jsx-a11y": "^6.2.3",
    "eslint-plugin-prettier": "^3.1.0",
    "eslint-plugin-react": "^7.14.3",
    "prettier": "^1.18.2",
    "pretty-quick": "^1.11.1",
    "ts-loader": "^6.0.4",
    "typescript": "^3.5.3",
    "webpack": "^4.38.0",
    "webpack-cli": "^3.3.6",
    "webpack-dev-server": "^3.7.2"
  },
  "dependencies": {
    "@types/react": "^16.8.23",
    "@types/react-dom": "^16.8.5",
    "react": "^16.8.6",
    "react-dom": "^16.8.6"
  }
}
```

## webpack.config.js

webpackの設定ファイルです。

```javascript
module.exports = {
  devtool: 'source-map',
  // モード値をproductionに設定すると最適化される状態で、
  // developmentに設定するとソースマップ有効でJSファイルが出力される。
  mode: 'development',

  // メインとなるjavascriptファイル（エントリーポイント）
  entry: './src/main.tsx',
  // ファイルの出力設定
  output: {
    // 出力ファイルのディレクトリ名
    path: `${__dirname}/dist`,
    // 出力ファイル名
    filename: 'main.js'
  },
  module: {
    rules: [
      {
        // 拡張子.tsもしくは.tsxの場合
        test: /\.tsx?$/,
        // TypeScriptをコンパイルする
        use: 'ts-loader'
      }
    ]
  },
  // import文で.tsや.tsxファイルを解決するため
  resolve: {
    extensions: ['.ts', '.tsx', '.js', '.json']
  },
  devServer: {
    contentBase: './dist',
    historyApiFallback: true,
    inline: true,
    hot: true,
    port: 5000,
    open: true
  }
}
```

## 動作確認

サンプルコードは[ここ](https://github.com/ics-creative/170330_webpack/tree/master/tutorial-typescript-react)を参照しました。

```sh
npm run lint

> webpack-ts-react@1.0.0 lint /Users/devtopia/apps/study/js/webpack-ts-react
> eslint src -c .eslintrc.json --ext ts,tsx; exit 0


/Users/devtopia/apps/study/js/webpack-ts-react/src/main.tsx
  5:1  error    Component should be written as a pure function              react/prefer-stateless-function
  6:3  error    Missing accessibility modifier on method definition render  @typescript-eslint/explicit-member-accessibility
  6:9  warning  Missing return type on function                             @typescript-eslint/explicit-function-return-type

/Users/devtopia/apps/study/js/webpack-ts-react/src/sub-component.tsx
  12:29  warning  Unexpected any. Specify a different type                      @typescript-eslint/no-explicit-any
  19:22  warning  Missing return type on function                               @typescript-eslint/explicit-function-return-type
  20:5   warning  Unexpected console statement                                  no-console
  23:14  error    Must use destructuring state assignment                       react/destructuring-assignment
  23:14  error    Use callback in setState when referencing the previous state  react/no-access-state-in-setstate
  27:3   error    Missing accessibility modifier on method definition render    @typescript-eslint/explicit-member-accessibility
  27:9   warning  Missing return type on function                               @typescript-eslint/explicit-function-return-type
  30:14  error    Must use destructuring props assignment                       react/destructuring-assignment
  31:15  error    Must use destructuring state assignment                       react/destructuring-assignment
  32:9   error    Missing an explicit type attribute for button                 react/button-has-type

✖ 13 problems (8 errors, 5 warnings)
```