Livewire 3 і 4 — що змінилося

Усе, що змінилося між Livewire 3 і Livewire 4: нові можливості, які варто взяти, кожна ламка зміна з кодом «було / стало» і чек-лист самого апгрейду. Включно з трьома змінами, які проходять ваші тести і ламають продакшен.

Livewire 3 → 4 Ламкі зміни Чек-лист міграції 14 розділів

1. Коротко

Livewire 4 — великий реліз із невеликим шляхом міграції. Модель компонента не змінилася: PHP-клас, Blade-шаблон, стан на сервері, HTML по дроту. Змінилося те, де лежать компоненти, як вони пишуться і яку частину сторінки зачіпає одне оновлення.

Класові компоненти продовжують працювати. Однофайловий формат — умовчання для нових компонентів, а не примусове переписування. Більшість застосунків на Livewire 3 оновлюються так: підняти версію, полагодити жменю ламких змін і поступово освоювати нові можливості.

Що ви отримуєте

  • Однофайлові компоненти — клас і розмітка в одному файлі.
  • Острови — ділянки з незалежним рендерингом, без розбиття на дочірні компоненти.
  • Слоти — батько передає розмітку в нащадка, обчислюється в контексті батька.
  • Оптимістичний UIwire:show, wire:text, wire:bind змінюють DOM без round trip.
  • Drag and dropwire:sort, без зовнішньої бібліотеки.
  • Скоуплений CSS — стилі компонента, що не протікають.

Що підставить

  1. Незакриті теги <livewire:…> мовчки не рендеряться.
  2. Модифікатори wire:model змінили сенс — поля перестають синхронізуватися.
  3. Ендпоінт оновлень переїхав на /livewire-{hash}/ — ламаються інфраструктурні правила.
  4. Модифікатори wire:transition прибрано.
  5. Volt увійшов у ядро.

2. Порівняння пліч-о-пліч

ОбластьLivewire 3Livewire 4
Файли компонента app/Livewire/Counter.php + resources/views/livewire/counter.blade.php resources/views/components/⚡counter.blade.php (один файл)
Команда створення php artisan livewire:make Counter php artisan make:livewire counter
Маршрутизація Route::get('/x', Dashboard::class) Route::livewire('/x', 'pages::dashboard')
Частковий рендеринг Розбиття на дочірні компоненти Ділянки @island
Слоти Не підтримуються {{ $slot }}, <wire:slot name="…">
Drag and drop SortableJS плюс обв’язка wire:sort
Миттєвий UI Alpine.js вручну wire:show, wire:text, wire:bind
CSS компонента Глобальний стайлшит Скоуплений <style> у компоненті
Паралельні дії Недоступні #[Async], wire:click.async
Ендпоінт оновлень /livewire/update /livewire-{hash}/update
JS-хуки Livewire.hook('request'|'commit') interceptRequest(), interceptMessage()
Volt Окремий пакет Увійшов у ядро
Тестування PHPUnit або Pest Рекомендується Pest, тести можуть лежати поруч із компонентом

3. Апгрейд за кроками

Bash
# 1. Підняти залежність
composer require livewire/livewire:^4.0

# 2. Очистити всі кеші
php artisan optimize:clear

# 3. Прогнати тести, перш ніж чіпати щось іще
php artisan test

Laravel Shift автоматизує значну частину механічної роботи. Незалежно від того, користуєтеся ви ним чи ні, пройдіться потім по чек-листу нижче — мовчазні поломки інструмент відзначає найгірше.

Порядок робіт, який зазвичай проходить гладко

  1. Оновити пакет і очистити кеші.
  2. Полагодити перейменування в конфігу (розділ 4).
  3. Полагодити маршрути (розділ 5).
  4. Знайти в коді незакриті теги компонентів (розділ 6).
  5. Перевірити кожен wire:model із модифікатором (розділ 7).
  6. Оновити інфраструктурні правила під новий ендпоінт (розділ 8).
  7. Замінити прибрані модифікатори wire:transition (розділ 9).
  8. Мігрувати компоненти Volt, якщо він використовувався (розділ 12).
  9. Прогнати тести знову і пройти застосунок руками.
  10. І тільки потім братися за острови, слоти й однофайлові компоненти.
Не поєднуйте апгрейд із переписуванням. Спершу доможіться зеленого застосунку на v4 з наявними класовими компонентами.

4. Перейменування в конфігурації

Livewire 3Livewire 4Примітка
layoutcomponent_layoutВикористовує namespace layouts::
lazy_placeholdercomponent_placeholder
smart_wire_keyssmart_wire_keysУмовчання змінилося з false на true
component_locationsКаталоги, де шукаються компоненти
component_namespacesІменовані корені, наприклад pages::
make_commandОднофайлова чи класова генерація
csp_safeЗбірка під сувору CSP
Livewire 4 — config/livewire.php
'component_layout' => 'layouts::app',

'component_placeholder' => 'livewire.placeholder',

