Skip to content

Commit

Permalink
Implement prototype assimilation for a more reliable hot-swapping
Browse files Browse the repository at this point in the history
  • Loading branch information
gaearon committed Jul 14, 2014
1 parent 0bde4fc commit fd7fade
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 31 deletions.
27 changes: 16 additions & 11 deletions example/a.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,26 @@ var A = React.createClass({
};
},

componentWillMount: function () {
window.setInterval(this.incrementNumber, 1000);
},

componentWillUnmount: function () {
window.clearInterval(this.incrementNumber);
window.alert('Unmounting parent');
},

incrementNumber: function () {
this.setState({
number: this.state.number + 1
});
},

render: function() {
return (
<div>
<p>Open an editor, edit and save <code>example/a.jsx</code>.</p>
<p><b>The number should not change.</b></p>
<p><b>The number should keep incrementing one by one.</b></p>

{this.renderStuff()}

Expand All @@ -31,16 +46,6 @@ var A = React.createClass({
<button onClick={this.incrementNumber}>Increment by one</button>
</div>
);
},

incrementNumber: function () {
this.setState({
number: this.state.number + 1
});
},

componentWillUnmount: function () {
window.alert('Unmounting parent');
}
});

Expand Down
71 changes: 51 additions & 20 deletions hot.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ var setPrototypeOf = Object.setPrototypeOf || function (obj, proto) {

module.exports = function (React) {
var mounted = [];

var Mixin = {
componentDidMount: function () {
mounted.push(this);
Expand All @@ -19,39 +18,71 @@ module.exports = function (React) {
}
};

var OriginalComponent;
var assimilatePrototype = (function () {
var storedPrototype;

function assimilateProperty(freshPrototype, key) {
function get() {
if (typeof storedPrototype[key] !== 'function' ||
key === 'type' ||
key === 'constructor') {

return storedPrototype[key];
}

return function () {
var value = storedPrototype[key];
if (typeof value === 'function') {
return value.apply(this, arguments);
} else {
console.warn('A call to ' + key + ' was made after it was deleted. Acting as no-op.');
}
};
}

function set(value) {
storedPrototype[key] = value;
}

storedPrototype[key] = freshPrototype[key];
Object.defineProperty(freshPrototype, key, {
configurable: false,
enumerable: true,
get: get,
set: set
});
}

return function assimilatePrototype(freshPrototype) {
storedPrototype = {};
for (var key in freshPrototype) {
assimilateProperty(freshPrototype, key);
}
};
})();

var Component;
return {
createClass: function (spec) {
spec.mixins = spec.mixins || [];
spec.mixins.push(Mixin);
OriginalComponent = React.createClass(spec);
return OriginalComponent;

Component = React.createClass(spec);
assimilatePrototype(Component.componentConstructor.prototype);

return Component;
},

updateClass: function (spec) {
var FreshComponent = React.createClass(spec),
oldProto = OriginalComponent.componentConstructor.prototype,
newProto = FreshComponent.componentConstructor.prototype;
var UpdatedComponent = React.createClass(spec);
assimilatePrototype(UpdatedComponent.componentConstructor.prototype);

mounted.forEach(function (instance) {
setPrototypeOf(instance, newProto);
instance.constructor.prototype = newProto;
instance._bindAutoBindMethods();
instance.forceUpdate();
});

var key;
for (key in oldProto) {
if (!newProto.hasOwnProperty(key)) {
delete oldProto[key];
}
}
for (key in newProto) {
oldProto[key] = newProto[key];
}

return OriginalComponent;
return Component;
}
};
};

0 comments on commit fd7fade

Please sign in to comment.