/livereactload

Live code editing with Browserify and React

Primary LanguageJavaScriptMIT LicenseMIT

LiveReactload

Live code editing with Browserify and React.

Gitter npm version

Motivation

There are many live reloading components for CSS (and its variants) but unfortunately JavaScript didn't have such tools yet. After I played a little bit with React, I realised that its architecture and programming principles are a perfect match that could solve the puzzle.

I did some Googling and managed to find one similar implementation for the Webpack. However, I'm a very satisfied Browserify user so I wanted one for it too. The results of this work can be found in this repository and npm.

What is LiveReactload

LiveReactload is a Browserify implementation similar to LiveReload but for JavaScript instead. Ok, LiveReload actually supports JavaScript reloading but it reloads the whole page when the changes occur. If you have done something with the page, then the state is lost and you have to do the same operations again.

LiveReactload solves the state propagation problem over JavaScript reloads. It provides the following features:

  • Automatic state propagation management for React components as a Browserify transform
  • Notification infrastructure for change event delegation
  • Automatic change event listening and bundle reloading (when the Browserify transform is enabled)
  • Custom state propagation by using LiveReactload API
  • Integration interfaces for build systems

And because one photo tells more than a thousand words, see the following video to see LiveReactload in action:

Video

Usage

Install LiveReactload as development dependency

npm install --save-dev livereactload

LiveReactload package is a standard Browserify transform so it can be used like any other transformation (e.g. reactify, uglifyify, ...).

ATTENTION: if you are using react-bootstrap or any other module that uses React as a peer dependency, then you must define LiveReactload as a global transform (use -g instead of -t).

However, just adding the transformation is not enough. It will give your codebase a capability to reload itself but the codebase itself does not know about code changes. That's why LiveReactload must be integrated to your watch system (e.g. watchify). LiveReactload provides the following commands to do it:

# starts a server tht listens for change events and delegates them to the browser
node_modules/.bin/livereactload listen

# sends a change event notification to the listening server
node_modules/.bin/livereactload notify

Here is an example bin/watch script that you can use (presuming that browserify and watchify have been installed locally) to watch your bundle changes:

#!/bin/bash

{ { node_modules/.bin/watchify site.js -v -t babelify -g livereactload -o static/bundle.js 1>&2; } 2>&1 \
  | while read result; do
    echo "$result"
    [[ "$result" =~ ^[0-9]+[[:space:]]bytes[[:space:]]written  ]] && node_modules/.bin/livereactload notify
  done
} &

node_modules/.bin/livereactload listen
wait

And finally just start watch and begin coding:

./bin/watch

For build system integrations, please see this example

How it works

The React programming model suits perfectly live code editing: components are just stupid ones that render the data they are told to render and the DOM changes are handled with VirtualDOM diff. This (hopefully) prevents developers from hiding the state inside the application.

If the state is managed by React components and those components are exported via module.exports, it is possible to weave those components with a logic that enables state propagation: when the bundle is reloaded, the new implementation replaces the old prototypes but not the actual state.

More detailed explanation about the idea coming later...

So basically if you code your React application using its best practices, then you can use this reloading component without any modifications to your code!.

When does it not work?

Well... if you hide your state inside the modules then the reloading will lose the state. For example the following code will not work:

// counter.js
var React = require('react')

var totalClicks = 0

module.exports = React.createClass({

  getInitialState: function() {
    return {clickCount: totalClicks}
  },

  handleClick: function() {
    totalClicks += 1
    this.setState({clickCount: totalClicks})
  },


  render: function() {
    return (
      <div>
        <button onClick={this.handleClick}>Increment</button>
        <div>{this.state.clickCount}</div>
      </div>
    )
  }
})

A second problem arises when you have "private" components inside your modules that are not exported with module.exports.

However, both of those challenges can be solved by using LiveReactload API. Please see the examples to see how to use the API in different situations.

License

MIT

Problems?

Please create a Github issue if something occurs.

Thanks

  • Dan Abramov - an amazing React Hot API that was used for the basis of the state propagation mechanism
  • Hannu - inspiring and sparring with this project