create-react-appでTypeScript用のReact開発環境を作る(入門者向け)

[React Advent Calendar 2016](http://qiita.com/advent-calendar/2016/react) に空きがあったのでねじ込みました。

仕事ではないけど普段はAngularでフロントエンドを書いています。ちきさんです。

[関数型ReactでビジュアルFizzBuzz](http://qiita.com/ababup1192/items/bf6d25845a4556a9e0ca) があまりにも読みやすいソースコードで、読んでるうちに僕もReactが書ける気がしてきたので挑戦してみました。

ところでAngularにはangular-cliという環境構築ツールがあります。
オレオレ環境構築は過去に散々やってきたので、またReactでオレオレは嫌だなと思っていたらツイッターで [facebookincubator/create-react-app](https://github.com/facebookincubator/create-react-app) の存在を教えていただきまして、使ってみたらあっさり環境が出来上がったのでワオと思ったのですがコレ当然なんですけどJavaScript用の環境なんですね。

そのままではTypeScript脳の僕には使えないので仕方なくタイトルのようなことをした次第。そして備忘録も兼ねて記事に残しておこうというわけです。

ちなみに僕が初めて書いたReactアプリは [ovrmrw/my-first-react-typescript](https://github.com/ovrmrw/my-first-react-typescript) です。オンラインデモは [こちら](https://ovrmrw.github.io/my-first-react-typescript/) です。宜しければどうぞ。

ではここから本題です。

## create-react-appをインストール
`create-react-app`という長い名前のCLIをインストールします。もうちょっと名前なんとかならなかったんですかね。
プロジェクト作成時に`yarn`が使われるので一応`yarn`も加えておきます。
後々必要になるのでついでに`typescript`と`tslint`もインストールしましょう。

```
$ npm i -g create-react-app yarn typescript tslint
```

## Reactプロジェクトを作る
適当なフォルダに移動して下記のコマンドを実行します。

```
$ create-react-app my-app
```

`my-app`というフォルダが作成されて、そこに必要なものがダウンロードされるでしょう。
結構時間かかります。

## npm run ejectで環境を一旦壊す
デフォルトではJavaScript + Babelで開発するようになっていますし、Webpackの設定も含めて諸々の設定ファイルが`node_modules`の中に隠蔽されてしまっています。
TypeScriptで書きたいのでこれでは困ってしまいますね。実はそういう人のためのコマンドが用意されています。

```
$ npm run eject
```

これは不可逆な操作なので取扱注意です。ドキュメントにも以下のように書いてあります。
> **Note: this is a one-way operation. Once you `eject`, you can’t go back!**

しかし我々はこれをやらなければ先には進めません。I'll never go back. 遠慮せずにやっちまいましょう。
すると`Are you sure you want to eject? This action is permanent. [y/N]`と聞かれるのでもちろん `y` と入力してエンターを叩きます。

僕のWindows環境では最後に`バッチ ファイルが見つかりません。`と表示されてエラーになりますが気にしなくてOKです。

## TypeScriptをインストール
TypeScriptとか必要そうなものをインストールします。とりあえず最低限これぐらいは入れておいた方がいいでしょう。

```
$ yarn add --dev typescript tslint awesome-typescript-loader @types/react @types/react-dom @types/node
```

## (VSCodeのみ)
僕はVSCodeしか使わないので他のエディタの場合はどうするのか知りませんが、VSCodeの場合はプロジェクトの**ワークスペース設定**に下記のように書く必要があります。

```json:.vscode/settings.json
{
  "typescript.tsdk": "./node_modules/typescript/lib"
}
```

これによりTypeScriptのエンジンとしてプロジェクトの`node_modules`フォルダにインストールされたものが使用されるようになります。

## tsconfig.jsonを作成する
プロジェクトのルートフォルダで下記のコマンドを実行します。

```
$ tsc --init
```

するとこういうファイルが作成されますね。

```json:tsconfig.json
{
    "compilerOptions": {
        "module": "commonjs",
        "target": "es5",
        "noImplicitAny": false,
        "sourceMap": false
    }
}
```

それを僕ならこんな感じに書き直します。

```json:tsconfig.json
{
  "compilerOptions": {
    "outDir": ".dest-tsc",
    "module": "commonjs",
    "moduleResolution": "node",
    "target": "es5",
    "jsx": "react",
    "lib": [
      "es2017",
      "dom"
    ],
    "typeRoots": [
      "node_modules/@types"
    ]
  },
  "include": [
    "src/**/*"
  ]
}
```

ついでに`src`フォルダにも`tsconfig.json`を作成します。
TypeScript 2.1からは`extends`を使って別のconfigを継承できるので試してみましょう。

```json:src/tsconfig.json
{
  "extends": "../tsconfig.json",
  "compilerOptions": {
    "sourceMap": true,
    "strictNullChecks": true,
    "skipLibCheck": true
  }
}
```

これでルートの`tsconfig.json`を継承して新しいconfigが作れました。実はこの2つのファイルは目的が違います。

- `/tsconfig.json` ... Webpackでバンドルするときに読み込まれるバンドル用の設定。というのもあるけどルートにtsconfig.jsonがないと何かと躓くことが多い。
- `/src/tsconfig.json` ... エディタやtslintが読み込んでリアルタイムに型チェックするエディタ用の設定。

僕は主に前者はnullチェックを入れずに、後者にはnullチェックを入れます。

## tslint.jsonを作成する
プロジェクトのルートフォルダで下記のコマンドを実行します。

```
$ tslint --init
```

すると`tslint.json`が作成されます。

そのままだと一部使い勝手が良くないので少々変更しましょう。
下記の部分を...

```json:tslint.json
    "quotemark": [
      true,
      "double"
    ],
    "semicolon": [
      true,
      "always"
    ],
```

下記のように書き換えます。

```json:tsling.json
    "quotemark": [
      true,
      "single",
      "jsx-double"
    ],
    "semicolon": [
      false,
      "always"
    ],
```

`jsRules`と`rules`の2セクションに分かれているので両方とも変更します。

その結果こうなります。

- 文字列はシングルクォーテーションで囲み、JSXの中だけはダブルクォーテーションで囲む。
- セミコロンは省略できる。

このへんは個人の好みがあるので各自好きなように設定しましょう。

## 動作確認してみる
TypeScript環境化する前に一度アプリを動かしてみましょう。

```
$ npm start
```

これでブラウザが起動して「Welcome to React」が表示されればOKです。

さて今はJSで動いていますが、これからTS化の作業をしていきます。

## Webpackの設定を書き換える
まず`config/paths.js`の下記の行を

```js:config/paths.js
  appIndexJs: resolveApp('src/index.js'),
```

下記のように変更します。

```js:config/paths.js
  appIndexJs: resolveApp('src/index.tsx'),
```

---

次に`config/webpack.config.dev.js`です。変更箇所が多いのでテンポ良くいきましょう。

Before

```js:config/webpack.config.dev.js
    extensions: ['.js', '.json', '.jsx', ''],
```

After

```js:config/webpack.config.dev.js
    extensions: ['.ts', '.tsx', '.js', '.json', '.jsx', ''],
```

---

Before

```js:config/webpack.config.dev.js
      {
        exclude: [
          /\.html$/,
          /\.(js|jsx)$/,
          /\.css$/,
          /\.json$/,
          /\.svg$/
        ],
        loader: 'url',
        query: {
          limit: 10000,
          name: 'static/media/[name].[hash:8].[ext]'
        }
      },
```

After

```js:config/webpack.config.dev.js
      {
        exclude: [
          /\.html$/,
          /\.(js|jsx)$/,
          /\.css$/,
          /\.json$/,
          /\.svg$/,
          /\.tsx?$/,
        ],
        loader: 'url',
        query: {
          limit: 10000,
          name: 'static/media/[name].[hash:8].[ext]'
        }
      },
```

---

Before

```js:config/webpack.config.dev.js
      {
        test: /\.svg$/,
        loader: 'file',
        query: {
          name: 'static/media/[name].[hash:8].[ext]'
        }
      }
```

After

```js:config/webpack.config.dev.js
      {
        test: /\.svg$/,
        loader: 'file',
        query: {
          name: 'static/media/[name].[hash:8].[ext]'
        }
      },
      {
        test: /\.tsx?$/,
        loader: 'awesome-typescript-loader',
      }
```

ここまでです。`config/webpack.config.prod.js`も同じように変更しておくと良いですね。ここでは省略します。

## index.js --> index.tsx
いよいよ大詰めです。

srcフォルダの`index.js`を`index.tsx`に変えましょう。拡張子を変えるだけです。
少しソースコードも書き換えましょう。

Before

```js:src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import './index.css';

ReactDOM.render(
  <App />,
  document.getElementById('root')
);
```

After

```ts:src/index.tsx
import * as React from 'react'; // 変更
import * as ReactDOM from 'react-dom'; // 変更
import App from './App';
import './index.css';

ReactDOM.render(
  <App />,
  document.getElementById('root')
);
```

## App.js --> App.tsx

`App.js`も`App.tsx`のように拡張子を変更します。
ソースコードも一部変更します。

Before

```js:src/App.js
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';

class App extends Component {
  render() {
    return (
      <div className="App">
        <div className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h2>Welcome to React</h2>
        </div>
        <p className="App-intro">
          To get started, edit <code>src/App.js</code> and save to reload.
        </p>
      </div>
    );
  }
}

export default App;
```

After

```ts:src/App.tsx
import * as React from 'react'; // 変更
const logo = require('./logo.svg'); // 変更
import './App.css';

class App extends React.Component<{}, {}> { // 変更
  render() {
    return (
      <div className="App">
        <div className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h2>Welcome to React</h2>
        </div>
        <p className="App-intro">
          To get started, edit <code>src/App.js</code> and save to reload.
        </p>
      </div>
    );
  }
}

export default App;
```

## 動かしてみよう
さあ、これで`create-react-app`で作った環境のTypeScript化が完了しました。
動かしてみましょう。

```
$ npm start
```

「Welcome to React」が表示されましたね。You did it!

## GitHub Pagesにデプロイする
ここからはGitHub Pagesにデプロイする方法を書きます。デプロイしたい方だけ読んでください。

まず`gh-pages`というライブラリをインストールします。

```
$ yarn add --dev gh-pages
```

---

次に`package.json`を変更します。

Before

```json:package.json
  "scripts": {
    "start": "node scripts/start.js",
    "build": "node scripts/build.js",
    "test": "node scripts/test.js --env=jsdom"
  },
```

After

```json:package.json
  "scripts": {
    "start": "node scripts/start.js",
    "build": "node scripts/build.js",
    "test": "node scripts/test.js --env=jsdom",
    "tsc": "tsc",
    "lint": "tslint --type-check \"src/**/*.ts\" \"src/**/*.tsx\" ",
    "deploy": "npm run build && gh-pages -d build"
  },
  "homepage": "https://<your-account-name>.github.io/<your-project-name>",
```

`"tsc"`と`"lint"`の行は無くても構いません。
`"homepage"`の行は重要です。account-nameとproject-nameのところをご自身の環境に合わせて変更してください。

---

これでデプロイできるようになりました。下記のコマンドを実行しましょう。

```
$ npm run deploy
```

デプロイが完了したら先程の`"homepage": "https://<your-account-name>.github.io/<your-project-name>"`で指定したURLをブラウザで開きます。

ちなみに僕がデプロイしたものは [こちら](https://ovrmrw.github.io/my-first-react-typescript/) です。

お疲れさまでした。