'component_locations' => [
    'resources/views/components',
    'resources/views/livewire',
],

'component_namespaces' => [
    'pages' => 'resources/views/pages',
],

// Продовжувати генерувати класові компоненти
'make_command' => [
    'type' => 'class',
],

5. Маршрутизація

Livewire 3
use App\Livewire\Dashboard;

Route::get('/dashboard', Dashboard::class)
    ->middleware('auth')
    ->name('dashboard');
Livewire 4
use App\Livewire\Dashboard;

Route::livewire('/dashboard', Dashboard::class)
    ->middleware('auth')
    ->name('dashboard');

// Або за іменем компонента
Route::livewire('/dashboard', 'pages::dashboard')
    ->middleware('auth')
    ->name('dashboard');
Швидкий спосіб знайти всі: grep -rn "Route::get(.*::class" routes/.

6. Теги компонентів мають закриватися

Найнеприємніше, бо ламається мовчки. Незакритий тег компонента не рендерить нічого — ні винятку, ні запису в лозі, просто зниклий блок на сторінці.
Livewire 3 — допускалося
<livewire:user-profile>
<livewire:order-list>
Livewire 4 — обов’язково
<livewire:user-profile />
<livewire:order-list />
Bash
# Теги компонентів, які не самозакриті
grep -rn "<livewire:[^>]*[^/]>" resources/views/

# Форма через директиву @livewire() не зачеплена
grep -rn "@livewire(" resources/views/

7. wire:model змінився двічі

7.1 Модифікатори керують синхронізацією на клієнті

Livewire 3
<input wire:model.blur="title">
<select wire:model.change="status">
Livewire 4 — та сама поведінка
<input wire:model.live.blur="title">
<select wire:model.live.change="status">

7.2 Більше немає спливання від дочірніх елементів

Livewire 3 — обгортка ловила
<div wire:model="value">
    <input type="text">
</div>
Livewire 4 — вмикаємо явно
<div wire:model.deep="value">
    <input type="text">
</div>
Bash
# Усі wire:model з модифікатором
grep -rn "wire:model\.[a-z]" resources/views/

# wire:model на не-input елементі — кандидат на проблему зі спливанням
grep -rn "<div[^>]*wire:model" resources/views/
Кастомні компоненти форм — датапікер, редактор, обгорнутий select — страждають від зміни спливання найчастіше, бо патерн обгортки це саме те, як вони зазвичай влаштовані.

8. Ендпоінт оновлень переїхав

Усі URL Livewire тепер містять хеш: /livewire/ став /livewire-{hash}/.

Ця зміна проходить усі тести і ламає продакшен. Ніщо у вашому PHP-коді не посилається на шлях, тому набір тестів залишається зеленим.

Де перевірити

  • Блоки location у nginx або Apache
  • Білі списки WAF і фаєрвола
  • Правила обходу кешу CDN
  • Rate limiter'и, прив’язані до шляху
  • Правила маршрутизації балансувальника
  • Моніторинг і uptime-перевірки
  • Записи connect-src у CSP
nginx — було
location /livewire/ {
    # …
}
nginx — стало
location ~ ^/livewire(-[a-z0-9]+)?/ {
    # …
}

9. wire:transition перероблено

Livewire 3
<div wire:transition.opacity.duration.300ms>
    …
</div>
Livewire 4
<div wire:transition>
    …
</div>
CSS
::view-transition-old(root),
::view-transition-new(root) {
    animation-duration: 300ms;
}
Підтримка View Transitions у браузерах неповна. Там, де її немає, вміст просто з’являється без анімації.

10. Зміни в PHP API

Стрімінг

Livewire 3
$this->stream(to: '#container', content: 'Hello', replace: true);
Livewire 4
$this->stream('Hello', replace: true, el: '#container');

Монтування компонента

Livewire 3
mount($name, $params = [], $key = null)
Livewire 4
mount($name, $params = [], $key = null, $slots = [])

Нові атрибути

АтрибутПризначення
#[Async]Виконати дію паралельно
#[Renderless]Пропустити перемальовування після дії
#[Prop]Оголосити властивість як props від батька
#[Json]Повернути дані прямо в JavaScript

11. Зміни в JavaScript API

Livewire 3
$wire.$js('showToast', { message: 'Збережено' });
Livewire 4
$wire.$js.showToast = { message: 'Збережено' };

Хуки стали перехоплювачами

Livewire 3
document.addEventListener('livewire:init', () => {
    Livewire.hook('request', ({ options, fail }) => {
        options.headers['X-Tenant'] = window.tenantId;

        fail(({ status, preventDefault }) => {
            if (status === 419) {
                preventDefault();
                window.location.reload();
            }
        });
    });
});
Livewire 4
document.addEventListener('livewire:init', () => {
    Livewire.interceptRequest(({ options, fail }) => {
        options.headers['X-Tenant'] = window.tenantId;

        fail(({ status, preventDefault }) => {
            if (status === 419) {
                preventDefault();
                window.location.reload();
            }
        });
    });

    Livewire.interceptMessage(({ component, succeed }) => {
        succeed(() => {
            console.debug('компонент оновлено', component.name);
        });
    });
});
Blade
{{-- Помилки валідації на клієнті --}}
<div x-show="$errors.has('email')" x-text="$errors.first('email')"></div>

