create-react-appとTypeScriptでサラッと作ったSPAをgh-pagesにスルッとデプロイすっぞ！

## SPAを手軽にリリースしたい！
昨今、ReactとかでサラッとSPA作ってる人多いじゃないですか！？
フロントエンドエンジニアでガンガンJS書いてて〜〜とかじゃない人でも日本語のドキュメントを少し漁ればなんとか作れちゃう時代になりつつありますよね。
だから私もReactで作ったものをリリースしたい！世に広めたい！そんなことを思うわけです。

## マークアップエンジニアに立ち塞がる「サーバサイド」の壁
デザインやマークアップをやっている人がFE領域に入り込もうとすると、どうしてもサーバのことがわかりません。

`create-react-app`で確かにReactを書く環境は作れる。
ローカルサーバも立ち上がって表示確認はできる。
データをセットすればDOMを勝手にレンダリングしてくれる感動を得られる！
`react-router-dom`を使ってルーティングまでも自分で実装できるようになってしまう！
テンションは上がる一方です！！

でも、、、

**これをどう世の中にリリースしたらいいか何もわからない！！！**

詰みまくりですね。

## 救世主「GitHub Pages」
そんな私みたいなエンジニアに朗報です。

GitHubにサイトのリポジトリを作成し
`gh-pages`というブランチを作ってそこにプッシュすると、なんとそれだけで静的なサイトが公開されてしまいます！

詳しくは以下を参考にしてみてください。
https://www.tam-tam.co.jp/tipsnote/html_css/post11245.html

また、`create-react-app`で作り出せるものは、結局`index.html`に`<script>`でjsを読み込んだだけのものなので、**静的なページ**というわけです。

なので、ちゃんとルーティングを実装したとしても、**GitHub Pageならサーバも借りずに自分のSPAをリリースすることができる**わけです！！
（さすがにサーバサイドでのroutingだったり、DBを使うものは難しいです＞＜）

というわけで、実際に作ってみましょう！

## 作る上で使うもの
以下のものを使って作っていきます。
- GitHub
- node（`v8.15.0`を使っています）
- npm（`6.8.0`を使っています）
- create-react-app
- TypeScript
- Sass
- react-router-dom
- gh-pages

私はTypeScriptとSassに慣れ親しんでいるので、これらが使える環境を用意したいと思います！笑

