Closed53

laravel inertia vue

mbsmbs

組み方をまとめておきたい

mbsmbs

vueを流行らせるぞ!!!

mbsmbs

適宜git commitしていくのがコツだよ

mbsmbs
$ composer create-project laravel/laravel test-laravel-inertia-vue3-ts

$ cd test-laravel-inertia-vue3-ts
$ php artisan serv

これで http://localhost:8000 を見ればHOMEが開けるはず

mbsmbs

次に Inertia.js を入れる
これは Laravel で Vue や React の SPA を使うためのもの
NUXT / NEXT の代わりに Laravel って思えばOK

https://inertiajs.com/

Breeze や Jetstream といったテンプレートもあるが、置き換えが面倒くさいので手動で入れる

$ composer require inertiajs/inertia-laravel
$ touch ./resources/views/app.blade.php

app.blade.php が基本となるHTML
このページが呼び出され、SPAが構築される

resources/views/app.blade.php
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
    @vite('resources/js/app.js')
    @inertiaHead
  </head>
  <body>
    @inertia
  </body>
</html>

次にmiddlewareを作成する
このmiddlewareはSPA側とのグローバル変数定義だと思ってOK

$ php artisan inertia:middleware

これを web Kernel に追加する

app/Http/Kernel.php
    protected $middlewareGroups = [
        'web' => [
            ...,
            \App\Http\Middleware\HandleInertiaRequests::class,
        ],
    ],
mbsmbs

次にフロント側

$ npm install @inertiajs/vue3

resources/js/app.js を上書きして以下に書き換える
これは各フレームワークのbootstrapの要素
(そのため、既存のbootstrap.jsは削除して問題ない)

resources/js/app.js
import { createApp, h } from 'vue'
import { createInertiaApp } from '@inertiajs/vue3'

createInertiaApp({
  resolve: name => {
    const pages = import.meta.glob('./pages/**/*.vue', { eager: true })
    return pages[`./pages/${name}.vue`]
  },
  setup({ el, App, props, plugin }) {
    createApp({ render: () => h(App, props) })
      .use(plugin)
      .mount(el)
  },
})

vite に vue の解釈ができるようにする

$ npm install -D @vitejs/plugin-vue

vite.config.js に vue() を増やす

vite.config.js
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import vue from "@vitejs/plugin-vue";

export default defineConfig({
    plugins: [
        vue(),
        laravel({
            input: ['resources/css/app.css', 'resources/js/app.js'],
            refresh: true,
        }),
    ],
});
mbsmbs

適当にページを作ってみる

ページは resources/js/pages/... に記載する
Laravel的には先頭大文字推奨なのだが、vueの流儀に則って小文字にする

resources/js/pages/index.vue
<template>
    <div>
        <Head title="ようこそ" />
        <h1>ようこそ</h1>
        <p>こんにちは {{ name }} さん。</p>
    </div>
</template>

<script setup>
import { Head } from '@inertiajs/vue3'

defineProps({ user: String })
</script>

このルーティングは web route に書き込む
これは blade とかと同じ書き方
変数を渡すと、defineProps として受け取れる

routes/web.php
Route::get('/', function () {
    return Inertia::render('index', [
        'name' => '山田太郎',
    ]);
})->name('home');

これは controller に書くことも可能(推奨)

mbsmbs

開発中の起動には2つのコマンドの常時実行が必要

$ npm run dev

$ php artisan serve

mbsmbs

Inertia はデフォルトだと js なので ts に書き換えていく

https://advanced-inertia.com/blog/typescript

$ npm install -D typescript @vue/tsconfig
$ touch tsconfig.json

tsconfig.json@vue/tsconfig をベースにする

tsconfig.json
{
    "extends": "@vue/tsconfig/tsconfig.dom.json",
    "include": [
        "resources/**/*.ts",
        "resources/**/*.d.ts",
        "resources/**/*.vue"
    ],
    "compilerOptions": {
        "baseUrl": ".",
        "types": ["vite/client"],
        "paths": {
            "@/*": ["resources/js/*"]
        }
    }
}

