20190218のReactに関する記事は4件です。

TypeScriptでimport React from 'react'って書きたい!

書いてみる

import React from 'react';

すると以下のエラー出る。。。

Module '"~~~~/node_modules/@types/react/index"' has no default export.

~~~~は各々のファイルの場所です。)

以下にすればエラーが出なくなる。

import * as React from 'react';

そういうことじゃない!!

tsconfigを直す

allowSyntheticDefaultImports: true

これを足せばいいだけ!

https://www.typescriptlang.org/docs/handbook/compiler-options.html
上記からallowSyntheticDefaultImportsの説明を引用すると、、、

Allow default imports from modules with no default export. This does not affect code emit, just typechecking.

英語がわからないのでGoogle翻訳にぶん投げます。

デフォルトのエクスポートなしでモジュールからのデフォルトのインポートを許可します。これはコードの発行には影響せず、単に型チェックを行います。

has no default exportに対して解決してそうですね!

めでたし!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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

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

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

create-react-appとSassでサラッと作った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

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

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Reactの静的サイトを自動デプロイ

はじめに

今回はCI/CDの手法を学ぶために、静的サイトの自動ビルドと自動デプロイをCircleCIとAWSを用いて構築したので、ぜひ参考にしてみてください。

参考資料

https://circleci.com/docs/2.0/ecs-ecr/#section=deploymen
https://qiita.com/Sekky0905/items/7f9aa94261e17e4fd040
https://circleci.com/docs/2.0/hello-world/
https://devblog.thebase.in/entry/2018/10/31/110000

概要

環境のイメージとしては以下のようになります。
cicd.png

前提条件

前提条件としてAWSのアカウントを保持していること、GitHubの基本が分かっていること、CircleCIのアカウントがGitHubのアカウントに紐づけられていることを前提に記事を書きます。

静的サイトのホスト

今回は静的サイトをAWSのS3にホストする形で勧めますので、S3にバケットを作って、静的サイトのホストを完了させた状態にしてください。

一応自分が書いた記事のリンクを貼っておきますので、分からなければ確認してみてください。
AWS S3で静的サイトを構築

CircleCIの簡単な使い方

CircleCIに詳しい人や、CircleCIのHello Worldドキュメントを読んだことがあり、CircleCIの簡単な使い方について理解している人は飛ばしてください。また公式ドキュメントの方が自分の記事より遥かに詳しく正確に書いてますので、自分のは参考程度に見てください。

CircleCI公式チュートリアル

CircleCIは内部にdockerを持っていてgithubのリポジトリのルートに.circleci/config.ymlに設定を書くことで、githubにpushしたのをトリガーにしてCircleCI内でdockerイメージのビルドと各種コマンドが実行されるものになります。

まずはgithubのリポジトリを作成してもらって、リポジトリのルートに.circleci/config.ymlを作成してください。

$ mkdir .circleci
$ ls -a
. .. .circleci .git
$ vim .circleci/config.yml

したら以下のコードを入力してください。

.circleci/config.yml
version: 2
jobs:
  build:
    docker:
      - image: circleci/node:4.8.2
    steps:
      - checkout
      - run: echo "hello world"

コードを書き込んだらgit pushでpushしてください。このときリポジトリにconfig.ymlが無いとCicleCIでトリガが掛けられません。

コードの説明を簡単にすると、docker imageをCircleCIで用意されていてダウンロードの時間を少なくできるNodejsのイメージを選択し、build後にechoコマンドでhello worldを出力するものです。

次にリポジトリにトリガーをかけるためにCircleCIのアカウントページから

  • Add Project
  • Set Up Project Screenshot from 2019-02-18 03-27-55.png

を選択後、特に何もいじらず

  • Start building をクリック Screenshot from 2019-02-18 03-30-43.png Screenshot from 2019-02-18 03-40-36.png

成功すると、以下のような画面になりecho "hello world"が実行され、hello worldが出力されているのが確認できます。

以上が簡単なCircleCIの説明になります。

Reactアプリのビルド

CircleCIを用いて、Reactで作成したアプリをビルドします。Reactについての詳しい説明は公式チュートリアルなどをご覧ください。

