lukeed / svelte-demo

Multi-page demo built Svelte 3.x and Rollup with code-splitting
MIT License
114 stars 19 forks source link

Dynamic components removed during hydration #13

Closed jimafisk closed 4 years ago

jimafisk commented 4 years ago

Hi @lukeed,

In my project there's a bug where dynamic components (<svelte:component this={route}>) are temporarily removed during hydration on initial page load. This results in a cumulative layout shift that looks like a flash of content to the user. You can see an example of it on the website for the project: https://plenti.co/

Also, here are some screenshots from the default starter of the project:

Screenshot during hydration ![pmissing](https://user-images.githubusercontent.com/5913244/96391751-db27ae00-1187-11eb-821e-15f361e40155.png)
Screenshot after being fully loaded ![pfull](https://user-images.githubusercontent.com/5913244/96391756-dfec6200-1187-11eb-866f-971f91367367.png)

I initially thought this might be a known hydration issue with svelte, and even opened an issue to confirm that. However, I tried replicating hydrating dynamic components without the router and it seems to work correctly: https://github.com/jimafisk/svelte-dynamic-components

Then I pulled down the Svelte demo for Navaid (https://github.com/lukeed/svelte-demo) and noticed the same issue. Here are some screenshots:

Screenshot during hydration ![missing](https://user-images.githubusercontent.com/5913244/96391738-d06d1900-1187-11eb-8948-ea7a6060cbe7.png)
Screenshot after being fully loaded ![full](https://user-images.githubusercontent.com/5913244/96391744-d4993680-1187-11eb-921f-fa7c7de3f49f.png)

I'm curious if maybe the Route isn't getting set to a class constructor in time during the hydration process. Do you happen to know what might be going on here? Thanks again for all the help, and great presentation at svelte-summit today :).

lukeed commented 4 years ago

Hey, it's not a bug actually. It makes sense if you think about what's going on:

Until the last step is reached, the Svelte app is told nothing is supposed to be on the page. This means that if you had SSR content there, Svelte clears it.

Because my svelte-demo isn't SSR'd, the approach provided is fine. There's nothing by default, which means nothing gets cleared.

However, in the SSR case, you have to defer that first "draw" until the first import resolves. In svelte world, that means waiting to do app = new App(...) until that import is finished -- and at that point you'll have a Route property too.

Lemme know if that helps! Happy to send over code sample tomorrow.

PS, Plenti looks awesome & I appreciated the shout-out 🙌 Looking forward to trying it out!

jimafisk commented 4 years ago

Thanks for the quick response @lukeed! I appreciate you breaking down the steps, that helps me understand what's happening conceptually. I think you've diagnosed the issue exactly right, I'm just still having a little trouble figuring out how to implement deferring the draw correctly. If you're able to send a code snippet without too much trouble, I'd really appreciate it! Thank you!

lukeed commented 4 years ago

Hey, I moved the issue here since it's more relevant to this repo – and as discussed, it's not a Navaid bug :D

Here's an example branch on this repo, showing how to defer hydration until the first import resolves: https://github.com/lukeed/svelte-demo/commit/410388efc36e81feb16c3c41d0158a8eae29f8d9

This is the typical setup I have (and what I also set up automatically within new freshie apps). Routing is controlled by the boot script. And with Svelte specifically, this allows you to move/separate routing logic from the Svelte app itself, which in turn allows your server to pass in the necessary prop values to render desired content.

So, for example, you can do something like this on the server:

import App from './App.js'; // built output
import * as About from './About.js'; // built output

async function render(req, Component) {
    let props = {
        params: req.params,
        Route: Component.default,
        pathname: req.path,
        // ...
    };

    if (Component.preload) {
        Object.assign(props, await Component.preload(req));
    }

    const output = App.render(props);

    // ...
}

//=> render(req, About)
jimafisk commented 4 years ago

Thanks for all the examples @lukeed! I was toying with something similar, but this really helped clarify things for me. I have a working model now: https://github.com/plentico/plenti/commit/1eccf048e2a8ffa4c36e6e4555f42753557626cf (Just need to fix an unrelated issue). I appreciate you taking the time to break things down and point me in the right direction!

lukeed commented 4 years ago

Not a problem, glad I could help!