George Danila's profile picture George Danila's Blog

LazyComponent - Easy Lazy Loading for Svelte Components

Published on

In web development, performance optimization is not just a goal; it’s a necessity. As applications grow in complexity, so does the need for efficient loading strategies. We’ve all seen horror stories about huge JS payloads, even in modern web apps and websites offered by large tech companies (you can read this excellent blog post about JS bloat written by Nikita Prokopov).

Svelte offers developers an elegant solution to this challenge with its ability to await component imports. Pairing this feature with chunking can considerably improve the initial bundle size and load speed of your web app. In this short article I will create an easy-to-use component that can be used to perform these lazy imports in a frictionless way.

Let’s start with a basic Svelte component. Let’s call it HelloWorld.svelte:

// HelloWorld.svelte
<script>
    export let message = "Hello world!";

    function handleClick() {
        alert(message);
    }
</script>

<button on:click={handleClick}>Click me!</button>

Using this component the normal way is straightforward. We import it and use it in a parent component (App.svelte):

// App.svelte
<script>
    import HelloWorld from "./HelloWorld.svelte";
</script>

<HelloWorld message="Hi, nice to meet you!" />

The official documentation explains that we can easily import and use the HelloWorld component asynchronously, like so:

// App.svelte
<script>
    const helloWorldLazy = import("./HelloWorld.svelte"); // helloWorldLazy is a Promise here
</script>

{#if helloWorldLazy}
    {#await helloWorldLazy}
        <span>Loading...</span>
    {:then {default: HelloWorld}}
        <HelloWorld message="Hi, nice to meet you!">
    {/await}
{/if}

Notice how we await the Promise for asynchronously imported HelloWorld component and we display a loading message while the promise is getting resolved. This is great but once you end up having to use this pattern in multiple places it gets a bit tedious to write.

Here’s my solution, the LazyComponent:

// LazyComponent.svelte
<script>    
    export let importPromise;
    export let message;
</script>

{#if importPromise}
    {#await importPromise}        
        {#if message}
            <span>{message}</span>
        {/if}
    {:then {default: ResolvedComponent}}
        <slot ResolvedComponent={ResolvedComponent}/>
    {/await}
{/if}

We make use of the slot props to pass in the resolved component to the user of the LazyComponent. In short, slot props enable Svelte components to pass information over to their parents. Rewriting the previous lazy import of the HelloWorld component using our new solutions is straightforward:

// App.svelte
<script>
    import LazyComponent from "./LazyComponent.svelte";
    const helloWorldLazy = import("./HelloWorld.svelte");
</script>

<LazyComponent 
    importPromise={helloWorldLazy}
    message="Loading..."
    let:ResolvedComponent={HelloWorld}
>
    <HelloWorld message="Hi, nice to meet you!">
</LazyComponent>

Employing lazy loading for components can significantly reduce the initial size of your application’s bundle, thereby enhancing the overall user experience. Hopefully you’ll find this solution as useful as I did!

Happy coding! 😀