docker-composeを用いてCreate React Appの環境を構築する

# 概要
　Reactで新しいシングルページアプリケーションを作成したい場合、Facebook社が公式で公開している[Create React App](https://ja.reactjs.org/docs/create-a-new-react-app.html#create-react-app)を利用すると簡単にReactアプリケーションを作成することができます。シングルページアプリケーションとは、単一のWebページでコンテンツ切り替えを行うことで、ページ遷移の必要がなく、ブラウザの挙動に縛られないWeb表現を可能となります。
　今回は、crete-react-appの環境をDockerを使用した仮想環境上で構築します。Dockerを使用するメリットとして、以下2点があります。
- 誰でも簡単に環境構築を行うことができる。（共同で開発をする場合非常に便利）
- 仮想環境上にツール等（yarn etc）をインストールするため、自身パソコン環境を汚さずに環境を構築できる
非常に便利なツールですので是非Dockerを使って環境構築をしましょう。

# 環境
WSL: `version2`
OS: `ubuntu 18.04LTS`
Docker: `20.10.3`
Docker-compose: `1.28.5`

# 目次
1. [作業ディレクトリの作成](#1. 作業ディレクトリの作成)
2. [Dockerfile及びdocker-compose.ymlの作成](#anchor2)
3. [Dockerimageを作成](#anchor3)
4. [コンテナの起動](#anchor4)
5. [コンテナ内に入り、create react appをインストール](#anchor5)
6. [reactアプリケーションの起動](#anchor6)
7. [`docker-compose up -d`でreactアプリが起動するように設定](#anchor7)
8. [最後に共同で開発するさいに](#anchor8)

<a id="anchor1"></a>
# 1. 作業ディレクトリの作成

　好きな名前のディレクトリを作成します。今回は例として、`BulletinBoard`というディレクトリを作成しました。

```:ターミナル
$ mkdir BulletinBoard
$ cd BulletinBoard
```

<a id="anchor2"></a>
# 2. Dockerfile及びdocker-compose.ymlの作成

#### Dockerfileの作成

　Node.js公式イメージがある[dockerhub](https://hub.docker.com/_/node?tab=description&page=1&ordering=last_updated)より必要なバージョンを指定し、ベースイメージをpullします。

```Dockerfile:Dockerfile
# docker-hubからベースのdocker imageをpull
FROM node:14.5.0-alpine

# コンテナ内で作業するディレクトリを指定
WORKDIR /usr/src/app/
```


#### docker-compose.ymlの作成

```yml:docker-compose.yml
# docker-composeのバージョン設定
version: "3"

services:
  # サービス名(任意)
  react:
    # ホスト側のディレクトリをコンテナ側のディレクトリにマウントする
    volumes:
      - ./app/:/usr/src/app/
    # Dockerfileの実行
    build:
      context: .
    # コンテナを起動させ続ける
    tty: true
    # コンテナ側とホスト側のポートの設定を行う
    ports:
      - 3000:3000
```

　`react`はサービス名です。任意の文字列を指定できます。
　`build`では、Dockerfileを実行させます。`context: .`より`docker-compose.yml`と同じディレクトリから`Dockerfile`を自動で認識し、実行します。
　`tty: true`は、コンテナを起動させ続ける設定です。`Dockerfile`を見ると、ベースイメージを持ってきて、作業ディレクトリを指定しているだけなので、通常はコンテナを立ち上げても、すぐに終了します。`tty: true`を指定することでコンテナを起動させ続けることができます。コンテナ内に入ってコマンドを打つために、コンテナを起動させ続ける必要があります。

<a id="anchor3"></a>
# 3. Docker imageを作成
　`$ docker-compose build`を実行し、`docker-compose.yml`,`Dockerfile`に従いDockerイメージを作成します。ERRORが無ければ成功。

```:ターミナル
$ docker-compose build
Building react
[+] Building 2.6s (6/6) FINISHED
 => [internal] load build definition from Dockerfile                                                                                                                                                                                    0.0s
 => => transferring dockerfile: 38B                                                                                                                                                                                                     0.0s
 => [internal] load .dockerignore                                                                                                                                                                                                       0.0s
 => => transferring context: 2B                                                                                                                                                                                                         0.0s
 => [internal] load metadata for docker.io/library/node:14.5.0-alpine                                                                                                                                                                   2.5s
 => [1/2] FROM docker.io/library/node:14.5.0-alpine@sha256:62c88cec4f64ee289824b257eb775fc24ed490eb6ce1be71884c1cceb6c51c5a                                                                                                             0.0s
 => CACHED [2/2] WORKDIR /usr/src/app/                                                                                                                                                                                                  0.0s
 => exporting to image                                                                                                                                                                                                                  0.0s
 => => exporting layers                                                                                                                                                                                                                 0.0s
 => => writing image sha256:fea6ac55de3b77d31e6f5d17896bb494c756b4f20b351b4199d424740b2ff582                                                                                                                                            0.0s
 => => naming to docker.io/library/bulletinboard_react                                                                                                                                                                                  0.0s
Successfully built fea6ac55de3b77d31e6f5d17896bb494c756b4f20b351b4199d424740b2ff582
```
<a id="anchor4"></a>
# 4. コンテナの起動
　`$ docker-compose up -d`を実行し、コンテナを立ち上げる。`$ docker-compose ps`を実行し、StatusがUpになっていれば成功。


```:ターミナル
$ docker-compose up -d
Starting bulletinboard_react_1 ... done 
$ docker-compose ps
        Name                     Command            State           Ports
----------------------------------------------------------------------------------
bulletinboard_react_1   docker-entrypoint.sh node   Up      0.0.0.0:3000->3000/tcp
```


<a id="anchor5"></a>
# 5. コンテナ内に入り、create react appをインストール
　`$ docker-compose exec [サービス名] sh`でコンテナ内に入り、コマンドラインを実行できるようになります。環境によっては、`sh`のところを`bash`に変更する必要があります。コンテナ内では、コマンドラインの受付は`$`ではなく`#`になります。


```:ターミナル
$ docker-compose exec react sh
/usr/src/app #
```

　`npx create-react-app --template typescript bulletinboard`のコマンドをコンテナ内で実行することで、create react appをインストールすることができます。
`--template typescript`のオプションを指定することでtypescriptが使えるプロジェクトを生成します。
最後の`bulletinboard`はプロジェクト名を表しており、大文字は使えないと怒られたので小文字を使用します。この名前のディレクトリが生成され、その中にreact appがインストールされます。

```:コンテナ内ターミナル
# npx create-react-app --template typescript bulletinboard
npx: installed 67 in 5.416s

Creating a new React app in /usr/src/app/bulletinboard.

~~~省略~~~

  cd bulletinboard
  yarn start

Happy hacking!
```
※コンテナ内のコマンドラインはホストと区別するため`#`とする。
 


　インストールが成功すると、ホスト側の`Dockerfile`や`docker-compose.yml`を配置しているディレクトリを見てみてください。
`app/bulletinboard`のプロジェクトディレクトリができていることが確認できます。react appのファイルを編集する際はこのディレクトリより編集が可能となります。このディレクトリはコンテナ側にマウントされているため、編集内容はコンテナ側にも反映されるます。

　次に、プロジェクトディレクトリ内に`.env`ファイルを作成し、以下を記述してください。これはcreate react appのhotreload機能をオンにするための記述です。hotreloadとは、ファイルの記述を変更すると即座にブラウザー側に反映される機能です。

```:.env
# hotreloadをonにする
CHOKIDAR_USEPOLLING=true
```

　この時点で、プロジェクトファイル内は以下のようになっていると思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/717054/051da5ff-4575-1aa3-2f04-c0c2ed3933da.png)


<a id="anchor6"></a>
# 6. reactアプリケーションの起動

　コンテナ内ターミナルで、プロジェクトディレクトリに移動し、`# yarn start`を実行することでreactアプリケーションを起動することができます。

```:コンテナ内ターミナル
# cd bulletinboard
# yarn start
Compiled successfully!

You can now view bulletinboard in the browser.

  Local:            http://localhost:3000
  On Your Network:  http://000.0.0.0:3000

Note that the development build is not optimized.
To create a production build, use yarn build.
```

　`http://localhost:3000`にアクセスすると以下の画面の表示されたら成功です。
hotreload機能をonにしているため、ファイルを変更し、ブラウザーをリロードするとすぐに反映されることがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/717054/d6eaa2dc-774e-a3e6-3815-154ac7c03430.png)



　これで、reactアプリ開発を行うことができるのですが、毎回`$ docker-compose up -d`、`$ docker-compose exec react sh`、`# cd bulletinboard`、`# yarn start`をするのは大変です。次に、`$ docker-compose up -d`だけで、コンテナが立ち上がるように設定をしていきます。

<a id="anchor7"></a>
# 7. `docker-compose up -d`でreactアプリが起動するように設定
　`Dockerfile`、`docker-compose.yml`を以下のように書き変えます。

```Dockerfile:Dockerfile
# docker-hubからベースのdocker imageをpull
FROM node:14.5.0-alpine

# コンテナ内で作業するディレクトリを指定
WORKDIR /usr/src/app/bulletinboard

# ファイルを全部作業用ディレクトリにコピー
COPY ./app/bulletinboard .

# パッケージをインストール
RUN yarn install

# コンテナを起動する際に実行されるコマンド
CMD[ "yarn", "start" ]
```


```yml:docker-compose.yml
version: "3"
services:
  react:
    build:
      context: .
    tty: true
    volumes:
      - ./app/:/usr/src/app/
      # ↓ここを追加
      - /usr/src/app/bulletinboard/node_modules
    ports:
      - 3000:3000
```

　`Dockerfile`に記述されている`WORKDIR`のパスが変更されています。
　`Dockerfile`に記述されている以下の記述は、ホスト側のディレクトリをコンテナ側にコピーし、パッケージをインストールするコマンドです。各種モジュールはプロジェクト内の`node_modules`下にインストールされていますが、通常はgitで管理しません。これは、容量が大きいことが理由である。`yarn install`で、なんのモジュールがインストールされているのかが記述されている`package.json`を参照して、必要なモジュールをインストールします。今は、作成者は、[5. コンテナ内に入り、create react appをインストール]()でモジュールはインストールされているので、作成者自身、この記述は意味がないですが、これから共同で開発する際に、この記述によりモジュールをインストールすることができます。
　最後の記述の`ENTRYPOINT [ "yarn", "start" ]`は、コンテナを立ち上げた際に、実行するコマンドとなっております。つまり、コンテナを起動した後に、コンテナ内で`$ yarn start`が実行されるという事です。

```
# ファイルを全部作業用ディレクトリにコピー
COPY ./app/bulletinboard .

# パッケージをインストール
RUN yarn install
```

　`docker-compose.yml`の以下の記述は、`node_modules`をvolume管理下にする記述です。`$ docker-compose build`で`node_modules`がインストールされ、そのあとにホスト側のディレクトリがコンテナ側にマウントされます。その際、ホスト側ではnode_modeulesがインストールされていないため上書きされてコンテナ内の`node_modules`が消得ます。それを防ぐために記述します。

```yml:docker-compose.yml
    volumes:
      - /usr/src/app/bulletinboard/node_modules
```

　これでホスト側のターミナルで以下のコマンドを実行してください。
これは、イメージを作成し、コンテナを起動を一つのコマンドにまとめたものです。

```:ターミナル
$ docker-compose up -d --build
```

これで、`http://localhost:3000`へアクセスすることができると思います。
2回目以降は、以下のコマンドでreactアプリケーションが起動します！

```:ターミナル
$ docker-compose up -d
```

<a id="anchor8"></a>
# 8. 最後に共同で開発する際に
　共同でアプリ開発をしてもらう際は、github等に管理しておき、各ローカル環境に落としてきて、`$ docker-compose up -d --build`とコマンド叩くだけなので、環境構築が非常に簡単になると思います。
　間違っている個所があれば、コメント等よろしくお願いします。