React公式チュートリアル

Reactチュートリアル: Intro To React【日本語翻訳】

まずReactのテンプレートを作ります。nodeをインストールしてもらってnpxを使える状態で以下のコマンドを入力してください。

npx create-react-app test
mv test/* ./
rm -r test/
npm run build
npm start

上記のコマンドで、reactアプリのテンプレートを作成し、localhost:3000でサービスが立ち上がるのを確認できるはずです。
(reactのテンプレートフォルダから中身を取り出している理由は、CircleCIでのビルド時にpackage.jsonの有るディレクトリにcheckoutしているのに、package.jsonが見つからないエラーが出たので、中身を取り出しています。原因が分からないので、もし分かる人は教えてください)
react.png

Reactアプリがたち上がったのを確認したら、以下のコードを.circleci/config.ymlに書いてください。

.circleci/config.yml
version: 2
jobs:
    build:
        docker:
            - image: circleci/node:11.8.0
        steps:
            - checkout
            - run: npm run build

リポジトリをpushして、CircleCIの画面のBuild画面から、成功すれば緑色になっているのが確認できます。(大量に赤くなっているのは、working_directoryとcheckout絡みで検証して挫折した結果です。)
Screenshot from 2019-02-18 04-23-25.png
Screenshot from 2019-02-18 04-22-10.png
成功すれば、build画面の詳細からnpm run buildが成功したことが確認できます。

AWS S3に自動デプロイ

AWS S3にデプロイするために、同じようにCircleCIでpythonイメージから、更にawscliというコマンドラインからawsのリソースにアクセスできるソフトをダウンロードしてBuildした結果をデプロイします。

まず以下のコードに.circleci/config.ymlを書き直しでください。(バケット名は各自で作成したバケット名を入力してください。)

.circleci/config.yml
version: 2
jobs:
    build:
        docker:
            - image: circleci/node:11.8.0
        steps:
            - checkout
            - run: pwd
            - run: npm run build
    deploy:
        docker:
            - image: circleci/python:2.7-jessie
        steps:
            - run:
                name: Install awscli
                command: sudo pip install awscli
            - checkout
            - run:
                name: Deploy to S3
                command: aws s3 sync build/ s3://バケット名

workflows:
    version: 2
    build-deploy:
        jobs:
            - build
            - deploy:
                requires:
                 - build
                filters:
                 branches:
                     only: master

重要な点はworkflowsという所で、ここでReactのアプリをBuildした後にS3にデプロイするように実行の順番を制御しています。

実際にAWSにデプロイしているのは以下のコマンドになります。

aws s3 sync build/ s3://バケット名

またこのままでは、S3への権限が無いのでCircleCIに権限を付与しなければアップロードできません。そのためにIAMロールユーザーを作成してください。
実行権限はS3へのFullAccessとかで良いと思います。
詳しいことは以下の記事とか読んでください。

IAMロールの公式ドキュメント
S3のアクセスコントロールまとめ

IAMロールユーザーが作成できたら、CircleCIのアカウントページから、

  • 左のBuildsボタンをクリック
  • Projectの歯車をクリック
    Screenshot from 2019-02-18 04-23-26.png

  • PERMISSIONSのAWS Permissionsをクリック

  • 先程作ったIAMロールユーザーのAccess Key IDとSeacret Access Keyを入力

  • Save AWS keysをクリック
    Screenshot from 2019-02-18 04-46-27.png

IAMロールユーザーの登録がすんだら、pushして結果を確認してみてください。

  • CircleCIのWorkflowsを開く
  • 詳細を見る
  • buildとdeployが成功していることを確認!

Screenshot from 2019-02-18 05-07-40.png

S3の静的ホスト先のURLを開いてデプロイ出来ているかの確認
Screenshot from 2019-02-18 05-10-43.png

成功!

まとめ

筆者はあまり、CI/CDについて詳しくは無いのでセキュリティなどや、もっと良いやり方が有るのかもしれませんが、取り敢えずgithubにpushするだけで自動ビルド&自動デプロイが出来たので知識の整理のために書きました。

間違っている点や、もっとこうするべきだという点が有りましたらコメント下さい!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む