npmのインストール後、ReactをTypeScriptで書くまで

## はじめに
なんだかんだWebのフロントで一番きついのは、環境構築だと思います。
フロントを始めた時に、こんなドキュメントがあればハマらなかっただろうな…と思うことを備忘します。
ただ、こんな記事を書いておいてなんですが、よくわからなければ**公式ドキュメントとエラーメッセージをよく読む**のが解決への近道です。

## 前提条件
タイトルにも書いてある通りNode.jsとnpmのインストールは一通り終わってる前提です。
そして、型がないと不安でプログラムが書けない人向けです。

## 目指す姿
- Linterできちんとビルド前に静的チェックされる
- TypeScriptで書いたコードが無事index.htmlとかとともにディレクトリに出力される
- ちゃんとデバッグできるようにする

## まず.gitignore
何はともあれ、.gitignoreを最初にコミットします。
[github/gitignore](https://github.com/github/gitignore)からNode.gitignoreとIDEのxxx.gitignore(筆者はWebstorm使ってるのでJetBrains.gitignore)、あと必要に応じてmacOS.gitignoreなどをマージして作ってます。

## package.jsonを作る
[参考](https://docs.npmjs.com/files/package.json)

package.jsonとはnpmの設定ファイルです。
ですので、これを置いておけば、同じ環境のnpmならnpmコマンドは同じように動きます。
作り方は簡単でリポジトリルートで

```sh
npm init
```
するだけです。そうすると

```shell-session
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help json` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
```

が表示されて、幾つかの質問をされるので、それに適当に応えるだけです。
殆どの項目はnpmモジュールとして切り出す際に使われるであろう項目だと思うので、そこまで真剣になる必要はないと思っています。
一応、聞かれる項目は以下の通り。
<dl>
    <dt>package name</dt>
    <dd>デフォルトでディレクトリの名前が入ってるので、そのままで</dd>
    <dt>version</dt>
    <dd>デフォルトで1.0.0が入ってるので、特段こだわりなければそのままで</dd>
    <dt>description</dt>
    <dd>このモジュールの説明を書いておきましょう</dd>
    <dt>entry point</dt>
    <dd>このモジュールがrequireされたときのエントリーポイントです、npmモジュールとして切り出したりするなら別ですがウェブサイトをそのままnpmパッケージにすることはないと思うので、デフォルトのままでOKです</dd>
    <dt>test command</dt>
    <dd>ここではテスト実行時に走らせるコマンドを登録するのですが、テストについては別記事で解説する予定なので、一旦空のままにしておきます</dd>
    <dt>git repository</dt>
    <dd>デフォルトで正しいアドレスが入ってれば、そのままでOK</dd>
    <dt>keywords</dt>
    <dd>キーワードなるものの設定、スペース区切りで複数設定可能</dd>
    <dt>author</dt>
    <dd>作者情報、これもgitのユーザー名がデフォルトで入ると思うので、こだわりなければそのままで</dd>
    <dt>license</dt>
    <dd>ライセンス情報、利用可能な文字列は[SPDX](https://spdx.org/licenses/)を参照</dd>
</dl>

### これらを指定するのが面倒なら
package.jsonで`private`というパラメータを使用することができます。これを`true`にすることで、上記の情報を設定しなくてもエラーが出なくなります。その場合は、以下のような`package.json`を自分で配置すればOKです。

```json:package.json
{
  "private": true,
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  }
}
```

## 必要なモジュールをインストールする
それでは必要なモジュールをインストールします。

### React
↓のコマンドでインストールできます。

```sh
npm install --save react react-dom
npm install --save-dev @types/react @types/react-dom
```

@types/xxxというやつは型定義ファイルというもので、JavaScriptで書かれたモジュールのTypeScriptの型を定義したものです。基本的にはTypeScriptでnpmライブラリを使うならば、@types/xxxを一緒にインストールしておけばよろしいのですが、パッケージそのものに型定義がついているものもあるので注意が必要です。(大抵は公式にインストールの仕方書いてあるし、間違えて@types/xxxインストールしたらnpmがwarnしてくれます。)
詳しくは[こちら](https://github.com/DefinitelyTyped/DefinitelyTyped)を参照。

### React以外
こっちが鬼門で、筆者は結局

1. TypeScriptで書く(じぶん)
2. TSLint通す(tslint)
2. JavaScriptに変換する(typescript)
3. いっこのjsファイルにする(webpack)

という流れに落ち着いてます。create-react-appとかparcelとかgulpとか色々ありまくりなのですが、これが、それなりの自由度で、それなりに息が長そうな組み合わせだと勝手に思ってます。
それぞれについてちょっとした説明と、インストールの仕方を以下に書いておきます。

#### webpackのインストールの仕方
[参考](https://webpack.js.org/guides/installation/#local-installation)

webpackはたくさんのjsファイルを1個にしてくれるすごいやつです。あと、いろんなloaderを注入できるのでこいつでビルドチェインを作ります。
webpackのインストールは↓

```sh
npm install --save-dev webpack webpack-cli
```

#### TypeScriptのインストールの仕方
[参考](https://webpack.js.org/guides/typescript/)

こいつのおかげで、型が付きます！ありがたいです！ts-loaderをwebpackに注入して使うとビルドの中で変換をやってくれます。
インストールは↓

```sh
npm install --save-dev typescript ts-loader
```

#### Tslintのインストールの仕方
[参考](https://github.com/wbuchwalter/tslint-loader)

TypescriptのLinterとwebpackにいれるtslint-loaderをインストールしておきます。
今回は設定はairbnbのものを使うので、それもまとめてインストールします。
インストールは↓

```sh
npm install --save-dev tslint tslint-loader tslint-config-airbnb
```

#### html-webpack-pluginをインストールしておく
[参考](https://webpack.js.org/plugins/html-webpack-plugin/)

index.htmlをビルドの時にターゲットディレクトリに配置してくれるすごいやつです。これがあれば細かいことは気にせず、ターゲットディレクトリをまるっとあげるだけでOKになります。
インストールは↓

```sh
npm install --save-dev html-webpack-plugin
```

#### webpack-serveもインストールしておく
[参考](https://github.com/webpack-contrib/webpack-serve#getting-started)

開発用の簡易Webサーバー。これがあるとブラウザで見れるので、いい感じ。
インストールは↓

```sh
npm install --save-dev webpack-serve
```

#### Babelとかいうやついらんの？
当初これがよくわかりませんでした。babel-preset-envがどうのこうのとか色々書いてあって、何がどうなっとるんだと。
結論から言うと**いりません**。BabelはナウでヤングなJavaScriptをどこでも動くようにするものです。TypeScriptがもし仮にナウでヤングなJavaScriptしか吐けないのであれば、組み合わせて使う必要があると思いますが、TypeScriptはビルドターゲットをES2015にできますので、これだけで充分です。

## webpackの設定
webpackは設定そのものがJavaScriptになっていて自由度が非常に高いですが、最低限のことを書くだけならとても簡単だし、モジュールが注入しやすいです。
webpackは実行時に設定ファイルを指定できますので、いくつか置いておいて叩きワケできるようにしておくとよいです。

### 本番用
本番用のビルド設定は、デバッグのための設定なしでみたいな感じで設定します。
ソースとコードコメントというかたちで何をやっているか書いておきます。

```webpack.config.prod.js
const path = require('path');                             // 絶対パスに変換するために
const htmlWebpackPlugin = require('html-webpack-plugin'); // index.htmlをビルドチェインの中で作っちゃう

module.exports = {
    mode: 'production',        // 使える文字列が決まってる、本番用なのでproduction。
    entry: './src/index.tsx',  // エントリポイントの指定、src下に書いていくので　src/index.tsxにしとく
    module: {
        rules: [
            {                             // Linterを走らせる
                enforce: 'pre',           // ビルド前処理だよってこと
                loader: 'tslint-loader',  // tslint-loaderを使う
                test: /\.tsx?$/,          // tslint-loaderに渡すファイルの正規表現。xxx.tsとxxx.tsxの正規表現。
                exclude: [                // 渡さないファイル
                    /node_modules/
                ],
                options: {
                    emitErrors: true      // これ設定しとくとTSLintが出してくれたwarningをエラーとして扱ってくれる、要するに-Wall
                }
            },
            {
                loader: 'ts-loader',      // ts-loaderを使う、こいつがトランスパイルしてくれる
                test: /\.tsx?$/,
                exclude: [
                    /node_modules/
                ],
                options: {
                    configFile: 'tsconfig.prod.json' // TypeScriptのコンパイル設定ファイル
                }
            }
        ]
    },
    resolve: {
        extensions: [ '.tsx', '.ts', '.js' ]    // importの時に、これらの拡張子は解決してもらえる、要するにHoge.tsxをimport Hoge from './Hoge'みたいに書ける
    },
    output: {
        filename: 'static/js/bundle.js',        // 仕上がりファイルの置き場
        path: path.resolve(__dirname, 'dist')   // 出力ディレクトリの指定の絶対パス
    },
    plugins: [
        new htmlWebpackPlugin({
            template: "index.html"    // 同じ階層にあるindex.htmlを元に、デプロイ用のindex.htmlを作って出力ディレクトリに配置してくれる
        })
    ]
};
```

### 開発用
開発用のビルド設定は、デバッグのための設定をいれてみたいな感じで設定します。
本番用とのdiffというかたちで乗っけておきます。

```diff:webpack.config.dev.js
const path = require('path');
const htmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
-    mode: 'production',
+    mode: 'development',
    entry: './src/index.tsx',
+    devtool: 'inline-source-map',    // デバッグできるように
    module: {
        rules: [
            {
                enforce: 'pre',
                loader: 'tslint-loader',
                test: /\.tsx?$/,
                exclude: [
                    /node_modules/
                ],
                options: {
                    emitErrors: true
                }
            },
            {
                loader: 'ts-loader',
                test: /\.tsx?$/,
                exclude: [
                    /node_modules/
                ],
                options: {
-                    configFile: 'tsconfig.prod.json'
+                    configFile: 'tsconfig.dev.json'
                }
            }
        ]
    },
    resolve: {
        extensions: [ '.tsx', '.ts', '.js' ]
    },
    output: {
        filename: 'static/js/bundle.js',
        path: path.resolve(__dirname, 'dist')
    },
    plugins: [
        new htmlWebpackPlugin({
            template: "index.html"
        })
    ]
};
```

### ブラウザ確認用
ブラウザ確認用のビルド設定は、開発用のビルド設定にwebpack-dev-serverの設定を加えます。
こちらは開発用とのdiffというかたちで乗っけておきます。

```diff:webpack.config.preview.js
const path = require('path');
const htmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    mode: 'development',
    entry: './src/index.tsx',
    devtool: 'inline-source-map',
    module: {
        rules: [
            {
                enforce: 'pre',
                loader: 'tslint-loader',
                test: /\.tsx?$/,
                exclude: [
                    /node_modules/
                ],
                options: {
                    emitErrors: true
                }
            },
            {
                loader: 'ts-loader',
                test: /\.tsx?$/,
                exclude: [
                    /node_modules/
                ],
                options: {
                    configFile: 'tsconfig.dev.json'
                }
            }
        ]
    },
    resolve: {
        extensions: [ '.tsx', '.ts', '.js' ]
    },
    output: {
        filename: 'static/js/bundle.js',
        path: path.resolve(__dirname, 'dist')
    },
    plugins: [
        new htmlWebpackPlugin({
            template: "index.html"
        })
    ],
+    serve: {
+        content: path.resolve(__dirname, 'dist'),
+        port: 3000,
+    }
};
```

## TypeScriptとTSLintの設定
webpack内で設定した、tsconfigとLinterの設定をここでつくります。

### tsconfig.xxx.json
[参考](https://www.typescriptlang.org/docs/handbook/compiler-options.html)

デバッグ用の設定を入れる入れないを切り分けるので、2つ作ってます。
違いは`"sourceMap": true`の部分だけですので、開発用は本番用を拡張しています。

```json:tsconfig.prod.json
{
    "compilerOptions": {
        "module": "es6",
        "target": "es5",
        "jsx": "react",
        "lib": ["es2018", "dom"],
        "moduleResolution": "node",
        "removeComments": true,
        "strict": true,
        "noUnusedLocals": true,
        "noUnusedParameters": true,
        "noImplicitReturns": true,
        "noFallthroughCasesInSwitch": true
    }
}
```

```json:tsconfig.dev.json
{
    "extends": "./tsconfig.prod.json",
    "compilerOptions": {
      "sourceMap": true
    }
}
```

<dl>
    <dt>sourceMap</dt>
    <dd>デバック用の設定</dd>
    <dt>module</dt>
    <dd>変換のためのモジュール指定、変換先がES2015のときはES6かES2015を指定しとく</dd>
    <dt>target</dt>
    <dd>できあがりはだいたいの環境で動くようにES2015にしとく</dd>
    <dt>jsx</dt>
    <dd>React用にreactを指定</dd>
    <dt>lib</dt>
    <dd>標準ライブラリの読み込み、es201xとdomを指定しておく</dd>
    <dt>moduleResolution</dt>
    <dd>モジュールの解決方式を指定、Node.js方式にするためnodeを指定</dd>
    <dt>removeComments</dt>
    <dd>ビルド成果物からコメントを取り除く</dd>
    <dt>strict</dt>
    <dd>怪しげな書き方をできないように</dd>
    <dt>noUnusedLocals</dt>
    <dd>未使用のローカル変数禁止</dd>
    <dt>noUnusedParameters</dt>
    <dd>未使用のパラメータ禁止</dd>
    <dt>noImplicitReturns</dt>
    <dd>怪しげなReturn禁止</dd>
    <dt>noFallthroughCasesInSwitch</dt>
    <dd>switchのときのdefaultし忘れ防止</dd>
</dl>

### tslint.json
[参考](https://palantir.github.io/tslint/rules/)

ベースはairbnbのものを使ってインデントサイズだけ4にしてます。
tslint.jsonというファイル名で他と同様ルートにおいておけばいい。

```tslint.json
{
    "extends": "tslint-config-airbnb",
    "rules": {
        "ter-indent": [true, 4]
    }
}
```

## package.jsonに設定を追加
最後に、`npm run xxx`でビルドとかできるように設定を追加しておきます。
package.jsonのscriptsというエレメント内にすでにtestがあるはずなので、その下に追加しましょう。
testの行は、今回使いませんので削っておきましょう。

```diff
  "scripts": {
-    "test": "echo \"Error: no test specified\" && exit 1"
+    "start": "webpack-serve --config webpack.config.preview.js",
+    "build": "webpack --config webpack.config.dev.js",
+    "build:prod": "webpack --config webpack.config.prod.js"
  },
