DockerなLaravel6でHMR（Hot Module Replacement）

# はじめに

Laravelでvue.jsを使った開発をするときは
laravel-mix付属のホットリロード機能（HMR）が便利です。

ただ、ホストで動かすことが想定されており、dockerベースでHMRな開発環境を作ろうとして戸惑ったので情報を整理します。

## 概要

Laravel6 + vue.js on Docker な環境でHMRをする設定を説明します。
Laradockは使いません。

ホスト側にバージョン管理が必要なツールはあまり入れたくないため、すべてDockerコンテナ内で完結させる方針です。
（yarnの実行はコンテナ内で実行すると遅いのですが、Ubuntu 上ではそれほど違いが感じられなかったため、コンテナ内で行う場合について記載しております。）

## 環境
- Ubuntu 18.04
- Docker 19.03.5
- docker-compose 1.25.0
- Laravel 6

# 結論

- WebサーバーはLaravelのビルドインサーバーを使う
- PHPとNode.jsは同じコンテナに設定
- Webpackのプロキシ機能で8080のアクセスを8000ポート(ビルドインサーバー)に転送

ソースはこちら
 https://github.com/odaryo/laravel6_vuejs_HMR

# 環境構築について

## ディレクトリ構成

```bash
┬－ docker （Dockerの設定）
│    └－ app
│        ├－ conf.d  （php設定ファイル）
│        │   ├－ php_settings.ini
│        │   └－ xdebug.ini
│        └－ Dockerfile
├－ src  （Laravelディレクトリ）
└－ docker-compose.yml
```

## Docker環境

### docker-compoe

appコンテナにはLaravelのビルドインサーバーを起動する設定を記載しています

```bash:docker-compose.yml
version: '3'

services:
  # laravel
  app:
    build:
      context: ./
      dockerfile: ./docker/app/Dockerfile
    depends_on:
      - db
    links:
      - db
    volumes:
      - ./app:/app:cached
      - ./docker/app/conf.d/xdebug.ini:/usr/local/etc/php/conf.d/xdebug.ini
      - ./docker/app/conf.d/php_settings.ini:/usr/local/etc/php/conf.d/php_settings.ini
    command: bash -c "php artisan serve --host 0.0.0.0" # 起動時にビルドインサーバーを起動
    ports:
      - "8000:8000"  # php artisan serve 用のポート
      - "8080:8080"  # HMR用のポート

  # DB (mysql)
  db:
    image: mysql:8
    environment:
      - MYSQL_DATABASE=testdb
      - MYSQL_USER=testuser
      - MYSQL_PASSWORD=password
      - MYSQL_ROOT_PASSWORD=root
      - TZ=Asia/Tokyo
    restart: always
    volumes:
      - dev_mysql_data:/var/lib/mysql
    ports:
      - "33306:3306"

volumes:
  dev_mysql_data:
    driver: local
```

### Dockerfile

PHPコンテナとNode.jsコンテナに分けるとうまく行かなかったため、マルチステージビルドの形でPHPコンテナにNode.jsを追加する

```bash:Dockerfile
# Nodeイメージ
FROM node:13-alpine as node

# PHPイメージ
FROM php:7.3-alpine

# Laravel環境に必要なパッケージをインストール
RUN apk update \
    && apk upgrade \
    && apk add --no-cache \
        bash \
        git \
        unzip \
        libpng \
        libpng-dev \
        libjpeg \
        icu \
        icu-dev \
        icu-libs \
        libxml2 \
        libxml2-dev \
        openssl \
        openssl-dev \
    && docker-php-ext-install \
        pdo_mysql \
        mysqli \
        gd \
        mbstring \
        intl \
        xml \
        opcache \
    && docker-php-ext-enable intl mbstring \
    && apk --update --no-cache add autoconf g++ make \
    # xdebugインストール
    && pecl install -f xdebug \
    && docker-php-ext-enable xdebug \
    && apk del --purge autoconf g++ make

# Composerのインストール
RUN curl -sS https://getcomposer.org/installer | php ;mv composer.phar /usr/local/bin/composer;
RUN composer global require hirak/prestissimo \
    && composer global require phpunit/phpunit

# Nodeコンテナからyarnとnodeをコピー
COPY --from=node /opt/yarn-v* /opt/yarn
COPY --from=node /usr/local/bin/node /usr/local/bin/

# 使いやすいようにシンボリックリンク作成
RUN ln -s /opt/yarn/bin/yarn /usr/local/bin/yarn \
    && ln -s /opt/yarn/bin/yarnpkg /usr/local/bin/yarnpkg

# ホスト側とuser_id, group_idを合わせる
ARG USER_ID
ARG GROUP_ID

RUN addgroup -g ${GROUP_ID} -S app-user && \
    adduser -u ${USER_ID} -S app-user -G app-user

USER app-user:app-user

# Setup working directory
WORKDIR /app
```

参考

- [Rails 6.0 × MySQL8でDocker環境構築（Alpineベース）](https://qiita.com/fujisawatk/items/41176d0155cb738ebbf8)
- [Multi-stage build でNode.jsのインストールをちょっぴり効率化する](https://tech.actindi.net/2019/06/20/084247)

## 構築手順

### docker起動

```bash
$ docker-compose build
$ docker-compose up -d
```

### laravelインストール

appコンテナに入って実行します。
※laravelインストールまではコンテナが起動しないため、runコマンドで実行する必要があります。

```bash
$ docker-compose run app /bin/bash
$ composer create-project --prefer-dist laravel/laravel .
```

### vue.jsを使う設定

laravel 6.0からデフォルトではインストールされないため、laravel/uiからインストールします。

```bash
$ composer require laravel/ui --dev
$ php artisan ui vue
$ exit
```

ここでdockerを再起動しておく

```bash
$ docker-compose up -d
```

``` http://localhost:8000 ```にアクセスして、スタート画面が表示されたらOK
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/536139/dfe3f0c8-e808-5714-1752-2df89936ef33.png)

### node_moduleのインストール

```bash
$ docker-compose exec app yarn
```

### HMRの設定

ホットリロードの確認用に、welcome.blade.phpのbody内にVue.jsのComponentを追加しておく
Componentはインストール時に作成されるサンプルを使用

```html:welcome.blade.php
<div id="app">
    <example-component></example-component>
</div>
<script src="{{ mix('js/app.js') }}"></script>
```

### webpack.mix.jsにHMRの設定を追加

8080へのアクセスではエラーとなり``` Cannot GET / ```が表示されてしまうため、ビルドインサーバーの8000ポートへプロキシしてやります

```js:webpack.mix.js
mix.webpackConfig({
    devServer: {
        host: '0.0.0.0',
        port: 8080,
        proxy: {
            '*': 'http://0.0.0.0:8000'
        },
        // Windows（Docker for windows）の場合は下記を追加する
        // watchOptions:{
        //     aggregateTimeout:200,
        //     poll:5000
        // },
    }
});
```

### ホットリロードの実行

```bash
$ docker-compose exec app yarn hot
```

``` http://localhost:8080 ```へアクセスして、コンポーネントの内容が表示されれば完了です。
コンポーネントを修正して、変更が反映されることを確認しましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/536139/8f8a41d7-8c7f-cc0f-8e49-b249441ef3cc.png)


参考

- [Laravel5.8+vue.jsのHot Module Replacement](https://reffect.co.jp/laravel/laravel-vue-js-hot-reload-replacement)


# 終わりに

ホットリロードでフロントエンド開発が捗ります。

注意点としては、HMRで監視するファイルはJavascriptやCSSなので、blade自体を更新した場合はブラウザの更新が必要となります。