Comments (28)
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.
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.
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.
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.
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.
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.
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 theReactor
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.
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 theReactor
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.
(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.
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.
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.
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.
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.
Also, Immutable.js has an open issue for a serialization mechanism:
immutable-js/immutable-js#336
from nuclear-js.
@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.
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.
@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.
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.
@jordangarcia I just published what I have so far for experimental-nuclear-js
.
from nuclear-js.
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.
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:
- in components for the mixin already linked to the singleton
- in the modules indexes to call
flux.registerStores
- in the actions to bind them to the reactor singleton
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.
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.
@Sinewyk - are we done with this ticket?
from nuclear-js.
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.
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.
Any plans to flesh out the isomorphic story for nuclearjs?
from nuclear-js.
@freynl It's merged. There's an isomorphic example using https://github.com/jordangarcia/nuclear-js-react-addons
from nuclear-js.
Closing this as we have a story :)
from nuclear-js.
Related Issues (20)
- Is it possible to use ReactMixin with ECMA6 class extension syntax? HOT 3
- Data Bindings as Props instead of State HOT 3
- [HELP]NuclearJS + Babel 6 as transpiler HOT 3
- Nuclear Module HOT 4
- [Question] How do dispatch one action within another action? HOT 2
- [Question] Data store dependency HOT 3
- Getter dependencies not being cached HOT 15
- ImmutableJS dependency HOT 7
- [HELP] NuclearJs reports "TypeError: Cannot read property 'dispatch' of undefined" HOT 5
- Logging fails on older Android browsers when called with `null` or `undefined` HOT 3
- Upgrade Immutable to the latest version HOT 2
- When using batch, do not receive error when calling dispatch inside of dispatch HOT 2
- reactor.dispatch throwing error 'VM31785:6061 Uncaught Error: `dispatch` cannot be called with an `undefined` action type.' HOT 1
- ES6 Support HOT 1
- Is this project still being maintained? HOT 3
- Disable Dispatch logs using debug packages HOT 3
- Observer not called HOT 2
- Active? HOT 2
- Github Project Template Files
- Online documentation is out of date vs source documentation
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from nuclear-js.