太陽がまぶしかったから

C'etait a cause du soleil.

Laravel 5.6 x React Docker 環境で npm watch と artisan serve を共存

最初のフロントエンドを選ぶのじゃ

 Laravel で Web アプリを作っていたのだけど、今どきの Web アプリケーションにおいてはサーバーサイドで API だけ作って画面表示は Javascript で生成するのが一般的なようだ。

 そんな時流において、サーバーサイドのテンプレートエンジンの学習に時間を使うのも勿体ないので、フロントエンドの最低限を作って API と結合することにした。

 そんなわけで、 『PHP 初心者による Laravel 入門 その4 MySQL の Docker コンテナ間接続 - 太陽がまぶしかったから』 で作成したLaravel Docker 開発環境で React を扱えるようにする。 Let's Yak Shaving !

Laravel から React を使うために

 Laravel は標準では Vue.js をサポートしているため、composer で Laravel プロジェクトを作ると Vue.js のサンプルファイルが含まれている。これを React 用に差し替えて Webpack を動かすには、以下のコマンドを実行すればよいだけのはずなのだけど、ローカルの編集を自動ビルドして Docker の artisan サーバーに反映するのにはいくつかの落とし穴があった。

$ php artisan preset react
$ npm install && npm run dev
$ npm run watch

 最終的には以下の Dockerfile と docker-compose.yml を利用している(MySQL コンテナについては 『PHP 初心者による Laravel 入門 その4 MySQL の Docker コンテナ間接続 - 太陽がまぶしかったから』 を参照)。

Dockerfile

FROM centos:latest

RUN yum update -y
RUN yum -y install autoconf gcc-c++ make automake \
  && yum -y install git vim unzip wget httpd opnessl nasm \
  && yum -y install libzip libzip-devel libpng-devel libtool

RUN yum -y install epel-release \
  && yum -y install http://rpms.famillecollet.com/enterprise/remi-release-7.rpm \
  && yum search php72 \
  && yum -y install php72
RUN ln -s /usr/bin/php72 /usr/bin/php
RUN yum -y install php-openssl php72-php-mbstring php72-php-pdo php72-php-gd php72-php-dom php72-php-mysql

RUN curl -sS https://getcomposer.org/installer \
  | php -- --install-dir=/usr/local/bin --filename=composer
RUN composer global require "Laravel/installer=~1.1"
RUN composer global update

RUN curl -sL https://rpm.nodesource.com/setup_8.x | bash -
RUN yum install -y nodejs && exit

WORKDIR /var/www
RUN composer create-project laravel/laravel laravelapp --prefer-dist

WORKDIR /var/www/laravelapp
RUN composer update
RUN php artisan preset react
RUN npm install && npm run dev

RUN echo -e "php artisan serve --host=0.0.0.0 --port=8000 & \nnpm run watch-poll\n" >> '../start.sh'
CMD sh ../start.sh

docker-compose.yml

version: '2'
services:
  mysql:
    image: "mysql:5.7"
    container_name: docker_mysql
    environment:
      MYSQL_ROOT_PASSWORD: xyz
      MYSQL_DATABASE: myapp_db
      MYSQL_USER: myapp_user
      MYSQL_PASSWORD: abc
    volumes:
      - ./db/mysql_data:/var/lib/mysql
    command: >
      mysqld
        --character-set-server=utf8
        --collation-server=utf8_bin
        --skip-character-set-client-handshake
    ports:
      - "3306:3306"
  web:
    build: ./web/
    container_name: docker_web
    volumes:
      - ./web/app_data/laravelapp:/var/www/laravelapp
    ports:
      - "50000:8000"
    links:
      - mysql

node.js のインストールと npm の実行

 npm は node.js のパッケージ管理ツール。利用するには node.js をインストールする必要がある。Cent OS に node.js をインストールするには curl から取得したスクリプトを実行してから yum install を実行する。

