Top View


Author Daiki Urata

Laravel9 + Inertia.js + Vue3 + TypeScript環境を構築する

2023/01/05

はじめに

Inertia.jsはモノリシックなSPAアプリを開発する場合には非常に強力なライブラリで、バックエンド側はLaravel/Rails、フロントエンド側はVue2/Vue3/React/Svelteで利用できます。

今回はLaravelのスターターキットBreezeを使って、Laravel + Inertia.js + Vue3環境を構築し、TypeScriptを導入する手順を紹介します。

環境

  • Vite v4.0.0
  • Vue v3.2.41
  •  Laravel v9.19

Laravelプロジェクトを作成する

最初にLaravelプロジェクトを新規で作成します。

スターターキットのBreeze を利用するとInertiaとVue3がセットアップ済みのプロジェクトが作成されます。

$ curl -s "https://laravel.build/laravel-inertia-vue-app?with=pgsql" | bash
$ cd laravel-inertia-vue-app

# Dockerコンテナ起動
$ ./vendor/bin/sail up -d

# Breezeのインストール
$ ./vendor/bin/sail composer require laravel/breeze --dev
$ ./vendor/bin/sail artisan breeze:install vue

# マイグレーションコマンド実行
$ ./vendor/bin/sail artisan migrate

# Viteの開発サーバー起動
$ ./vendor/bin/sail npm install
$ ./vendor/bin/sail npm run dev

無事にViteの開発サーバーが起動したら http://localhost へアクセスすると画面が見られるようになります。

Seedファイルを用意する

Breezeですでに用意されたダッシュボード画面へログインしたいので、初期ユーザーを作成するSeedファイルを用意します。

database/seeders/DatabaseSeeder.phpを以下のように編集します。

<?php

namespace Database\Seeders;

// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     *
     * @return void
     */
    public function run()
    {
        \App\Models\User::factory(10)->create();

        \App\Models\User::factory()->create([
            'name' => 'Test User',
            'email' => 'test@example.com',
        ]);
    }
}

Seedコマンドを実行してDBにユーザーデータを追加します。

$ ./vendor/bin/sail artisan db:seed

http://localhost/login へアクセスして test@example.com/password でログインするとダッシュボード画面へ遷移されます。

.vueファイルをTypeScriptで書く

Breezeで作成したプロジェクトはVite環境のため、とくに設定は何もせずにそのままTypeScriptが動きます。

resources/js/Pages/Dashboard.vueを以下のように書き換えることができます。

<script setup lang="ts">
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout.vue';
import { Head, usePage } from '@inertiajs/inertia-vue3';

type Page = { auth: { user: { name: string, email: string }} }
const page = usePage<Page>();
</script>

<template>
    <Head title="Dashboard" />

    <AuthenticatedLayout>
        <template #header>
            <h2 class="font-semibold text-xl text-gray-800 leading-tight">Dashboard</h2>
        </template>

        <div class="py-12">
            <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
                <div class="font-medium text-base text-gray-800">{{ page.props.value.auth.user.name }}</div>
                <div class="font-medium text-sm text-gray-500">{{ page.props.value.auth.user.email }}</div>
            </div>
        </div>
    </AuthenticatedLayout>
</template>

usePageはInertia側で提供している関数で、バックエンド側で共通データとして渡ってきています。

渡す共通データを定義している箇所はapp/Http/Middleware/HandleInertiaRequests.phpで行われているので確認してみてください。

型チェックをする

TypeScriptを導入したので、CIなどで型チェックすると思います。

そこでvue-tscをインストールして型チェックコマンドを追加します。

$ ./vendor/bin/sail npm install -D vue-tsc

# tsconfig.jsonを作成
$ npx tsc --init

今回は作成したtsconfig.jsonを以下のように設定しました。

{
  "compilerOptions": {
    "target": "ESNext",
    "module": "ESNext",
    "moduleResolution": "node",
    "baseUrl": "./",
    "paths": {
      "@/*": ["resources/js/*"]
    },
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true,
    "allowJs": true,
	  "types": [
	    "vite/client"
	  ]
  },
  "include": ["resources/js/**/*"],
  "exclude": ["node_modules", "public"],
}

その後、package.jsonへ以下のようなコマンドを追加します。

{
	"scripts": {
        "type:check": "vue-tsc --noEmit"
    },
}

追加後は以下コマンドで型チェックが走るようになったので、CI上などで実行してチェックできるようになります。

