Docker+webpackを用いたReact.js開発環境の構築

**2018-10-26追記**
本記事は2017-05-03に投稿したものです。
[Create React App](https://github.com/facebook/create-react-app)が出る前にwebpack環境構築しようと試行錯誤した記録なので、今となっては参考になる要素が少ないかもしれませんのでご留意ください。

---

React.jsの学習を行う環境をDockerで構築します。

- ホストPCでコーディングする
- webpackがコードを監視し、babel-loaderでビルドする
- ビルドしたバンドルデータをnginxが公開する

## 環境について

| 環境               | バージョン                                   |
|:-------------------|:---------------------------------------------|
| CentOS Atomic Host | CentOS Linux release 7.3.1611                |
| Docker             | Docker version 1.12.6, build 96d83a5/1.12.6  |
| docker-compose     | docker-compose version 1.11.2, build dfed245 |

docker-composeはCentOS Atomic Hostに入っていません。以下のコマンドでインストールします。

```shell-session
$ curl -L https://github.com/docker/compose/releases/download/1.11.2/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
$ sudo chmod +x /usr/local/bin/docker-compose
$ docker-compose --version
docker-compose version 1.11.2, build dfed245
```

## ディレクトリ構成

作業ホームを`~/reactjs`として以下の構成をゴールとします。

```text
~/reactjs
|- app
|  |- dist
|  |  |- bundle.js
|  |  `- index.html
|  `- src
|     `- app.js
|- docker-compose.yml
|- nginx
|  |- Dockerfile
|  `- nginx.conf
`- webpack
   |- Dockerfile
   `- webpack.config.js
```

## ファイルの作成

### webpackコンテナ
#### webpack/webpack.config.js

webpackの設定ファイルを作成します。

```js:webpack.config.js
const path = require('path');

module.exports = {
  entry: {
    bundle: './src/app.js'
  },
  output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name].js'
  },
  module: {
    loaders: [
      {
        loader: 'babel-loader',
        exclude: /node_modules/,
        test: /\.js[x]?$/,
        query: {
          cacheDirectory: true,
          presets: ['react', 'es2015']
        }
      }
    ]
  }
};
```

webpackのコンテナを作成するDockerfileを定義します。

- Dockerイメージのベースを`node:7.9.0`とします。
- 作業ディレクトリを`/app`とします。
- ホストPCに配置した`webpack.config.js`をコンテナ側の作業ディレクトリ`/app`に配置します。
- npmを初期化し、webpackをはじめとする今回必要なパッケージをインストールします。
- コンテナが作られた際に`webpack -d --watch`を実行するように定義します。

#### webpack/Dockerfile

```dockerfile:Dockerfile
FROM node:7.9.0

WORKDIR /app
COPY ./webpack.config.js /app/webpack.config.js

RUN npm init -y
RUN npm install -g webpack
RUN npm install --save react react-dom
RUN npm install --save-dev babel-loader babel-core babel-preset-es2015 babel-preset-react

CMD ["webpack", "-d", "--watch"]
```

#### app/src/app.js

React.jsのコードです。id="app"のDOMエレメントに対して`<h1>Hello, world!</h1>`を設定します。

```js:app.js
import React from 'react';
import ReactDOM from 'react-dom';

ReactDOM.render(
  <h1>Hello, world!</h1>,
  document.getElementById('app')
);
```

#### app/dist/index.html

ブラウザで表示されるHTMLファイルです。app.jsでid="app"に対してレンダリングを行うよう記述しましたので、divエレメントを定義します。また、バンドルデータとなるbuild.jsを読み込む記述をしています。

```html:index.html
<!DOCTYPE html>
<html lang="ja">
  <meta charset="UTF-8">
  <title>Reactjs Tutorial</title>
<head>
</head>
<body>
  <div id="app"></div>
  <script src="bundle.js"></script>
</body>
</html>
```

### nginxコンテナ

#### nginx/nginx.conf

nginxの設定ファイルを作成します。
[PASTEBIN](https://pastebin.com/)のsample nginx.confを用います。

```shell-session
# curl -o nginx.conf https://pastebin.com/raw/7gbdCRDk
```

ダウンロードしたファイルの内容は以下のようになっています。

```nginx:nginx.conf
user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/conf.d/*.conf;

    server {
        listen 8080;
        root /wwwroot;
    }
}
```

#### nginx/Dockerfile

nginxのコンテナを作成するDockerfileを定義します。

- Dockerイメージのベースを`nginx:1.13.0`とします。
- nginx.confで定義されているドキュメントルート`/wwwroot`ディレクトリを作成します。
- ホストPCに配置した`nginx.conf`をコンテナ側の`/etc/nginx/nginx.conf`にコピーします。
- コマンド`service nginx start`を実行するようにします。

```dockerfile:Dockerfile
FROM nginx:1.13.0

RUN mkdir /wwwroot
COPY ./nginx.conf /etc/nginx/nginx.conf

RUN service nginx start
```

### docker-compose

webpack, nginxのDockerfileをdocker-composeでまとめます。

webpackコンテナのwebpack.config.jsで`/app/src/app.js`を監視し、バンドルを`/app/dist`に出力するように定義しましたので、`volumes`でホスト側の`app/src`と`app/dist`にマウントします。`src`は読み取り専用**ro**、`dist`はwebpackが書き出すので**z**オプションを付けています。

nginxコンテナではドキュメントルートを`/wwwroot`としており、webpackが生成するバンドルは`app/dist`へマウントしましたので、nginxコンテナでもマウントします。この時nginx側では読むだけですので**ro**オプションを付けています。また、nginxコンテナ側でnginxを8080ポートとして実行するように設定しました。ホスト側では80番ポートとなるようポートフォワード設定を行います。

> **Note**
zオプションを付けることでボリュームラベルを付与することができます。ボリュームラベルを付与しないとSELinuxがファイル更新を妨ぎます。
詳しくは[Docker-docs-ja 1.9beta > コンテナでデータを管理する > データ・ボリューム > ボリューム・ラベル](http://docs.docker.jp/engine/userguide/dockervolumes.html#id6)をご参照ください。

```yaml:docker-compose.yml
version: '2'
services:
  nginx:
    build:
      context: ./nginx
    image: reactjs_nginx
    container_name: reactjs_nginx_container
    ports:
      - "80:8080"
    volumes:
      - ./app/dist:/wwwroot:ro
  webpack:
    build:
      context: ./webpack
    image: reactjs_webpack
    container_name: reactjs_webpack_container
    volumes:
      - ./app/src:/app/src:ro
      - ./app/dist:/app/dist:z
```

## Docker起動

docker-compose.ymlがあるディレクトリで以下のコマンドを実行します。

```shell-session
# docker-compose up -d
```

ブラウザから`http://<ホストPCのIP>/`でアクセスすると**Hello, world!**という文字列が表示されます。

以上でホストPCにあるapp/src/app.jsを編集していくことにより自動的にバンドルデータが生成されブラウザで確認することができます。

## Conclusion

React.jsの学習のみであれば[Codepen](https://codepen.io/)を用いるだけで充分ですし、Dockerを使うにしてもnodeをベースとしたコンテナにwebpack-dev-serverを用いる方法もあります。

今回は開発～本番環境のデプロイメントまでを学習するため、開発環境として利便性のあるwebpack-dev-serverの選択は控えました。

React.jsの学習を進めていき、より開発効率の良い構成を模索していきます。