$ curl -sL https://rpm.nodesource.com/setup_8.x | bash -
$ yum install -y nodejs && exit

 また Laravle プロジェクトの package.json に定義された npm install を完了するためにはいくつかの CentOS側前提モジュールが必要となっており、こちらも yum install しておく必要がある(編集済みの Dockerfile は後述)。

Docker 環境では npm run watch-poll を使う

 npm run watch を動作させておくと指定パス配下の変更を検知して自動的にビルドしてくれる。 React は ES6 や JSX などの拡張構文が利用されており、ブラウザで実行を確認するためにはトランスパイラの実行が不可欠となっているため、自動ビルドの機構を作っておかないと煩雑化する。

特定の環境のWebpackでは、ファイル変更時に更新されないことがあります。自分のシステムでこれが起きた場合は、watch-pollコマンドを使用してください。

アセットのコンパイル(Laravel Mix) 5.6 Laravel

 通常であれば npm run watch を使えばよいのだが、 Docker 環境下においてはファイルシステム変更検知がうまく動かないらしく、 npm run watch-poll を使う必要があるとのこと。

ローカルの変更を自動反映するためのデータボリューム定義

 サーバーサイドでの変更は自動ビルドされるようになったが、ローカルの変更がそのままビルドに反映されてこそ価値がでる。これを実現するために Docker のデータボリュームをローカルファイル側に定義する。

 ローカル側のディレクトリが空になっていると、Docker 側のディレクトリも空になってしまうため、初回起動時には npm run dev 完了後の laravelapp 配下をローカルにコピーしておく。

  web:
    build: ./web/
    container_name: docker_web
    volumes:
      - ./web/app_data/laravelapp:/var/www/laravelapp
    ports:
      - "50000:8000"
    links:
      - mysql

Docker の1コンテナ1アプリケーション縛りを迂回

 ローカルファイルの反映と自動ビルドを確認して、Docker イメージをビルドしてバックグラウンド起動させてみたが artisan サーバーと watch-pool の共存ができずに難儀。

 Docker ではパフォーマンス上の制約からフォアグラウンド起動中のプロセスだけがコンテナで生き続ける動作をするため、両方を起動するラッパースクリプトを生成して、ラッパースクリプトをフォアグラウンド実行させるようにした。

RUN echo -e "php artisan serve --host=0.0.0.0 --port=8000 & \nnpm run watch-poll\n" >> '../start.sh'
CMD sh ../start.sh

 もっといいやり方があるはずなのだが、React の開発が目的なので調査打ち切り。

React の動作確認

 作成した Dockerfile と docker-compose.yml で docker-compose up -d を実行してブラウザ表示。ローカル側の resources/views/welcome.blade.php を以下のように変更してブラウザを更新。

<html>
<head>
    <meta name="csrf-token" content="{{ csrf_token() }}">    
</head>
<body>
    <div id="example"></div>
    <script src="{{ mix('js/app.js') }}"></script>
</body>
</html>

f:id:bulldra:20180505124124j:plain

 自動反映確認。 resources/assets/js/components/Example.js に以下の編集。

import React, { Component } from 'react';
import ReactDOM from 'react-dom';

export default class Example extends Component {
    render() {
        return (
            <div className="container">
                <div className="row justify-content-center">
                    <div className="col-md-8">
                        <div className="card">
                            <div className="card-header">Example Component</div>

                            <div className="card-body">
                                React キミにきめた!
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        );
    }
}

if (document.getElementById('example')) {
    ReactDOM.render(<Example />, document.getElementById('example'));
}

 保存してから、2秒ほど待ってブラウザ更新。

f:id:bulldra:20180505124450j:plain

 ローカル側の js ファイルの変更に追随してトランスパイラも自動起動することを確認。もっと洗練の余地があるかと思われるが、とりあえず React x Laravel x MySQL の連携開発を行うための前提環境がととのった。