```

これで、`npm run start`でブラウザ確認用、`npm run build`で開発用、`npm run build:prod`で本番用ビルドが走ります。

## 最後に
これで環境は完成です。こんなディレクトリ構成になっているはずです。

```
./
 ├ node_modules        // 外部パッケージのインストール先、バージョン管理対象外
 ├ .gitignore
 ├ package.json
 ├ package-lock.json   // npmが勝手に作る管理用ファイル、バージョン管理対象
 ├ README.md
 ├ tsconfig.dev.json
 ├ tsconfig.prod.json
 ├ tslint.json
 ├ webpack.config.dev.json
 └ webpack.config.prod.json
```

ここに実際にindex.htmlとTypeScriptソースを追加してみます。

```HTML:index.html
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>react sample</title>
    </head>
    <body>
        <div id="root"></div>
    </body>
</html>
```

```tsx:src/index.tsx
import * as React from 'react';
import * as ReactDOM from 'react-dom';

ReactDOM.render(
    <div>Hello world</div>,
    document.getElementById('root'));
```

`npm run build`もしくは、`npm run build:prod`するとdistディレクトリに全てが出力されます。

```
./
 ├ dist
 │  ├ index.html
 │  └ static
 │     └ js
 │        └ bundle.js
 │
 ├ node_modules
 ├ src
 │  └ index.tsx
 ├ .gitignore
 ├ index.html
 ├ package.json
 ├ package-lock.json
 ├ README.md
 ├ tsconfig.dev.json
 ├ tsconfig.prod.json
 ├ tslint.json
 ├ webpack.config.dev.json
 └ webpack.config.prod.json
```

また`npm run start`すれば、http://localhost:3000 でHello worldが表示されます。(事前ビルド不要)

できあがったソース群はこちら
https://github.com/IgnorantCoder/webpack-typescript-react-sample

## おまけ (Debug on WebStorm)
WebStormによるデバッグがやや癖があったのでおまけに記載。

[参考](https://www.jetbrains.com/help/webstorm/running-and-debugging-typescript.html)

- Run > Edit Configurations
- +ボタンからJavaScript Debugを追加、URLにhttp://localhost:3000 を追加
- npm run startしてから、この追加した構成をDebug buildするとアタッチされる