TypeScript
React
VisualStudioCode
HMR
create-react-app

なるべく何も考えないでそこそこ快適に TypeScript + React で開発を始める (create-react-app)

動機

どんなものでも、オレオレな作り込みをすればするほどプロジェクトの寿命が短くなるという呪いを受けます。また、引き継ぎが大変になります。

その当然の帰結として「なるべく作り込みたくない」「標準に従いたい」という欲求が出てくると思います。

そういった欲求を考えてSPAプロジェクトを設定していきます。単純なSPAを作る想定です。

開発環境を用意する

VS Code こと Visual Studio Code をエディタとして使います。

今回の開発のための拡張として下記を入れます。 VS Code を立ち上げてもいいですが、コマンドで入れることも可能です。

# Prettier (コードフォーマッタ)
$ code --install-extension esbenp.prettier-vscode

# TSLint (lintツール)
$ code --install-extension eg2.tslint

新しめの Node.js や git も入れておきましょう。

開発プロジェクトを作成する

何も考えたくないので、 webpack でゴリゴリ設定ファイルを書きたくありません。定番のプロジェクト構成も自分で書きたくありません。

そういう人のために create-react-app という便利なものがあるので、それの TypeScript版 を利用しましょう。

$ npx create-react-app my-app --scripts-version=react-scripts-ts
$ cd my-app
$ git init
$ npm install
$ code .

VS Code で create-react-app によって作成された my-app プロジェクトが開くはずです。

今日時点(2018/05/16)では、下記のようなものができます。

  • そこそこ一般的なReact付きのSPA (TypeScript)
  • PWA対応
  • tslint 付き

作成されたプロジェクトのファイルを確認する

もうちょっと詳しくファイルを見ていきましょう。

.
├── README.md         # 好きなこと書こう
├── images.d.ts       # TypeScriptで利用するグローバルなアドオン型定義。CSSファイルからモジュール的なクラス名を持ってくる場合など。
├── package-lock.json # 依存性ロックファイル (yarn のみを使う場合は不要)
├── package.json      # 依存性定義
├── public
│   ├── favicon.ico
│   ├── index.html    # SPAの index.html となるテンプレートファイル。消すな。
│   └── manifest.json # PWA のマニフェストとなるファイル。
├── src # アプリの実コード
│   ├── App.css
│   ├── App.test.tsx
│   ├── App.tsx
│   ├── index.css
│   ├── index.tsx     # SPAのエントリポイント。消すな。
│   ├── logo.svg
│   └── registerServiceWorker.ts # PWAのServiceWorker登録用JS
├── tsconfig.json      # 開発ビルド or ベースとなる tsconfig.json
├── tsconfig.prod.json # 本番ビルド用。 tsconfig.json から派生
├── tsconfig.test.json # ユニットテスト用。
├── tslint.json        # TSLint。要らないルールはここでオフにしましょう。
└── yarn.lock          # npm だけ使うなら不要。

(node_modules は省略しています)

開発用のローカルサーバ(dev server)を立ち上げる

SPAの開発において最低限下記が出来なければ死ぬと思っています。

  • Dev server を立ち上げられる。つまり、開発用の小さなWebサーバを立ち上げられる。
  • HMR (Hot Module Replacement) できる。つまり React のコンポーネントを書き換えたら画面がリロードレスで書き換わる。
  • history-api-fallback できる。つまりファイルが見つからなかったら /index.html の内容を返す。

Reactのコンポーネント以外もHMRできると便利ですが、面倒なので諦めます。 (ClojureScriptは楽にできてよかった。)

まあ、まずは Dev Server 立ち上げてみましょう。

$ npm start

...

You can now view my-app in the browser.

  Local:            http://localhost:3000/
  On Your Network:  http://192.168.1.32:3000/

http://localhost:3000/hogehogefoobar を開いてみましょう。 index.html の内容が返されますね。やったね。 history-api-fallbackできてるね。

じゃあ、今度は HMR の確認のために、 App.tsx あたりを書き換えてみましょう。

すると、どうでしょう!! HMR さ れ て い ま せ ん !!

画面がリロードされてるよ。このままだと死ぬよ。ウギャー!!!

