With the release of Rails 5.1, it will now make more sense to use Webpacker.
Using browserify-rails gem to hook up NPM packages to Rails Asset Pipeline.
react-rails
gem here is used for its 'react_ujs' view helpers, which means we do not directly //= require react, but require() them through npm packages.
Also implemented Bootstrap js.
bundle
andnpm install
rails db:create
rails s
and visitlocalhost:3000
Sources:
https://collectiveidea.com/blog/archives/2016/04/13/rails-react-npm-without-the-pain https://collectiveidea.com/blog/archives/2016/03/09/modern-javascript-and-rails https://gist.github.com/oelmekki/c78cfc8ed1bba0da8cee#file-doc-md
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.
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).
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
andstage-0
babel presets allow to parse cutting edge es7react
babel preset allows to parse jsxsyntax-async-function
andtransform-regenerator
allow to use es7 async functions
//= 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.
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 thanapplication.js
, because this is the file server side prerendering will load. Anything declared inapplication.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 userequire(...).default
instead
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;
From here, it's just stuff as usual:
.container
.row
.col-md-12
= react_component 'HelloWorld', { name: current_user.name }, prerender: true