Skip to content

Commit 537f326

Browse files
committed
Make fibers optional, add new features, see CHANGELOG
1 parent 93b3b2a commit 537f326

18 files changed

+378
-144
lines changed

.jshintrc

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"expr": true,
66
"globalstrict": true,
77
"globals": {
8+
"console": false,
89
"window": false,
910
"require": false,
1011
"module": false,

CHANGELOG.md

+19
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,22 @@
1+
## 0.6.0
2+
3+
- Fibers are now optional, they are only needed if you want to pre-render
4+
React components by fetching async state recursively, e.g. using
5+
ReactAsync.renderComponentToStringWithAsyncState
6+
7+
- Remove ReactAsync.createClass, React.createClass with ReactAsync.Mixin
8+
should be used instead.
9+
10+
- Remove ReactAsync.renderComponent, React.renderComponent should be used
11+
instead
12+
13+
- Rename ReactAsync.renderComponentToString to
14+
ReactAsync.renderComponentToStringWithAsyncState
15+
16+
- Add ReactAsync.isAsyncComponent
17+
18+
- Add ReactAsync.prefetchAsyncState
19+
120
## 0.5.1
221

322
- Check if async component is still mounted before updating its state from and

Makefile

+18-7
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,29 @@
1-
BIN = ./node_modules/.bin
2-
REPO = $(shell cat .git/config | grep url | xargs echo | sed -E 's/^url = //g')
3-
REPONAME = $(shell echo $(REPO) | sed -E 's_.+:([a-zA-Z0-9_\-]+)/([a-zA-Z0-9_\-]+)\.git_\1/\2_')
1+
BIN = ./node_modules/.bin
2+
PATH := $(BIN):$(PATH)
3+
4+
TEST_SUITES = $(wildcard tests/*.js)
5+
TEST_SUITES_COMMON = $(filter-out %-browser.js %-server.js, $(TEST_SUITES))
6+
TEST_SUITES_BROWSER = $(filter %-browser.js, $(TEST_SUITES))
7+
TEST_SUITES_SERVER = $(filter %-server.js, $(TEST_SUITES))
48

59
install link:
610
@npm $@
711

812
lint:
9-
@$(BIN)/jshint --verbose *.js
13+
@jshint --verbose *.js lib/*.js
14+
15+
test:: test-server test-browser
16+
17+
test-server::
18+
@mocha -R spec $(TEST_SUITES_COMMON) $(TEST_SUITES_SERVER)
1019

11-
test::
12-
@$(BIN)/mocha -R spec specs/*.js
20+
test-browser:
21+
@browserify -d -p [ mocaccino -R spec ] \
22+
$(TEST_SUITES_COMMON) $(TEST_SUITES_BROWSER) \
23+
| phantomic
1324

1425
example::
15-
@$(BIN)/node-dev --no-deps example/server.js
26+
@node-dev --no-deps example/server.js
1627

1728
release-patch: test lint
1829
@$(call release,patch)

browser.js

+16-56
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,31 @@
11
"use strict";
22

3-
var React = require('react');
4-
var invariant = require('react/lib/invariant');
5-
var getComponentFingerprint = require('./getComponentFingerprint');
3+
var BaseMixin = require('./lib/BaseMixin');
4+
var getComponentFingerprint = require('./lib/getComponentFingerprint');
65

7-
var AsyncStateMixin = {
6+
var Mixin = {
7+
mixins: [BaseMixin],
88

9-
getInitialState: function() {
10-
return {};
11-
},
12-
13-
componentDidMount: function() {
9+
getDefaultProps: function() {
1410
if (window.__reactAsyncStatePacket === undefined) {
15-
this.getInitialStateAsync(function(err, state) {
16-
if (err) {
17-
throw err;
18-
}
19-
if (this.isMounted()) {
20-
this.setState(state);
21-
}
22-
}.bind(this));
11+
return {};
2312
}
24-
}
25-
};
26-
27-
function createClass(spec) {
28-
29-
invariant(
30-
spec.render,
31-
'ReactAsync.createClass(...): Class specification must implement a `render` method.'
32-
);
33-
34-
invariant(
35-
spec.render,
36-
'ReactAsync.createClass(...): Class specification must implement a `getInitialStateAsync` method.' +
37-
'Otherwise you should use React.createClass(...).'
38-
);
3913

40-
var render = spec.render;
14+
var fingerprint = getComponentFingerprint(this);
4115

42-
spec.render = function() {
43-
if (window.__reactAsyncStatePacket !== undefined) {
44-
var state = window.__reactAsyncStatePacket[getComponentFingerprint(this)];
45-
for (var k in state)
46-
this.state[k] = state[k];
16+
if (window.__reactAsyncStatePacket[fingerprint] === undefined) {
17+
return {};
4718
}
48-
return render.call(this);
49-
}
50-
51-
spec.mixins = spec.mixins || [];
52-
spec.mixins.push(AsyncStateMixin);
53-
54-
return React.createClass(spec);
55-
}
5619

57-
function renderComponent(component, element) {
58-
component = React.renderComponent(component, element);
20+
var state = window.__reactAsyncStatePacket[fingerprint];
21+
delete window.__reactAsyncStatePacket[fingerprint];
5922

60-
// invalidate data after first render
61-
if (window.__reactAsyncStatePacket !== undefined) {
62-
window.__reactAsyncStatePacket = undefined;
23+
return {asyncState: state};
6324
}
64-
65-
return component;
66-
}
25+
};
6726

6827
module.exports = {
69-
createClass: createClass,
70-
renderComponent: renderComponent
28+
prefetchAsyncState: require('./lib/prefetchAsyncState'),
29+
isAsyncComponent: require('./lib/isAsyncComponent'),
30+
Mixin: Mixin
7131
};

example/client.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ var ReactAsync = require('../');
44

55
ReactMount.allowFullPageRender = true;
66

7-
var App = ReactAsync.createClass({
7+
var App = React.createClass({
8+
mixins: [ReactAsync.Mixin],
89

910
getInitialStateAsync: function(cb) {
1011
setTimeout(function() {
@@ -20,7 +21,8 @@ var App = ReactAsync.createClass({
2021
}
2122
});
2223

23-
var Nested = ReactAsync.createClass({
24+
var Nested = React.createClass({
25+
mixins: [ReactAsync.Mixin],
2426

2527
getInitialStateAsync: function(cb) {
2628
setTimeout(function() {

example/server.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ var App = require('./client');
66
express()
77
.get('/bundle.js', browserify(__dirname + '/client', {debug: true, watch: true}))
88
.get('/', function(req, res, next) {
9-
ReactAsync.renderComponentToString(App(), function(err, markup, data) {
9+
ReactAsync.renderComponentToStringWithAsyncState(App(), function(err, markup, data) {
1010
if (err) return next(err);
1111

1212
markup = ReactAsync.injectIntoMarkup(markup, data, ['./bundle.js']);

index.js

+48-63
Original file line numberDiff line numberDiff line change
@@ -2,52 +2,58 @@
22

33
var React = require('react');
44
var invariant = require('react/lib/invariant');
5-
var Future = require('fibers/future');
6-
var Fiber = require('fibers');
7-
var getComponentFingerprint = require('./getComponentFingerprint');
5+
var BaseMixin = require('./lib/BaseMixin');
6+
var getComponentFingerprint = require('./lib/getComponentFingerprint');
7+
var injectIntoMarkup = require('./lib/injectIntoMarkup');
88

9-
/**
10-
* Create a component class which can get a part of its state by executing async
11-
* routine.
12-
*
13-
* @param {Object} spec
14-
*/
15-
function createClass(spec) {
16-
17-
invariant(
18-
spec.render,
19-
'ReactAsync.createClass(...): Class specification must implement a `render` method.'
20-
);
21-
22-
invariant(
23-
spec.getInitialStateAsync,
24-
'ReactAsync.createClass(...): Class specification must implement a `getInitialStateAsync` method. ' +
25-
'Otherwise you should use React.createClass(...) method to create components with no async ' +
26-
'data fetching'
27-
);
28-
29-
var render = spec.render;
30-
31-
spec.render = function() {
32-
var getInitialStateAsync = Future.wrap(spec.getInitialStateAsync.bind(this));
33-
var state = getInitialStateAsync().wait();
34-
Fiber.current.__reactAsyncStatePacket[getComponentFingerprint(this)] = state;
35-
this.state = this.state || {};
36-
for (var k in state)
37-
this.state[k] = state[k];
38-
return render.call(this);
39-
}
9+
var Mixin = {
10+
mixins: [BaseMixin],
11+
12+
getDefaultProps: function() {
13+
14+
var Fiber;
15+
16+
try {
17+
Fiber = require('fibers');
18+
} catch(err) {
19+
20+
}
21+
22+
if (Fiber === undefined || Fiber.current === undefined) {
23+
return {};
24+
}
25+
26+
invariant(
27+
typeof this.getInitialStateAsync === 'function',
28+
this.displayName + ' component must implement a `getInitialStateAsync` method. ' +
29+
'Otherwise you should not use ReactAsyncMixin.Mixin'
30+
);
31+
32+
var Future = require('fibers/future');
4033

41-
return React.createClass(spec);
34+
var getInitialStateAsync = Future.wrap(this.getInitialStateAsync);
35+
var asyncState = getInitialStateAsync().wait();
36+
var fingerprint = getComponentFingerprint(this);
37+
Fiber.current.__reactAsyncStatePacket[fingerprint] = asyncState;
38+
return {asyncState: asyncState};
39+
}
4240
}
4341

4442
/**
45-
* Render component markup asynchronously.
43+
* Prefetch async state recursively and render component markup asynchronously.
4644
*
4745
* @param {ReactComponent} component
4846
* @param {Function<Error, String, Object>} cb
4947
*/
50-
function renderComponentToString(component, cb) {
48+
function renderComponentToStringWithAsyncState(component, cb) {
49+
50+
try {
51+
var Fiber = require('fibers');
52+
} catch (err) {
53+
console.error('install fibers: npm install fibers');
54+
throw err;
55+
}
56+
5157
Fiber(function() { // jshint ignore:line
5258
try {
5359
Fiber.current.__reactAsyncStatePacket = {};
@@ -69,31 +75,10 @@ function renderComponentToString(component, cb) {
6975
}).run();
7076
}
7177

72-
/**
73-
* Inject data and optional client scripts into markup.
74-
*
75-
* @param {String} markup
76-
* @param {Object} data
77-
* @param {?Array} scripts
78-
*/
79-
function injectIntoMarkup(markup, data, scripts) {
80-
var injected = '<script>window.__reactAsyncStatePacket=' + JSON.stringify(data) + '</script>';
81-
82-
if (scripts) {
83-
injected += scripts.map(function(script) {
84-
return '<script src="' + script + '"></script>';
85-
}).join('');
86-
}
87-
88-
if (markup.indexOf('</body>') > -1) {
89-
return markup.replace('</body>', injected + '$&');
90-
} else {
91-
return markup + injected;
92-
}
93-
}
94-
9578
module.exports = {
96-
renderComponentToString: renderComponentToString,
97-
injectIntoMarkup: injectIntoMarkup,
98-
createClass: createClass
79+
prefetchAsyncState: require('./lib/prefetchAsyncState'),
80+
isAsyncComponent: require('./lib/isAsyncComponent'),
81+
Mixin: Mixin,
82+
renderComponentToStringWithAsyncState: renderComponentToStringWithAsyncState,
83+
injectIntoMarkup: injectIntoMarkup
9984
};

lib/BaseMixin.js

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
"use strict";
2+
/**
3+
* ReactAsyncMixin defines a mixin and a set of method for working with
4+
* components which can fetch part of their state using an asynchronous method,
5+
* `getInitialStateAsync(cb)`.
6+
*/
7+
8+
var invariant = require('react/lib/invariant');
9+
var isAsyncComponent = require('./isAsyncComponent');
10+
11+
/**
12+
* Mixin for asynchronous components.
13+
*
14+
* Asynchronous state is fetched via `getInitialStateAsync(cb)` method but also
15+
* can be injected via `asyncState` prop.
16+
*
17+
* In the latter case `getInitialStateAsync` won't be called at all.
18+
*/
19+
var BaseMixin = {
20+
21+
getInitialState: function() {
22+
return this.props.asyncState || {};
23+
},
24+
25+
componentDidMount: function() {
26+
27+
invariant(
28+
isAsyncComponent(this),
29+
"%s uses ReactAsync.Mixin and should provide getInitialStateAsync(cb) method",
30+
this.displayName
31+
);
32+
33+
if (!this.props.asyncState) {
34+
this.getInitialStateAsync(this._onStateReady);
35+
}
36+
},
37+
38+
_onStateReady: function(err, state) {
39+
if (err) {
40+
throw err;
41+
}
42+
43+
if (this.isMounted()) {
44+
this.setState(state);
45+
}
46+
}
47+
};
48+
49+
module.exports = BaseMixin;
File renamed without changes.

lib/injectIntoMarkup.js

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
"use strict";
2+
3+
/**
4+
* Inject data and optional client scripts into markup.
5+
*
6+
* @param {String} markup
7+
* @param {Object} data
8+
* @param {?Array} scripts
9+
*/
10+
function injectIntoMarkup(markup, data, scripts) {
11+
var injected = '<script>window.__reactAsyncStatePacket=' + JSON.stringify(data) + '</script>';
12+
13+
if (scripts) {
14+
injected += scripts.map(function(script) {
15+
return '<script src="' + script + '"></script>';
16+
}).join('');
17+
}
18+
19+
if (markup.indexOf('</body>') > -1) {
20+
return markup.replace('</body>', injected + '$&');
21+
} else {
22+
return markup + injected;
23+
}
24+
}
25+
26+
module.exports = injectIntoMarkup;

0 commit comments

Comments
 (0)