HMR したい

create-react-app, どうやら自動でHMRしてくれないようです。

嫌で嫌で仕方ありませんが、 下記のコードを src/index.tsx に足しましょう。

src/index.tsx
/// <reference types="webpack-env" />
if (module.hot) {
  module.hot.accept("./App", () => {
    const NewApp = require("./App").default;
    ReactDOM.render(<NewApp />, document.getElementById("root") as HTMLElement);
  });
}

module.hot というのは標準のTypeScript定義で生えていないものなので、外部の型定義も追加しましょう。

$ npm i --save -D @types/webpack-env

これでReactコンポーネントについてはHMRされるはず。

ただし、下記点に留意しておきましょう。

  1. 一見コードの状態がシームレスに反映されているように見えるが、結局はHMR時にReactコンポーネントをルートから描画しなおしていることに違いはないので、コンポーネント内部の状態は揮発する。要するに仮想DOM全書き直しや。
    • 状態管理ライブラリを使ってReactコンポーネントに状態を持たさなければ、そんなに問題にならない。
    • 逆に言えば Reactコンポーネントに状態をもたせたい場合は全く使えない 手法です。
  2. ./App.tsx から辿れない、あるいは ./App.tsx 以外からも辿れてしまうモジュールを更新すると画面がリロードされる
    • 他のモジュールについてもHMR対応させることは論理的には可能だけど、コードを何度評価しても平気なように作っておく必要がある。(ClojureScriptは楽にできてよかった。)
    • たぶん Redux はHMR対応はそこまで大変じゃないはず。非同期イベントで終わりきっていないイベントハンドラとか何かのはずみで出来ているレキシカルクロージャとか考えると頭痛してくるけどね。完璧なものはない。

ユニットテストする

$ npm test

これだけです。 jest および ts-jest が使われます。

個人的には、高速に動くし、カバレッジも楽に取れるしで jest は好きです。

ビルドする

$ npm run build

これで ./build 以下に下記のようなファイルができます。

.
├── asset-manifest.json
├── favicon.ico
├── index.html
├── manifest.json
├── service-worker.js
└── static
    ├── css
    │   ├── main.29266132.css
    │   └── main.29266132.css.map
    ├── js
    │   ├── main.35955d27.js
    │   └── main.35955d27.js.map
    └── media
        └── logo.5d5d9eef.svg

4 directories, 10 files

404 時に index.html を返す設定をしたWebサーバに、アップロードするなりしましょう。

あと必要なもの

  • 細かいコード規約
  • 足りないSPAのパーツ、ライブラリ
    • 状態管理ライブラリ (e.g. mobx, redux)
    • React用のUIライブラリ
    • URLルーティングライブラリ
  • CI (Continuous Integration)
  • APIサーバの位置など環境依存をどう管理するか?
    • 今回の手法だと create-react-app 側にAPIサーバをプロキシさせられないので、APIサーバ側でCORSしたりする必要がある。
  • 他プロジェクトとコードシェアしたいなどの場合にどうするか?
  • 保守・リリース方法 などなど

最近の私ベースの話だと、イントラのシステムとしてSPAを作ることが多く、プロジェクトのパッケージ成果物としてはNginxでSPAをサーブするDockerイメージを作るように構成しています。

まとめ

これで最終的に楽になっているか? というと、やや楽になっているかな?、という触感です。

もちろん小回りが効かないので、create-react-app が受けいれられる方法で色々な拡張を検討することになるでしょう。

たとえば、 *.scss ファイルを読む方法なんかは公式のガイドだと、ファイル更新監視して webpack の外で *.scss*.css に変換するフローになっています。webpackをそのまま扱う場合は sass-loader を使うだけの話なんですが。

どうであれ、最終的には npm run eject して create-react-app をやめて何でもいじれるようにすればいいだけの話です。そこから要らない設定を削っていく。

実際に webpack.config.js やらを手で書いたとして、汎用性を異常に持たせない限り、そこまで黒魔術になることはありません。

「いやそもそも、こういう発想の人はAngularの方がいいんじゃね?」と思うかもしれませんが、Angular知らんからごめんな。

リンク