Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Rails + Browserify + React + es7

1. Gemfile

gem 'browserify-rails', '1.5.0' # until fix: https://github.com/browserify-rails/browserify-rails/issues/101
gem 'react-rails'

Browserify-rails allows to use browserify within assets pipeline. React-rails is here only to allow to use #react_component (and thus, prerendering).

Note that jquery-rails can be removed from Gemfile, the npm version of jquery and jquery-ujs will be used instead.

2. package.json

Here is a typical package json for es7 + react + rails:

Versions may need update. npm-check is killing it for that.

{
  "name": "my app",
  "dependencies": {
    "babel-plugin-syntax-async-functions": "^6.3.13",
    "babel-plugin-transform-regenerator": "^6.3.18",
    "babel-polyfill": "^6.3.14",
    "babel-preset-es2015": "^6.3.13",
    "babel-preset-react": "^6.3.13",
    "babel-preset-stage-0": "^6.3.13",
    "babelify": "^7.2.0",
    "browserify": "^12.0.1",
    "browserify-incremental": "^3.0.1",
    "es6-promise": "^3.0.2",
    "fetch": "^0.3.6",
    "jquery": "^2.1.4",
    "jquery-ujs": "^1.0.4",
    "react": "^0.14.3",
  },
  "license": "MIT",
  "engines": {
    "node": ">= 0.10"
  },
  "devDependencies": {
    "babelify": "^7.2.0"
  }
}

The fetch plugin is a polyfill that allows to use fetch in any browser.