12. Volt увійшов у ядро

VoltLivewire 4
Livewire\Volt\ComponentLivewire\Component
Volt::route()Route::livewire()
Volt::test()Livewire::test()
Сервіс-провайдер VoltВидалити
Пакет livewire/voltВидалити залежність
Bash
composer remove livewire/volt
Було (Volt)
<?php

use Livewire\Volt\Component;

new class extends Component {
    public string $title = '';
};

?>

<div>…</div>
Стало (Livewire 4)
<?php

use Livewire\Component;

new class extends Component {
    public string $title = '';
};

?>

<div>…</div>

13. Освоєння нових можливостей

1. Острови на найповільнішій сторінці

Було — перемальовується весь компонент
<div>
    <div class="card">Виручка: {{ $this->revenue }}</div>
    <div class="card">Завдань у черзі: {{ $this->queue }}</div>
    <div class="card">Остання активність: …</div>
</div>
Стало — кожна картка оновлюється сама
<div>
    @island(name: 'revenue')
        <div class="card">Виручка: {{ $this->revenue }}</div>
    @endisland

    @island(name: 'queue', poll: '5s')
        <div class="card">Завдань у черзі: {{ $this->queue }}</div>
    @endisland

    @island(name: 'activity', lazy: true)
        @placeholder
            <div class="card skeleton animate-pulse h-48"></div>
        @endplaceholder

        <div class="card">Остання активність: …</div>
    @endisland
</div>

2. Замінити round trip клієнтськими директивами

Було
<button wire:click="$toggle('showFilters')">Фільтри</button>

@if ($showFilters)
    <div class="filters">…</div>
@endif
Стало — взагалі без запиту
<button wire:click="$toggle('showFilters')">Фільтри</button>

<div class="filters" wire:show="showFilters">…</div>

3. Викинути SortableJS

Стало
<ul wire:sort="reorder">
    @foreach ($tasks as $task)
        <li wire:sort:item="{{ $task->id }}" wire:key="task-{{ $task->id }}">
            <span wire:sort:handle>⠿</span> {{ $task->title }}
        </li>
    @endforeach
</ul>

4. Схлопнути дочірні компоненти, створені лише заради рендеру

Якщо в нащадка немає власного стану і він не перевикористовується, острів у батькові легший — один компонент, один клас, один snapshot.

5. #[Renderless] для невидимої роботи

PHP
use Livewire\Attributes\Renderless;

#[Renderless]
public function trackView(): void
{
    $this->post->increment('views');
}

6. Переведення на однофайловий формат — останнім

Це косметика. Робіть принагідно, коли й так правите файл, а не окремим спринтом міграції.

14. Чек-лист апгрейду

До початку

  • Тести проходять на Livewire 3.
  • Окрема гілка і бекап бази, якщо оточення спільне.
  • Хтось готовий пройти застосунок руками після — мовчазні поломки бачить лише людина.

Механічні правки

  • composer require livewire/livewire:^4.0
  • php artisan optimize:clear
  • layoutcomponent_layout
  • lazy_placeholdercomponent_placeholder
  • Route::get(…, Component::class)Route::livewire(…)
  • Усі теги <livewire:…> самозакриті
  • wire:model.blur / .change → додати .live
  • wire:model на обгортці → додати .deep
  • wire:scrollwire:navigate:scroll
  • Модифікатори wire:transition → у CSS
  • Порядок аргументів $this->stream()
  • $wire.$js('name', …)$wire.$js.name = …
  • Livewire.hook(…)interceptRequest() / interceptMessage()
  • Класи, маршрути, тести і пакет Volt видалені

Інфраструктура

  • Location-блоки nginx / Apache розширені під /livewire-{hash}/
  • Правила WAF, CDN і фаєрвола оновлені
  • Rate limiter'и і моніторинг перенацілені
  • connect-src у CSP переглянутий

Перевірка

  • Тести зелені
  • Усі форми надсилаються і валідуються
  • Завантаження файлів працює, включно з індикатором прогресу
  • Таблиці гортаються, сортуються і фільтруються
  • Модальні вікна відкриваються і закриваються
  • Сторонні віджети всередині wire:ignore ініціалізуються
  • Консоль браузера чиста на основних сценаріях
  • Запити оновлення повертають 200 на проді, а не 403 від WAF
Викочуйте канарково, якщо є можливість. Обидві поломки, що переживають зелені тести — незакриті теги і ендпоінт, що переїхав — проявляються на реальному трафіку одразу.

Офіційні ресурси

Livewire 4 швидко розвивається. Якщо ця стаття й офіційний upgrade guide розходяться, має рацію офіційне джерело.