resources/jsresources/ts に置き換える

app.jsapp.ts に変更する

resources/ts/app.ts
import { createApp, h } from 'vue'
import { createInertiaApp } from '@inertiajs/vue3'
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
import type { DefineComponent } from "vue";

createInertiaApp({
  resolve: (name) => resolvePageComponent(
    `./pages/${name}.vue`,
    import.meta.glob<DefineComponent>("./pages/**/*.vue")
  ),
  setup({ el, App, props, plugin }) {
    createApp({ render: () => h(App, props) })
      .use(plugin)
      .mount(el)
  },
})

先程作ったpage要素も ts に変換する

resources/ts/pages/index.vue
<script setup lang="ts">
import { Head } from '@inertiajs/vue3'

defineProps<{
  name: string,
}>()
</script>

以下のjsパスをtsに書き換える

resources/views/app.blade.php
tsconfig.json
vite.config.ts

@vite('resources/ts/app.ts')  

'resources/ts/app.ts'  

"@/*": ["resources/ts/*"]

npm run dev を実行してエラーが出なければ成功!!

mbsmbs

vue の auto import を入れる

https://reffect.co.jp/vue/unplugin-import/

unplugin シリーズを入れると、NUXT と同じように無駄な import を書かなくて済むようになる
無くても問題はないが、入れて損は無い

$ npm install -D unplugin-vue-components unplugin-auto-import

vite.config.js に組み込む

vite.config.js
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import vue from "@vitejs/plugin-vue";
import Components from 'unplugin-vue-components/vite';
import AutoImport from 'unplugin-auto-import/vite';

export default defineConfig({
  plugins: [
    vue(),
    Components({
      dts: 'resources/ts/types/components.d.ts',
      dirs: ['resources/ts/components'],
    }),
    AutoImport({
      dts: 'resources/ts/types/auto-imports.d.ts',
      dirs: ['resources/ts/composables'],
      imports: ['vue'],
    }),
    laravel({
      input: ['resources/ts/app.ts'],
      refresh: true,
    }),
  ],
});

components は dirs にあるvueコンポーネントをvueのどこからでも呼び出せるようになる

auto-import は imports dirs の設定にファイルを入れると、vueのどこからでも呼び出せるようになる
vueはプリセットがある
また、特定のフォルダ(composable)や外部ライブラリ(imports)にも使うことが可能

これらの生成ファイルは types ってフォルダに入れるのがおすすめ
なので resources/types/** に型ファイルを作成するようにする
git に入れるものでは無いので .gitignore に入れておく

resources/ts/types/.gitignore
auto-imports.d.ts
components.d.ts

npm run dev など、ビルドが走ったときにファイルが更新される
初めての起動時はIDEの再起動などが必要になる場合あり

試しに使ってみる

resources/ts/composables/useCounter.ts
export const useCounter = () => {
  const counter = ref<number>(0)

  const increment = () => { counter.value ++ }
  const decrement = () => { counter.value -- }

  return {
    value: readonly(counter),
    increment,
    decrement,
  }
}
resources/ts/pages/index.vue
<template>
  <div>
    <Head title="ようこそ" />
    <h1>ようこそ</h1>
    <p>こんにちは {{ name }} さん。</p>
    <Counter />
  </div>
</template>

<script setup lang="ts">
import { Head } from '@inertiajs/vue3'

defineProps<{
  name: string,
}>()
</script>
resources/ts/components/Counter.vue
<template>
  <div>
    <p>counter: {{ counter.value }}</p>
    <button @click="counter.increment()">+</button>
    <button @click="counter.decrement()">-</button>
  </div>
</template>

<script setup lang="ts">
const counter = useCounter()
</script>

こんな感じ

@inertiajs/vue3 も自動インポートに適用可能だが、名称がかぶると大変なので、一旦は適用しない

mbsmbs

ziggy を入れる

https://github.com/tighten/ziggy

これは Laravel の route() 関数をフロントでも使えるようにするもの

$ composer require tightenco/ziggy

$ npm install ziggy-js
$ npm install -D @types/ziggy-js

これを使えるようにする
公式だと、vueのpluginで入れるように書いてあるが、少々面倒くさい
また隠匿されるわけでもなく、route型も特に効かないので、auto-import で入れるほうが簡単

https://github.com/tighten/ziggy/discussions/565

mbsmbs
$ composer require tightenco/ziggy
$ npm install ziggy-js
$ npm install -D @types/ziggy-js

vite.config.js の autoImport に生やす

vite.config.js
    AutoImport({
      dts: 'resources/ts/types/auto-imports.d.ts',
      dirs: ['resources/ts/composables'],
      imports: [
        'vue',
        {
          'ziggy-js': [['default', 'route']],
        }
      ],
    }),

これは import * as route fron 'ziggy-js' と同義らしい

app.blade.php に @routes を増やす
これにURL配列が展開される

resources/views/app.blade.php
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
    @routes
    @vite('resources/ts/app.ts')
    @inertiaHead
  </head>
  <body>
    @inertia
  </body>
</html>

そして indev.vue にでも書いてみる

console.log(route('home'))

baseURLも勝手に展開される

残念ながら typehint はできないみたい...(調査中)

mbsmbs
$ npm install --save-dev eslint eslint-plugin-vue eslint-config-standard vite-plugin-eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin
    "scripts": {
        "lint": "eslint resources/**/*.{js,ts,vue}"
    },