To add a js plugin in your application, simply add a line in the dependencies object and run npm-check -u (or npm install if you don't like sexyness).

3. config/application.rb

Add at the end of Application class:

config.browserify_rails.commandline_options = "-t [ babelify --presets [ es2015 react stage-0 ] --plugins [ syntax-async-functions transform-regenerator ] ]"
  • es2015 and stage-0 babel presets allow to parse cutting edge es7
  • react babel preset allows to parse jsx
  • syntax-async-function and transform-regenerator allow to use es7 async functions

4. app/assets/javascripts/application.js

//= require_self
//= require react-server
//= require react_ujs

window.$ = window.jQuery = global.$ = require('jquery');
var React = window.React = global.React = require('react');
require( 'jquery-ujs' );
require( 'fetch' );
require( './components' );

Sprockets is only used to require current script and helpers from react-rails. All other files will be imported using browserify.

5. app/assets/javascripts/components.js

require( 'babel-polyfill' );

global.MyFirstComponent = require( 'components/my_first_component' ).default;
global.MySecondComponent = require( 'components/my_second_component' ).default;

This is the central requiring script, that will load all files in the pipeline. Think of it as this root file in ruby gems that just require all the other files (although, I prefer to only require root components, and let them require further subcomponents they need).

A few remarks:

  • this is in components.js rather than application.js, because this is the file server side prerendering will load. Anything declared in application.js won't be available to prerendering
  • requiring babel-polyfill is mandatory to use es7 async functions
  • for a reason I don't understand, it is not possible to use the es7 import syntax in that root file, so you have to use require(...).default instead

6. write some es7 code \o/

Here is an example:

// app/assets/javascripts/components/hello_world.js

import Title      from 'components/title';
import LoremIpsum from 'components/lorem_ipsum';

var propTypes = {
  name: React.PropTypes.string.isRequired,
};

export default class HelloWorld extends React.Component {
  constructor( props ){
    super( props );
    this.state = { name: this.props.name };
  }

  changeName = async () => {
    let resp = await fetch( '/get_new_name' );
    resp = await resp.json();
    this.setState({ name: resp.name });
  }

  render(){
    return (
      <div onClick={this.changeName}>
        <Title>Hello {this.state.name}!</Title>
        <LoremIpsum />
      </div>
    );
  }
}

HelloWorld.propTypes = propTypes;

Do not forget to require this file in your component.js:

global.HelloWorld = require( 'components/hello_world' ).default;

7. load the component in view

From here, it's just stuff as usual:

.container
  .row
    .col-md-12
      = react_component 'HelloWorld', { name: current_user.name }, prerender: true
@iamfree-com

This comment has been minimized.

Show comment Hide comment
@iamfree-com

iamfree-com May 30, 2016

You sir is a super star. I wish I could give you a job that pays a million. Thank you. This really works and now Isomorphic/Universal all the way.

You sir is a super star. I wish I could give you a job that pays a million. Thank you. This really works and now Isomorphic/Universal all the way.

@skflowne

This comment has been minimized.

Show comment Hide comment
@skflowne

skflowne Jun 10, 2016

This is really nice, thanks for putting the info out there.

skflowne commented Jun 10, 2016

This is really nice, thanks for putting the info out there.

@bdelmas

This comment has been minimized.

Show comment Hide comment
@bdelmas

bdelmas Jun 22, 2016

We can now delete the specific version of browserify-rails in the Gemfile because the issue #101 has been corrected.

bdelmas commented Jun 22, 2016

We can now delete the specific version of browserify-rails in the Gemfile because the issue #101 has been corrected.

@n8rzz

This comment has been minimized.

Show comment Hide comment
@n8rzz

n8rzz Aug 23, 2016

Thank you for posting this! I've been looking to put together a workable browserify + es2015 + react solution with a minimal amount of gems.

Correct me if I'm wrong, but I do believe there is a typo in your constructor:

this.state = { name: this.props.name };

should instead be:

this.state = { name: props.name };

Component props are not available on this inside of the constructor.

n8rzz commented Aug 23, 2016

Thank you for posting this! I've been looking to put together a workable browserify + es2015 + react solution with a minimal amount of gems.

Correct me if I'm wrong, but I do believe there is a typo in your constructor:

this.state = { name: this.props.name };

should instead be:

this.state = { name: props.name };

Component props are not available on this inside of the constructor.

@borisrorsvort

This comment has been minimized.

Show comment Hide comment
@borisrorsvort

borisrorsvort Aug 31, 2016

if you don't use prerender and add turbolinks 5 it only works with full page reload… any idea?

if you don't use prerender and add turbolinks 5 it only works with full page reload… any idea?

@thebadmonkeydev

This comment has been minimized.

Show comment Hide comment
@thebadmonkeydev

thebadmonkeydev Sep 30, 2016

@borisrorsvort I had a similar issue and mine was caused by requiring turbolinks after the react_ujs package.

@borisrorsvort I had a similar issue and mine was caused by requiring turbolinks after the react_ujs package.

@jcuna

This comment has been minimized.

Show comment Hide comment
@jcuna

jcuna Oct 18, 2016

This looks great, I was using it with brunch but it was causing too much trouble, however, after I stripped down brunch I'm now getting a "Uncaught ReferenceError: global is not defined" in the lines where I require jquery/react and assign global.$, etc. Anybody has any idea?

jcuna commented Oct 18, 2016

This looks great, I was using it with brunch but it was causing too much trouble, however, after I stripped down brunch I'm now getting a "Uncaught ReferenceError: global is not defined" in the lines where I require jquery/react and assign global.$, etc. Anybody has any idea?

@fastdivision

This comment has been minimized.

Show comment Hide comment
@fastdivision

fastdivision Oct 19, 2016

If you encounter this lovely error:

addComponentAsRefTo(...): Only a ReactOwner can have refs. You might be adding a ref to a component that was not created inside a component's render method, or you have multiple copies of React loaded.

Try dropping //= require react-server and require the following in app/assets/javascripts/application.js:

var ReactDOM = window.ReactDOM = global.ReactDOM = require('react-dom');
var ReactDOMServer = window.ReactDOMServer = global.ReactDOMServer = require('react-dom/server');

Also include react-dom in your package.json via npm install react-dom --save / yarn add react-dom --save

If you encounter this lovely error:

addComponentAsRefTo(...): Only a ReactOwner can have refs. You might be adding a ref to a component that was not created inside a component's render method, or you have multiple copies of React loaded.

Try dropping //= require react-server and require the following in app/assets/javascripts/application.js:

var ReactDOM = window.ReactDOM = global.ReactDOM = require('react-dom');
var ReactDOMServer = window.ReactDOMServer = global.ReactDOMServer = require('react-dom/server');

Also include react-dom in your package.json via npm install react-dom --save / yarn add react-dom --save

@lichtamberg

This comment has been minimized.

Show comment Hide comment
@lichtamberg

lichtamberg Oct 21, 2016

for a reason I don't understand, it is not possible to use the es7 import syntax in that root file, so you have to use require(...).default instead

Which error message do you get? Maybe this? 'import' and 'export' may appear only with 'sourceType: module'

for a reason I don't understand, it is not possible to use the es7 import syntax in that root file, so you have to use require(...).default instead

Which error message do you get? Maybe this? 'import' and 'export' may appear only with 'sourceType: module'

@onebree

This comment has been minimized.

Show comment Hide comment
@onebree

onebree Dec 5, 2016

https://gist.github.com/oelmekki/c78cfc8ed1bba0da8cee#4-appassetsjavascriptsapplicationjs

My original application.js includes bootstrap-sprockets (for the gem bootstrap-sass), and ends with require_tree .

Should I add Bootstrap instead via NPM? And is require_tree . necessary?

onebree commented Dec 5, 2016

https://gist.github.com/oelmekki/c78cfc8ed1bba0da8cee#4-appassetsjavascriptsapplicationjs

My original application.js includes bootstrap-sprockets (for the gem bootstrap-sass), and ends with require_tree .

Should I add Bootstrap instead via NPM? And is require_tree . necessary?

@idoo

This comment has been minimized.

Show comment Hide comment
@idoo

idoo Dec 29, 2016

@oelmekki does it work only with default export?

idoo commented Dec 29, 2016

@oelmekki does it work only with default export?

@egoens

This comment has been minimized.

Show comment Hide comment
@egoens

egoens Dec 29, 2016

If you run into a "ReactDOM is not defined" error in the console, the add the following to the application.js file:

var ReactDOM = window.ReactDOM = global.ReactDOM = require('react-dom');

egoens commented Dec 29, 2016

If you run into a "ReactDOM is not defined" error in the console, the add the following to the application.js file:

var ReactDOM = window.ReactDOM = global.ReactDOM = require('react-dom');

@graysonwright

This comment has been minimized.

Show comment Hide comment
@graysonwright

graysonwright Jan 24, 2017

If you get this error:

Invariant Violation: ReactDOM.render(): Invalid component element. This may be caused by unintentionally loading two independent copies of React.

follow @fastdivision's advice:

Remove

//= require react-server

and add

var ReactDOMServer = window.ReactDOMServer = global.ReactDOMServer = require('react-dom/server');

to your application.js – it works like a charm!

Thanks for the great work on this, @oelmekki!

If you get this error:

Invariant Violation: ReactDOM.render(): Invalid component element. This may be caused by unintentionally loading two independent copies of React.

follow @fastdivision's advice:

Remove

//= require react-server

and add

var ReactDOMServer = window.ReactDOMServer = global.ReactDOMServer = require('react-dom/server');

to your application.js – it works like a charm!

Thanks for the great work on this, @oelmekki!

@jefree

This comment has been minimized.

Show comment Hide comment
@jefree

jefree Jan 29, 2017

When I run rake assets:precompile on production mode, I'm getting the next error:

ExecJS::RuntimeError: SyntaxError: Unexpected token: operator (>)

I guess it's because the react components are not being transpiled to from jsx to js.

Any idea ?

jefree commented Jan 29, 2017

When I run rake assets:precompile on production mode, I'm getting the next error:

ExecJS::RuntimeError: SyntaxError: Unexpected token: operator (>)

I guess it's because the react components are not being transpiled to from jsx to js.

Any idea ?

@graysonwright

This comment has been minimized.

Show comment Hide comment
@graysonwright

graysonwright Feb 1, 2017

This solution ended up being too slow for me in development mode – often taking > 6 s to compile assets for each request. After a few other dead ends, ended up switching to https://github.com/mipearson/webpack-rails for a blazing fast development cycle.

This solution ended up being too slow for me in development mode – often taking > 6 s to compile assets for each request. After a few other dead ends, ended up switching to https://github.com/mipearson/webpack-rails for a blazing fast development cycle.

@Tetramputechture

This comment has been minimized.

Show comment Hide comment
@Tetramputechture

Tetramputechture Jan 18, 2018

As someone who has been struggling to implement a Jest testing framework on a Rails app using the react-rails gem, thank you. I have finally done it, after 4 days, thanks to this tutorial finally getting both my tests and my rendering to work. You are great!

As someone who has been struggling to implement a Jest testing framework on a Rails app using the react-rails gem, thank you. I have finally done it, after 4 days, thanks to this tutorial finally getting both my tests and my rendering to work. You are great!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment