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

Server-side rendering #148

Merged
merged 7 commits into from
Dec 7, 2016
Merged

Server-side rendering #148

merged 7 commits into from
Dec 7, 2016

Conversation

Rich-Harris
Copy link
Member

This is a slightly bigger PR than just adding SSR, because we need to shuffle some files around. But the tl;dr is that you can now render Svelte components in Node.

How to use it

require( 'svelte/ssr/register' );

var thing = require( './components/Thing.html' );
var html = thing.render({ foo: 'bar', baz: 42 });

Note that components don't have the same API that they do in the browser, because they're doing a different job. Rather than being stateful, you call the render method anew each time you want to generate HTML.

You can use default data, helpers, computed properties, nested components and so on. The lifecycle hooks (onrender and onteardown) are not called.

How it works

Just like the main Svelte compiler, the SSR compiler generates code specific to the component – though in this case, it has a much easier job which is to concatenate strings together.

Using require.extensions makes it nice and easy for nested components to work, without the app developer having to mess around adding resolver hooks and whatnot.

Things to be aware of

  • <style> tags are not currently supported. Coming soon. One of the things I'd like to do is make it possible to extract encapsulated styles from a component tree so that it can be rendered to a single CSS block, either as a standalone .css file or as inline styles. There's a couple of pieces that need to be moved into place first though
  • If you have any other require.extensions hooks that intercept .html files, this will probably not end well. We'll figure out a way to handle that eventually, but for now I expect that's an imaginary problem
  • import declarations are transpiled to the equivalent require statements, meaning your components' dependencies will need to be Node-friendly otherwise your app will blow up. (If you're using browser-specific stuff inside methods or lifecycle hooks, that's fine – just as long as the mere presence of those dependencies doesn't break Node when they load. In most situations, you'll probably be fine.)
  • The flip side of SSR is client-side 'hydration', i.e. reusing elements rather than creating new ones. That's not yet implemented – we need to evaluate our options. Replacing server-rendered DOM with client-rendered DOM isn't the worst thing in the world though.
  • This is not yet battle-tested! Bug reports to the usual place...

@PaulBGD
Copy link
Member

PaulBGD commented Dec 7, 2016

What are the options for reusing elements created by the server? Wouldn't we have to diff the DOM against the client-side changes?

I suppose if we know it's pre-rendered we can just skip the initial rendering and instead just grab the element (assuming we can find it.)

@Rich-Harris
Copy link
Member Author

I was wondering about something along the lines of the following pseudo-code:

<!-- for a template like this: -->
<p>foo</p>
var children = [ ...target.childNodes ];

var p;
var text;
if ( children[0] && children[0].nodeName === 'p' ) {
  p = children.shift();
  var p_children = [ ...p.childNodes ];

  if ( p_children[0] && p_children[0].nodeType === 3 && p_children[0].data === 'foo' ) {
    text = p_children.shift();
    text.data = 'foo';
  } else {
    text = document.createTextNode( 'foo' );
    p.appendChild( text );
  }

  while ( p_children.length ) detachNode( p_children.pop() );
} else {
  p = document.createElement( 'p' );
  text = document.createTextNode( 'foo' );
  p.appendChild( text );
  target.appendChild( p );
}

while ( children.length ) detachNode( children.pop() );

Which is pretty horrendous, though someone smarter than me could probably do a much better job.

I'm curious about whether hydration has tangible benefits over simply trashing the server-rendered DOM. Does anyone know of any research into this?

Here's how Vue deals with hydration, incidentally.

Anyway, hydration is something we can tackle down the road – will merge this in for now and raise an issue for the CSS stuff.

@Rich-Harris Rich-Harris merged commit 6d890f1 into master Dec 7, 2016
@Rich-Harris Rich-Harris deleted the gh-1 branch December 7, 2016 19:45
@DylanPiercey
Copy link

DylanPiercey commented May 7, 2017

@Rich-Harris bootstrapping server side code would be awesome. The main benefit is that in many cases it allows the DOM to be intractable after the first meaningful paint. Basically a user can begin filling out forms (and interact with some pure css components) while the JavaScript is downloading.

I have often been able to substantially improve perceived performance setting this sort of bootstrapping up and also leverage above the fold css components (accordions, toggles, menus, modals, etc).

Another big benefit of bootstrapping from the server is that you could allow full page rendering (building the entire DOM with svelte). This opens up the ability to edit things like header information easily and also makes code bases very isomorphic.

I'm going to be playing around these next few days with a Svelte+Rill combination and am excited to dig deeper. Looks like an awesome framework!

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

Successfully merging this pull request may close these issues.

3 participants