mbsmbs
vite.config.js
import { defineConfig } from 'vite'
import laravel from 'laravel-vite-plugin'
import vue from '@vitejs/plugin-vue'
import eslint from 'vite-plugin-eslint'
import Components from 'unplugin-vue-components/vite'
import AutoImport from 'unplugin-auto-import/vite'

const isProd = process.env.NODE_ENV = 'production'
export default defineConfig({
  base: './',

  plugins: [
    vue(),
    Components({
      dts: 'resources/ts/types/components.d.ts',
      dirs: ['resources/ts/components'],
    }),
    AutoImport({
      dts: 'resources/ts/types/auto-imports.d.ts',
      dirs: ['resources/ts/composables'],
      imports: [
        'vue',
        {
          'ziggy-js': [['default', 'route']],
        }
      ],
      eslintrc: {
        enabled: true,
      },
    }),
    !isProd && eslint({
      lintOnStart: true,
      include: 'resources/ts/**/*.{js,jsx,ts,tsx,vue}',
      fix: true,
    }),
    laravel({
      input: ['resources/ts/app.ts'],
      refresh: true,
    }),
  ],
})

mbsmbs
.eslintrc.json
{
    "env": {
        "node": true,
        "commonjs": true
    },
    "extends": [
        "eslint:recommended",
        "plugin:@typescript-eslint/recommended",
        "plugin:vue/vue3-recommended",
        "./.eslintrc-auto-import.json",
        "standard"
    ],
    "parserOptions": {
        "parser": "@typescript-eslint/parser",
        "sourceType": "module"
    },
    "rules": {
        "comma-dangle": ["warn", "only-multiline"],
        "no-unused-vars": "warn",
        "@typescript-eslint/no-unused-vars": "warn",
        "vue/no-unused-vars": "warn",
        "require-await": "warn",
        "no-unreachable": "warn",

        "vue/multi-word-component-names": "off",
        "vue/no-v-model-argument": "off"
    }
}

mbsmbs

ついでに vite.config.js を vite.config.ts に置き換える

mbsmbs

larastan

composer require nunomaduro/larastan --dev
phpstan.neon
includes:
    - ./vendor/nunomaduro/larastan/extension.neon

parameters:

    paths:
        - app/

    # Level 9 is the highest level
    level: 5

#    ignoreErrors:
#        - '#PHPDoc tag @var#'
#
#    excludePaths:
#        - ./*/*/FileToBeExcluded.php
#
#    checkMissingIterableValueType: false

mbsmbs

pint は最初から入ってるみたい

mbsmbs
pint.json
{
    "preset": "laravel",
    "rules": {
        "no_superfluous_phpdoc_tags": false,
        "phpdoc_separation": false
    }
}

