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

Full Page Rendering #577

Closed
DylanPiercey opened this issue May 7, 2017 · 5 comments
Closed

Full Page Rendering #577

DylanPiercey opened this issue May 7, 2017 · 5 comments

Comments

@DylanPiercey
Copy link

DylanPiercey commented May 7, 2017

Currently it is not possible to use Svelte to render/update the entire document. This feature would be useful for isomorphic apps and allows Svelte to easily update things like meta and title tags.

As discussed on gitter this would make it possible to express your entire template with Svelte and not just components inside the body tag.

Usage would look something like the following:

components/page.js

<!doctype html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>{{title}}</title>
  </head>
  <body>
    {{yield}}
    <!-- script that gets rendered -->
    <script src='/bundle.js'></script>
  </body>
</html>

routes/home.js

<Page title="Hello World">
  {{#if foo}}
    <p>foo!</p>
  {{else}}
    <p>not foo!</p>
  {{/if}}
</Page>

<script>
  import Page from '../components/page.html'
  export default {
    components: { Page }
  }
</script>

Rendering

import HomePage from './routes/home.html'

// Server side
const html = HomePage.render({ foo: true })

// Client side
const page = new HomePage({
  target: document.documentElement,
  data: { foo: true }
})

I think something that should come first is the ability to bootstrap server rendered DOM. Currently svelte always inserts or replaces an existing DOM node but this cannot work for the full document for obvious reasons.

I imagine this could be achieved by changing the 'mount' api to something like this:

(Note this is based on the HelloWorld example).

function create_main_fragment(state, component) {
  var attached = false;
  var text_1_value, h1, text_1;

  return {
    mount: function ( target ) {
      // This extracts elements based on the compiled locations of the dom.
      // We would have to add some checks here to make sure the DOM is the same.
      // React does this with a checksum at the root element.
      h1 = target
      text_1 = target.childNodes[0]
      attached = true
    },
    
    insert: function (target, anchor) {
      // This does what mount use to do.
      h1 = createElement( 'h1' );
      appendNode( createText( "Hello " ), h1 );
      text_1 = createText( text_1_value = state.name );
      appendNode( text_1, h1 );
      appendNode(createText("!"), h1);
      insertNode(h1, target, anchor);
      attached = true
    }

    update: function (changed, state) {...},

    destroy: function (detach) {...}
  };
}

function App ( options ) {
  ...
  this._fragment = create_main_fragment( this._state, this );
  if ( options.insert ) this._fragment.insert( options.insert, null );
  else if ( options.mount ) this._fragment.mount( options.mount );
}

This could then be used like:

// Bootstrap from existing dom.
const page = new HomePage({
  mount: document.documentElement,
  ...
})

// Inject new dom.
const page = new HomePage({
  insert: '#my-element',
  ...
})

I could be way off though.

@Rich-Harris Rich-Harris mentioned this issue Jun 7, 2017
12 tasks
@Rich-Harris
Copy link
Member

Finally starting to dig into this. I actually thought that full-page rendering in the sense of SSR'ing a document (as opposed to a fragment) wasn't possible — that maybe it would choke on <!doctype> etc — but it turns out it works fine. So that's the first hurdle already crossed.

The more I think about this, the less sure I am about full-page rendering in the client. A lot of what goes inside a <head> element isn't supposed to be mucked about with past the initial load — <title> is an obvious exception, but things like <meta http-equiv> and so on should probably be considered immutable. Things like <link rel='stylesheet'> tags can be swapped in and out, but the likelihood is you'd want to do that programmatically rather than declaratively so that you can listen for load events and avoid rendering glitches.

So while Svelte could in theory pull out the 'mutable' bits of <head> and update those, to me it feels like we'd be better off focusing on updating the DOM inside <body> and, if necessary, coming up with convenient idiomatic ways of updating things like document.title.

Would be great to get hydration with or without this. Having thought about it for a bit I reckon it'd be good to avoid checksums — it makes it hard to do things like this, where loading becomes false once the client-side JavaScript loads:

{{#if loading}}
  <p>loading, please wait</p>
{{else}}
  <div>
    we are interactive
  </div>
{{/if}}

I wonder if we could do something like this:

// without hydration
function create_main_fragment(state, component) {
  var text_value, nodes;

  return {
    mount (target, anchor) {
      nodes = [
        createElement('h1'),
        createText('hello '),
        createText(text_value = state.name),
        createText('!')
      ];

      appendNode(nodes[0], nodes[1]);
      appendNode(nodes[0], nodes[2]);
      appendNode(nodes[0], nodes[3];
      appendNode(target, nodes[0]);
    },

    // ...
  };
}

// with hydration
function create_main_fragment(state, component) {
  var text_value, nodes;

  return {
    mount (target, anchor) {
      nodes = hydrate(target, [
        {
          name: 'h1',
          children: [
            'hello ',
            text_value = state.name,
            '!'
          ]
        }
      ]);
    },

    // ...
  };
}

...where hydrate is not only responsible for finding the right nodes, but for creating them if they don't exist and destroying them if they shouldn't exist.

@DylanPiercey
Copy link
Author

DylanPiercey commented Jun 12, 2017

@Rich-Harris I can understand your hesitation re full page rendering and your concerns are valid. In practise I have had no issues doing this with react. Another thing that's nice about reacts implementation is that it can tell the difference between server rendered nodes and client inserted ones. This way you can have the best of both worlds if you need to handle load events and such as you mentioned.

Having said that another way to achieve the same effect is using something like react-helmet or hairdresser so it's not a deal breaker for me if that doesn't make it in although it would be nice to have an an endorsed way to do this with svelte.

Hydration is much more important however. Your idea of a 'hydrate' function makes a lot of sense. My ramblings from before was merely an idea to avoid having another function inserted to keep with the 'no framework' theme. In this case it may very well make more sense to add another function though.

@PaulBGD
Copy link
Member

PaulBGD commented Jun 13, 2017

I'm very interested in having hydration, I'm currently maintaining a website that renders most of the webpage through svelte serverside but currently has to just delete all the nodes the server sent since there's no hydration.

@roman-vanesyan
Copy link

Is this an active issue? Seems the problem was already fixed.

@DylanPiercey
Copy link
Author

Was resolved.

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