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

HTML renderer for Server-side / isomorphism #34

Closed
4 tasks done
staltz opened this issue Nov 22, 2014 · 24 comments
Closed
4 tasks done

HTML renderer for Server-side / isomorphism #34

staltz opened this issue Nov 22, 2014 · 24 comments
Assignees

Comments

@staltz
Copy link
Member

staltz commented Nov 22, 2014

  • renderAsHTML()
  • Refactor/split src/render.js
  • Example (home '/', '/about', '/page/:id', hydration/dehydration, context, express server)
  • Documentation for renderAsHTML()

References: https://github.com/yahoo/flux-examples, https://github.com/reactjs/react-page, https://github.com/yahoo/flux-examples/blob/master/todo/server.js

@OliverJAsh
Copy link

I was just about to raise an issue to discuss this.

You can stringify a virtual-dom tree: https://github.com/alexmingoia/virtual-dom-stringify

@staltz
Copy link
Member Author

staltz commented Nov 24, 2014

Yeah, it seems to be doable with virtual-dom. The solutions for isomorphic rendering are just ideas in my head right now, but all the necessary parts seem to be in place. It won't be that much conceptually different than React/Flux.

After I finish #33 and #36, I'll start drafting this one.

@staltz
Copy link
Member Author

staltz commented Nov 24, 2014

To share a bit what I have in my mind, in the server a DataFlowSource is built for the incoming request, which will be fed to the whole data flow system, and the response is asynchronously available through an HTMLRenderer, created with Cycle.createHTMLRenderer (which should be the brother of Cycle.createRenderer, using virtual-dom-stringify internally). The rendered HTML should have javascript code that will build the client's DataFlowSource.

In the client-side, the client DataFlowSource will be fed to the same data flow system (in essence, this is app.js), and the Renderer will be the default one Cycle.createRenderer.

@staltz staltz self-assigned this Nov 26, 2014
@OliverJAsh
Copy link

Perhaps we could start by working on the server side story, and then work on making the library work in an isomorphic way.

@staltz
Copy link
Member Author

staltz commented Nov 27, 2014

Yes, but to make the server side "make sense" we need to have at least two pages, because the server needs to process the request and make an appropriate response. If we had one page, then server side rendering would be just about providing one static html that contains cycle.js app, and that is a no-brainer with what the framework already provides.

I've done routing with cycle.js (in a closed source project), and it's not complicated. It is outlined here.

So we need first that multi-page example, before putting it in the server.

@OliverJAsh
Copy link

Has any progress been made on this?

@staltz
Copy link
Member Author

staltz commented Jan 3, 2015

@OliverJAsh not yet, but I have been doing preparations. I've been discussing the matter with some people (beyond GitHub), and keeping it in mind whenever I implement current features. The plan is to implement HTMLRenderer and use that on the server side instead of the normal Renderer.

The framework is evolving quickly, so I believe I'll be working on server-side rendering soon. The priority at the moment is to build features that bring breaking changes. Those need to come before as early as possible.

@staltz staltz added minor and removed question labels Jan 5, 2015
@staltz staltz changed the title Server-side and isomorphic rendering HTMLRenderer for Server-side (and isomorphic) rendering Jan 11, 2015
@staltz
Copy link
Member Author

staltz commented Jan 11, 2015

Cycle.registerCustomElement() should call both Renderer.registerCustomElement() (static) and HTMLRenderer.registerCustomElement() (static). HTMLRenderer.registerCustomElement() should implement replaceCustomElements in the HTMLRenderer, and take(1) from the dataFlowNode vtree$. replaceCustomElements will yield a vtree that may include vtree$ as leafs, so somehow we must invert that structure.

@staltz
Copy link
Member Author

staltz commented Jan 14, 2015

Raynos/mercury#55 related

@antris
Copy link

antris commented Jan 25, 2015

+1 for this!

@schrepfler
Copy link
Contributor

I suppose to make all isomorphic the target runtime will be node/iojs?
For the sake of interop with the Java world, do we think it would be possible to make it possible to run on top of the JVM as well (vert.x or Rhino/Nashhorn)?
How about if someone would like to mix it with Scala/Scala.js, or rx-java/rx-scala on the server side, is there a clean boundary/service interface which can be specified for interop? Is it the virtual-dom or is there more to it?

