CakePHP4 で Vue.js ( Laravel-Mix ) を使えるようにして docker-compose で動くようにする

[CakePHP4 を docker-compose で動くようにする](https://qiita.com/katsuhiko/items/f18c488457af33abdf66)

の続きです。

CakePHP4 のプロジェクトから Vue.js を使えるようにします。
Vue.js は Laravel-Mix を使って導入します。


# この記事でわかること

- CakePHP4 へ Laravel-Mix 導入し Vue.js を使えるようにします。
- この記事内のソースは以下で公開しています。
    - https://github.com/katsuhiko/cakephp-vue-study


# 事前準備

- Docker Desktop のインストール
    - https://www.docker.com/products/docker-desktop
- CakePHP4 プロジェクトの作成
    - 以下が参考になります。
        - [CakePHP4 を docker-compose で動くようにする](https://qiita.com/katsuhiko/items/f18c488457af33abdf66)

僕の Mac の各種バージョンは以下です。

|  | バージョン |
|:-----------------|:------------------|
| docker           | Docker version 19.03.5, build 633a0ea |
| docker-compose   | docker-compose version 1.25.4, build 8d51620a |
| CakePHP4         | 4.0.4 |
| Vue.js           | 2.6.11 |
| laravel-mix      | 5.0.1 |


# 手順の流れの説明

Laravel の仮プロジェクト作り、そこから必要なファイルをコピーします。
その後、CakePHP4 のディレクトリ構成に合わせた変更や docker-compose の変更を行います。


# Laravel 仮プロジェクトの作成

CakePHP プロジェクトと同列（同じディレクトリ）に Laravel の仮プロジェクトを作成します。
ここでは CakePHPプロジェクト「cakephp-vue-study」のディレクトリにいるところ始めるので、まずは1つ上のディレクトリへ移動します。

ローカル環境に依存したくないで一時的なコンテナを立ち上げて入ります。
一時的なコンテナが独自なのは、最低限必要なライブラリを入れているためです。
( https://github.com/katsuhiko/docker-php-fpm-base/blob/master/Dockerfile )

```sh
cd ..
docker run --rm -it -v "$(pwd):/home/app" -w /home/app katsuhikonagashima/php-fpm-base:7.4-buster /bin/bash
```

ここからはコンテナ内での作業になります。
composer を取得して、Laravelプロジェクトを作ります。
その後、ui として Vue.js を導入します。

```sh
curl -sS https://getcomposer.org/installer | php

php composer.phar create-project --prefer-dist laravel/laravel laravel-template

cd laravel-template
php ../composer.phar require laravel/ui
php artisan ui vue
exit
```

コンテナ内での作業はここまでです。


# 必要なファイルをコピー

CakePHP4プロジェクトへ必要なファイルをコピーします。
Vue.js の各種ファイルは ` ./assets/ ` 配下に格納して開発していきます。
Laravel に合わせて ` ./resources/ ` 配下でも良いと思いますが、自分は locale 系のファイルと一緒にあることに違和感を感じるので ` ./assets/ ` 配下にしています。

```sh
cd ./cakephp-vue-study/

cp ../laravel-template/package.json ./
cp ../laravel-template/webpack.mix.js ./

mkdir -p ./assets
cp -r ../laravel-template/resources/js ./assets/js
cp -r ../laravel-template/resources/sass ./assets/sass
```


# ライブラリのインストール

` npm install ` も一時的なコンテナを立ち上げて実施します。
一応、現時点で LTS となっている node 12系を使っています。

```sh
docker run --rm -it -v "$(pwd):/home/app" -w /home/app node:12 npm install
```

必要になると思うので、このタイミングで vue-router を入れても良いと思います。

```sh
docker run --rm -it -v "$(pwd):/home/app" -w /home/app node:12 npm install --save-dev vue-router
```


# webpack.mix.js の変更

` ./webpack.mix.js ` をCakePHP4 のディレクトリ構成にあった設定へ変更します。
` ./assets/ ` を ` ./webroot/assets/ ` 配下へビルドした結果が格納されるようにします。

```js:webpack.mix.js
mix.setPublicPath('webroot')
    .js('assets/js/app.js', 'assets/js')
    .sass('assets/sass/app.scss', 'assets/css')
    .sourceMaps().webpackConfig({devtool: 'source-map'});
```


# javascript, css ファイルにタイムスタンプをつける

javascript, css ファイルのキャッシュが表示されてしまう問題に対応するために CakePHP の Asset.timestamp 機能を使います。
` ./config/app.php ` を変更します。

本番でも有効にしたいので、Asset.timestamp へ ` force ` を指定します。

```:config/app.php
    'Asset' => [
        'timestamp' => 'force',
        // 'cacheTime' => '+1 year'
    ],
```


# docker-compose.yml への記述追加

` ./docker-compose.yml ` で Vue.js に関するファイルが変更された場合、ビルドを実施するコンテナ watch を追加します。

```yml:docker-compose.yml
services:
  # ... 以下が追加の記述です。
  watch:
    image: node:12
    container_name: watch
    working_dir: /home/app
    command: "npm run watch"
    volumes:
      - ./:/home/app
    networks:
      - frontend
```

コンテナを立ち上げ直します。

```sh
docker-compose down
docker-compose up -d
```

` ./webroot/assets/js ` , ` ./webroot/assets/css ` 配下に js ファイルや map ファイルができていたら適切にビルドできています。

※ Windowsの場合、変更ファイルの検知ができない可能性があるため ` command: "npm run watch" ` を ` command: "npm run watch-poll" ` にする必要があると思います。


# .gitignore への記述追加

ライブラリとビルドで作られるファイルを ` ./.gitignore ` へ追加しソース管理の対象から外します。

```:.gitignore
# Laravel Mix specific files #
##############################
/node_modules
/webroot/assets
/webroot/mix-manifest.json
```


# 動作確認のための変更

簡易的な対応を実施して、Vue.js が動作していることを確認します。


## routes.php の変更

` ./config/routes.php ` で、どんなアクセスがあっても同じ PagesController#index() が呼び出されるようにします。

```php:config/routes.php
$routes->scope('/', function (RouteBuilder $builder) {
    $builder->registerMiddleware('csrf', new CsrfProtectionMiddleware([
        'httpOnly' => true,
    ]));
    $builder->applyMiddleware('csrf');

    //$builder->connect('/', ['controller' => 'Pages', 'action' => 'display', 'home']);
    $builder->connect('/pages/*', ['controller' => 'Pages', 'action' => 'display']);

    // Vue.js のルートページ表示用です。
    $builder->connect('/*', ['controller' => 'Pages', 'action' => 'index']);

    $builder->fallbacks();
});
```


## PagesController の変更

` ./src/Controller/PagesController.php ` へ index メソッドを追加します。

```php:src/Controller/PagesController.php
class PagesController extends AppController
{
    // この index メソッドを追加します。
    public function index(): ?Response
    {
        return $this->render();
    }
    // ...
}
```


## Pages/index.php の追加

` ./templates/Pages/index.php ` ファイルを追加します。
CakePHP4 から ctp ファイルではなく、php ファイルになったようです。
確認のため、example-component タグを記述しています。

```php:templates/Pages/index.php
<div id="app">
    <example-component></example-component>
</div>
<?= $this->Html->script('/assets/js/app.js')?>
```

# 動作確認

http://localhost し以下の文言が表示されていることを確認してください。

```
Example Component
I'm an example component.
```


# 補足） テストの追加と実行方法

` ./tests/TestCase/Controller/PagesControllerTest.php ` へ PagesController で追加したメソッドのテストを追加します。

```php:tests/TestCase/Controller/PagesControllerTest.php
class PagesControllerTest extends TestCase
{
    // ... 以下の２つのテストを追加します。
    /**
     * @return void
     */
    public function testVueルートページを表示(): void
    {
        $this->get('/');
        $this->assertResponseOk();
        $this->assertResponseContains('<div id="app">');
    }

    /**
     * @return void
     */
    public function test_どんなアクセスでもVueルートページを表示(): void
    {
        $this->get('/hoge/bar');
        $this->assertResponseOk();
        $this->assertResponseContains('<div id="app">');
    }
    // ...
}
```

コンテナを使って、テストを実行します。

```sh
docker exec -it app php composer.phar test
```

コミット前に ` composer check ` を行うと test と codesniffer による check を行えます。

```sh
docker exec -it app php composer.phar check
```

この composer で実行するコマンドは、 composer.json の scripts に記載があります。