Sapper/Svelte - need @html included content to invoke component

Viewed 69

I'm building a site using sapper that uses the [slug].svelte convention for individual blog entries. The blog content comes out of a (simulated) database, and contains html.

The html is included at the bottom, like so:

...
<div class="content">
  {@html post.html}
</div>
...

All well and good, it renders the html just peachy.

However, consider the following:

In the [slug].svelte file:

import AComponent from '../../components/AComponent.svelte'

And, in the included html rendered inside the {@html post.html}:

<p>yada yada yada</p>
<AComponent prop="data" />
<p>More yada yada yada...</p>

AComponent doesn't get instantiated or invoked.

Is there a way to make this happen? Or am I trying to do something not possible?

(I know the component is ok - it's been tested in another file with complete html.)

Thanx

1 Answers

I don't believe that's possible with the @html directive alone.

From the docs:

The expression should be valid standalone HTML — {@html "<div>"}content{@html "</div>"} will not work, because is not valid HTML.

Since <AComponent prop="data" /> is a Svelte component and not standalone HTML, it won't instantiate itself when using @html.

However, you could wrangle together a solution using <svelte:component> to render a component from string content dynamically.

Here's a quick proof of concept in the Svelte REPL. There's a bunch of edge cases left uncovered, but it shows that it's possible. I've also pasted the code below.

<script>
    import ComponentA from './ComponentA.svelte';
    import ComponentB from './ComponentB.svelte';
    
    let raw = `<p>yada yada yada</p>
<ComponentA name="testing" />
<p>More yada yada yada...</p>`;
    
    $: rawLines = raw.split('\n');
    
    function getComponent(line) {
        const componentName = line.split(' ')[0].substring(1);
        switch (componentName) {
            case 'ComponentA':
                return ComponentA;
            case 'ComponentB':
                return ComponentB;
        }
        return null;
    }
    
    function getComponentProps(line) {
        const props = line.split(' ').slice(1, -1);
        const kvPairs = props.map(p => p.replaceAll('"', '').split('='));
        return Object.fromEntries(kvPairs);
    }
</script>

<textarea bind:value={raw} />
{#each rawLines as line}
    {#if line[1] === line[1].toUpperCase()}
        <svelte:component this={getComponent(line)} {...getComponentProps(line)}></svelte:component>
    {:else}
        {@html line}
    {/if}
{/each}
Related