## リポジトリ作成と環境構築
まず、GitHubの`GitHub Pages`を使うからにはここにリポジトリがないと始まらないので、会員登録とリポジトリ作成を行ってください。
GitHubは[こちら](https://github.com/)。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/213205/c86a5101-12e6-1032-65aa-109fd03316d2.png)
会員登録をしたら、Repositories横の「New」というボタンから作成してください。
私は適当に`react-app-sandbox`というリポジトリ名にしました。

そうしたら、`https://github.com/ユーザ名/react-app-sandbox.git`というのが出てくるのでそれをコピーします。
（リポジトリ名は、私が勝手につけた名前にしています。みなさんが別の名前をつけていればその名前になります。）

次に、ターミナル等でローカルの好きなフォルダまで移動します。
私は`~/Documents/git`に移動しています。（私はMacユーザです。）

そこで以下のコマンドを叩きます。

```bash
git clone https://github.com/ユーザ名/リポジトリ名.git
```

GitHubのユーザ名とパスワードを聞かれた場合はそれを打ち込んでenterをターンッ！ください。
そうすると今いるフォルダの中に**リポジトリ名のフォルダ**が出来上がります。
そのフォルダ内では、GitHubのリモートリポジトリにpushしたりpullしたりできるようになっています。

ただ、まだそのフォルダには移動せずに、次に以下のコマンドを叩きます。

```bash

npx create-react-app react-app-sandbox --typescript
```

`npx`を使って、一時的に`create-react-app`をインストールして実行し、リポジトリ名と同じ名前でReact appを作成します。しかもTypeScript版で。
`npx`を使えば、常に使うことのない`create-react-app`を無駄にインストールしなくて良いので大変便利です。
（もしTypeScirptを使いたくない場合は、`--typescript`をつけないで実行してください。）

作り出されたリポジトリ名のフォルダに移動して、確認のために以下のコマンドを叩いてみましょう。

```bash
cd react-app-sandbox
npm start
```

そうすると自動でブラウザが立ち上がり、`http://localhost:3000`にアクセスされて以下のページが出ればひとまず完成です！
![image.png](https://qiita-image-store.s3.amazonaws.com/0/213205/3f78079c-9ea4-05dd-4aad-f2cc527d7c70.png)
Reactのロゴが回っていますね。

## Sassを書けるようにする
まだまだ私はSassから抜け出せないので、Sassでかけるように設定します。
`src`フォルダの中は以下のようになっています。

```
src
├── App.css
├── App.test.tsx
├── App.tsx
├── index.css
├── index.tsx
├── logo.svg
├── react-app-env.d.ts
└── serviceWorker.ts
```

とりあえずSassにしたいので、`index.css`と`App.css`の拡張子を無邪気に`scss`に変えてみちゃいましょう！＾＾

```
src
├── App.scss
├── App.test.tsx
├── App.tsx
├── index.scss
├── index.tsx
├── logo.svg
├── react-app-env.d.ts
└── serviceWorker.ts
```

このように変えられたら、一度ターミナル上で`control + c`を押して`npm start`のプロセスを終了し、もう一度`npm start`を実行してみましょう。

```bash
npm start

Failed to compile.

./src/App.tsx
Module not found: Can't resolve './App.css' in '/Users/senshu/Documents/git/react-app-sandbox/src'
```

エラーです＾＾世の中そううまくはいきません＾＾

`App.tsx`は`App.css`をimportするように書かれているので、このファイル内の拡張子を変えてあげないといけません。
`index.tsx`の中身も同様なので、ついでに`index.css`を`index.scss`に変えましょう。

そしてもう一度`control + c`を押してから`npm start`を実行すると、、、

**なぜかうまくいく！！！世の中ーーーー！！！**

これ`node-sass`を入れていないのでうまくいかないと思ったのですが、なぜかうまくいっちゃいました。。。
`package-lock.json`にも`node-sass`は入っていないので、なんでなんだろうーって思っていまして、誰か詳しい方いたら教えてください。。

とりあえずうまくいくので次にいきましょう！w

## Routerを入れる
今回のテーマは「SPA」なので、ブラウザ側でルーティングできるようにしたいです。
URLを直で叩かれたら、そのURLのページがちゃんと表示されるようにしたいですよね。

ただ、今回注意したいのは、**サーバ側でルーティングさせられない**という点です。
サーバ側のルーティングはGitHub側でコントロールされてしまっているので、そのURLに相当するファイルを用意しないと`404`のページになってしまいます。
URLに相当するページを作るのももちろん良いですが、**それはもうSPAではなくなってしまいます…笑**
なので、今回は**ハッシュ**によるルーティングを実現させようと思います。
なのでURLが`https://~~~~/#/hoge`という形になりますが、そこはご容赦ください＞＜
（こうすればハッシュじゃなくてもできるよというやり方があれば、ぜひ教えてください。）

ルーティングを実現させるために、プロジェクトルート（リポジトリ名のフォルダ）で以下のコマンドを叩きます。

```bash
npm i -S react-route-dom @types/react-router-dom
```

`@types`はTypeScriptで書いているので入れています。

ここから、Reactをちょこちょこいじっていきます。
以下のサイトを参考に作っていきますので、ぜひみなさんも参考にしてみてください。
https://reacttraining.com/react-router/web/

ここで、もし`npm start`を終了している場合は、これから開発していくので`npm start`を叩いた状態にしておいてください。

あちこち行くと分かりづらいので、基本的に`App.tsx`をいじっていきます。

```tsx:App.tsx
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.scss';

class App extends Component {
  render() {
    return (
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <p>
            Edit <code>src/App.tsx</code> and save to reload.
          </p>
          <a
            className="App-link"
            href="https://reactjs.org"
            target="_blank"
            rel="noopener noreferrer"
          >
            Learn React
          </a>
        </header>
      </div>
    );
  }
}

export default App;
```

上記は、`App.css`を`App.scss`に変えたこと以外は何も変更していないソースコードです。
これを以下のように変えます。

```tsx:App.tsx
import React, { Component } from 'react';
// react-router-domから必要なものを追加
import { HashRouter, Switch, Route } from 'react-router-dom';
import logo from './logo.svg';
import './App.scss';

class App extends Component {
  render() {
    return (
      // 全体をHashRouterでWrap
      <HashRouter>
        <div className="App">
          <header className="App-header">
            <img src={logo} className="App-logo" alt="logo" />
            <p>
              Edit <code>src/App.tsx</code> and save to reload.
            </p>
            <a
              className="App-link"
              href="https://reactjs.org"
              target="_blank"
              rel="noopener noreferrer"
            >
              Learn React
            </a>
          </header>
          {/* ルーティングがわかりやすいようにdivを追加 */}
          <div className="_RoutingArea">
            {/* Switchで囲んで、Routeで設定したcomponentが呼ばれるように設定 */}
            <Switch>
              {/* exactにしてrootにアクセスされたときはだけ「Top!」と表示されるように設定 */}
              <Route exact path="/" component={() => <>Top!</>} />
              {/* exactではないので、「detail/hoge」でも以下のcomponentは呼ばれる */}
              <Route path="/detail/" component={() => <>Detail!</>} />
            </Switch>
          </div>
        </div>
      </HashRouter>
    );
  }
}

export default App;
```

ついでに、Sassは簡単に以下を追加しています。

```scss:App.scss
._RoutingArea {
  padding: 100px;
  background-color: #eaeaea;
}
```

上記で保存すると、以下のように表示されると思います。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/213205/e93c42ef-0d42-3aa5-19f1-8e995d113b84.png)

下側にグレー背景で「Top!」と表示されたかと思います。
また、`http://localhost:3000/#/`というURLで表示されるようにもなったはずです。

ここで、URLを以下に変えてみましょう。
`http://localhost:3000/#/detail/`

![image.png](https://qiita-image-store.s3.amazonaws.com/0/213205/7a6f63da-6f00-2e95-7630-0a9b3cae2426.png)

上記画像のとおりになっていれば成功です！

ちゃんと、`/`でアクセスすると`Top!`と表示され、`/detail/`でアクセスすると`Detail!`と表示されるようになりました。
今は簡単なFunctionComponentを差し込んで文字だけ表示させていますが、ここをちゃんとしたcomponentを差し込んであげれば、URLに応じてページごとのcomponentを切り替えるというようなことができるようになります。

## URLによって表示コンテンツを変える
ルーティングをするとなると、例えば、`/detail/hoge`と`detail/fuga`でアクセスされたときのテンプレートは一緒だけど、中身のコンテンツは変えたい！というようなことがありますよね。

ちゃんと、`react-router-dom`はそれができるようになっています。

まずは、`/detail/`というURLで待ち受けている設定を、以下の設定に変えます。

```tsx
<Route path="/detail/:id" component={() => <>Detail!</>} />
```

今回は`/detail/`の後ろに`:id`というのを追加しました。
**こうすることによって、`/detail/hoge`というURLでアクセスすると`id`という名前で`hoge`という文字列を受け取ることができるようになります！**便利すぎる。。

ただ、現在引き受ける先のcomponentが、ただただ`() => <>Detail!</>`と書かれていて何もできない状態なので、detail用のcomponentを作成していきます。

```tsx:App.tsx
import React, { Component, FunctionComponent } from 'react';
import { HashRouter, Switch, Route, match } from 'react-router-dom';
import logo from './logo.svg';
import './App.scss';

const Detail: FunctionComponent<{ match: match<{ id: string }> }> = ({ match }) => (
  <>id: {match.params.id}</>
);

class App extends Component {
  // 省略...
}
```

`App`というclassの上に、`Detail`というcomponentを作成しました。
TypeScriptで書いているので少し複雑になっていますね。少しずつ説明します。

まず、`Detail`はpropsとして`match`というものを受け取ります。
この`match`というものは`react-router-dom`が自動でpropsに流してくれるものらしいです。
ES6等で書いている場合はただ受け取ればよいのですが、TypeScriptの場合は`match`という受け取り物は何者なのかを知る必要があるので、それを宣言します。
`match`が何者かは、`react-router-dom`からimportできるため、今回新たにimportし、まず`Detail`の型が以下のようになります。

```tsx
const Detail: FunctionComponent<{ match: match }>
```

Reactが用意してくれている`FunctionComponent`という型自体を定義し、さらに**ジェネリック**を使って、**受け取るpropsの型**を定義しています。
`FunctionComponent`は、ちゃんとpropsの型を自由に定義できるように`FunctionComponent<T>`の`T`のところに型を入れられるようにしてくれています。
それを活用して、今回は`match`というkey名で、値の型が`match`のものをpropsで受け取るよーと宣言しています。（`match`がかぶっちゃっていて分かりづらくてすみません。。）

ただ、上記だけだとまだエラーを起こしていまいます。

今回、`Route`のpathのところで`/Detail/:id`という風に設定したので、`id`というkeyで値を取得したいです。
それが入っているのが`match.params`の中らしい（`react-router-dom`のリファレンスにありました）ので、取得の仕方は`match.params.id`ということになります。
ただ、`match.params.id`と書いてしまうと「`id`なんてkeyないんですけどー！」というTypeScriptのエラーを起こしてしまいます。
なので「`id`というkeyがあって、しかもその`id`の値の方は`string`なんだよ〜」というのを教えてあげなければいけません。

`react-dom-router`の`match`という型は、ちゃんと上記のような怒られ方をするのを予期してくれているので、ジェネリックでparamsの型を渡すことができます。
それをちゃんと実装すると、以下のような型になるわけです。

```tsx
const Detail: FunctionComponent<{ match: match<{ id: string }> }>
```

ちょっと長めの型になっちゃいましたね…笑
長くて嫌だなーという方は、以下のように`interface`を使ってまとめてあげても良いです。

```tsx
interface DetailMatchParams {
  id: string;
}

interface DetailProps {
  match: match<DetailMatchParams>
}

const Detail: FunctionComponent<DetailProps>
```

こっちのほうが見通しが良さそうですね。
最後に、作った`Detail`をRouteに登録してあげて、動作確認をしてみましょう。
全体のコードは以下になります。

```tsx:App.tsx
import React, { Component, FunctionComponent } from 'react';
import { HashRouter, Switch, Route, match } from 'react-router-dom';
import logo from './logo.svg';
import './App.scss';

interface DetailMatchParams {
  id: string;
}

interface DetailProps {
  match: match<DetailMatchParams>
}

const Detail: FunctionComponent<DetailProps> = ({ match }) => (
  <>id: {match.params.id}</>
);

class App extends Component {
  render() {
    return (
      <HashRouter>
        <div className="App">
          <header className="App-header">
            <img src={logo} className="App-logo" alt="logo" />
            <p>
              Edit <code>src/App.tsx</code> and save to reload.
            </p>
            <a
              className="App-link"
              href="https://reactjs.org"
              target="_blank"
              rel="noopener noreferrer"
            >
              Learn React
            </a>
          </header>
          <div className="_RoutingArea">
            <Switch>
              <Route exact path="/" component={() => <>Top!</>} />
              <Route path="/detail/:id" component={Detail} />
            </Switch>
          </div>
        </div>
      </HashRouter>
    );
  }
}

export default App;
```

そして`http://localhost:3000/detail/hoge`でアクセスした結果が以下です。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/213205/fd3a48b3-683a-3900-4951-6ec45ac2a858.png)

ページ上に`hoge`が表示されています！
別の文字列でも試してみます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/213205/cd50e25f-a78e-ce9c-4755-72b650ffc36f.png)