@jareware
Copy link

One core problem with the isomorphic story is deciding when the app is done requesting its initial data from all over: that's the point where you call it a day, serialize everything and send the UI down (the "isomorphic halting problem", if you will :).

Do you have ideas on how to solve this in Cycle for specific cases? For the general case?

Very interesting stuff!

@staltz
Copy link
Member Author

staltz commented Feb 23, 2015

Yes, the "isomorphic halting problem" IHP (I like the name 😄) is the most important one to solve for the isomorphic story. The draft solution I have in mind will probably exploit the fact that all Rx Observables can have a "complete" event after it emits events. So one naïve solution is to apply combineLatest on all Views, then use last to get the final virtual tree on the complete event (notice the vertical line marking the complete).

Most of the obstacles against this simplistic approach will probably be discovered while building, so I don't know yet.

@jareware
Copy link

I was under the impression that Cycle Models would (under normal operation) never complete, though, so that the Views can keep on observing them..? Would there thus need to be some special condition on some server-side-aware Models that would allow them to complete, whereas on the client-side they wouldn't? It's very likely anything requiring server-side interaction on the client will want to be aware of where it's running, though, so that might be a nice place to fork at.

@cgeorg
Copy link

cgeorg commented Feb 23, 2015

I can see a situation where Cycle models would want to complete on the client side. A component could finish its useful lifespan, for instance an "Add item" component - when the user clicks add and all validation passes, the component's useful lifecycle is through, and it's final emission of the information about the item to add could be exposed, and its observable chain shut down.

@staltz
Copy link
Member Author

staltz commented Feb 23, 2015

Very good point @jareware, I was actually supposed to mention that, but forgot.
By default, Cycle apps should be thought as "all observables are infinite" (don't have the completed event), but they can be made finite if needed. Gladly, it's actually easy to make a Cycle Model become finite on the server-side without changing any code related to that Model. Instead, we change the Intents. That's because Models are often defined like this:

var Model = Cycle.createModel((InitialConditions, Intent) =>
  ({
    name$: Rx.Observable.merge(
      InitialConditions.get('name$'),
      Intent.get('changeName$')
    )
  })
});

An Intent on the client-side is naturally an infinite stream, since the user could theoretically interact with the user interface for an indeterminate amount of time. On the server-side, though, we know for sure there is no ongoing user interaction, so we can set all Intent streams to empty. Actually the infinite nature of Intents is contagious: they make Model streams become infinite, and View streams (derived from Model stream) become infinite too. "User interfaces as an infinitely recursive experience" is one theme I want to explore in an upcoming conference presentation.

So for the example above, on the client-side, InitialConditions.name$ is finite (has a complete event) and Intent.changeName$ is infinite (no complete event), hence the resulting merge will be infinite. On the server-side, both InitialConditions.name$ and Intent.changeName$ are finite, so the resulting merge will be finite. Check this rxmarbles for the semantics of merge with complete events (drag around the complete marker).

The problem is then just figuring how to replace Intents on the server-side so that they always return an empty completed stream, while trying to minimize as much as possible the amount of changes a developer needs to do on his app codebase.

@staltz staltz self-assigned this Mar 8, 2015
@staltz staltz removed their assignment Mar 18, 2015
@staltz staltz changed the title HTMLRenderer for Server-side (and isomorphic) rendering HTML renderer for Server-side / isomorphism Apr 23, 2015
@staltz staltz self-assigned this Apr 23, 2015
@staltz
Copy link
Member Author

staltz commented Apr 25, 2015

Done in v0.20.4

@staltz staltz closed this as completed Apr 25, 2015
@jareware
Copy link

Epic. :)

@antris
Copy link

antris commented Apr 27, 2015

Nice!

@OliverJAsh
Copy link

Do we have any examples of this? Would make a good addition to https://github.com/vic/awesome-cyclejs!

@staltz
Copy link
Member Author

staltz commented Aug 4, 2015

@googol
Copy link

googol commented Oct 26, 2015

Has this been removed since? I can't seem to find this functionality in the current version.

@staltz
Copy link
Member Author

staltz commented Oct 26, 2015

@googol
Copy link

googol commented Oct 26, 2015

oh right, thanks

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

7 participants