Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SSR dynamic component imports? #9775

Closed
john-michael-murphy opened this issue Apr 26, 2023 · 6 comments
Closed

SSR dynamic component imports? #9775

john-michael-murphy opened this issue Apr 26, 2023 · 6 comments

Comments

@john-michael-murphy
Copy link

john-michael-murphy commented Apr 26, 2023

Describe the problem

We have a SvelteKit app that is attached to a block-based CMS.

In the CMS you define blocks, like text, image, video, etc. Data from our CMS then gets passed to +page.svelte, where we loop through the defined blocks and render them to the page:

 <!-- +page.svelte -->
<script>
import Text from "./Text.svelte";
import Image from "./Image.svelte";
import Video from "./Video.svelte";

export let data;
</script>

{#each data.body as block}
    {#if block.type === 'Text'}
        <Text {block} />
    {:else if block.type === 'Image'}
        <Image {block} />
    {:else if block.type === 'Video'}
        <Video {block} />
    {:else}
        <span>Not found!</span>
    {/else}
{/each}

As the number of blocks we support grows, however, the more unused code we will likely ship to the client. A page that contains only Text and Image blocks, for example, would also ship code for the Video component.

One solution is to dynamically import components...

 <!-- +page.svelte -->
{#each data.body as block}
    {#await import(`./${block.type}.svelte`) then { default: Component }}
        <Component />
    {/await}
{/each}

...but it doesn't seem like SSR works with this approach.

Is there a good way to solve this problem or best practices folks can suggest?

Describe the proposed solution

I know this is footgun territory, but...

<script>
	const Example = await import('./Example.svelte')
</script>

...I'm pretty sure this will create more problems than it solves!

Alternatives considered

We've thought about using a preprocessor to generate the body loop, but that requires static analysis to happen before component hydration and is a little obtuse. It also locks our body loop inside of a preprocessor and makes page-level customization a lot harder.

Importance

nice to have

Additional Information

No response

@benmccann
Copy link
Member

I think the way to do this would probably be to select the proper component and do an await import it in your load function so that data includes the component and you don't have to use {#await}

@john-michael-murphy
Copy link
Author

@benmccann we have load functions in +page.server.js. Are you saying we can return the object in props and sveltekit will somehow stringify it?

@Rich-Harris
Copy link
Member

This is the sort of advanced use case we want to have a better solution for in a future version of Svelte. For now, you can achieve it by combining a +page.server.js with a +page.js, like this: https://learn.svelte.dev/tutorial/using-both-load-functions

Here's one way you could do it to load only the components that are needed for the blocks you're using: https://stackblitz.com/edit/sveltejs-kit-template-default-nsesrk?file=src%2Flib%2Fblocks%2Fred.svelte,src%2Flib%2Fblocks%2Fgreen.svelte,src%2Flib%2Fblocks%2Fblue.svelte,src%2Froutes%2F%2Bpage.svelte,src%2Froutes%2F%2Bpage.js,src%2Froutes%2F%2Bpage.server.js&terminal=dev

@john-michael-murphy
Copy link
Author

Ah! Didn't realize both load functions could be used in combination. Thanks for the example, Rich.

@carstenjaksch
Copy link

carstenjaksch commented Nov 16, 2023

This is the sort of advanced use case we want to have a better solution for in a future version of Svelte. For now, you can achieve it by combining a +page.server.js with a +page.js, like this: https://learn.svelte.dev/tutorial/using-both-load-functions

Here's one way you could do it to load only the components that are needed for the blocks you're using: https://stackblitz.com/edit/sveltejs-kit-template-default-nsesrk?file=src%2Flib%2Fblocks%2Fred.svelte,src%2Flib%2Fblocks%2Fgreen.svelte,src%2Flib%2Fblocks%2Fblue.svelte,src%2Froutes%2F%2Bpage.svelte,src%2Froutes%2F%2Bpage.js,src%2Froutes%2F%2Bpage.server.js&terminal=dev

@Rich-Harris Could you share an example with TypeScript? I have trouble with correct types.

@aatishb
Copy link

aatishb commented Apr 26, 2024

Hi, (a year later!) we've been using a solution along the lines of what @Rich-Harris proposed here for our block-based sveltekit app. (i.e. we're using vite's dynamic import to import svelte files in +page.js and render them based on the contents of our blocks array). But there's one issue with this step:

components[block.type] = (await import(`$lib/blocks/${block.type}.svelte`)).default;

Vite will include all the svelte files in this folder in the build, not just the ones that we're importing via the CMS (which are known during server execution). Is there any way to limit this import to a specific set of blocks based on the contents of the blocks array, so we have smaller client-side bundles?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants