Matt-Esch / virtual-dom

A Virtual DOM and diffing algorithm
MIT License
11.65k stars 780 forks source link

Streams #348

Open OliverJAsh opened 8 years ago

OliverJAsh commented 8 years ago

Is it possible to define a stream that transforms some input state into a VTree?

I am making an async call to get some data, but I would like to start responding to the user with HTML as soon as possible. I can't render body until the async data arrives, but I would like to respond to the request with the head immediately. This is good for performance, because the user can start parsing the page and downloading scripts, stylesheets, etc. Pseudo code:

var stream = h('html', [
  h('head', [ h('script', {}) ]),
  asyncData.pipe(h('body', {}))
])

stream.on('data', data => res.send(data));

Ideally the stream would emit once with the head and then again with the rest of the tree once the async data has arrived.

staltz commented 8 years ago

@OliverJAsh check transposition in Cycle.js, that's what we do to support these use cases. It requires a little bit of changes to virtual-hyperscript.

OliverJAsh commented 8 years ago

Interesting, thanks @staltz

OliverJAsh commented 8 years ago

@staltz In this example, I expect foo to display immediately, and bar to display 2 seconds later: http://jsbin.com/minowi/1/edit?html,js,output

const {h, makeDOMDriver} = CycleDOM;

function main(responses) {
  const requests = {
    DOM: Rx.Observable.just(false)
      .map(toggled =>
        h('div', [
          'foo',
          Rx.Observable.timer(2000).map(() => h('span', 'bar'))
        ])
      )
  };
  return requests;
}

Cycle.run(main, {
  DOM: makeDOMDriver('#app')
});

Or is this not the expected behaviour? If not, that's what I'm after.

staltz commented 8 years ago
        h('div', [
          'foo',
          Rx.Observable.timer(2000).map(() => h('span', 'bar'))
+           .startWith(h('span', ''))
        ])
OliverJAsh commented 8 years ago

Got it! Thank you.

OliverJAsh commented 8 years ago

Hmm, I actually need something slightly different. I am using virtual dom on the server and want to respond to a HTML request with the head ASAP:

<head><title>foo</title></head>

And then, when the async operation finishes, send the body:

<body>foo</body>

This is known as chunking.

@staltz Your example will emit the HTML for the whole tree with each change (http://jsbin.com/minowi/4/edit?html,js,console,output). I want something closer to streams, whereby we first emit the head and then later the body.

OliverJAsh commented 8 years ago

I am really keen to use the new streams API to improve load times, as detailed in https://jakearchibald.com/2016/streams-ftw/. Re-opening to see if anyone has any ideas on this.

leeoniya commented 8 years ago

@OliverJAsh

Dunno how invested you are specifically in virtual-dom, but have a look at my ThreaditJS implementation in domvm [1]. It loads parts of the interface via async/fetch promises as you're describing here.

There is also a way to do this with async props from the watch module, eg:

["body",
    w.prop("Loading...", w.get("/body-chunk"))
]

[1] http://leeoniya.github.io/domvm/demos/threaditjs/

(If it seems slow at times, it's because the ThreaditJS API intentionally throttles, randomly causes 500/404 errors and other fun things. Open console for accurate timings and network request/response times.) See other impls here: http://threaditjs.com/. It's currently the fastest impl by 100% over Mithril.

Zolmeister commented 8 years ago

Because virtual-dom is all about immutable data structures, streams don't have a natural fit. However, a component system built on top of virtual-dom is a viable option ref: https://zorium.org/tutorial/streams