いい感じですね！！！
これで、URLに応じてコンテンツを変えることもできそうです！！めでたし！

## SPAをdeployする
ついにここまでやってきました！
まず、一旦作ったものが完成したのでコミットしましょう。

```bash
git add .
git commit
git push
```

簡単ですね。
そして、いよいよ、**GitHub Pagesに公開するときがやってまいりました！**

まず、以下のコマンドを叩いてパッケージをインストールします。

```bash
npm i -D gh-pages
```

こちらは、**`gh-pages`というブランチを勝手に生成して、勝手にプッシュまでしてくれる優れものです！**
前述したとおり、`gh-pages`というブランチに特定のファイルをプッシュすれば公開されるので、その作業を**完全に自動化できちゃう**わけです！

gh-pagesというパッケージは、普通に使うのであれば以下のように使えばOKです。

```bash
gh-pages -d プッシュするディレクトリ名
```

ただ、今回は**buildして、そのbuildしたファイルだけを公開したい**です。
なので、その一連を`npm run deploy`でできるようにしたいと思います。

やることは簡単です。一行追加するだけです。
`package.json`を開くと、`scripts`という場所があるはずです。（）

```json:package.json（scriptsのみ抜粋）
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
```

ここに対して、以下のように編集を加えます。

```json:package.json（scriptsのみ抜粋）
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject",
    "deploy": "npm run build && gh-pages -d build"
  },
```

