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

The holy-grail of server rendering #284

Merged
merged 2 commits into from
Jun 2, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"env": {
"node": true,
"browser": true,
"es6": true
},
"ecmaFeatures": {
Expand Down
20 changes: 13 additions & 7 deletions dist/alt-with-addons.js
Original file line number Diff line number Diff line change
Expand Up @@ -1568,18 +1568,24 @@ var StoreMixin = {
return x;
};

var makeActionHandler = function makeActionHandler(action) {
return function (x) {
var fire = function fire() {
loadCounter -= 1;
action(intercept(x, action, args));
};
return typeof window === 'undefined' ? function () {
return fire();
} : fire();
};
};

// if we don't have it in cache then fetch it
if (shouldFetch) {
loadCounter += 1;
/* istanbul ignore else */
if (spec.loading) spec.loading(intercept(null, spec.loading, args));
spec.remote.apply(spec, [state].concat(args)).then(function (v) {
loadCounter -= 1;
spec.success(intercept(v, spec.success, args));
})['catch'](function (v) {
loadCounter -= 1;
spec.error(intercept(v, spec.error, args));
});
return spec.remote.apply(spec, [state].concat(args)).then(makeActionHandler(spec.success))['catch'](makeActionHandler(spec.error));
} else {
// otherwise emit the change now
_this.emitChange();
Expand Down
20 changes: 13 additions & 7 deletions dist/alt.js
Original file line number Diff line number Diff line change
Expand Up @@ -986,18 +986,24 @@ var StoreMixin = {
return x;
};

var makeActionHandler = function makeActionHandler(action) {
return function (x) {
var fire = function fire() {
loadCounter -= 1;
action(intercept(x, action, args));
};
return typeof window === 'undefined' ? function () {
return fire();
} : fire();
};
};

// if we don't have it in cache then fetch it
if (shouldFetch) {
loadCounter += 1;
/* istanbul ignore else */
if (spec.loading) spec.loading(intercept(null, spec.loading, args));
spec.remote.apply(spec, [state].concat(args)).then(function (v) {
loadCounter -= 1;
spec.success(intercept(v, spec.success, args));
})['catch'](function (v) {
loadCounter -= 1;
spec.error(intercept(v, spec.error, args));
});
return spec.remote.apply(spec, [state].concat(args)).then(makeActionHandler(spec.success))['catch'](makeActionHandler(spec.error));
} else {
// otherwise emit the change now
_this.emitChange();
Expand Down
22 changes: 13 additions & 9 deletions src/alt/store/StoreMixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,20 +51,24 @@ const StoreMixin = {
: value == null
const intercept = spec.interceptResponse || (x => x)

const makeActionHandler = (action) => {
return (x) => {
const fire = () => {
loadCounter -= 1
action(intercept(x, action, args))
}
return typeof window === 'undefined' ? (() => fire()) : fire()
}
}

// if we don't have it in cache then fetch it
if (shouldFetch) {
loadCounter += 1
/* istanbul ignore else */
if (spec.loading) spec.loading(intercept(null, spec.loading, args))
spec.remote(state, ...args)
.then((v) => {
loadCounter -= 1
spec.success(intercept(v, spec.success, args))
})
.catch((v) => {
loadCounter -= 1
spec.error(intercept(v, spec.error, args))
})
return spec.remote(state, ...args)
.then(makeActionHandler(spec.success))
.catch(makeActionHandler(spec.error))
} else {
// otherwise emit the change now
this.emitChange()
Expand Down
24 changes: 24 additions & 0 deletions src/utils/AltIso.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import Iso from 'iso'
import * as Render from './Render'

export default {
define: Render.withData,

render(alt, Component, props) {
// recycle state
alt.recycle()

if (typeof window === 'undefined') {
return Render.toString(Component, props).then((markup) => {
return Iso.render(markup, alt.takeSnapshot())
})
} else {
return Promise.resolve(
Iso.bootstrap((state, _, node) => {
alt.bootstrap(state)
Render.toDOM(Component, props, node)
})
)
}
}
}
124 changes: 124 additions & 0 deletions src/utils/Render.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import React from 'react'

export function withData(fetch, MaybeComponent) {
function bind(Component) {
return React.createClass({
contextTypes: {
buffer: React.PropTypes.object.isRequired
},

childContextTypes: {
buffer: React.PropTypes.object.isRequired
},

getChildContext() {
return { buffer: this.context.buffer }
},

componentWillMount() {
if (!this.context.buffer.locked) {
this.context.buffer.push(
fetch(this.props)
)
}
},

render() {
return this.context.buffer.locked
? React.createElement(Component, this.props)
: null
}
})
}

// works as a decorator or as a function
return MaybeComponent ? bind(MaybeComponent) : Component => bind(Component)
}

function usingDispatchBuffer(buffer, Component) {
return React.createClass({
childContextTypes: {
buffer: React.PropTypes.object.isRequired
},

getChildContext() {
return { buffer }
},

render() {
return React.createElement(Component, this.props)
}
})
}

class DispatchBuffer {
constructor(renderStrategy) {
this.promisesBuffer = []
this.locked = false
this.renderStrategy = renderStrategy
}

push(v) {
this.promisesBuffer.push(v)
}

fill(Element) {
return this.renderStrategy(Element)
}

clear() {
this.promisesBuffer = []
}

flush(Element) {
return Promise.all(this.promisesBuffer).then((data) => {
// fire off all the actions synchronously
data.forEach((f) => {
if (Array.isArray(f)) {
f.forEach(x => x())
} else {
f()
}
})
this.locked = true

return this.renderStrategy(Element)
}).catch(() => {
// if there's an error still render the markup with what we've got.
return this.renderStrategy(Element)
})
}
}


function renderWithStrategy(strategy) {
return (Component, props) => {
// create a buffer and use context to pass it through to the components
const buffer = new DispatchBuffer((Node) => {
return React[strategy](Node)
})
const Container = usingDispatchBuffer(buffer, Component)

// cache the element
const Element = React.createElement(Container, props)

// render so we kick things off and get the props
buffer.fill(Element)

// flush out the results in the buffer synchronously setting the store
// state and returning the markup
return buffer.flush(Element)
}
}

export function toDOM(Component, props, documentNode) {
const buffer = new DispatchBuffer()
buffer.locked = true
const Node = usingDispatchBuffer(buffer, Component)
const Element = React.createElement(Node, props)
buffer.clear()
return React.render(Element, documentNode)
}

export const toStaticMarkup = renderWithStrategy('renderToStaticMarkup')
export const toString = renderWithStrategy('renderToString')
Loading