$ ./vendor/bin/sail npm run type:check

エントリファイルを用意する

resources/js/app.jsをapp.tsに変えて以下のように記述します。

import './bootstrap';
import '../css/app.css';

import { createApp, h } from 'vue';
import { createInertiaApp } from '@inertiajs/inertia-vue3';
import { InertiaProgress } from '@inertiajs/progress';
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
import { ZiggyVue } from '../../vendor/tightenco/ziggy/dist/vue.m';

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

createInertiaApp({
    title: (title) => `${title} - ${appName}`,
    resolve: (name) => resolvePageComponent(`./Pages/${name}.vue`, import.meta.glob('./Pages/**/*.vue') as any),
    setup({ el, app, props, plugin }) {
        createApp({ render: () => h(app, props) })
            .use(plugin)
            .use(ZiggyVue, Ziggy)
            .mount(el);
    },
});

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

resources/views/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.bunny.net/css2?family=Nunito:wght@400;600;700&display=swap">

        <!-- Scripts -->
        @routes
        <!-- app.js→app.tsに変更 -->
        @vite(['resources/js/app.ts', "resources/js/Pages/{$page['component']}.vue"])
        @inertiaHead
    </head>
    <body class="font-sans antialiased">
        @inertia
    </body>
</html>

vite.config.jsもvite.config.tsへファイル名を変更します。そしてinputの記述も変えます。

import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import vue from '@vitejs/plugin-vue';

export default defineConfig({
    plugins: [
        laravel({
            input: 'resources/js/app.ts', // app.js→app.tsへ変更する
            refresh: true,
        }),
        vue({
            template: {
                transformAssetUrls: {
                    base: null,
                    includeAbsolute: false,
                },
            },
        }),
    ],
});

Ziggy関連のTSエラー

つまずいた点としてZiggy周りのTSエラーでした。

.vueファイルではZiggyが提供しているグローバル関数である route() をscriptタグやtemplateタグ内でよく使います。

これはLaravel側からレンダリング時にグローバル関数として用意されるものであるため、TSで認識されておらず、TSエラーになります。

これを解消するために別途.d.tsファイルを用意する必要がありました。

$ ./vendor/bin/sail npm install -D @types/ziggy-js ziggy-js

resources/js/index.d.tsを作成して以下を記述します。

import { Config, RouteParam, RouteParams, Router } from "ziggy-js";

declare global {
    declare function route(
        name?: undefined,
        params?: RouteParamsWithQueryOverload | RouteParam,
        absolute?: boolean,
        config?: Config
    ): Router;

    declare function route(
        name: string,
        params?: RouteParamsWithQueryOverload | RouteParam,
        absolute?: boolean,
        config?: Config
    ): string;
    declare const Ziggy: any;
}
declare module "vue" {
    interface ComponentCustomProperties {
        route: ((
            name?: undefined,
            params?: RouteParamsWithQueryOverload | RouteParam,
            absolute?: boolean,
            config?: Config
        ) => Router) &
            ((
                name: string,
                params?: RouteParamsWithQueryOverload | RouteParam,
                absolute?: boolean,
                config?: Config
            ) => string);
    }
}

もっと良さげな書き方があるかもしれませんが、とりあえず @types/ziggy-js で定義されているroute関数の型を引っ張ってきてscriptタグとtemplateタグ内で使えるようにしました。

LaravelのModelをTypeScriptの型として使えるようにする

このようなモノリシックなLaravelとTypeScriptプロジェクトで開発を続けていると発生する問題の1つに「LaravelのModelをTypeScriptで同じような型定義をしなければならない」というむだな作業が発生すると思います。

それを解決してくれるライブラリでlaravel-typegen というものがあります。

これはLaravel専用のnpmライブラリで、LaravelのModel/Enum/Routing情報をTypeScriptの型定義ファイルとして生成してくれるものです。

詳しい利用方法はここでは紹介しませんが、興味のある方はREADMEをご覧ください。

おわりに

今回Breezeで構築したプロジェクトからTypeScriptの導入までやってみました。

Vite環境だったおかげですぐにTypeScriptが動いて、ほぼjsをtsに書き換える作業だけで終わりました。

これでより安全なコードが書けるようになると思いますので、Laravel + Inertia.jsプロジェクトでTypeScriptを導入してみてはいかがでしょうか。

Daiki Urata

Daiki Urata

Twitter X

フロントエンド/モバイルアプリなどを主に開発しています。