Как создать компонент Vue в Laravel через Vite
Чтобы создать компонент Vue в Laravel, подключите Vue 3 и плагин Vite, смонтируйте компонент через createApp и передайте данные из Blade через props. Сквозной пример покажет счетчик, GET- и POST-запросы, CSRF-защиту, запуск HMR и production-сборку.
- Что понадобится
- Установка Vue и плагина для Vite
- Создание компонента Vue
- Подключение компонента в app.js
- Вывод компонента в шаблоне Blade
- Передача данных из Laravel в компонент
- Запрос к серверу из компонента
- Запуск и сборка
- Ошибки при подключении Vue к Laravel
- Альтернативы: стартовый набор с Vue и Inertia
- Если проект на Laravel Mix и Vue 2
- Какой способ подключения выбрать
Что понадобится
Отправная точка – работающее приложение Laravel на Blade с настроенным Vite. Пример использует API Vue 3 и однофайловые компоненты.
Совместимость инструкции определяют возможности Laravel и требования зависимостей конкретного проекта. Используйте окружение, которое соответствует всем условиям таблицы.
| Компонент окружения | Условие совместимости |
|---|---|
| Laravel | Проект поддерживает директиву Blade @vite и класс Illuminate\Support\Js. Маршруты в routes/web.php используют сессии и CSRF-защиту. |
| PHP и расширения | Окружение удовлетворяет требованиям пакетов из composer.lock. Команда composer check-platform-reqs завершается без ошибок требований платформы. |
| Composer | Composer читает lock-файл проекта и поддерживает API установленных Composer-плагинов. Команда composer install завершается без ошибок совместимости. |
| Node.js и npm | Node.js соответствует полю engines выбранных пакетов Vite и его плагинов. Установка npm-зависимостей проходит без предупреждений EBADENGINE и конфликтов зависимостей. |
| Фронтенд-зависимости | Проект использует Vue 3. Пакеты vite, laravel-vite-plugin и @vitejs/plugin-vue удовлетворяют взаимным ограничениям peerDependencies. |
Для существующего приложения сохраните совместимую связку Laravel, PHP и Composer. Подключение Vue само по себе не требует обновления серверного фреймворка. Если проект использует Mix, сначала подготовьте переход на Vite.
Все команды выполняйте из корня приложения. Перед изменениями сохраните проект в системе контроля версий, включая конфигурацию и lock-файлы.
В терминале из корня приложения:
php -v
composer --version
node -v
npm -v
composer install
composer check-platform-reqs
composer show laravel/framework
npm install
npm ls vite laravel-vite-pluginПервые команды показывают версии окружения. Composer устанавливает зависимости и проверяет требования к PHP и расширениям. Команда composer show показывает установленный Laravel, а npm ls – пакеты сборщика.
Если npm сообщает о несовместимости, устраните ее до подключения Vue. Для EBADENGINE установите Node.js из диапазона engines, указанного в сообщении. При конфликте peerDependencies подберите версии пакетов под ограничения установленного сборщика.
Не обходите ограничения флагами --force или --legacy-peer-deps: установка не исправит несовместимость сборщика.
Для нового приложения сначала подготовьте обычный Laravel-проект на Blade с Vite. Для примера нужны маршрут из routes/web.php и рабочая папка resources. Если проект уже использует Inertia или другую точку входа, сохраните ее импорт и подключайте Vue внутри существующей архитектуры.
Установка Vue и плагина для Vite
Пакет Vue отвечает за интерфейс. Плагин @vitejs/plugin-vue обрабатывает однофайловые компоненты, а laravel-vite-plugin связывает Vite с точками входа Laravel.
Установите зависимости в корне проекта.
npm install vue@3
npm install --save-dev vite laravel-vite-plugin @vitejs/plugin-vueОткройте файл vite.config.js. Сохраните уже существующие точки входа и плагины, если проект использует их для других ресурсов.
Файл vite.config.js:
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import vue from '@vitejs/plugin-vue';
export default defineConfig({
plugins: [
laravel({
input: [
'resources/css/app.css',
'resources/js/app.js',
],
refresh: true,
}),
vue({
template: {
transformAssetUrls: {
base: null,
includeAbsolute: false,
},
},
}),
],
});Параметр transformAssetUrls помогает Vite обработать ресурсы в шаблонах Vue. Эти опции приведены в документации Laravel по Vue и Vite.
Такой конфигурации достаточно, когда приложение импортирует однофайловые компоненты из JavaScript.
Сохраните файл и запустите сборщик после создания точки входа. Если проект содержит собственный vite.config.js, не заменяйте его целиком: добавьте плагин Vue в существующий список.
Создание компонента Vue
Создайте каталог resources/js/components и файл CounterCard.vue. Компонент будет хранить счетчик, принимать объект через prop и обращаться к двум маршрутам Laravel.
Файл resources/js/components/CounterCard.vue:
<script setup>
import { ref } from 'vue';
const props = defineProps({
initialData: {
type: Object,
default: () => ({}),
},
});
const count = ref(props.initialData.initialCount ?? 0);
const loading = ref(false);
const message = ref('');
const error = ref('');
async function requestServer(method) {
loading.value = true;
message.value = '';
error.value = '';
try {
const options = {
method,
credentials: 'same-origin',
headers: { Accept: 'application/json' },
};
let url = props.initialData.statusUrl;
if (method === 'POST') {
const token = document.querySelector(
'meta[name="csrf-token"]'
)?.content;
if (!token) {
throw new Error('На странице отсутствует CSRF-токен.');
}
url = props.initialData.saveUrl;
options.headers['Content-Type'] = 'application/json';
options.headers['X-CSRF-TOKEN'] = token;
options.body = JSON.stringify({ count: count.value });
}
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`Сервер вернул HTTP ${response.status}.`);
}
const data = await response.json();
message.value = data.message;
} catch (exception) {
error.value = exception instanceof Error
? exception.message
: 'Не удалось выполнить запрос.';
} finally {
loading.value = false;
}
}
</script>
<template>
<section class="counter-card">
<h2>{{ props.initialData.heading || 'Счетчик Vue' }}</h2>
<p>Значение: {{ count }}</p>
<button type="button" :disabled="loading" @click="count++">
Увеличить
</button>
<button type="button" :disabled="loading" @click="requestServer('GET')">
Получить ответ
</button>
<button type="button" :disabled="loading" @click="requestServer('POST')">
Отправить счетчик
</button>
<ul>
<li v-for="item in props.initialData.items || []" :key="item.id">
{{ item.title }}
</li>
</ul>
<p v-if="loading" role="status">Загрузка...</p>
<p v-if="message" role="status">{{ message }}</p>
<p v-if="error" role="alert">{{ error }}</p>
</section>
</template>
<style scoped>
.counter-card {
max-width: 36rem;
padding: 1.5rem;
border: 1px solid #ccc;
border-radius: 0.75rem;
}
button {
margin-right: 0.5rem;
padding: 0.5rem 0.75rem;
}
</style>Синтаксис script setup объявляет props и функции прямо внутри компонента. Функция ref создает реактивное значение: в JavaScript оно доступно через count.value, а в шаблоне Vue раскрывает его как count.
Кнопка меняет локальное состояние без запроса.
Две другие кнопки используют URL из props, поэтому компонент не хранит адреса контроллера в нескольких местах.
Подключение компонента в app.js
Точка входа импортирует компонент, создает приложение и монтирует его в элемент #app. Объект из глобальной переменной станет prop корневого компонента.
Файл resources/js/app.js:
import { createApp } from 'vue';
import CounterCard from './components/CounterCard.vue';
const root = document.querySelector('#app');
if (root) {
createApp(CounterCard, {
initialData: window.vueExampleData ?? {},
}).mount(root);
}Такой способ сразу монтирует CounterCard и не требует регистрации тега. После загрузки страницы Vue заменит содержимое корневого элемента разметкой компонента.
Как монтировать несколько независимых компонентов
Создайте отдельный экземпляр Vue для каждого контейнера. Каждый экземпляр получит собственный prop и собственное состояние.
Файл resources/js/app.js для нескольких виджетов:
import { createApp } from 'vue';
import CounterCard from './components/CounterCard.vue';
const widgets = window.vueExampleWidgets ?? {};
for (const element of document.querySelectorAll('[data-vue-counter]')) {
const key = element.dataset.vueCounter;
createApp(CounterCard, {
initialData: widgets[key] ?? {},
}).mount(element);
}На странице разместите несколько элементов с разными значениями data-vue-counter. Объект window.vueExampleWidgets должен содержать данные по этим ключам.
Не запускайте оба варианта на одной странице: один корень не должен получать два приложения.
Вывод компонента в шаблоне Blade
Layout подключает CSS и JavaScript через @vite. Корневой элемент оставьте пустым, потому что импортированный компонент сам создаст разметку.
Файл resources/views/layouts/app.blade.php:
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>Компонент Vue в Laravel</title>
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body>
@yield('content')
</body>
</html>Файл resources/views/vue-example.blade.php:
@extends('layouts.app')
@section('content')
<div id="app"></div>
<script>
window.vueExampleData = {{ Illuminate\Support\Js::from($initialData) }};
</script>
@endsectionСкрипт задает данные до запуска модуля Vite. После загрузки страницы в #app появится карточка со счетчиком.
Добавьте маршруты страницы и запросов к серверу.
Файл routes/web.php:
<?php
use App\Http\Controllers\VueExampleController;
use Illuminate\Support\Facades\Route;
Route::get('/vue-example', [VueExampleController::class, 'show'])
->name('vue-example.show');
Route::get('/vue-example/status', [VueExampleController::class, 'status'])
->name('vue-example.status');
Route::post('/vue-example/save', [VueExampleController::class, 'save'])
->name('vue-example.save');В существующем файле сохраните остальные маршруты и не дублируйте импорты. Для открытия страницы также понадобится контроллер VueExampleController.
Передача данных из Laravel в компонент
Контроллер подготовит объект, а Blade безопасно передаст его в JavaScript. В prop отправляйте только поля, которые разрешено показывать посетителю.
Файл app/Http/Controllers/VueExampleController.php:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class VueExampleController extends Controller
{
public function show()
{
$initialData = [
'heading' => 'Счетчик из контроллера',
'initialCount' => 2,
'items' => [
['id' => 1, 'title' => 'Данные переданы через props'],
['id' => 2, 'title' => 'Кнопки обрабатывает Vue'],
],
'statusUrl' => route('vue-example.status', [], false),
'saveUrl' => route('vue-example.save', [], false),
];
return view('vue-example', compact('initialData'));
}
public function status()
{
return response()->json([
'message' => 'Laravel ответил на GET-запрос.',
]);
}
public function save(Request $request)
{
$validated = $request->validate([
'count' => ['required', 'integer', 'min:0', 'max:1000'],
]);
return response()->json([
'message' => 'Сервер получил значение: ' . $validated['count'],
'count' => (int) $validated['count'],
]);
}
}Js::from формирует JavaScript-выражение с экранированием для HTML-контекста. Не оборачивайте результат в JSON.parse: выражение уже создает объект.
Используйте показанный вызов Js::from($initialData), чтобы передать данные из Laravel во Vue. Он подходит для вложенных массивов и объектов.
Секреты, токены доступа и закрытые поля в prop не передавайте.
Когда нужен компилятор шаблонов Vue
При вызове createApp(CounterCard, { initialData }).mount(element) шаблон уже обработал Vite. Для этого варианта пустого корневого контейнера достаточно, поэтому alias не нужен.
Если Blade содержит тег компонента внутри #app, Vue должен разобрать этот DOM-шаблон. Для такого способа нужна сборка Vue с компилятором. Разницу между сборками объясняет документация Vue по инструментам разработки.
Чтобы выводить компонент через тег, замените три файла следующими вариантами. Контроллер, маршруты, layout и сам CounterCard.vue остаются прежними.
Файл vite.config.js для варианта с тегом компонента:
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import vue from '@vitejs/plugin-vue';
export default defineConfig({
resolve: {
alias: {
vue: 'vue/dist/vue.esm-bundler.js',
},
},
plugins: [
laravel({
input: [
'resources/css/app.css',
'resources/js/app.js',
],
refresh: true,
}),
vue({
template: {
transformAssetUrls: {
base: null,
includeAbsolute: false,
},
},
}),
],
});Файл resources/js/app.js для варианта с тегом компонента:
import { createApp } from 'vue';
import CounterCard from './components/CounterCard.vue';
const root = document.querySelector('#app');
if (root) {
const app = createApp({
data() {
return {
initialData: window.vueExampleData ?? {},
};
},
});
app.component('counter-card', CounterCard);
app.mount('#app');
}Файл resources/views/vue-example.blade.php для варианта с тегом компонента:
@extends('layouts.app')
@section('content')
<div id="app">
<counter-card :initial-data="initialData"></counter-card>
</div>
<script>
window.vueExampleData = {{ Illuminate\Support\Js::from($initialData) }};
</script>
@endsectionМетод app.component регистрирует тег counter-card внутри этого приложения. Привязка :initial-data передает объект из корневого состояния в prop компонента.
Перезапустите Vite после изменения конфигурации и перезагрузите страницу. Результат останется прежним: карточка покажет заголовок и начальное значение 2. Для одиночного виджета прямое монтирование проще: оно обходится без компилятора в браузере.
Запрос к серверу из компонента
Компонент отправляет GET для статуса и POST со значением счетчика. Контроллер возвращает JSON, а компонент показывает состояние загрузки, сообщение или ошибку.
Маршруты связывают URL с методами status и save. GET-маршрут возвращает сообщение без входных данных. POST-маршрут принимает поле count и проверяет его до формирования ответа.
Для POST компонент читает CSRF-токен из meta-тега и передает его в заголовке X-CSRF-TOKEN. Параметр credentials: 'same-origin' отправляет cookies текущего сайта вместе с запросом.
После нажатия «Получить ответ» сервер вернет JSON со статусом. Кнопка «Отправить счетчик» отправит текущее число и покажет сообщение контроллера.
Этот пример не сохраняет счетчик в базе данных.
Ответ fetch нужно проверять через response.ok, потому что HTTP-ошибка сама по себе не попадает в catch. Описание Fetch API есть в документации MDN.
Запуск и сборка
В разработке Laravel и Vite работают одновременно. Первый процесс отдает страницу, второй обслуживает модули Vue и обновляет их через HMR.
В двух терминалах из корня приложения:
php artisan serve
npm run devЗапустите команды по одной в каждом терминале. Возьмите адрес сервера из вывода php artisan serve и добавьте к нему путь /vue-example. Откройте полученный адрес в браузере.
Вы увидите заголовок «Счетчик из контроллера», значение 2 и три кнопки. Нажатие «Увеличить» меняет число без перезагрузки страницы.
После изменения CounterCard.vue браузер обновит модуль без ручной сборки. Нажмите кнопки запросов и откройте Network в инструментах разработчика. GET и POST должны вернуть JSON, а карточка – показать сообщение сервера.
Для production-сборки остановите Vite сочетанием Ctrl+C. Дождитесь завершения процесса и возвращения приглашения командной строки.
В терминале из корня приложения:
npm run buildКоманда подготовит CSS и JavaScript для production. Откройте страницу с остановленным Vite: браузер должен загрузить готовую сборку. В Network убедитесь, что ресурсы загружаются с сервера приложения. Счетчик и обе кнопки запросов должны работать без dev-сервера.
Для просмотра состояния компонента используйте Vue Devtools в режиме разработки. Откройте официальную документацию Vue по расширению и перейдите по ссылке Vue Devtools на страницу установки.
Установите расширение для вашего браузера, запустите npm run dev и перезагрузите страницу. Откройте инструменты разработчика и панель расширения Vue. В дереве компонентов найдите CounterCard, затем посмотрите prop initialData и реактивное значение count.
Чтобы отменить подключение, восстановите измененные файлы и lock-файлы из сохраненной версии проекта. После восстановления выполните npm install и заново соберите ресурсы.
Ошибки при подключении Vue к Laravel
Симптом показывает, на каком участке оборвалась цепочка: загрузка entry-файла, монтаж, связь с Vite или ответ Laravel.
| Симптом | Причина | Исправление |
|---|---|---|
Пустой #app | Entry-файл не загрузился или корневой элемент отсутствует | Посмотрите первую ошибку Console и наличие resources/js/app.js в Network. Сверьте #app в Blade и JavaScript. |
| Runtime-only build | Vue получил DOM-шаблон без компилятора | Монтируйте импортированный CounterCard напрямую в пустой контейнер, как в примере. Для DOM-шаблонов нужен alias vue/dist/vue.esm-bundler.js. |
| Vite manifest not found | Нет dev-сервера и production-сборки | Для разработки запустите npm run dev, для production выполните npm run build. |
| Vite недоступен в Docker | Порт контейнера не опубликован или HMR использует недоступное имя | Опубликуйте порт Vite и задайте host, который доступен браузеру. |
| Стили или изображения не загружаются | Путь не совпадает с точкой входа или местом хранения | Сопоставьте путь в @vite с input. Создайте resources/css/app.css, если файла нет; для примера он может быть пустым. Ресурсы из public вызывайте от корня сайта, ресурсы из resources импортируйте через Vite. |
| POST возвращает 419 | CSRF-токен или session cookie не отправлены | Проверьте meta-тег, заголовок X-CSRF-TOKEN и cookies в Network. |
| POST возвращает 422 | Данные не прошли валидацию | Передайте целое число от 0 до 1000, как требует контроллер. |
Для Docker или Sail добавьте сетевые параметры в объект defineConfig файла vite.config.js, рядом с plugins. Этот фрагмент задает порт примера, который также нужно опубликовать в конфигурации контейнера.
Дополнение к vite.config.js внутри объекта конфигурации:
server: {
host: '0.0.0.0',
port: 5173,
strictPort: true,
hmr: {
host: 'localhost',
},
},Если браузер работает на другой машине, замените localhost на имя, доступное этой машине. После изменения конфигурации перезапустите Vite.
Альтернативы: стартовый набор с Vue и Inertia
Ручное подключение подходит для Blade-сайта, где Vue нужен отдельной формой, счетчиком или виджетом. Laravel сохраняет серверную навигацию, а Vue отвечает только за интерактивный блок.
Inertia удобнее для приложения, где Vue управляет целыми страницами. Контроллер передает props странице Vue, поэтому глобальные переменные и отдельные корни обычно не нужны.
Выберите официальный стартовый набор Laravel с Vue и Inertia, если основную часть интерфейса планируете писать на Vue.
В существующем Inertia-проекте добавляйте компонент в уже работающий корень: второй вызов createApp не нужен.
Если проект на Laravel Mix и Vue 2
Файлы webpack.mix.js и зависимость laravel-mix указывают на старую схему сборки. Команды берутся из раздела scripts файла package.json, поэтому название npm run dev само по себе не определяет сборщик.
В терминале из корня Mix-проекта, если такие scripts заданы:
npm run dev
npm run watchКоманда watch следит за изменениями в проектах, где ее добавили в scripts. Она не переводит приложение на Vite автоматически.
Официальная поддержка Vue 2 завершилась.
Для нового кода выбирайте Vue 3 и переносите зависимости, конфигурацию, импорты и подключение ресурсов отдельными изменениями. Порядок перехода описывает руководство Laravel по миграции с Mix на Vite.
Не смешивайте new Vue и createApp в одной точке входа. Для Blade-виджета оставьте один корневой экземпляр, а для приложения со страницами рассмотрите Inertia.
Какой способ подключения выбрать
Для одного или нескольких интерактивных блоков на Blade достаточно Vite, createApp и однофайловых компонентов. Такой путь дает props из Laravel, запросы fetch и привычную серверную маршрутизацию.
Если Vue должен управлять страницами целиком, берите связку Laravel и Inertia. Она уменьшает ручной код между контроллером, Blade и корнем приложения.
Совет от нашей редакции: сначала соберите один компонент через пустой #app, затем добавляйте props и запросы. Так ошибка сборки не смешается с ошибкой маршрута или CSRF.
Популярное за неделю
- Как создать сайт самому в 2026: 4 способа + пошаговая инструкция 34 мин чтения
- Лучшие конструкторы для создания сайтов — плюсы, минусы и сравнения 22 мин чтения
- Как ускорить разработку на Vue.js: практические приёмы 15 мин чтения
- Как установить и создать проект с Vue CLI 6 мин чтения
- Аутентификация в Vue с Vuex и токенами 6 мин чтения
- Как перейти с jQuery на Vue на практике 6 мин чтения
Комментарии