Coder Social home page Coder Social logo

Comments (28)

jordangarcia avatar jordangarcia commented on August 29, 2024

Hi!

While not having used NuclearJS specifically for isomorphic apps personally, I built it with full support for the idea of running it server side and serving rendered content.

There is nothing browser specific in NuclearJS. It can function fine in a node environment.

The main challenge with isomorphic rendering lies with the component framework. To render apps server side you need a way to take the state of the application, whether it be from NuclearJS stores and the usage of getDataBindings() or with getInitialState(), and render the HTML string of your components. React currently has the renderToStaticMarkup method.

The second thing is you need a way to rehydrate your state once that app has been served. This is necessary because serving an HTML page doesn't capture the JS runtime and what is loaded in memory. So a strategy like serializing all of your store data then rehydrating them when the page loads is necessary.

A quick example off the top of my head may look like this:

var React = require('react');
var express = require('express');
var Nuclear = require('nuclear-js');
var db = require('./db');

var flux = new Nuclear.Reactor();
flux.registerStores({
  'test': require('./stores/test-store')
})

var testGetter = ['test'] // get the value of the test store

var server = express();

var Component = React.createClass({
  mixins: NuclearReactMixin(flux),
  getDataBindings: function() {
    return {
      test: testGetter,
    }
  },
  render: function() {
    return <div>{this.state.test}</div>
  }
});

/**
 * React class to handle the rendering of the HTML head section
 *
 * @class Html
 * @constructor
 */
var Html = React.createClass({
    /**
     * Refer to React documentation render
     *
     * @method render
     * @return {Object} HTML head section
     */
    render: function() {
        return (
            <html>
            <head>
                <meta charSet="utf-8" />
                <title>{this.props.title}</title>
            </head>
            <body>
                <div id="app" dangerouslySetInnerHTML={{__html: this.props.markup}}></div>
            </body>
            <script dangerouslySetInnerHTML={{__html: this.props.state}}></script>
            <script src="/public/js/client.js" defer></script>
            </html>
        );
    }
});

