Render Svelte Components from Laravel Blade

Alpine is great but it doesn’t work when you need some complex interactions. It could also be skill issue but I struggled for a long time to get drag and drop working in Alpine with and without the alpine sort plugin. At that point, I was begging to have a better view library in my stack.

Svelte is pretty minimal, doesn’t include a runtime and the builds are pretty small. This makes it perfect to sprinkle some interactivity using Svelte. Even though I have the most experience with React it took me less than an hour to understand and get productive in Svelte.

Fast forward a few days and I had a pretty nice drag-and-drop system in Svelte. However, it was still a pain to render them. However, I found a pretty slick way to render svelte components directly from blade templates.

Let’s start by installing dependencies:

Bash
npm install svelte @sveltejs/vite--plugin-svelte

Let’s modify vite.config.js to compile svelte files:

vite.config.js
import { defineConfig } from "vite";
import laravel from "laravel-vite-plugin";
import { svelte, vitePreprocess } from "@sveltejs/vite-plugin-svelte";

export default defineConfig({
    build: {
        sourcemap: process.env.NODE_ENV === "development",
    },
    plugins: [
        svelte({
            preprocess: vitePreprocess(), // For typescript support
            compilerOptions: {
                dev: process.env.NODE_ENV === "development",
            },
        }),
        // ... rest of the laravel vite config
    ],
});

We need to direct vite to compile all svelte files in the svelte directory. This functionality is provided by vite’s glob imports. This will make all svelte components compiled into window.svelteComponents keyed by the component name.

resources/js/app.js
// at the beginning after all imports of app.js

// Compile all svelte components;
window.svelteComponents = import.meta.glob("./svelte/*.svelte", {
    import: "default",
    eager: true,
});

Not, let’s create a blade component, which will be responsible for rendering the svelte component on the page.

resources/views/components/svelte.blade.php
{{-- This file mounts svelte component by it's name. Allows passing in props --}}

@props([
    "componentName" => null,
    "props" => [],
])

<?php $random = Str::random(8); ?>

<div id="{{ $random }}"></div>
<div>
    <script data-navigate-once>
        if (typeof mount{{ $random }} === 'undefined') {
            function mount{{ $random }}() {
                const target = document.getElementById('{{ $random }}');
                if (!target) {
                  console.error('Target dom element not found for {{ $componentName }}');
                  return;
                }

                const component =
                    window.svelteComponents[
                        './svelte/{{ $componentName }}.svelte'
                        ];
                if (!component) {
                    console.error('Component {{ $componentName }} not found');
                    return;
                }

                // If component already mounted, empty the node before mounting
                if (target.children.length > 0) {
                    target.innerHTML = '';
                }

                new component({
                    target,
                    props: {{ Js::from($props) }},
                });
            }

            window.addEventListener('livewire:navigated', mount{{ $random }});
        }
    </script>
</div>

Put all your svelte components in resources/js/svelte folder. All svelte components must have .svelte extension.

Usage example:

example.blade.php
@props(['user', 'sort'])
<div>
  <x-svelte
      componentName="UserProfile"
      :props="[
        'user' => $user,
        'sort' => $sort
      ]"
  />
</div>

Let me know in the comments if you faced any difficulties in getting this to work.


Leave a Reply

Your email address will not be published. Required fields are marked *