概要
Aidemy アドベントカレンダー12日目担当の @km42428 です。
今回は、Dockerで簡易版Aidemyをつくってみようシリーズのその2です。
前回の記事では全体の構成をお伝えしましたが、今回からは実際に開発したいと思います。
今回の目標
今回は、React によるフロントエンドを作成し、docker-compose
コマンドで一発で起動するのが目標です。
フロントエンドはユーザと直接やりとりする部分で、ユーザの入力を受けたり、その情報をバックエンドへ送信、バックエンドから情報を取得して表示する役割があります。Reactについては公式サイトをご覧ください。
作成するのは、いわゆるWebページです。ソースは以下に公開しています。
https://github.com/km42428/aidemy-simple-pack/tree/frontend
今回の記事は frontend
ブランチで動作しています。
master
ブランチは適宜更新しているので、この記事と同じ動作をさせたい場合は、
以下のコマンドを入力してください。
※自分の作業ブランチ
は適宜書き換えてください。
# クローン
$ git clone https://github.com/km42428/aidemy-simple-pack.git
# リモートのfrontendブランチにチェックアウト
$ git fetch && git checkout origin/frontend
# ローカルの自分の作業ブランチにチェックアウト
$ git checkout -b {自分の作業ブランチ}
フロントエンドの機能としては、エディタ部分にコードが入力できるようにし、
送信ボタンを押すと、イベントが発火して今回はalertが表示されるようにします。
ただし、cssについてはあまり手を加えないことにします。
理由は、僕がデザインセンスが皆無なことと、
今回の趣旨はReactでの簡単なデータの扱い方を見るのが目的だからです。
ページ構成 (ルーティング)
今回は以下のようなページ構成を考えます。
プレビューが小さくて見えない場合は、画像をクリックすれば大きく見えます。
ルート | ページ | プレビュー |
---|---|---|
/ | 問題一覧 | |
/exercises/:exerciseId | 演習画面 |
ちなみに、 /exercises/:exerciseId
のRUN押下時は下のようになります。
事前準備
フロントエンドの挙動をローカルで確認するには、
node
と npm
を事前にインストールする必要があります。
インストール手順については
以下の記事を参照してください。
※ 今回の最終目的はDocker
を使って環境開発不要で起動することです。
なので、環境開発なんてしたくないやいっ!という方は、
ソースをcloneした上で、Dockerによる操作からお読みください。
Ubuntuの場合
Macの場合
Windowsの場合
nodeの管理にはバージョン管理ツールが必要なのですが、
個人的には n
というツールが使いやすいです。
インストール後バージョン確認をしましょう。
僕の場合は以下のようになりました。
$ node -v
v8.6.0
$ npm -v
5.3.0
ローカルでの開発
create-react-app のインストール
$ npm install -g create-react-app
create-react-app によるプロジェクト作成
aidemy-frontend
という名前で作成します
$ create-react-app aidemy-frontend
localhost:3000 で動作確認
以下を実行すると、 http://localhost:3000 で動作確認できると思います。
$ cd aidemy-frontend
$ npm start
react-router-dom
ルーティングは react-router-dom を用いてやっていきます。
react-route-dom は react-router の一機能ですが、今回は事足りるということでこれでいきます。
react-router-dom をインストールします。
僕の場合はバージョンは 4.3.1
でした
$ npm install react-router-dom
react-ace
エディター部分には React Ace
を使用します。
こちらは、React用のエディターで、よしなにカスタマイズすることができます。
react-ace をインストールします。
僕の場合はバージョンは 6.2.0
でした
$ npm install react-ace
App.jsの編集
フロントエンドのメイン箇所となるsrc/App.jsを編集していきます。
import React, { Component } from "react";
import { BrowserRouter, Route, Link } from "react-router-dom";
import AceEditor from "react-ace";
// react-aceのテーマ情報
import "brace/mode/python";
import "brace/snippets/python";
import "brace/theme/tomorrow";
// マスターデータ
const exercises = [
{
exerciseId: "exercise1",
title: "1. Hello world",
script: "# Hello worldを出力しましょう\n"
},
{
exerciseId: "exercise2",
title: "2. コメントの入力",
script: "# 3 + 5 の結果を出力しましょう\n"
}
];
// exerciseIdからexerciseを取得する関数
function getExercise(exerciseId) {
return exercises.find(el => {
return el.exerciseId === exerciseId;
});
}
// 表示される土台の部分
// Link: クリック時にtoで指定されたページに遷移
// Route: 特定のurlの時に表示するコンポーネントを定義
const App = () => (
<BrowserRouter>
<div>
<nav>
<Link to="/">Home</Link>
</nav>
<hr />
<Route exact path="/" render={props => <Exercises />} />
<Route
path="/exercises/:id"
render={props => <Exercise match={props.match} />}
/>
</div>
</BrowserRouter>
);
// exerciseの一覧
class Exercises extends Component {
constructor() {
super();
this.state = {
exercises: []
};
}
// コンポーネントのレンダリングが終了したら呼び出される
componentDidMount() {
this.setState({
exercises
});
}
// 実際の表示部分
render() {
return (
<div>
<h2>Exercises</h2>
<p>エクササイズの一覧です</p>
<ul>
{(this.state.exercises || []).map(exercise => {
return (
<li key={exercise.exerciseId}>
<Link
to={"/exercises/" + exercise.exerciseId}
key={exercise.exerciseId}
>
{exercise.title}
</Link>
</li>
);
})}
</ul>
</div>
);
}
}
// 特定のexerciseの演習部分
class Exercise extends Component {
constructor() {
super();
this.state = {
exerciseId: "",
title: "",
script: ""
};
this.onChange = this.onChange.bind(this); // はじめにonChangeをthisにbindしておく
}
// コンポーネントのレンダリングが終了したら呼び出される
componentDidMount() {
const exercise = getExercise(this.props.match.params.id);
if (exercise) {
this.setState({
exerciseId: exercise.exerciseId,
title: exercise.title,
script: exercise.script
});
return;
}
this.setState({
exerciseId: "",
title: "",
script: ""
});
}
// エディタに入力があるたびに呼び出され、stateを変更する
onChange(newValue) {
this.setState({
script: newValue
});
}
// RUN押下時に呼び出される関数。現状alertを表示するのみ。
run() {
alert(`以下のコードが実行されます\n\n${this.state.script}`);
}
// 実際の表示部分
render() {
return (
<div>
<h2>Exercise</h2>
<p>{this.state.title}</p>
<AceEditor
mode="python"
onChange={this.onChange}
editorProps={{ $blockScrolling: true }}
editorSetting="editorLightTheme"
theme="tomorrow"
value={this.state.script}
/>
<input type="button" value="RUN" onClick={this.run.bind(this)} />
</div>
);
}
}
export default App;
※静的ファイルをホスティングの準備(本番モード)
これは好みによるのでやらなくてもいいです。
Reactの起動方法には以下の二つがあります。
- 開発モード
- 本番モード
開発モード
は文字通り開発に適したモードで、
WarningやErrorがコンソール画面でみれたり、ソースを書き換えるたびに再度レンダリングされます。
ただし、ソースの書き換えを見張っていたり、ログを多めに出すので、リソースはかなり食います。
本番モード
は開発モードとは異なり、
WarningやErrorはあまりコンソールにはでません。
また、ビルドされた(静的なHTML, CSS, Javascript などに出力された)ファイルをホスティングするので、
元のソースが書き換わっても再レンダリングは発生しません。
なのでリソースを小さく抑えることができますが、変更があった場合は再度ビルドしてホスティングする必要があります。
今回はDockerで起動するときは本番モード
で起動することにします。
serveのinstall
静的ファイルのホスティングのために serveをinstallします。
ちなみに、 npm install
は 省略して npm i
でもOKです。
$ npm i serve
package.jsonに以下を追加
{
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
+ "serve": "serve -s build",
"eject": "react-scripts eject"
}
}
静的ファイルホスティング
はじめにビルドして静的ファイルを生成します。
$ npm run build
静的ファイルをホスティングします。
問題なければ http://localhost:5000 で立ち上がります。
$ npm run serve
Dockerによる操作
docker-compose で動作するために
aidemy-frontendフォルダ内にDockerfileを配置
└ aidemy-frontend/
└ Dockerfile
Dockerfileを記述
本番モード
で起動
FROM node:8
WORKDIR /usr/src/app
COPY . .
RUN npm install
RUN npm run build
CMD npm run serve
開発モード
で起動
FROM node:8
WORKDIR /usr/src/app
COPY . .
RUN npm install
CMD npm run start
aidemy-frontendフォルダと同じ階層にdocker-compose.yml 配置
├ aidemy-frontend/
│ └ Dockerfile
└ docker-compose.yml
docker-compose.ymlを記述
version: "3"
services:
aidemy-frontend:
build: ./aidemy-frontend
image: aidemy-frontend
environment:
- ENV
ports:
- 5000:5000
※もし、開発モードで起動したい場合は、ポートを 5000:5000
-> 2000:2000
へ変更
docker-composeによる起動
docker imageの作成
以下のコマンドを実行すると、aidemy-frontendのimageが作成されます。
$ docker-compose build
imageの確認
Docker
のimage確認は以下のコマンドで行います。
$ docker images
以下のような出力が得られればOKです。
REPOSITORY TAG IMAGE ID CREATED SIZE
aidemy-frontend latest 948452abd4d5 30 minutes ago 1.12GB
node 8 3b7ecd51ffe5 7 days ago 889MB
aidemy-frontend
は1.12GBもあるんですね。。
元となったnode:8
が889MBなので仕方ないですね。。
PCの容量にはお気をつけください!
フォアグラウンドでの起動
$ docker-compose up
http://localhost:5000 で動作すればOK
もし、ローカルでも起動していた場合バッティングするので、ローカルで起動したものを止めましょう。
※バックグラウンドでの起動
$ docker-compose up -d
※バックグラウンドで起動したプロセスの終了
$ docker-compose down
今回は以上になります。
Dockerの使い方には慣れてきたでしょうか?
次回は12/17にバックエンドを開発していきます!