🀄

AppRunnerで三目並べReactアプリ10分クッキング

2023/03/08に公開

完成形


↑これ を作ります。
ソースコードは以下です。
https://github.com/hiyasichuka/react-app-runner

事前準備

作る前に以下の準備を完了しておきます。

  • GitHubアカウント作成
  • GitHubとの接続設定
  • VSCodeのインストール
  • Node v16以上のセットアップ

Let'sクッキング

GitHubでリポジトリを新規作成する

https://github.com/new

適当なリポジトリ名を入力して、作成します。
ローカルにリポジトリをクローンして移動します。

Reactをセットアップする

インターネットブログなどを参考にセットアップしたけど、うまくいかなかった経験ありませんか?
Reactを始めとした新しい技術は日々進化しますので、公式ドキュメントに頼るのが正解です。
息を吸って吐くように公式ドキュメントを確認します。

https://ja.reactjs.org/tutorial/tutorial.html

オプション 2: ローカル開発環境 を参考に環境をセットアップします。
Gitリポジトリの直下に、Reactのプロジェクトを新規作成します。

terminal
cd Gitリポジトリパス
npx create-react-app .

プロジェクトを作成後、Reactのアプリが立ち上がることを確認します。

terminal
npm i
npm start

アプリケーションコードを作成する

こちらの公式チュートリアルの通り進めていきます。
https://ja.reactjs.org/tutorial/tutorial.html

10分クッキングのため、完成済みコードをコピペで作成します。
作成するファイルはindex.jsとindex.cssとなります。
以下に準備しておきました。

index.js
import React from 'react'
import ReactDOM from 'react-dom/client'
import './index.css'

function Square(props) {
  return (
    <button className="square" onClick={props.onClick}>
      {props.value}
    </button>
  )
}

class Board extends React.Component {
  renderSquare(i) {
    return (
      <Square
        value={this.props.squares[i]}
        onClick={() => this.props.onClick(i)}
      />
    )
  }

  render() {
    return (
      <div>
        <div className="board-row">
          {this.renderSquare(0)}
          {this.renderSquare(1)}
          {this.renderSquare(2)}
        </div>
        <div className="board-row">
          {this.renderSquare(3)}
          {this.renderSquare(4)}
          {this.renderSquare(5)}
        </div>
        <div className="board-row">
          {this.renderSquare(6)}
          {this.renderSquare(7)}
          {this.renderSquare(8)}
        </div>
      </div>
    )
  }
}

class Game extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      history: [
        {
          squares: Array(9).fill(null)
        }
      ],
      stepNumber: 0,
      xIsNext: true
    }
  }

  handleClick(i) {
    const history = this.state.history.slice(0, this.state.stepNumber + 1)
    const current = history[history.length - 1]
    const squares = current.squares.slice()
    if (calculateWinner(squares) || squares[i]) {
      return
    }
    squares[i] = this.state.xIsNext ? 'X' : 'O'
    this.setState({
      history: history.concat([
        {
          squares: squares
        }
      ]),
      stepNumber: history.length,
      xIsNext: !this.state.xIsNext
    })
  }

  jumpTo(step) {
    this.setState({
      stepNumber: step,
      xIsNext: step % 2 === 0
    })
  }

  render() {
    const history = this.state.history
    const current = history[this.state.stepNumber]
    const winner = calculateWinner(current.squares)

    const moves = history.map((step, move) => {
      const desc = move ? 'Go to move #' + move : 'Go to game start'
      return (
        <li key={move}>
          <button onClick={() => this.jumpTo(move)}>{desc}</button>
        </li>
      )
    })

    let status
    if (winner) {
      status = 'Winner: ' + winner
    } else {
      status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O')
    }

    return (
      <div className="game">
        <div className="game-board">
          <Board
            squares={current.squares}
            onClick={(i) => this.handleClick(i)}
          />
        </div>
        <div className="game-info">
          <div>{status}</div>
          <ol>{moves}</ol>
        </div>
      </div>
    )
  }
}

// ========================================

const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<Game />)

function calculateWinner(squares) {
  const lines = [
    [0, 1, 2],
    [3, 4, 5],
    [6, 7, 8],
    [0, 3, 6],
    [1, 4, 7],
    [2, 5, 8],
    [0, 4, 8],
    [2, 4, 6]
  ]
  for (let i = 0; i < lines.length; i++) {
    const [a, b, c] = lines[i]
    if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
      return squares[a]
    }
  }
  return null
}
index.css

body {
  font: 14px "Century Gothic", Futura, sans-serif;
  margin: 20px;
}

ol,
ul {
  padding-left: 30px;
}

.board-row:after {
  clear: both;
  content: "";
  display: table;
}

