Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
16
Help us understand the problem. What is going on with this article?

More than 1 year has passed since last update.

Organization

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はこちら
image.png
会員登録をしたら、Repositories横の「New」というボタンから作成してください。
私は適当にreact-app-sandboxというリポジトリ名にしました。

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

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

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

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

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

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


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

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

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

cd react-app-sandbox
npm start

そうすると自動でブラウザが立ち上がり、http://localhost:3000にアクセスされて以下のページが出ればひとまず完成です!
image.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.cssApp.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を実行してみましょう。

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.tsxApp.cssをimportするように書かれているので、このファイル内の拡張子を変えてあげないといけません。
index.tsxの中身も同様なので、ついでにindex.cssindex.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という形になりますが、そこはご容赦ください><
(こうすればハッシュじゃなくてもできるよというやり方があれば、ぜひ教えてください。)

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

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をいじっていきます。

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.cssApp.scssに変えたこと以外は何も変更していないソースコードです。
これを以下のように変えます。

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は簡単に以下を追加しています。

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

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

image.png

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

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

image.png

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

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

URLによって表示コンテンツを変える

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

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

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

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

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

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

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の型が以下のようになります。

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-routermatchという型は、ちゃんと上記のような怒られ方をするのを予期してくれているので、ジェネリックでparamsの型を渡すことができます。
それをちゃんと実装すると、以下のような型になるわけです。

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

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

interface DetailMatchParams {
  id: string;
}

interface DetailProps {
  match: match<DetailMatchParams>
}

const Detail: FunctionComponent<DetailProps>

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

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

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

image.png

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

SPAをdeployする

ついにここまでやってきました!
まず、一旦作ったものが完成したのでコミットしましょう。

git add .
git commit
git push

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

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

npm i -D gh-pages

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

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

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

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

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

package.json(scriptsのみ抜粋)
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },

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

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を最後にいじる必要があります!
これも一行入れればいいだけです。

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

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

さらに、、、
image.png
ちゃんとルーティングもできてる!!!

言うことなしですね!!

さいごに

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

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

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

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

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

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

16
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
16
Help us understand the problem. What is going on with this article?