Laravel9 × React InertiaでTypeScript開発

## はじめに
Laravel9 × React Inertiaの組み合わせでTypeScript環境を構築したかったのですが、思いのほか手間取ったので、簡単に手順を残しておきます。

## 開発環境
* PC -> M1 MacBook Pro 2021
* php -> 8.1.5
* Laravel -> 9.20.0
* vite -> 2.9.14
* Docker Desktop

## Laravel環境構築
Laravel Sailを使ってLaravelの環境をローカルに構築。
```
$ curl -s "https://laravel.build/アプリ名?with=mysql" | bash

$ cd アプリ名

$ ./vendor/bin/sail up -d
```

## Inertia React環境構築
認証機能も使用したいためbreezeをreact指定でインストール。
```
$ ./vendor/bin/sail composer require laravel/breeze --dev

$ ./vendor/bin/sail artisan breeze:install react

$ npm install && npm run dev
```

## TypeScript環境の初期設定
### 必要そうなパッケージをインストール
```
npm install -D typescript @types/node @types/react @types/react-dom 
```

### app.jsの編集
app.jsの拡張子をtsxに変更して、コードを下記のように修正。
```
import "./bootstrap";
import "../css/app.css";

import React from "react";
import { render } from "react-dom";
import { createInertiaApp } from "@inertiajs/inertia-react";
import { InertiaProgress } from "@inertiajs/progress";
import { resolvePageComponent } from "laravel-vite-plugin/inertia-helpers";

const appName =
    window.document.getElementsByTagName("title")[0]?.innerText || "Laravel";

createInertiaApp({
    title: (title) => `${title} - ${appName}`,
    resolve: (name) =>
        resolvePageComponent(
            `./Pages/${name}.tsx`,
            import.meta.glob("./Pages/**/*.tsx")
        ),
    setup({ el, App, props }) {
        return render(<App {...props} />, el);
    },
});

InertiaProgress.init({ color: "#4B5563" });
```

### vite-env.d.tsの追加
vite-env.d.tsファイルをapp.tsxと同じ階層に作成して、下記のコードを記述。
```
interface ImportMeta {
    readonly glob: any;
}
declare var route: any;
```

### tsconfig.jsonの作成
下記のコードをターミナルで実行すると、tsconfig.jsonが作成される。
```
$ tsc --init   
```
tsconfig.jsonの中身を下記のコードに書き換える。
```
{
    "compilerOptions": {
        "baseUrl": ".",
        "target": "ESNext",
        "useDefineForClassFields": true,
        "lib": ["DOM", "DOM.Iterable", "ESNext"],
        "allowJs": false,
        "skipLibCheck": true,
        "esModuleInterop": false,
        "allowSyntheticDefaultImports": true,
        "strict": true,
        "forceConsistentCasingInFileNames": true,
        "module": "ESNext",
        "moduleResolution": "Node",
        "resolveJsonModule": true,
        "isolatedModules": true,
        "noEmit": true,
        "jsx": "react-jsx",
        "paths": {
            "@/*": ["resources/js/*"]
        }
    },
    "include": ["resources/js/**/*"],
    "references": [{ "path": "./tsconfig.node.json" }]
}
```
### tsconfig.node.jsonの作成
tsconfig.jsonと同じ階層にtsconfig.node.jsonを作成して、下記のコードを記述。
TypeScriptの設定ファイルがtsconfig.jsonとtsconfig.node.jsonで二つあるのは、TypeScriptを実行する環境が二つあるから、とのこと。

https://stackoverflow.com/questions/72027949/why-does-vite-create-two-typescript-config-files-tsconfig-json-and-tsconfig-nod
```
{
    "compilerOptions": {
        "composite": true,
        "module": "esnext",
        "moduleResolution": "node"
    },
    "include": ["vite.config.ts"]
}
```

### app.blade.phpの内容を編集
app.blade.phpのコードを下記に修正。
```
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <title inertia>{{ config('app.name', 'Laravel') }}</title>

        <!-- Fonts -->
        <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700&display=swap">

        <!-- Scripts -->
        @routes
        @viteReactRefresh
        @vite('resources/js/app.tsx')
        @inertiaHead
    </head>
    <body class="font-sans antialiased">
        @inertia
    </body>
</html>
```

### vite.config.jsの編集
vite.config.jsの拡張子をtsに変更した後に、コードを下記の内容に修正。
```
import { defineConfig } from "vite";
import laravel from "laravel-vite-plugin";
import react from "@vitejs/plugin-react";

export default defineConfig({
    plugins: [
        laravel({
            input: "resources/js/app.tsx",
            refresh: true,
        }),
        react(),
    ],
});
```

### tailwind.config.jsの内容を変更
```
const defaultTheme = require("tailwindcss/defaultTheme");

/** @type {import('tailwindcss').Config} */
module.exports = {
    content: [
        "./vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php",
        "./storage/framework/views/*.php",
        "./resources/views/**/*.blade.php",
        "./resources/js/**/*.tsx",
    ],

    theme: {
        extend: {
            fontFamily: {
                sans: ["Nunito", ...defaultTheme.fontFamily.sans],
            },
        },
    },

    plugins: [require("@tailwindcss/forms")],
};
```



## 認証機能コンポーネントをTypeScript仕様に変更
次にbreezeによって生成されたAuth用コンポーネントを全てTypeScript仕様に書き換える必要があります。これはかなり面倒なので、下記のGitHubリポジトリからコードを持ってくることをオススメします。
resources/jsフォルダ配下のComponents・Layouts・Pagesフォルダにある全ファイルを、下記のリポジトリのものに置き換えてください。
これが終われば設定完了です。

https://github.com/shomffl/breeze-typescript-component

## viteでの型チェックについて
ここでちょっとした注意点が1つ。ViteってTypeScriptを使うことはできるのですが、型チェックは実行してくれないんですよね...

>Vite は .ts ファイルをインポートすることをサポートしています。
Vite は .ts ファイルに対してトランスパイルをするだけで、型チェックは 実行しません。

https://ja.vitejs.dev/guide/features.html#hot-module-replacement

正直、これはかなり違和感がありました。npx create-react-appで作ったアプリは型が間違っていると、処理が停止してブラウザ上にデカデカとエラーが出たりしたのですが、Viteではそういうのが全くないです。
写真のようにどっからどう見てもやばい型エラーがあっても、何事もなかったかのようにアプリは動き続けます。

![スクリーンショット 2022-07-16 21.29.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2359210/b1c4572a-8ac5-f90b-673c-44e10a29c12c.png)
![スクリーンショット 2022-07-16 21.30.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2359210/1f80ddbd-befc-4d67-cff0-ca5447c656b3.png)

まあ、VScodeなどを使っていれば、赤波線が入ったりするので、そこまで心配するほどのことではないのですが、最初はやはり違和感がありました。
一応、```tsc --noEmit```を実行すれば、こんな感じでチェックはできるので、GitHubにpushする前などには、実行して確認するのがベストだと思います！
![スクリーンショット 2022-07-16 21.34.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2359210/a6888ebd-e2bb-1e6d-9c60-26901a82f62f.png)