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 and progressive enhancement #1

Closed
Rich-Harris opened this issue Nov 21, 2016 · 9 comments
Closed

SSR and progressive enhancement #1

Rich-Harris opened this issue Nov 21, 2016 · 9 comments

Comments

@Rich-Harris
Copy link
Member

SSR could potentially be something like this:

require( 'svelte/ssr/register' );

const renderWidget = require( './components/Widget.html' );
const html = renderWidget({
  foo: 1,
  bar: 2
});

Not exactly sure how progressive enhancement would be implemented, and whether it can be done in a zero-cost way, but it'd be pretty cool. I don't know whether it looks more like this (i.e. all components can be rendered as progressive enhancements)...

const widget = new Widget({
  target: document.querySelector( 'main' ),
  enhance: true
});

...or this (i.e. only components that we know up-front we will want to use that way get the extra code):

<!-- Widget.html -->
<p>i am a widget</p>

<script>
  export default {
    enhance: true,
    // ...
  };
</script>
@evs-chris
Copy link
Contributor

In fiddling about with SSR, I've come to the conclusion that there are two ways that I would approach it:

  1. Add a toHTML to all of the generated components, and just use the same code for both server and client rendering.
  2. Add a second generator that just works with strings and have a flag to generate server/string producing code.

I like the second approach a lot more, as it jives with the "don't add unnecessary stuff to the generated code" philosophy that svelte seems to be built around. There are some challenges that would have to be overcome, like building two different flavours, client and server, of each module, publishing them, and then using the correct flavour for the correct environment. I suppose it could be as simple as exporting the client flavour as default, the server flavour as special named export, and letting the server rendering process pull the server flavour when rendering. Tree-shaking could take care of the excess flavours anywhere a bundler is being used. Does that seem reasonable, or have I wandered too far into left field?

I don't think progressive enhancement is all that difficult once SSR is done, as it seems it's just a matter of adding a helper to create nodes rather than using document.createElement and friends directly. The helper would check for the enhance flag, and if present, would look for a suitable existing node to reuse before creating one. The enhance flag check could be limited to the root node, which would gather up the contents of the target element and pass them along the render chain, triggering automatic use of the existing DOM as children rendered. Of course, the presence of a helper kinda depends on the outcome of #9. Bonus: the helper refs would minify much better than document methods, which are >20b per node.

@Rich-Harris
Copy link
Member Author

Definitely option 2 😀

Whereas in Ractive, a component in node has the full API so that you can set data etc, I was thinking it'd be nice and easy to just expose a render(data) -> html method – i.e. components would be totally stateless.

The two parts I've been struggling with a bit:

  • Nested components – could be solved with a require.extensions hook, though I always feel like I'm doing something wrong when I muck around with that. Alternative is to intercept imported components some other way, which seems quite brittle
  • Lifecycle hooks – there isn't an oninit like in Ractive (there could be, but it's been a source of confusion in the past so initially I just added two lifecycle hooks, render and teardown) so there wouldn't be any place for setup work to happen. Might not be an issue if components are stateless though

The compiler can generate CommonJS files (rewriting import declarations as appropriate), so it should be relatively straightforward to get components working in Node as long as they don't have any browser-only dependencies. Hmm, think I'll spin up a svelte-ssr repo and start hacking.

I have some thoughts re #9, will leave them there. Definitely excited for us to solve the hydration problem.

@evs-chris
Copy link
Contributor

I've not yet looked into how nested components work, but I kinda assumed they'd be late binding. When the parser runs across a camel-cased element, it calls it a component. Then when the generator needs to render a component, it pops in code to find the component constructor from up the hierarchy in some sort of registry (new SomeComponent({ components: { Foo, Bar }, ... })), though maybe just on the parent. I'm probably a ways off though, because I don't know how you'd meld that with a single-file component structure.

Is there a way to contribute code to the component constructor? If there is, and the component is constructed with whatever its params are, then I'd say there's really no need for an onint.

@Rich-Harris
Copy link
Member Author

I kinda assumed they'd be late binding

The trouble is they're imported:

<script>
  import Widget from './Widget.html'

  export default {
    components: { Widget }
  };
</script>

So any compilation has to happen before (or during) the import. I wondered about doing the same thing Ractive does – <link rel='svelte' href='...'> but the static analysis win you get from using import and declaring the dependency in components is definitely worth something. (So many times I've left links to unused Ractive components in my apps because my linter didn't yell at me!)

Is there a way to contribute code to the component constructor?

If there was it'd be called oninit 😀 I'm tempted to roll with just render and teardown (especially makes sense if components are stateless on the server) and see how far we get... there's something appealing about only having two unambiguous hooks.

@proyb6
Copy link

proyb6 commented Nov 30, 2016

In SEO, I think it doesn't work quite well with contents in Javascript?

@ghost
Copy link

ghost commented Nov 30, 2016

I know this is veering away from SSR, but in terms of the late binding components mentioned - I've been using the concept extensively in Ractive and it's been a huge win.

Some of the benefits / use cases of late binding and a component registry:

  • hot reloading could become swapping the component instance in the registry and re-rendering
  • dynamic registration of components and so there's an integration point for plugin/component libraries to expose UI kits

I guess the downside is that you don't know the entire component tree at compile time but, unless that's a big win, I think a standardised component registration point and the flexibility of dynamically swapping in and out components at runtime are two big pluses.

Oh, and thanks @Rich-Harris and @evs-chris for the incredible work on Ractive (and also now Svelte) - it's awesome stuff - hat's off to you guys for building things that are so incredibly powerful and yet super simple at the same time. Thanks! 👍

@Rich-Harris
Copy link
Member Author

@simond-14 thanks!

Knowing the component tree at compile time definitely does have advantages. Apart from the fact that you don't need a registry (which has to be managed at runtime somehow – which could easily cause problems if you have e.g. multiple apps on the page, potentially compiled with different versions), it makes everything that much more explicit when your dependencies are clearly expressed – the compiler can give useful errors, and it opens up possibilities like app-level static analysis (for catching missing/badly-typed data, unused CSS selectors, etc etc) and extracting app styles into a single .css file.

I've never implemented hot reloading before – hopefully a fixed component tree isn't a showstopper for that...

Anyway: came here to say that server-side rendering is now implemented in https://github.com/sveltejs/svelte-ssr. As mentioned in https://github.com/sveltejs/svelte-ssr/issues/4 I think it makes sense to merge it into this repo, unless anyone talks me out of it.

@PaulBGD
Copy link
Member

PaulBGD commented Jan 13, 2017

Hey any update on this? Was trying out SSR and something a little funky happened when I loaded my webpage:
Image

I guess for now I'll remove existing DOM nodes.

@Rich-Harris
Copy link
Member Author

This can be closed, as SSR and hydration are both supported

Rich-Harris pushed a commit that referenced this issue Mar 30, 2019
Remove unused r variable.
Rich-Harris pushed a commit that referenced this issue May 26, 2019
Rich-Harris pushed a commit that referenced this issue Nov 9, 2019
…ation

site/tutorials - Added extra explanation for object reactivity
Rich-Harris pushed a commit that referenced this issue Jun 2, 2020
trueadm added a commit that referenced this issue Jul 25, 2024
Rich-Harris added a commit that referenced this issue Jul 25, 2024
* Revert "Revert "breaking: avoid flushing queued updates on mount/hydrate" (#1…"

This reverts commit 8d13921.

* fix legacy wrapper

* lint

* docs

* duplicate

---------

Co-authored-by: Rich Harris <[email protected]>
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

4 participants