Skip to content

Commit

Permalink
Merge pull request #284 from goatslacker/server-rendering
Browse files Browse the repository at this point in the history
The holy-grail of server rendering
  • Loading branch information
goatslacker committed Jun 2, 2015
2 parents 1e84f23 + 2513a15 commit 4cf98e3
Show file tree
Hide file tree
Showing 9 changed files with 578 additions and 27 deletions.
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

0 comments on commit 4cf98e3

Please sign in to comment.