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はこちら。
会員登録をしたら、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
にアクセスされて以下のページが出ればひとまず完成です!
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
を実行してみましょう。
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
という形になりますが、そこはご容赦ください><
(こうすればハッシュじゃなくてもできるよというやり方があれば、ぜひ教えてください。)
ルーティングを実現させるために、プロジェクトルート(リポジトリ名のフォルダ)で以下のコマンドを叩きます。
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
をいじっていきます。
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
に変えたこと以外は何も変更していないソースコードです。
これを以下のように変えます。
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は簡単に以下を追加しています。
._RoutingArea {
padding: 100px;
background-color: #eaeaea;
}
上記で保存すると、以下のように表示されると思います。
下側にグレー背景で「Top!」と表示されたかと思います。
また、http://localhost:3000/#/
というURLで表示されるようにもなったはずです。
ここで、URLを以下に変えてみましょう。
http://localhost:3000/#/detail/
上記画像のとおりになっていれば成功です!
ちゃんと、/
でアクセスするとTop!
と表示され、/detail/
でアクセスするとDetail!
と表示されるようになりました。
今は簡単なFunctionComponentを差し込んで文字だけ表示させていますが、ここをちゃんとしたcomponentを差し込んであげれば、URLに応じてページごとのcomponentを切り替えるというようなことができるようになります。
URLによって表示コンテンツを変える
ルーティングをするとなると、例えば、/detail/hoge
とdetail/fuga
でアクセスされたときのテンプレートは一緒だけど、中身のコンテンツは変えたい!というようなことがありますよね。
ちゃんと、react-router-dom
はそれができるようになっています。
まずは、/detail/
というURLで待ち受けている設定を、以下の設定に変えます。
<Route path="/detail/:id" component={() => <>Detail!</>} />
今回は/detail/
の後ろに:id
というのを追加しました。
**こうすることによって、/detail/hoge
というURLでアクセスするとid
という名前でhoge
という文字列を受け取ることができるようになります!**便利すぎる。。
ただ、現在引き受ける先のcomponentが、ただただ() => <>Detail!</>
と書かれていて何もできない状態なので、detail用のcomponentを作成していきます。
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-router
のmatch
という型は、ちゃんと上記のような怒られ方をするのを予期してくれているので、ジェネリックでparamsの型を渡すことができます。
それをちゃんと実装すると、以下のような型になるわけです。
const Detail: FunctionComponent<{ match: match<{ id: string }> }>
ちょっと長めの型になっちゃいましたね…笑
長くて嫌だなーという方は、以下のようにinterface
を使ってまとめてあげても良いです。
interface DetailMatchParams {
id: string;
}
interface DetailProps {
match: match<DetailMatchParams>
}
const Detail: FunctionComponent<DetailProps>
こっちのほうが見通しが良さそうですね。
最後に、作ったDetail
をRouteに登録してあげて、動作確認をしてみましょう。
全体のコードは以下になります。
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
でアクセスした結果が以下です。
ページ上にhoge
が表示されています!
別の文字列でも試してみます。
いい感じですね!!!
これで、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
という場所があるはずです。()
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
ここに対して、以下のように編集を加えます。
"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
を最後にいじる必要があります!
これも一行入れればいいだけです。
{
"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
をしてみましょう!!
ちゃんとhttp://localhost:3000
で見ていたまんまのものが表示されているはずです!
うれしーーーーー!
言うことなしですね!!
さいごに
という感じで、TypeScriptとSassを使った環境で作ったReact Appを、静的なページを公開できるGitHub Pagesに無事公開するところまでできました!
これで好きなページを作ることができるようになりました!
あとは、おしゃなデザイン作ってSassできれいにして、redux-sagaとか使って外部APIからデータ取ってきて、Reduxとかでデータの管理をちゃんとして、、、とやっていけば、結構やりたいことはできるのではないかと思います!!
道のりは長い。。。笑
ただここまでできるなんて、ほんといい時代になりましたね!
今回試しに作ったもののリポジトリはこちらですので、参考までにご覧になってください〜
https://github.com/hiraryo0213/react-app-sandbox
また、なにか不明点、疑問点、指摘点あれば、コメント欄によろしくお願いいたします〜