server.use(function (req, res, next) {
  db.fetchUser({ id: 123 })
    .then(function(user) {
      var nuclearState = flux.evaluateToJS([]); // evaluates the entire app state and returns JS obj
      var state = 'window.appState = ' + JSON.serialize(nuclearState) + ';'
      // now the App state is written as JSON on the page and the flux system needs to replace hydrate itself with that state
      var html = React.renderToStaticMarkup(React.createElement(HtmlComponent, {
          markup: React.renderToString(React.createElement(Component)),
          state: state,      
      ));
      res.send(html);
    });
});

var port = process.env.PORT || 3000;
server.listen(port);

This example was improvised from yahoo's dispatchr + examples. You can see more here: https://github.com/yahoo/flux-examples/blob/master/chat/server.js

Isomorphic rendering is fully fleshed out as Nuclear needs a way to hydrate the entire app state. This is something that I'd like to plan for 0.6.1. 0.6.0 will be released soon.

from nuclear-js.

rattrayalex avatar rattrayalex commented on August 29, 2024

Awesome stuff! Really appreciate the response & detailed example. Looking forward to seeing what's to come!

(as a side note, I really love the model you guys have - it reminds me a lot of how I used Bacon.js with Immutable.js, and seems to address some of my biggest pain points while maintaining the niceties. Really excited. )

I actually don't think the __dangerouslySetHtml should even be needed... Here's a quick rewrite if that's okay:

var React = require('react');
var express = require('express');
var Nuclear = require('nuclear-js');
var db = require('./db');

var flux = new Nuclear.Reactor();
flux.registerStores({
  'test': require('./stores/test-store')
})

var testGetter = ['test'] // get the value of the test store

var server = express();

var MainComponent = React.createClass({
  mixins: NuclearReactMixin(flux),
  getDataBindings: function() {
    return {
      test: testGetter,
    }
  },
  render: function() {
    return <div>{this.state.test}</div>
  }
});

/**
 * React class to handle the rendering of the HTML head section
 *
 * @class Html
 * @constructor
 */
var Html = React.createClass({
    /**
     * Refer to React documentation render
     *
     * @method render
     * @return {Object} HTML head section
     */
    render: function() {
        return (
            <html>
            <head>
                <meta charSet="utf-8" />
                <title>{this.props.title}</title>
            </head>
            <body>
                <div id="app"><MainComponent/></div>
            </body>
            <!-- Perhaps not needed anyway, as the html is fully rendered and it'll fetch new stuff later -->
            if (this.props.initialState) {<script>window.initialAppState = {this.props.initialAppState};</script>}
            <script src="/public/js/client.js" defer></script>
            </html>
        );
    }
});

server.use(function (req, res, next) {
  db.fetchUser({ id: 123 })
    .then(function(user) {
      var nuclearState = flux.evaluateToJS([]); // evaluates the entire app state and returns JS obj
      // now the App state is written as JSON on the page and the flux system needs to replace hydrate itself with that state
      var html = React.renderToStaticMarkup(React.createElement(HtmlComponent, {
          initialAppState: nuclearState,
      ));
      res.send(html);
    });
});

var port = process.env.PORT || 3000;
server.listen(port);

Key features:

  • you can use even the Html component on the frontend as well, the only awkwardness is the "intialState" shenanigans, since you obviously don't want to rerender that... not sure what's best to do there.
  • the MainComponent is just rendered like normal.
  • (I think) you don't even need to quote/serialize the appState.

Questions:

  • How would you load the initialAppState into the Stores? getInitialState: => window.initialAppState.test? But then that wouldn't work on the server, right?
  • Tactically, how would you put the results of the DB query on the server into the Stores? (you didn't use the user that was fetched in the example and I'm not sure how that would be done...)

Thanks!

from nuclear-js.

jordangarcia avatar jordangarcia commented on August 29, 2024

You can definitely replace the markup prop with the component. The idea Yahoo had with the HtmlComponent is it is reusable for any page you want to render.

I have not tried rendering a script tag like that with simply the JSON markup as the contents. I'd be interested to know if it works though!

from nuclear-js.

rattrayalex avatar rattrayalex commented on August 29, 2024

Cool!

For loading the initial data on both the client and the server, would creating a Store factory function (which takes the initial state) be a good idea?

eg;

var testStoreCreator = function(initialTestData){
  return Store({
    getInitialState: initialTestData,
    initialize: => {/*...*/};
  })
}

Server:

db.fetch(whatever).then((data)=>{
  var flux = new Nuclear.Reactor()
  flux.registerStores({test: testStoreCreator(data.test)})
  /* wait... how do I put the `flux` into the Component? 
The components would `require()` the Store, wouldn't they? 
And there's many individual stores, not one root store, right? 
Nested subcomponents individually choose what data they listen to, 
right - it doesn't all come from the top via props? */
});

Client:

var flux = new Nuclear.Reactor()
flux.registerStores({test: testStoreCreator(window.initialAppData.test)})
// same problem as above...

Am I on the right track here? What would you suggest?

from nuclear-js.

jordangarcia avatar jordangarcia commented on August 29, 2024

hm I haven't thought of that pattern. It would definitely work. I dont see too big of a downside to it either.

One pattern I was thinking of was implementing a method on the reactor that is reactor.loadState that would take the entire state map. That way you could use the same actions on the server to load the state of your stores and not bypass the store handling the state responsibility.

Hydrating state clientside from serverside is pretty direct. I dont see the need to do it at a store level it seems like 99% of the use cases would simply be taking the entire app state server side and dumping it in client side whenever the page loads.

I think I will plan to put a hydrateStores or loadState method on the reactor in 0.6.0

from nuclear-js.

rattrayalex avatar rattrayalex commented on August 29, 2024

Awesome! I think loadState sounds like a terrific/simple solution. Would
hydrateStores be called on the Reactor object?

On Mon, Apr 20, 2015 at 11:58 PM Jordan Garcia [email protected]
wrote:

hm I haven't thought of that pattern. It would definitely work. I dont see
too big of a downside to it either.

One pattern I was thinking of was implementing a method on the reactor
that is reactor.loadState that would take the entire state map. That way
you could use the same actions on the server to load the state of your
stores and not bypass the store handling the state responsibility.

Hydrating state clientside from serverside is pretty direct. I dont see
the need to do it at a store level it seems like 99% of the use cases would
simply be taking the entire app state server side and dumping it in client
side whenever the page loads.

I think I will plan to put a hydrateStores or loadState method on the
reactor in 0.6.0


Reply to this email directly or view it on GitHub
#24 (comment)
.

from nuclear-js.

jordangarcia avatar jordangarcia commented on August 29, 2024

Yep,

It'd be something like:

var toImmutable = require('nuclear-js').toImmutable;
var reactor = require('reactor');
var state = toImmutable(window.appState);

reactor.loadState(state);

On Mon, Apr 20, 2015 at 12:32 PM, Alex Rattray [email protected]
wrote:

Awesome! I think loadState sounds like a terrific/simple solution. Would
hydrateStores be called on the Reactor object?

On Mon, Apr 20, 2015 at 11:58 PM Jordan Garcia [email protected]
wrote:

hm I haven't thought of that pattern. It would definitely work. I dont
see
too big of a downside to it either.

One pattern I was thinking of was implementing a method on the reactor
that is reactor.loadState that would take the entire state map. That way
you could use the same actions on the server to load the state of your
stores and not bypass the store handling the state responsibility.

Hydrating state clientside from serverside is pretty direct. I dont see
the need to do it at a store level it seems like 99% of the use cases
would
simply be taking the entire app state server side and dumping it in
client
side whenever the page loads.

I think I will plan to put a hydrateStores or loadState method on the
reactor in 0.6.0


Reply to this email directly or view it on GitHub
<
https://github.com/optimizely/nuclear-js/issues/24#issuecomment-94530972>

.


Reply to this email directly or view it on GitHub
#24 (comment)
.

from nuclear-js.

rattrayalex avatar rattrayalex commented on August 29, 2024

Nice!

On Tue, Apr 21, 2015 at 1:09 AM Jordan Garcia [email protected]
wrote:

Yep,

It'd be something like:

var toImmutable = require('nuclear-js').toImmutable;
var reactor = require('reactor');
var state = toImmutable(window.appState);

reactor.loadState(state);

On Mon, Apr 20, 2015 at 12:32 PM, Alex Rattray [email protected]
wrote:

Awesome! I think loadState sounds like a terrific/simple solution.
Would
hydrateStores be called on the Reactor object?

On Mon, Apr 20, 2015 at 11:58 PM Jordan Garcia <[email protected]

wrote:

hm I haven't thought of that pattern. It would definitely work. I dont
see
too big of a downside to it either.

One pattern I was thinking of was implementing a method on the reactor
that is reactor.loadState that would take the entire state map. That
way
you could use the same actions on the server to load the state of your
stores and not bypass the store handling the state responsibility.

Hydrating state clientside from serverside is pretty direct. I dont see
the need to do it at a store level it seems like 99% of the use cases
would
simply be taking the entire app state server side and dumping it in
client
side whenever the page loads.

I think I will plan to put a hydrateStores or loadState method on the
reactor in 0.6.0


Reply to this email directly or view it on GitHub
<
#24 (comment)

.


Reply to this email directly or view it on GitHub
<
https://github.com/optimizely/nuclear-js/issues/24#issuecomment-94548258>
.


Reply to this email directly or view it on GitHub
#24 (comment)
.

from nuclear-js.

rattrayalex avatar rattrayalex commented on August 29, 2024

(basically off-topic at this point as it really pertains to the Yahoo example, but a generic HtmlComponent could simply place this.props.children inside the body tag).

from nuclear-js.

appsforartists avatar appsforartists commented on August 29, 2024

I'd expect a method like loadState to support isomorphism, so I think you're on the right track. One potential sharp edge though: you'd want to make sure you set all the objects to their correct types. What if someone's using an OrderedSet - how do you make sure you deserialize that correctly? What if someone stores a POJO as an attribute on a Map; how do you make sure it isn't coerced to a Map or List on the client?

from nuclear-js.

rattrayalex avatar rattrayalex commented on August 29, 2024

Presumably loadState would except Immutable objects as well as POJO's. If you would want something other than the default Immutable type (eg; Map for an object), you would have to convert it to an Immutable before passing to loadState. Immutable works on Node, so this shouldn't be too much of a problem.

from nuclear-js.

appsforartists avatar appsforartists commented on August 29, 2024

I think there needs to be an optional serialize and deserialize hook in each Store to support that. At the Nuclear level, it should be safe to call reactor.deserialize(reactor.serialize()) without affecting the values in the stores. Doing so allows the isomorphism to be abstracted away by something like Ambidex.

Before discovering Nuclear, I was experimenting with designing my own functionally-reactive Flux library. Here's how I handled serialization at the Store level.

from nuclear-js.

jordangarcia avatar jordangarcia commented on August 29, 2024

At Optimizely we only use Immutable Map and List for our store data, which can easily be marshalled from JSON.

I'd be interested to see how a store serialize and deserialize method work in practice. I was thinking of leaving the marshalling responsibilities to the user, but if defining these methods at the store level is convenient and effective I'd be willing to explore them further.

Version 1.1.0 will address the Isomorphic story :)

from nuclear-js.

WinstonFassett avatar WinstonFassett commented on August 29, 2024

Also, Immutable.js has an open issue for a serialization mechanism:
immutable-js/immutable-js#336

from nuclear-js.

appsforartists avatar appsforartists commented on August 29, 2024

@jordangarcia Is there an ETA for 1.1? Are you playing with it internally?

As I've mentioned, I've been playing with isomorphic React/Flux apps. I had similar ideas to you regarding reactivity and composability. I've even gone so far as to prototype an implementation. I was drafting a blog post outlining my thoughts when I happened upon Nuclear.

I expect to start playing with Nuclear either this week or next. If it meets my needs, I'd rather help you improve it than maintain a competing but similar project.

As for serialization, my recommendation would be to have a reactor.serialize method and a coorresponding reactor.deserialize. When called, they'd iterate over all the stores and look for store.serialize/store.deserialize. If found, you'd pass in the last known state of that store and use the result to build your (de)serialized tree. If not, fallback to Immutable's to/fromJSON methods. For cases like yours, you can rely on the defaults and have the exact same experience you're already describing (isomorphism for free with reactor.serialize() and reactor.deserialize()). For cases where specific types are important, store-level serialize/deserialize hooks give you that flexibility.

Did you follow that? What do you think?

BTW, I work at eBay about a block from you guys, if you'd ever like to chat in person.

from nuclear-js.

rattrayalex avatar rattrayalex commented on August 29, 2024

I was thinking the same things as Benton fwiw.

On Fri, May 1, 2015, 3:27 AM Brenton Simpson [email protected]
wrote:

@jordangarcia https://github.com/jordangarcia Is there an ETA for 1.1?
Are you playing with it internally?

As I've mentioned, I've been playing with isomorphic React/Flux apps. I
had similar ideas to you regarding reactivity and composability. I've even
gone so far as to prototype an implementation
https://github.com/appsforartists/funx. I was drafting a blog post
https://gist.github.com/appsforartists/3fbb573ee46239c42ed5#comment-1440015
outlining my thoughts when I happened upon Nuclear.

I expect to start playing with Nuclear either this week or next. If it
meets my needs, I'd rather help you improve it than maintain a competing
but similar project.

As for serialization, my recommendation would be to have a
reactor.serialize method and a coorresponding reactor.deserialize. When
called, they'd iterate over all the stores and look for store.serialize/
store.deserialize. If found, you'd pass in the last known state of that
store and use the result to build your (de)serialized tree. If not,
fallback to Immutable's to/fromJSON methods. For cases like yours, you can
rely on the defaults and have the exact same experience you're already
describing (isomorphism for free with reactor.serialize() and
reactor.deserialize()). For cases where specific types are important,
store-level serialize/deserialize hooks give you that flexibility.

Did you follow that? What do you think?

BTW, I work at eBay about a block from you guys, if you'd ever like to
chat in person.


Reply to this email directly or view it on GitHub
#24 (comment)
.

from nuclear-js.

jordangarcia avatar jordangarcia commented on August 29, 2024

@appsforartists I read your blog post draft and I we are thinking along the same lines of utilizing functional transforms to achieve composability...really great stuff!

I think your approach to using serialize deserialize makes a lot of sense for the isomorphic story. I am very open to contributors for 1.1 features if you are interested. Also we should meet in person and chat about all of this.

Feel free to contact me via email and we can setup a time ([email protected])

from nuclear-js.

appsforartists avatar appsforartists commented on August 29, 2024

Awesome. Will do.

I started working on experimental-nuclear-js yesterday. It separates out the method declaration from instantiation (a pattern I've found essential in isomorphic apps, so you can instantiate per-request). I may also implement serialize/deserialize in it. I'd be happy to show you.

from nuclear-js.

appsforartists avatar appsforartists commented on August 29, 2024

@jordangarcia I just published what I have so far for experimental-nuclear-js.

Here's (de)serialize.

from nuclear-js.

kyleboyle avatar kyleboyle commented on August 29, 2024

Version 1.1.0 will address the Isomorphic story :)

Will the story also include a chapter on dealing with async xhr stuff on the server?

from nuclear-js.

Sinewyk avatar Sinewyk commented on August 29, 2024

I maybe be wrong, but I think you are focusing on some non critical things.

Imo, the only hurdle for going full isomorphic is that right now we have no way to easily insert the reactor context inside our components

On client side we have the flux.js file pattern where we export the Nuclear singleton and we simply require it:

That goes right out the window and breaks everything once we start to want to go isomorphic. We need to be able to replicate all that on per request basis with everything fully separated from each other.

Something along those lines.

var http = require('http');
var Nuclear = require('nuclear-js')
var React = require('react');
// this component uses the reactor mixin to do some stuff
var Component = require('./Component.jsx');

http.createServer(function (req, res) {
  // reactor for THIS request
  var reactor = new Nuclear.Reactor();
  // pass the reactor as a prop argument
  // somehow find a way to make it available for every component ?
  var reactMarkup = React.renderToString(React.createElement(Component, {reactor: reactor}));
  var clientState = reactor.deserialize();
  reactor.destroy() // cleanup necessary ?
  // insert react markup + state inside html
  // create reactor instance client side, load state, profit
}).listen(1337, '127.0.0.1');

No idea how all these immutable transactions would stress the VM. Would it properly get GC-ed at the end of the request or do we need a destroy method ... etc.

Anyway, unless I'm missing something the primary stuff we should focus on is a way to context the mixins, the modules, and the actions (everything that has a call to the flux.js file should actually be capable of getting injected the reactor for the current request.

On top of my head what we could do (no idea if some of them are already true):

  • make flux.registerStores idempotent that way, instead of registering the stores in the modules, we register in the top components willMount/getInitialState lifecycles with the reactor that was somehow passed to it.
  • same thing for the mixin, we attach it non initialized, and we initialize just before calling the registerStores
  • actions should export a function that receives the reactor and returns the action binded to that reactor

I would be prepared to work my ass off for this, unless I'm completely off.

edit: fix for clarity

from nuclear-js.

Sinewyk avatar Sinewyk commented on August 29, 2024

Duh, I can't believe I forgot react context. If we pass the reactor to the top level component that we wrap to declare that it has a context ... TADA we can pass it through all the tree by using the context, so ... all actions/mixins could be then using the reactor.

Anyway, we could learn a lot from the architecture of https://github.com/yahoo/fluxible, they seem to be the only one to actually make it work right now.

React context is here to stay (confirmed for React 1.0), so it should be pretty safe to rework the architecture quite a bit to make it work perfectly for server side.

edit: A good plan would be to imitate their bootstrapping procedure. Wrap the top level component to initiate the context to pass through the reactor to the whole tree. The mixin could be then context aware, throw if the expected reactor is not here with a message ala React invariant style, and at first because I don't know how to proceed with the actions, we would simply edit so that each actions take the reactor as the first argument.

from nuclear-js.

bhamodi avatar bhamodi commented on August 29, 2024

@Sinewyk - are we done with this ticket?

from nuclear-js.

Sinewyk avatar Sinewyk commented on August 29, 2024

Hmm, once we figure out the deal with the recommended architecture being updated and maybe an upgrade path for people to undertake if they want to be universal compliant regarding nuclear-js and the example is merged I think we're good. Unless I'm missing something.

from nuclear-js.

Sinewyk avatar Sinewyk commented on August 29, 2024

And there's a release and I've updated the doc ... so with the merged example + the doc of https://github.com/jordangarcia/nuclear-js-react-addons you guys should be able to do isomorphic stuff now that the singleton problem is out. Let us know what you think.

from nuclear-js.

 avatar commented on August 29, 2024

Any plans to flesh out the isomorphic story for nuclearjs?

from nuclear-js.

Sinewyk avatar Sinewyk commented on August 29, 2024

@freynl It's merged. There's an isomorphic example using https://github.com/jordangarcia/nuclear-js-react-addons

from nuclear-js.

jordangarcia avatar jordangarcia commented on August 29, 2024

Closing this as we have a story :)

from nuclear-js.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.