Laravel8 + Vue3 + Typescript + Vuetify で Hello World しただけ


# はじめに
昨年のQiita記事の投稿数が5しかないという結果を受けてなんとか5を6にしてやろうと書いた記事です。

最近、Laravel8, Vue3, Typescript, Vuetify でアプリを作成したので備忘録として残します。




# 手順


## 1. Laravel アプリの作成


```terminal
curl -s "https://laravel.build/laravel-vue3-typescript-vuetify" | bash
```


## 2. docker-compose.ymlを整理

コンテナのビルドに時間かけるの嫌だったので、とりあえずlaravelのコンテナだけにしました。

```yml:docker-compose.yml
# For more information: https://laravel.com/docs/sail
version: "3"
services:
    laravel.test:
        build:
            context: ./vendor/laravel/sail/runtimes/8.1
            dockerfile: Dockerfile
            args:
                WWWGROUP: "${WWWGROUP}"
        image: sail-8.1/app
        extra_hosts:
            - "host.docker.internal:host-gateway"
        ports:
            - "${APP_PORT:-80}:80"
        environment:
            WWWUSER: "${WWWUSER}"
            LARAVEL_SAIL: 1
            XDEBUG_MODE: "${SAIL_XDEBUG_MODE:-off}"
            XDEBUG_CONFIG: "${SAIL_XDEBUG_CONFIG:-client_host=host.docker.internal}"
        volumes:
            - ".:/var/www/html"
        networks:
            - sail
        # depends_on:
        #     - mysql
        #     - redis
        #     - meilisearch
        #     - selenium
    # mysql:
    #     image: 'mysql/mysql-server:8.0'
    #     ports:
    #         - '${FORWARD_DB_PORT:-3306}:3306'
    #     environment:
    #         MYSQL_ROOT_PASSWORD: '${DB_PASSWORD}'
    #         MYSQL_ROOT_HOST: "%"
    #         MYSQL_DATABASE: '${DB_DATABASE}'
    #         MYSQL_USER: '${DB_USERNAME}'
    #         MYSQL_PASSWORD: '${DB_PASSWORD}'
    #         MYSQL_ALLOW_EMPTY_PASSWORD: 1
    #     volumes:
    #         - 'sailmysql:/var/lib/mysql'
    #     networks:
    #         - sail
    #     healthcheck:
    #         test: ["CMD", "mysqladmin", "ping", "-p${DB_PASSWORD}"]
    #         retries: 3
    #         timeout: 5s
    # redis:
    #     image: 'redis:alpine'
    #     ports:
    #         - '${FORWARD_REDIS_PORT:-6379}:6379'
    #     volumes:
    #         - 'sailredis:/data'
    #     networks:
    #         - sail
    #     healthcheck:
    #         test: ["CMD", "redis-cli", "ping"]
    #         retries: 3
    #         timeout: 5s
    # meilisearch:
    #     image: 'getmeili/meilisearch:latest'
    #     platform: linux/x86_64
    #     ports:
    #         - '${FORWARD_MEILISEARCH_PORT:-7700}:7700'
    #     volumes:
    #         - 'sailmeilisearch:/data.ms'
    #     networks:
    #         - sail
    #     healthcheck:
    #         test: ["CMD", "wget", "--no-verbose", "--spider",  "http://localhost:7700/health"]
    #         retries: 3
    #         timeout: 5s
    # mailhog:
    #     image: 'mailhog/mailhog:latest'
    #     ports:
    #         - '${FORWARD_MAILHOG_PORT:-1025}:1025'
    #         - '${FORWARD_MAILHOG_DASHBOARD_PORT:-8025}:8025'
    #     networks:
    #         - sail
    # selenium:
    #     image: 'selenium/standalone-chrome'
    #     volumes:
    #         - '/dev/shm:/dev/shm'
    #     networks:
    #         - sail
networks:
    sail:
        driver: bridge
# volumes:
#     sailmysql:
#         driver: local
#     sailredis:
#         driver: local
#     sailmeilisearch:
#         driver: local

```

## 3. Sailを使ってローカルで立ち上げ

もし何かエラーでたら、こちらご覧ください。

https://qiita.com/soma_sekimoto/items/18b0b0d2579e0cf34bae


```terminal
./vendor/bin/sail up
```


## 4. Vue3系の設定

### インストール

```terminal
./vendor/bin/sail npm install vue@next vue-loader@next @vue/compiler-sfc
```

### webpack.mix.js の編集

```js:webpack.mix.js
const mix = require("laravel-mix");

/*
 |--------------------------------------------------------------------------
 | Mix Asset Management
 |--------------------------------------------------------------------------
 |
 | Mix provides a clean, fluent API for defining some Webpack build steps
 | for your Laravel applications. By default, we are compiling the CSS
 | file for the application as well as bundling up all the JS files.
 |
 */

mix.js("resources/js/app.js", "public/js")
    .postCss("resources/css/app.css", "public/css", [
        //
    ])
    .vue(); //追記
```



## 5. コンポーネントの作成

### app.blade.php の作成

```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">

    <!-- CSRF Token -->
    <meta name="csrf-token" content="{{ csrf_token() }}">

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

    <!-- Scripts -->
    <script src="{{ asset('js/app.js') }}" defer></script>

    <!-- Fonts -->
    <link rel="dns-prefetch" href="//fonts.gstatic.com">
    <link href="https://fonts.googleapis.com/css?family=Nunito" rel="stylesheet">

    <!-- Styles -->
    <link href="{{ asset('css/app.css') }}" rel="stylesheet">
</head>

<body>
<div id="app"></div>
</body>

</html>

```


