diff --git a/README.md b/README.md index 4b77be16..2842851e 100644 --- a/README.md +++ b/README.md @@ -1,47 +1,4 @@ react-hot-boilerplate ===================== -The minimal dev environment to enable live-editing React components. - -### Usage - -``` -npm install -npm start -open http://localhost:3000 -``` - -Now edit `src/App.js`. -Your changes will appear without reloading the browser like in [this video](http://vimeo.com/100010922). - -### Linting - -This boilerplate project includes React-friendly ESLint configuration. - -``` -npm run lint -``` - -### Using `0.0.0.0` as Host - -You may want to change the host in `server.js` and `webpack.config.js` from `localhost` to `0.0.0.0` to allow access from same WiFi network. This is not enabled by default because it is reported to cause problems on Windows. This may also be useful if you're using a VM. - -### Missing Features - -This boilerplate is purposefully simple to show the minimal configuration for React Hot Loader. For a real project, you'll want to add a separate config for production with hot reloading disabled and minification enabled. You'll also want to add a router, styles and maybe combine dev server with an existing server. This is out of scope of this boilerplate, but you may want to look into [other starter kits](https://github.com/gaearon/react-hot-loader/blob/master/docs/README.md#starter-kits). - -### Dependencies - -* React -* Webpack -* [webpack-dev-server](https://github.com/webpack/webpack-dev-server) -* [babel-loader](https://github.com/babel/babel-loader) -* [react-hot-loader](https://github.com/gaearon/react-hot-loader) - -### Resources - -* [Demo video](http://vimeo.com/100010922) -* [react-hot-loader on Github](https://github.com/gaearon/react-hot-loader) -* [Integrating JSX live reload into your workflow](http://gaearon.github.io/react-hot-loader/getstarted/) -* [Troubleshooting guide](https://github.com/gaearon/react-hot-loader/blob/master/docs/Troubleshooting.md) -* Ping dan_abramov on Twitter or #reactjs IRC +This branch is a playground for the future version of React Hot Loader. Right now it doesn't even use it. diff --git a/package.json b/package.json index cd36e2dc..f591b4b7 100644 --- a/package.json +++ b/package.json @@ -32,11 +32,12 @@ "babel-eslint": "^3.1.9", "babel-loader": "^5.1.2", "eslint-plugin-react": "^2.3.0", - "react-hot-loader": "^1.2.7", + "react-proxy": "^0.6.3", "webpack": "^1.9.6", "webpack-dev-server": "^1.8.2" }, "dependencies": { + "autobind-decorator": "^1.3.1", "react": "^0.13.0" } } diff --git a/src/App.js b/src/App.js index d890320b..e5563350 100644 --- a/src/App.js +++ b/src/App.js @@ -1,10 +1,13 @@ import React, { Component } from 'react'; +import autobind from 'autobind-decorator'; +import hot from './hot'; +@hot('RendererA') class RendererA extends Component { constructor(props) { super(props); this.state = { counter: 0 }; - setInterval(() => this.tick(), 10); + this.interval = setInterval(() => this.tick(), 1000); } tick() { @@ -13,20 +16,27 @@ class RendererA extends Component { }); } + componentWillUnmount() { + clearInterval(this.interval); + } + + // Feel free to edit this render() { return ( - - {this.state.counter}, {this.props.children} - +
+

{this.props.children}

+

Renderer own state: {this.state.counter}

+
); } } +@hot('RendererB') class RendererB extends Component { constructor(props) { super(props); this.state = { counter: 0 }; - setInterval(() => this.tick(), 10); + this.interval = setInterval(() => this.tick(), 1000); } tick() { @@ -35,11 +45,17 @@ class RendererB extends Component { }); } + componentWillUnmount() { + clearInterval(this.interval); + } + + // Feel free to edit this render() { return ( - - {this.state.counter}, {this.props.children} - +
+

{this.props.children}

+

Renderer own state: {this.state.counter}

+
); } } @@ -49,31 +65,54 @@ function makeSomething(Renderer, speed) { constructor(props) { super(props); this.state = { counter: 0 }; - setInterval(() => this.tick(), speed); } + componentDidMount() { + this.tick(); + } + + componentWillUnmount() { + clearTimeout(this.timeout); + } + + @autobind tick() { this.setState({ counter: this.state.counter + 1 + }, () => { + this.timeout = setTimeout(this.tick, speed); }); } + // Feel free to edit this render() { - return

{this.state.counter}

; + return ( +
+ HOC state: {this.state.counter} +
+ ); } }; } -let Something1 = makeSomething(RendererA, 10); -let Something2 = makeSomething(RendererB, 1000); +let Something1 = hot('makeSomething$Something1')( + // Feel free to edit this + makeSomething(RendererA, 100) +); +let Something2 = hot('makeSomething$Something2')( + // Feel free to edit this + makeSomething(RendererB, 100) +); +@hot('App') export default class App extends Component { + // Feel free to edit this render() { return (
- nice + nice ugh
); } diff --git a/src/hot.js b/src/hot.js new file mode 100644 index 00000000..f81ddf6d --- /dev/null +++ b/src/hot.js @@ -0,0 +1,29 @@ +import React from 'react'; +import { createProxy, getForceUpdate } from 'react-proxy'; + +if (!window.__reactProxies) { + window.__reactProxies = {}; +} + +const forceUpdate = getForceUpdate(React); + +export default function hot(id, ...dependencies) { + id = [id, ...dependencies.map(dep => dep.__proxyId || dep)].join('$$$'); + + return function (ReactClass) { + if (window.__reactProxies[id]) { + console.info(`updating ${id}`); + const instances = window.__reactProxies[id].update(ReactClass); + requestAnimationFrame(() => { + instances.forEach(forceUpdate); + }); + } else { + console.info(`creating ${id}`); + window.__reactProxies[id] = createProxy(ReactClass); + } + + let ProxyClass = window.__reactProxies[id].get(); + ProxyClass.__proxyId = id; + return ProxyClass; + }; +}