`eject`の下に`deploy`というのを加えました。
**これで、`npm run deploy`を叩けば、必ず最新版がdeployできるようになりました！！**

通常だと`npm run build`を叩いて、`build`フォルダにリリース用のファイルを生成させて、その中身を他の手段でdeployしないといけないのですが、`gh-pages`パッケージを使うことで、**1回コマンド叩くだけでリリースができます！超便利！！**

というわけで、**以下のURLにアクセスしてみましょう！！**
`https://ユーザID.github.io/リポジトリ名/`
（私の場合は、https://hiraryo0213.github.io/react-app-sandbox/ です。）

さぁ、ページは表示されたでしょうか…！！

　

**されていないはずです！！**

がーーーん。


chromeのデベロッパーツールとかで見てみると、もろもろファイルが`404`になってしまっています。
これは、**読み込むべきJSやCSSのファイルパスがうまく設定できていなくて起きてしまっています。。。**

それを解消するために、`package.json`を最後にいじる必要があります！
これも一行入れればいいだけです。

```json:package.json（一部抜粋）
{
  "name": "react-app-sandbox",
  "version": "0.1.0",
  "private": true,
  "homepage": "https://hiraryo0213.github.io/react-app-sandbox/",
```

場所はどこでもいいのですが、`homepage`というkeyに、自分のページのURLを記載してあげます。
アクセスして真っ白になってしまったページのURLを入れてあげればOKです。

