Proposal: Streamable apps
See original GitHub issueProposal: Stream-able apps
This proposal seeks to add streaming abilities to DoneJS, providing an improved user experience. By streaming, we mean we are able to write HTML out to the user as soon as we have the data for it. Instead of waiting for all the data to return from the database or a service call, we are able to stream records as they are retrieved into meaningful output to a user. Specifically, we seek to enable streaming during initial rendering (SSR) and also once the browser has loaded.
This should have a significant impact on performance, especially SSR performance. When rendering an HTML page on the server, we will be able to stream out the <head>
immediately which typically includes <link>
tags that load external resources. Getting that HTML to the browser immediately, means that the browser can start loading those resources while the database is still hypothetically returning data.
SSR flow
Overview of streaming flow
- Browser makes a request to the
todos.html
page. - SSR server begins rendering an instance of the application.
<head>{{title}}</head>
and<link href=“theme.css”>
are not waiting on anything asynchronous, so they are streamed to the HTML result.- The
<ul>
(probably) isn’t waiting on any asynchronous behavior, so it is streamd to the result. - The
{{#each todosPromise.value}}
does the following:- Does a request for
Todo.getList({})
which results in a promise that resolves immediately to aDefineList
. - This fires off a
.fetch()
request to/api/todos.ndjson
whose results will be streamed to thatDefineList
. - That
DefineList
is passed tocan-view-live.list
. - The
DefineList
,can-view-live.list
and the streaming DOM serializer are able to coordinate in such a way to notify the DOM serializer to wait.
- Does a request for
- Services server handles the
/api/todos.ndjson
request. - Services server gets a pooled connection and makes a
SELECT
query to the database. - As records come back, the services server converts them into newline delimited JSON, and writes the records in chunks back to the SSR server.
- The SSR server parses out the new
JSON
records,.push()
each record into theDefineList
. - By adding the record into the
DefineList
, a new<li>
is created - The streaming DOM serializer is able to identify this change, serialize the DOM of the
<li>
andres.write
it to the browser. - The browser displays the contents of the
<li>
.
Steps 8-12 repeat as records come back from the Database.
Once the database records have completed:
- The Services server response wil be ended
res.end()
. - The asynchronous behavior associated with the DefineList will complete.
can-view-live.list
will mark all of its nodes as being ready for serialization.- The streaming DOM serializer will move onto the closing
</ul>
. - The SSR server will write out the closing
</ul>
to the browser.
Things to build
- - A
ndjson
stream transformer:fetch("todos.ndjson") .then( (response) => { return ndjsonStream( response.body ); }).then( (todosStream) => { });
- - A streaming can-connect behavior:
// get a streaming list of todos Todo.getList({}).then(function(todos){ template({ todos: todos }); });
<!-- template updates as todos are returned --> <ul> {{#each todos}} <li>{{name}}</li> {{/each}} </ul>
- - A streaming DOM serializer:
var serialize = require('vdom-streaming-serializer'); http.createServer(function(request, response){ var document = makeDocument(); // ... ADD APP TO DOCUMENT ... myApp(document); // send back HTML as it is "completed" var stream = serialize(document.documentElement); stream.pipe(response); });
- - A notification mechanism for asynchronous and/or streaming behavior:
var observationZone = require("can-observation-zone"); // A stateful object var todos = new DefineList([ {id: 1, name: "lawn"} ]); // Indicates the length is going to change in the future var updatedTodos = observationZone.add(todos, "length"); var completed = observationZone.completed(todos,"length"); completed.then(function(){ console.log("zone is completed"); }) setTimeout(function(){ todos.push({id: 2, name: "dishes"}); updatedTodos(); console.log("updatedTodos") },1); // logs // - updatedTodos // - zone is completed
- - Streaming aware live-binding utilities
- - A demo app that puts it all together
donejs-streaming-dev-server
Links
- CSV + fetch streaming example JSBin
- NDJson
- postgresql and node
- streams and express - actually done with http.ServerResponse
Issue Analytics
- State:
- Created 7 years ago
- Comments:12 (11 by maintainers)
Top GitHub Comments
That is so cool. Very elegant. I am going to have to play with this, and possibly rig up an objective speed comparison test.
Going to close in favor of https://github.com/donejs/donejs/issues/787, of course we will being making future streaming enhancements in the future.