久々にLaravelを触ったら、コンテナやフロントエンド開発が簡単になっていた

最近、ちょっと時間ができたのでキャッチアップのために `Laravel8`を触ってみました。
知識が`Laravel5`前後で止まっていたので、進化したエコシステムに驚いたので書き記しておきます。
**ちなみに、次のLTSは`Laravel9`(2022/1/25リリース予定)になるので、この手の機能をプロダクションで使うときは、しかるべき時にアップデートをしましょう。**

## コンテナを使っての開発環境構築が楽になってる！(Laravel Sail)
ちょっと前まではコンテナで`Laravel`を動かす時には自分で適当な`DockerFile`を書くか、`Laradoc`使って構築するケースが多かったと思います。
`Laradock`は大きくなりすぎて余計な機能もあったりしたので、私は自前でDockerFileを書く派でした。これだけでも、ちょっと「面倒くさいな。。。」なんて思っていました。
ここで`Laravel Sail`の登場です。
`Sail`を使っての環境構築は、

```sh
$ curl -s "https://laravel.build/[ディレクトリ名]" | bash
$ cd [ディレクトリ名]
# バックグラウンドで動かすなら、-dを付ければ OK
# 下記の様にaliasを設定しておくとsailコマンドを使うのが簡単になる
# alias sail='bash vendor/bin/sail'
$ ./vendor/bin/sail up
```

たったこれだけで、PHP + MySql + mailhog + redis + selenium を使ったプロジェクトが立ち上がります。
seleniumはブラウザテストの時なんかに便利ですね。

```sh
# 立ち上げたときの表示
$ sail up -d
lara-sample_laravel.test_1   start-container                  Exit 255   0.0.0.0:80->80/tcp,:::80->80/tcp, 8000/tcp                                        
Shutting down old Sail processes...
Creating network "lara-sample_sail" with driver "bridge"
Creating lara-sample_mysql_1       ... done
Creating lara-sample_selenium_1    ... done
Creating lara-sample_mailhog_1      ... done
Creating lara-sample_meilisearch_1 ... done
Creating lara-sample_redis_1       ... done
Creating lara-sample_laravel.test_1 ... done
```

ちなみに、

```sh
sail artisan sail:publish
```

でDockerFileなどが編集できる様になるので、独自のPHP拡張などを入れるのも簡単ですね:tada:

## React(Vueも)+認証の構築が楽になってる！(Laravel Breeze)
とりあえず、`Sail`を使ってプロジェクトが動かせる様になったら認証を作って行きます。
元々は`laravel/ui`があったのですが、こちらか `Jetstream`を使っていく方針の様です。
細かな認証の組み込みが不要な場合や初学者には`Breeze`がお薦めですね。

認証をReactやVueで作っていくためには、

```sh
php artisan breeze:install vue
# Vueの場合はこちら
php artisan breeze:install react

npm install
npm run dev
php artisan migrate
```

で導入が可能です。ただ、初期インストール時に作成されるのは`JavaScript`になるので`TypeScript`を使う時には下記の修正が必要です。
* `webpack.mix.js`の修正
* `tsconfig.json`の作成
* `resources/js`配下のTypeScript化

下記の様にオプションをつけられる様になったら、もっと楽になると思っています。

```sh
# こちらはできません！
php artisan breeze:install react --typescript
```

ただ、非公式ですが`Breeze + React + TypeScript`の[GitHubリポジトリ](https://github.com/ecavalcanti/laravel-breeze-inertia-react-typescript)があるので問題なく対処できるのではないでしょうか。

### inertia.jsの衝撃(modern monolithとの出会い)
`Breeze`を使ったReactの導入には[inertia.js](https://inertiajs.com/)が使われています。

> Build single-page apps, without building an API.(SPAをAPIを使うことなく構築できる)

ということで、Reactを使ったSPAのルーティング、サーバサイドとのデータの受け渡しなどを`blade`と同じ様な感覚で行うことができる様になっています。
おなじみの`routes/web.php`のルーティング情報を使えるのので、メンテナンスを行う上で非常に楽になると思います。
(下記は会員登録画面のサンプル。`post(route('register'));`が、そこに該当します。Laravelからは`Props`経由で値を受け取れています。)

```typescript
import Button from '@/Components/Button';
import Guest from '@/Layouts/Guest';
import Input from '@/Components/Input';
import Label from '@/Components/Label';
import React, { useEffect } from 'react';
import ValidationErrors from '@/Components/ValidationErrors';
import { InertiaLink } from '@inertiajs/inertia-react';
import { useForm } from '@inertiajs/inertia-react';
import route from 'ziggy-js';

type Props = {
    element1: string
}

export default function Register({ element1 }: Props) {
    const { data, setData, post, processing, errors, reset } = useForm({
        name: '',
        email: '',
        password: '',
        password_confirmation: '',
    });

    useEffect(() => {
        return () => {
            reset('password', 'password_confirmation');
        };
    }, []);

    const onHandleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
        setData(event.target.name as "name" | "email" | "password" | "password_confirmation", event.target.type === 'checkbox' ? event.target.checked + '' : event.target.value);
    };

    const submit = (e: React.SyntheticEvent) => {
        e.preventDefault();

        post(route('register'));
    };


    return (
        <Guest>
            <ValidationErrors errors={errors} />
            <form onSubmit={submit}>
                <div>
                    <Label forInput="name" value="Name" />
                    {element1}
                    <Input
                        type="text"
                        name="name"
                        value={data.name}
                        className="mt-1 block w-full"
                        autoComplete="name"
                        isFocused={true}
                        handleChange={onHandleChange}
                        required
                    />

                </div>

                <div className="mt-4">
                    <Label forInput="email" value="Email" />

                    <Input
                        type="email"
                        name="email"
                        value={data.email}
                        className="mt-1 block w-full"
                        autoComplete="username"
                        handleChange={onHandleChange}
                        required
                    />
                </div>

                <div className="mt-4">
                    <Label forInput="password" value="Password" />

                    <Input
                        type="password"
                        name="password"
                        value={data.password}
                        className="mt-1 block w-full"
                        autoComplete="new-password"
                        handleChange={onHandleChange}
                        required
                    />
                </div>

                <div className="mt-4">
                    <Label forInput="password_confirmation" value="Confirm Password" />

                    <Input
                        type="password"
                        name="password_confirmation"
                        value={data.password_confirmation}
                        className="mt-1 block w-full"
                        handleChange={onHandleChange}
                        required
                    />
                </div>

                <div className="flex items-center justify-end mt-4">
                    <InertiaLink href={route('login')} className="underline text-sm text-gray-600 hover:text-gray-900">
                        Already registered?
                    </InertiaLink>

                    <Button className="ml-4" processing={processing}>
                        Register
                    </Button>
                </div>
            </form>
        </Guest>
    );
}

```
フロントエンドとサーバサイドを分業せずに開発しているプロダクトなどでは非常に有用に思います。

他にもリリースノートを見ると

* Models Directoryの導入(結局作るんかい！と思いましたが:sweat_smile:)
* Migration Squashing(マイグレーションを圧縮してディレクトリの肥大化を防ぐ)

などなど、面白そうな機能がたくさんありました！
わずか1年強、キャッチアップから離れていただけなのですが進化の速さに驚かされます。
勉強は続けないといけないですね。