.status {
  margin-bottom: 10px;
}

.square {
  background: #fff;
  border: 1px solid #999;
  float: left;
  font-size: 24px;
  font-weight: bold;
  line-height: 34px;
  height: 34px;
  margin-right: -1px;
  margin-top: -1px;
  padding: 0;
  text-align: center;
  width: 34px;
}

.square:focus {
  outline: none;
}

.kbd-navigation .square:focus {
  background: #ddd;
}

.game {
  display: flex;
  flex-direction: row;
}

.game-info {
  margin-left: 20px;
}

アプリの動作確認する

早速アプリの動作を確認しましょう。
ルートディレクトリにて、以下のコマンドを実行します。

terminal
npm start

ブラウザが立ち上がって、三目並べすることができます。

AppRunnerとは何か

先述の通り、新しい技術やサービスを扱うときは、まず公式ドキュメントからが鉄則でした。
AWS公式ドキュメントに目を通します。

https://docs.aws.amazon.com/ja_jp/apprunner/latest/dg/what-is-apprunner.html

AWS App Runner is an AWS service that provides **a fast, simple, and cost-effective way to deploy from source code or a container image directly to a scalable and secure web application in the AWS Cloud.

なるほど。ソースコードまたはコンテナイメージをAWSにデプロイするサービスですね。
早くてシンプルでコスパに優れている。すなはち、日清カップヌードルという理解で良さそうです(違
今回はGitHub上で管理しているソースコードをAppRunnerへデプロイしてみます。

AppRunnerとGitHubを連携する。

AWS管理コンソールにログインして、AppRunnerへアクセスします。

https://us-east-1.console.aws.amazon.com/console/home?region=us-east-1#

今回、米国リージョンを利用します。
米国の方が日本よりもちょっと安いので、検証であれば米国を積極的に使っていきます。
早速、サービスを作成をクリックします。

先述したとおり、GitHubで管理しているソースコードをデプロイしたいので、ソースコードデプロイを選択します。

新規追加をクリックして、「別のアプリケーションをインストールする」を選択してGitHubとの連携をします。
GitHub側でアプリに権限を渡すリポジトリを選択しInstall をクリックします。

これで次へを押すと、GitHubリポジトリとの連携ができます。

選択したリポジトリのみに権限設定した場合は、GitHubアカウント設定から以下のようにリポジトリを選択しておきます。AWS Connector for GitHubのConfigureをクリックして表示すると設定画面が出てきます。

AppRunnerのデプロイをセットアップする

GitHubとの連携ができましたら、デプロイしたいリポジトリとブランチを選択します。
今回、自動デプロイを選択します。こちらを選択することで、指定ブランチの変更を検知して自動デプロイを行ってくれます。めっちゃ便利!

続いて、構築設定です。Reactアプリを動かすためのの設定を行います。
Reactのデフォルトポート3000に変更するのをお忘れなく。

  • ランタイム: Nodejs 16
  • 構築コマンド: npm i
  • 開始コマンド: npm start
  • ポート: 3000

続いて、サービス設定です。ここでは、サービス名を入力して、 次へいきます。

最後に確認画面が出てきます。作成とデプロイ を押します。

こちらの画面が出てきて、デプロイが始まります。5分ぐらいかかります。

AppRunnerの動作を確認する

無事にデプロイできました!

デフォルトドメインのURLをクリックして確認してみます。

ちゃんと動きました!!バンザイ!
10分ぐらいでできたんでないでしょうか!


おまけ

構築設定をyaml化する

先程、構築設定をブラウザから手入力で設定しました。
しかし手入力ですと、タイポが発生する可能性があるため、あれ?動かない。といった事象に遭遇することがあります。
そこで、設定ファイルをyaml化することができます。
こちらをご確認ください。

https://docs.aws.amazon.com/ja_jp/apprunner/latest/dg/service-source-code-nodejs.html

apprunner.yamlというファイルをルートディレクトリに作成すれば良いみたいです。

例を参考につります。

apprunner.yaml
version: 1.0
runtime: nodejs16
build:
  commands:
    build:
      - npm i
run:
  command: npm start
  network:
    port: 3000

apprunner.yamlを作成後、GitHubにpushします。

yamlファイルを使用する

以下のように、設定ファイルを使用する。に変更します。

ちゃんと動きました!

おわりに

GitHubでソース管理して、AppRunnerを作成設定するだけで、アプリを動かすことができました。
サーバを調達したり、EC2インスタンスやECSなどを用意せずとも動かすことができるので、便利!
プライベートサブネットのDBや他AWSサービスとの連携などについては、気が向いたら書きます。

以上。

Discussion