ijpatricio/mingle
MingleJS lets you use React or Vue components inside Laravel Livewire apps. It renders a server-side div and mounts the JS component client-side, passing data from PHP and enabling easy server actions via $wire for “islands” of interactivity.
Installation:
composer require ijpatricio/mingle
npm install @vue/compiler-sfc vue@next react react-dom # or your preferred JS framework
Add MingleJS to your resources/js/app.js (or Vite config):
import { Mingle } from 'minglejs';
window.Mingle = Mingle;
First Use Case:
Create a Livewire component (php artisan make:livewire TodoList) and a Vue/React component (resources/js/components/TodoList.vue or TodoList.jsx).
Livewire Component (TodoList.php):
public function render()
{
return view('livewire.todo-list', [
'todos' => Todo::all(),
]);
}
Blade Template (todo-list.blade.php):
<div>
@mingle('TodoList', [
'todos' => $todos,
])
</div>
Vue Component (TodoList.vue):
<template>
<ul>
<li v-for="todo in todos" :key="todo.id">
{{ todo.title }}
</li>
</ul>
</template>
<script>
export default {
props: ['todos'],
}
</script>
Register the Component:
In your JS entry file (e.g., resources/js/app.js), register the component:
Mingle.register('TodoList', () => import('./components/TodoList.vue'));
Run the App:
npm run dev
php artisan serve
@mingle directive:
@mingle('UserProfile', ['user' => $user, 'settings' => $settings])
$wire object inside JS components:
<script>
export default {
methods: {
updateProfile() {
this.$wire.call('updateProfile', this.profileData);
}
}
}
</script>
// Livewire component
$this->emit('profileUpdated', $user);
// Vue component
methods: {
mounted() {
window.Livewire.on('profileUpdated', user => {
this.user = user;
});
}
}
Mingle.register('HeavyComponent', () => import('./components/HeavyComponent.vue'));
// Global (app.js)
Mingle.register('GlobalComponent', () => import('./GlobalComponent.vue'));
// Per-page (e.g., in a specific Livewire component's JS)
Mingle.register('PageSpecificComponent', () => import('./PageSpecificComponent.vue'));
<style scoped>
/* Scoped styles */
</style>
// resources/js/app.js
import './assets/styles.css';
import './assets/fonts.css';
@mingle('TodoList', ['todos' => $todos], fallback: '<p>Loading todos...</p>')
<script>
export default {
errorCaptured(err) {
console.error('Component error:', err);
return false; // Render fallback
}
}
</script>
Livewire::test():
public function test_mingle_component()
{
$this->livewire(TodoList::class)
->assertSee('TodoList component');
}
// Filament Resource
public static function getWidgets(): array
{
return [
MingleWidget::make('AnalyticsDashboard')
->props(['data' => $this->getAnalyticsData()]),
];
}
@mingle('CustomFormField', ['field' => $field])
Mingle.register('LazyComponent', () => import(/* webpackChunkName: "lazy" */ './LazyComponent.vue'));
// In a Vue component
import { debounce } from 'lodash';
methods: {
search: debounce(function() {
this.$wire.search(this.query);
}, 300),
}
Livewire::configureLogging(function ($log) {
$log->debug();
});
Hydration Mismatches:
v-if or v-show in Vue to conditionally render components based on client-side state:
<template v-if="isMounted">
<!-- Client-side only content -->
</template>
ssr: false in your build tool (e.g., Vite).Prop Binding:
json_encode() in Livewire:
@mingle('Component', ['data' => json_encode($complexData)])
Livewire::dispatch() to stream data via events.Event Conflicts:
click) conflict, causing unintended behavior.<button @click.stop.prevent="handleClick">Click Me</button>
@mingle-click).Build Tool Conflicts:
vite.config.js includes MingleJS assets:
export default defineConfig({
build: {
rollupOptions: {
input: {
app: './resources/js/app.js',
mingle: './vendor/ijpatricio/mingle/dist/mingle.js',
},
},
},
});
@vite(['resources/js/app.js', 'resources/js/mingle-components.js']) in Blade.Livewire 3.x Breaking Changes:
wire:model.live).if (app()->version() >=
How can I help you explore Laravel packages today?