```vue:resources/js/components/App.vue
<template>
  <div>{{ message }}</div>
</template>

<script>
import { defineComponent, ref } from "vue";

export default defineComponent({
  setup() {
    const message = "Hello World";
    return {
      message
    };
  }
});
</script>

```

## 6. ルーティング追加

```php:routes/web.php

Route::get('/app', function () {
    return view('app');
});
```

## 7. ビルド

```terminal
./vendor/bin/sail npm run dev
```

![スクリーンショット 2021-12-31 18.41.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569968/c1ccbcde-43e7-7238-a72f-f0a846bdfad5.png)


とりあえず Vue3は導入できました。


## 8. Typescript

### インストール

```terminal
./vendor/bin/sail npm install typescript ts-loader @types/webpack-env -D
```


### app.js を app.tsにファイル名変更

それと同時に、webpack.mix.jsも変更

```js:webpack.mix.js
const mix = require('laravel-mix');
mix.ts('resources/js/app.ts', 'public/js') //mix.jsをmix.tsに、app.jsをapp.tsに変更
    .postCss('resources/css/app.css', 'public/css', [
        //
    ]).vue();
```

mix.ts に変更できてなかったら、ビルドした際にこんなエラー出るのできちんと変更しましょう。

```terminal
Module build failed (from ./node_modules/babel-loader/lib/index.js):
SyntaxError: /var/www/html/resources/js/components/App.vue: Unexpected token, expected ","
```

### tsconfig.json作成

```json:tsconfig.json
{
    "compilerOptions": {
        "target": "esnext",
        "module": "esnext",
        "strict": true,
        "jsx": "preserve",
        "importHelpers": true,
        "moduleResolution": "node",
        "esModuleInterop": true,
        "allowSyntheticDefaultImports": true,
        "sourceMap": true,
        "baseUrl": ".",
        "typeRoots": ["./resources/js/@types"],
        "lib": ["esnext", "dom", "dom.iterable", "scripthost"]
    },
    "include": [
        "resources/js/**/*.ts",
        "resources/js/**/*.tsx",
        "resources/js/**/*.vue"
    ],
    "exclude": ["node_modules"]
}

```

## 9. @types以下の作成


```ts:resources/js/@types/shims-vue.d.ts
declare module "*.vue" {
    import {defineComponent} from "vue";
    const component: ReturnType<typeof defineComponent>;
    export default component;
}
```

## 10. App.vue 変更

```vue:App.vue

<template>
  <div>{{ message }}</div>
</template>


<script lang='ts'> //変更
import { defineComponent, ref } from "vue"; //変更

export default defineComponent({
  setup() {
    const message = ref<string>("Hello World"); //変更
    return {
      message
    };
  }
});
</script>

```
## 11. Vuetify の導入


```terminal
sail npm install vuetify@next -D
```

### app.tsの変更

```ts:resources/js/app.ts
import "vuetify/styles";
import { createApp } from "vue";
import { createVuetify } from "vuetify";
import * as components from "vuetify/lib/components";
import * as directives from "vuetify/lib/directives";
import App from "./components/App.vue";

const app = createApp(App);
const vuetify = createVuetify({ components, directives });
app.use(vuetify);
app.mount("#app");
```

### App.vueの変更

```vue:resources/js/components/App.vue
<template>
    <v-app>
        <v-card>
            <v-card-title>{{ message }}</v-card-title>
        </v-card>
    </v-app>
</template>

<script lang="ts">
import { defineComponent, ref } from "vue";
export default defineComponent({
    setup() {
        const message = ref<string>("Hello World");
        return {
            message,
        };
    },
});
</script>
```

### tsconfig.json

```json:tsconfig.json
{
    "compilerOptions": {
        "target": "esnext",
        "module": "esnext",
        "strict": true,
        "jsx": "preserve",
        "importHelpers": true,
        "moduleResolution": "node",
        "esModuleInterop": true,
        "allowSyntheticDefaultImports": true,
        "sourceMap": true,
        "baseUrl": "./",
        "typeRoots": ["resources/js/@types"],
        "types": ["vuetify"], ← これを追記
        "lib": ["esnext", "dom", "dom.iterable", "scripthost"]
    },
    "include": [
        "resources/js/**/*.ts",
        "resources/js/**/*.tsx",
        "resources/js/**/*.vue"
    ],
    "exclude": ["node_modules"]
}

```

```terminal
sail npm run dev
```

これでいけるはず


![スクリーンショット 2022-01-09 12.58.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569968/efb75da8-9290-598d-c381-57c5af7b46d0.png)

レポジトリも載せておきます。


https://github.com/SomaSekimoto/laravel8-vue3-typescript-vuetify-demo

# おわりに

僕はこれにvue-router を加えて開発しました。

次は nuxt3 x Vuetify3 x Typescript でやってみたいですね。


間違ってるところとか、これ通りにできなければ教えてください。





## 参考記事

https://qiita.com/tronicboy/items/64a477f8b434f185f9f3

https://github.com/SomaSekimoto/laravel8-vue3-typescript-vuetify-demo

https://next.vuetifyjs.com/en