laravel + vue.js でチャットと他のコンポーネント

(2025-02-24)

laravel + vue.js でコンポーネントを作っていこうと思ったのですが、動かそうとすると今度はリアルタイムチャットが動かなくなりました。

vue.js は「id=“app”」に html 要素を貼り付けますが、それを二重にすることはできません。

app.blade.php に id=“app”

layouts/app.blade.php はデフォルトで、

<body class="bg-gray-100">
<div id="app">

が設定されており、通常はこの「<div id="app">」をターゲットにして html 要素がマウントされます。

import './bootstrap'; // Laravel の初期化
import { createApp } from 'vue';
import registerHelloWorld from './helloWorld';
import registerGoodBye from './goodbye';
const app = createApp({});
registerHelloWorld(app);
registerGoodBye(app);
app.mount('#app');

この場合、helloWorld と goodbye は外部ファイル化されていますが、外部ファイルで vue を読み込んで作ったものを最終的に app にマウントします。

ところが、chat.js ではその内部でもマウントするので二重にマウントしてしまうのです。

resources/js/chat.js
.component('chat-messages', ChatMessages)
.component('chat-form', ChatForm)
.mount('#chat-app');

こういうことをするともちろんエラーになります。
どうしたらいいんでしょう?

chat.js を外部ファイルにする

chat.js を外部ファイル化して最後にマウントすればいいのではないかと思ったのですが、結局はうまく行きませんでした。

chatGPT が示したコードは、

chatApp.js
// chatApp.js
import { createApp, ref } from 'vue';
import ChatMessages from './components/ChatMessages.vue';
import ChatForm from './components/ChatForm.vue';
export function createChatApp() {
const app = createApp({
setup() {
const messages = ref([]);
fetchMessages();
window.Echo.private('chat')
.listen('MessageSent', (e) => {
messages.value.push({
message: e.message.message,
user: e.user
});
});
function fetchMessages() {
axios.get('/messages').then(response => {
messages.value = response.data;
});
}
function addMessage(message) {
messages.value.push(message);
axios.post('/messages', message).then(response => {
// success
});
}
return {
messages,
fetchMessages,
addMessage
};
}
});
app.component('chat-messages', ChatMessages);
app.component('chat-form', ChatForm);
return app; // アプリケーションインスタンスを返す
}

それを app.js でインポート。

import { createChatApp } from './chatApp';

でも動きませんでした。

この辺のコードは実は理解できません。JavaScript は本当にわかりにくい。

base-layout.blade.php

そこで、id=“chat-app”をデフォルトにする他のブレードを作成しました。

しかし、ここでも一苦労。

chatGPT の説明では

<x-app-layout>

は layouts/app.bla.de.php をベースにしているから、

<x-base-layout>

は当然 layouts/base.blade.php をベースにするのかと思ったのですが、これが大間違い。

これは、

resources/views/components/base-layout.blade.php

をベースにしているのです。こんなことわかりません。laravel の最も厄介なのはこういう点です。どこかに設定ファイルがあるはずなのにそれがわかりません。

chatGPT もいい加減なことばかりコメントします。それでかなり時間を浪費しました。

resource/views/components/base-layout.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>
<!-- Fonts -->
<link rel="dns-prefetch" href="//fonts.bunny.net">
<link href="https://fonts.bunny.net/css?family=Nunito" rel="stylesheet">
<!-- Scripts -->
@vite(['resources/css/app.css', 'resources/js/app.js'])
@stack('scripts')
</head>
<body class="bg-gray-100">
<div id="chat-app">
@include('partials.header')
<main class="py-4">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
{{ $slot }}
</div>
</main>
</div>
<script>
function toggleDropdown() {
document.getElementById("dropdown-menu").classList.toggle("hidden");
}
</script>
</body>
</html>

一応動く

base-layout.blade.php に id=“chat-app”を設定して、それをターゲットに chat.js を編集するとリアルタイムチャットは動きます。

resources/views/chat.blade.php
<x-base-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
会員チャット
</h2>
</x-slot>
<div class="max-w-7xl mx-auto p-6">
<div class="bg-white shadow-md rounded-lg">
<div class="px-4 py-3 bg-gray-200 border-b border-gray-300 font-semibold">
会員チャット
</div>
<div class="px-4 py-6">
<!-- チャットメッセージの表示部分 -->
<chat-messages :messages="messages" :user="{{ auth()->user() }}"></chat-messages>
</div>
<div class="px-4 py-3 bg-gray-200 border-t border-gray-300">
<!-- メッセージ送信部分 -->
<chat-form v-on:messagesent="addMessage" :user="{{ auth()->user() }}"></chat-form>
</div>
</div>
</div>
@push('scripts')
@vite(['resources/js/chat.js'])
@endpush
</x-base-layout>

chat.js

resources/js/chat.js
import { createApp, ref } from 'vue';
import ChatMessages from './components/ChatMessages.vue';
import ChatForm from './components/ChatForm.vue';
createApp({
setup () {
const messages = ref([]);
fetchMessages();
window.Echo.private('chat')
.listen('MessageSent', (e) => {
messages.value.push({
message: e.message.message,
user: e.user
});
});
function fetchMessages() {
axios.get('/messages').then(response => {
messages.value = response.data;
});
}
function addMessage(message) {
messages.value.push(message);
axios.post('/messages', message).then(response => {
// success
});
}
return {
messages,
fetchMessages,
addMessage
}
}
})
.component('chat-messages', ChatMessages)
.component('chat-form', ChatForm)
.mount('#chat-app');

別ウインドウにしてやればいいんでしょうけど、理解が浅いので最終的には挫折するような嫌な予感が。

chatGPT は限定された領域ではすごい実力を発揮しますが、ちょっと外れるととんでもない提案をしてきます。

多くの無駄なことを要求してきますが、その中でダメな要素を排除する能力を持っていないと引きずれられて猛烈に時間を浪費します。賢く付き合わないとひどい目に会います。

pusher にかかる費用

あるサイトによれば、

「無料プランでは、月間200万のメッセージと100の同時接続が許可されています。」

私は実験として散々利用しましたが、それでも 1 週間で 200 も行きません。この 10,000 倍もひと月に利用するとは思えません。

それを超えると有償のものでは、最低で 49 ドル/月かかるようです。でも、月間200万のメッセージなんて絶対超えませんね。