mbsmbs

lint と stan を追加する

    "scripts": {
        "lint": [
            "./vendor/bin/pint"
        ],
        "stan": [
            "./vendor/bin/phpstan analyse --memory-limit=2G"
        ]
    },
mbsmbs

taiwind追加

npm install -D tailwindcss@latest postcss@latest autoprefixer@latest

npx tailwindcss init

追記

tailwind.config.js
/** @type {import('tailwindcss').Config} */
export default {
  content: [
    './resources/**/*.{vue,js,ts,php}'
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

mbsmbs
resources/css/app.css
@import "tailwindcss/base";
@import "tailwindcss/components";
@import "tailwindcss/utilities";

mbsmbs

tsに css を食わせる

resources/ts/app.ts
import { createApp, h } from 'vue'
import { createInertiaApp } from '@inertiajs/vue3'
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers'
import type { DefineComponent } from 'vue'

import '../css/app.css'

createInertiaApp({
mbsmbs

postcss に食わせる

postcss.config.cjs
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}

mbsmbs

デフォルトのデザインが消えたが、ちゃんとtailwindの色がついている

mbsmbs

stylelintをeslintに組み込む

mbsmbs
npm install eslint-config-stylelint --save-dev

{
"extends": ["stylelint"]
}

mbsmbs
.stylelintrc.json
{
  "extends": "stylelint-config-standard",
  "rules": {}
}
mbsmbs

stylelintはまた今度やる

mbsmbs
vite.config.ts
import { PrimeVueResolver } from 'unplugin-vue-components/resolvers'

const isProd = process.env.NODE_ENV = 'production'
export default defineConfig({
  base: './',

  plugins: [
    vue(),
    Components({
      dts: 'resources/ts/types/components.d.ts',
      dirs: ['resources/ts/components'],
      resolvers: [PrimeVueResolver()],
    }),
mbsmbs
app.ts
import { createApp, h } from 'vue'
import { createInertiaApp } from '@inertiajs/vue3'
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers'
import type { DefineComponent } from 'vue'
import PrimeVue from 'primevue/config'
import Tailwind from 'primevue/passthrough/tailwind'

import '../css/app.css'

createInertiaApp({
  resolve: (name) => resolvePageComponent(
    `./pages/${name}.vue`,
    import.meta.glob<DefineComponent>('./pages/**/*.vue')
  ),
  setup ({ el, App, props, plugin }) {
    createApp({ render: () => h(App, props) })
      .use(plugin)
      .use(PrimeVue, { unstyled: true, pt: Tailwind })
      .mount(el)
  },
})

mbsmbs

tailwind.config.jsにはんえい

tailwind.config.js
/** @type {import('tailwindcss').Config} */
export default {
  content: [
    './resources/**/*.{vue,js,ts,php}',
    './node_modules/primevue/**/*.{vue,js,ts,jsx,tsx}',
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

mbsmbs

data-pc-section ってのがキー
それに対して未指定ならclassをつけられる

mbsmbs

clockwork ide-helper lang を入れていく

mbsmbs
composer require --dev barryvdh/laravel-ide-helper
php artisan ide-helper:generate

php artisan ide-helper:generate
php artisan ide-helper:models --nowrite
php artisan ide-helper:meta

db に繋がってないとエラーだよ

ignore二追加する

_ide_helper.php
_ide_helper_models.php
.phpstorm.meta.php
mbsmbs
composer require laravel-lang/common --dev
php artisan lang:add ja
php artisan lang:update
mbsmbs

config を調整していく

  • app.php
    • 'timezone' => 'Asia/Tokyo'
    • 'locale' => 'ja',
    • 'faker_locale' => 'ja_JP'
mbsmbs

自分以外をgitignoreするとき

*
!.gitignore

mbsmbs

ziggy は vendor:publish がないため、自作する

config/ziggy.php
<?php

return [

    'output' => [
        'path' => 'resources/ts/types/ziggy.js'
    ],
];

php artisan ziggy:generate --types-only
このスクラップは2023/10/09にクローズされました