上記を対応したら、もう一度`npm run deploy`をしてみましょう！！

アクセスすると、以下のようになっていると思います。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/213205/8e6c5301-6da8-67bf-b1f5-832e5e6fff0a.png)

ちゃんと`http://localhost:3000`で見ていたまんまのものが表示されているはずです！
うれしーーーーー！

さらに、、、
![image.png](https://qiita-image-store.s3.amazonaws.com/0/213205/920a670f-cc20-504e-6301-db3820165889.png)
ちゃんとルーティングもできてる！！！

**言うことなしですね！！**

## さいごに
という感じで、TypeScriptとSassを使った環境で作ったReact Appを、静的なページを公開できるGitHub Pagesに無事公開するところまでできました！

これで好きなページを作ることができるようになりました！

あとは、おしゃなデザイン作ってSassできれいにして、redux-sagaとか使って外部APIからデータ取ってきて、Reduxとかでデータの管理をちゃんとして、、、とやっていけば、結構やりたいことはできるのではないかと思います！！

道のりは長い。。。笑
ただここまでできるなんて、ほんといい時代になりましたね！

今回試しに作ったもののリポジトリはこちらですので、参考までにご覧になってください〜
https://github.com/hiraryo0213/react-app-sandbox

また、なにか不明点、疑問点、指摘点あれば、コメント欄によろしくお願いいたします〜