hotwired-laravel / hotreload
Hot reloading for Hotwire
Requires
- illuminate/http: ^11.36
- illuminate/support: ^11.36
Requires (Dev)
- orchestra/testbench-dusk: ^9.11
- tightenco/duster: ^3.1
Suggests
- ext-inotify: Required for a better performance.
README
It enhances development feedback loops by detecting source code changes and updating the page smoothly without requiring manual reloads.
This package is still under development.
Inspiration
This package was heavily inspired by the Hotwire Spark gem. The JavaScript that makes this all work in the browser was copied from there. The package was reimplemented in PHP to work with Laravel.
Installation
composer require hotwired-laravel/hotreload --dev
That's it!
By default, a simple file watcher will be used. There's another file watcher that is more efficient but requires the inotify extension, which you may install via PECL:
pecl install inotify
Don't forget to enable it in your php.ini
. Since this package is for local development only, you'll only need that extension locally. Once you have the extension installed and enabled, the package should automatically pick the correct file watcher.
Optionally, you may force the use of a specific file watcher by calling this code in your AppServiceProvider@boot
method (don't forget to wrap it for local env only):
<?php use HotwiredLaravel\Hotreload\Hotreload; if (app()->environment('local') && class_exists(Hotreload::class)) { Hotreload::withInotifyWatcher(); // or the simple one... Hotreload::withSimpleWatcher(); }
Hot it works
The package injects a script into your application via a middleware. That script will start an EventSource
subscribed to a Server-Sent Events (aka. SSE) that will start watching files for changes in the configured directory. There are a couple of reloaders which are activated depending on which directory you configured (or the default ones):
- HTML files
- CSS
- Stimulus controllers
This package was built for Turbo Laravel, so it expects that you're using Importmaps Laravel and something like TailwindCSS Laravel. How it will work:
- HTML change with morphing: it fetches the new document body and updates the current body with morphing, then it reloads the Stimulus controllers in the page. It uses idiomorph under the hood
- HTML change with replacing: it reloads the page with a Turbo visit.
- CSS change: it fetches and reloads the stylesheet that changed.
- Stimulus controllers change: it fetches the Stimulus controller that changed and reloads all the controllers in the page.
Configuration
On PHP Processes
For this to work, we need at least 2 processes, since the SSE route will cause the process to hang indefinitely. If you're using php artisan serve
(which Laravel Sail uses by default). For that reason, make sure you set the number of PHP processes on your .env
file:
PHP_CLI_SERVER_WORKERS=4
Laravel does that by default nowadays.
HTML Replacing Method
By default, it will using morphing to replace HTML changes. You may want to use HTML replace instead of morphing on some pages that are more JS-heavy (like if you have a rich text editor like Trix on it, for instance). To do so, you may control this on a per-page basis using a meta tag somewhere on that page's view:
@env('local') <meta name="hotwire-hotreload:html-reload-method" content="morph"> @endenv
Enable Logging
If you want to, you may enable logging with a meta tag on the page (you may place this somewhere global like a layout file):
@env('local') <meta name="hotwire-hotreload:logging" content="true"> @endenv
Monitoring Paths
By default, the package will watch for changes on a few default directories (you may configure extra ones):
You may configure additional paths by calling the respective method on the Hotreload
class in your AppServiceProvider@boot
method (don't forget to wrap it for local env only):
<?php use HotwiredLaravel\Hotreload\Hotreload; if (app()->environment('local') && class_exists(Hotreload::class)) { Hotreload::addHtmlPath(resource_path('images')); Hotreload::addCssPath(resource_path('sass')); Hotreload::addStimulusPath(resource_path('js/bridge')); }