-
Notifications
You must be signed in to change notification settings - Fork 799
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit cc161c6
Showing
9 changed files
with
255 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
node_modules | ||
.DS_Store |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
# react-hot-loader | ||
|
||
This is a **highly experimental** proof of concept of [React live code editing](http://www.youtube.com/watch?v=pw4fKkyPPg8). | ||
It marries React with Webpack [Hot Module Replacement](http://webpack.github.io/docs/hot-module-replacement.html) by monkeypatching `React.createClass`. | ||
|
||
Inspired by [react-proxy-loader](https://github.com/webpack/react-proxy-loader). | ||
|
||
### Running Example | ||
|
||
``` | ||
npm install | ||
cd example | ||
webpack-dev-server --hot | ||
open http://localhost:8080/webpack-dev-server/bundle | ||
``` | ||
|
||
Then edit `example/a.jsx` and `example/b.jsx`. | ||
Your changes should be displayed live, without unmounting components or destroying their state. | ||
|
||
### Limitations | ||
|
||
* You have to include component's displayName in `require` call | ||
|
||
### Implementation Notes | ||
|
||
Currently, it keeps a list of mounted instances and updates their prototypes when an update comes in. | ||
A better approach may be to make monkeypatch `createClass` to return a proxy object [as suggested by Pete Hunt](https://github.com/webpack/webpack/issues/341#issuecomment-48372300): | ||
|
||
## Installation | ||
|
||
`npm install react-hot-loader` | ||
|
||
## Usage | ||
|
||
[Documentation: Using loaders](http://webpack.github.io/docs/using-loaders.html) | ||
|
||
```javascript | ||
// Currently you have to pass displayName to require call: | ||
var Button = require('react-hot?Button!./button'); | ||
``` | ||
|
||
When a component is imported that way, changes to its code should be applied **without unmounting it or losing its state**. | ||
|
||
# License | ||
|
||
MIT (http://www.opensource.org/licenses/mit-license.php) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
/** @jsx React.DOM */ | ||
|
||
var React = require('react'), | ||
B = require('react-hot?B!./b'); | ||
|
||
var A = React.createClass({ | ||
getInitialState: function () { | ||
return { | ||
number: Math.round(Math.random() * 100) | ||
}; | ||
}, | ||
|
||
render: function() { | ||
return ( | ||
<div> | ||
<p>Open this editor, edit and save <code>example/a.jsx</code>.</p> | ||
<p><b>The number should not change.</b></p> | ||
|
||
{this.renderStuff()} | ||
|
||
<p>This should also work for children:</p> | ||
<B /> | ||
</div> | ||
); | ||
}, | ||
|
||
renderStuff: function () { | ||
return ( | ||
<div> | ||
<input type='text' value={this.state.number} /> | ||
<button onClick={this.incrementNumber}>Increment by one</button> | ||
</div> | ||
); | ||
}, | ||
|
||
incrementNumber: function () { | ||
this.setState({ | ||
number: this.state.number + 1 | ||
}); | ||
}, | ||
|
||
componentWillUnmount: function () { | ||
window.alert('Unmounting parent'); | ||
} | ||
}); | ||
|
||
module.exports = A; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
/** @jsx React.DOM */ | ||
|
||
var React = require('react'), | ||
A = require('react-hot?A!./a'); | ||
|
||
React.renderComponent(<A />, document.body); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
/** @jsx React.DOM */ | ||
|
||
var React = require('react'); | ||
|
||
var B = React.createClass({ | ||
render: function() { | ||
return ( | ||
<div style={{background: 'purple',color: 'white'}}> | ||
<p>I am <code>example/b.jsx</code>, feel free to edit me.</p> | ||
<img src='http://facebook.github.io/react/img/logo_og.png' width='200' /> | ||
</div> | ||
); | ||
}, | ||
|
||
componentWillUnmount: function () { | ||
window.alert('Unmounting child'); | ||
} | ||
}); | ||
|
||
module.exports = B; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
var path = require('path'); | ||
var webpack = require('webpack'); | ||
|
||
module.exports = { | ||
entry: ['webpack/hot/dev-server', './app'], | ||
output: { | ||
path: path.join(__dirname, 'output'), | ||
filename: 'bundle.js' | ||
}, | ||
resolveLoader: { | ||
modulesDirectories: ['..', 'node_modules'] | ||
}, | ||
resolve: { | ||
extensions: ['', '.jsx', '.js'] | ||
}, | ||
module: { | ||
loaders: [ | ||
{ test: /\.jsx$/, loader: 'jsx-loader' } | ||
] | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
module.exports = function() {}; | ||
module.exports.pitch = function (remainingRequest) { | ||
this.cacheable && this.cacheable(); | ||
var displayName = this.query.substring(1); | ||
var moduleRequest = "!!" + remainingRequest; | ||
|
||
return [ | ||
'var HotUpdateMixin = require(' + JSON.stringify(require.resolve('./makeHotUpdateMixin')) + ')();', | ||
'var React = require("react");', | ||
'function runWithMonkeyPatchedReact(f) {', | ||
' var realCreateClass = React.createClass;', | ||
' var injected = 0;', | ||
' React.createClass = function createHotUpdateClass(spec) {', | ||
' if (spec.displayName === ' + JSON.stringify(displayName) + ') {', | ||
' if (!spec.mixins) spec.mixins = [];', | ||
' spec.mixins.push(HotUpdateMixin.Mixin);', | ||
' injected++;', | ||
' }', | ||
' return realCreateClass(spec);', | ||
' };', | ||
' f();', | ||
' if (injected === 0) {', | ||
' console.warn(\'Could not find component with displayName: ' + JSON.stringify(displayName) + '\');', | ||
' } else if (injected > 1) {', | ||
' console.warn(\'Found more than one component with displayName: ' + JSON.stringify(displayName) + '\');', | ||
' }', | ||
' React.createClass = realCreateClass;', | ||
'}', | ||
'runWithMonkeyPatchedReact(function () {', | ||
' module.exports = require(' + JSON.stringify(moduleRequest) + ');', | ||
'});', | ||
'if (module.hot) {', | ||
' module.hot.accept(' + JSON.stringify(moduleRequest) + ', function() {', | ||
' runWithMonkeyPatchedReact(function () {', | ||
' module.exports = require(' + JSON.stringify(moduleRequest) + ');', | ||
' });', | ||
' HotUpdateMixin.acceptUpdate(module.exports);', | ||
' });', | ||
'}' | ||
].join('\n'); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
var setPrototypeOf = Object.setPrototypeOf || function (obj, proto) { | ||
/* jshint proto:true */ | ||
obj.__proto__ = proto; | ||
return obj; | ||
}; | ||
|
||
module.exports = function () { | ||
var mounted = []; | ||
|
||
var Mixin = { | ||
componentDidMount: function () { | ||
mounted.push(this); | ||
}, | ||
|
||
componentWillUnmount: function () { | ||
mounted.splice(mounted.indexOf(this), 1); | ||
} | ||
}; | ||
|
||
function forceUpdates() { | ||
mounted.forEach(function (instance) { | ||
instance.forceUpdate(); | ||
}); | ||
} | ||
|
||
function acceptUpdate(FreshComponent) { | ||
var freshProto = FreshComponent.componentConstructor.prototype; | ||
|
||
mounted.forEach(function (instance) { | ||
setPrototypeOf(instance, freshProto); | ||
instance.constructor.prototype = freshProto; | ||
instance._bindAutoBindMethods(); | ||
}); | ||
|
||
window.setTimeout(forceUpdates, 0); | ||
} | ||
|
||
return { | ||
Mixin: Mixin, | ||
acceptUpdate: acceptUpdate | ||
}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
{ | ||
"name": "react-hot-loader", | ||
"version": "0.1.0", | ||
"description": "Webpack loader that enables live-editing React components without unmounting or losing their state", | ||
"main": "index.js", | ||
"directories": { | ||
"example": "example" | ||
}, | ||
"devDependencies": { | ||
"react": "^0.10.0", | ||
"jsx-loader": "^0.10.2", | ||
"webpack": "^1.3.1" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/gaearon/react-hot-loader.git" | ||
}, | ||
"keywords": [ | ||
"react", | ||
"webpack", | ||
"hmr", | ||
"livereload" | ||
], | ||
"author": "Dan Abramov", | ||
"license": "MIT", | ||
"bugs": { | ||
"url": "https://github.com/gaearon/react-hot-loader/issues" | ||
}, | ||
"homepage": "https://github.com/gaearon/react-hot-loader" | ||
} |