Skip to content

Using npm addons with react-rails #413

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
SylarRuby opened this issue Nov 25, 2015 · 48 comments
Closed

Using npm addons with react-rails #413

SylarRuby opened this issue Nov 25, 2015 · 48 comments

Comments

@SylarRuby
Copy link

This is not an issue. I just need to know how to go about this.

How to add addons, let's say from react-components.com, to use with this gem?

@hayesgm
Copy link

hayesgm commented Nov 27, 2015

It would be great to add a section about this in README.md or on a wiki page.

@butsjoh
Copy link

butsjoh commented Nov 27, 2015

Not clear to me either how to use custom babel plugins :(

@gauravtiwari
Copy link

You could use browserify-rails (1.5.0) gem or bower-rails gem or Rails Assets to pull in third party plugins or components (depending on where they are available).

With browserify you will have to use Common JS standard to write modular components. With others you can just use global objects, like: window.jQuery

If you decide to use browserify, just follow the gem installation instructions and setup your app like so:

Using browserify

Application.js Setup

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

// Setup React in global scope
var React = window.React = global.React = require('react');
var ReactDOM= window.ReactDOM = global.ReactDOM = require('react-dom');
window.$ = window.jQuery = require('jquery')
require('jquery-ujs')

Components.js

// Setup app into global name space for server rendering
var app = window.app = global.app = {};

// Component::Manifest
var NoContent = require('./components/no_content.es6.js');

// Include into app namespace
app.NoContent = NoContent;

Loading NPM modules

// Require React
React = require('react/addons');
import component from './component.js'
import mui from 'material-ui';
// Your third party add-ons from react component
// Define component
const NoContent = React.createClass({

  render() {
    return (
        <span className="no_content">
         Empty content
        </span>
      );
  },
});

module.exports = NoContent;
# render in rails views
<%= react_component 'app.NoContent', {prerender: true} %>

For Babel Plugins:

From babel 6.0, there are no plugins included by default, you will have to use .babelrc file in project root (in this case rails root) to define presets and plugins like so or pass command line options:

{
  "plugins": ["syntax-jsx"],
  "presets": ["es2015", "react"]
}

browserify script.js -o bundle.js \  -t [ babelify --presets [ es2015 react ] ] (// application.rb with browserify)

You could also use react-rails configuration to load es6 functions or plugins like so:

gem 'babel-transpiler

   # application.rb
    config.react.jsx_transform_options = {
      plugins: ['transform-class-properties']
    }

Make sure you install the plugin via npm npm install babel-plugin-transform-class-properties --save

There are others solutions like using: Webpack with separate/integrated client application, for example.

@smnplk
Copy link

smnplk commented Dec 31, 2015

@gauravtiwari in you application.js file, why is it needed to require react via sprockets ? Wouldn't that load react two times ?

@gauravtiwari
Copy link

@smnplk Yeah you are right :) I will edit the comment, thanks

@smnplk
Copy link

smnplk commented Jan 2, 2016

Ok, thank you :) Also, I think folks that use react >= 0.14.0 need to require ReactDOM too, because react_ujs.js depends on it. So if you can add the following line:

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

@gauravtiwari
Copy link

@smnplk Yes that's right :) As you have pointed out - React DOM is now a separate package therefore, it must be included for rendering components. I have updated the comment.

@TSMMark
Copy link

TSMMark commented Mar 30, 2016

I can not get this working without multiple copies of React being loaded.

Uncaught Invariant Violation: 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

@gauravtiwari
Copy link

Hey @TSMMark Just double check if you are loading it twice anyhow - both in Sprockets and NPM. You may also want to reinstall node modules (rm -rf node_modules directory first) with node(4 or 5) and npm(3.5+) to make sure there aren't duplicated copies installed.

@awe2m2n2s
Copy link

@gauravtiwari: Thanks for showing a way with browserify. Do you also know how to do it with the asset-pipeline?
Sorry for asking but we are new to the whole world and are wondering about the best way to use react and rails. Unfortunately, until yet, we only found solutions where we had to install npm on the server, is that really necessary? -> sorry for asking that dumb but we cannot figure out how to get react-leaflet working on rails: PaulLeCam/react-leaflet#133 without installing npm on the server which seems from our side to be a big overload (having npm and ror on the same server)

Thank you for any support!

@rmosolgo
Copy link
Member

rmosolgo commented Apr 7, 2016

You could try copying this file into your app: https://github.com/PaulLeCam/react-leaflet/blob/master/dist/react-leaflet.js

For example, if you copied it to app/assets/javascripts/vendor/react-leaflet.js, you could require it with the asset pipeline:

//= require leaflet 
//= require react 
//= require vendor/react-leaflet

This line (root["ReactLeaflet"] = factory(root["L"], root["React"], root["ReactDOM"]);) tells me that it depends on a few globals: L, React, and ReactDOM. react-rails will provide the last two; I assume L is leaflet?

FWIW, that's how I bring third party components into my app:

  • Find "compiled", non-minified version, either in /dist or on a CDN
  • Copy it into app/assets/javascripts/vendor
  • Add the version number to the filename, for record-keeping (eg, react-leaflet-v0.10.2.js)
  • Require it with sprockets //= require ./vendor/react-leaflet-v0.10.2

@lesliev
Copy link

lesliev commented Apr 8, 2016

Hi @gauravtiwari, thanks for the guide above - can you help with a few questions?

I am trying to reconcile your instructions with those at browserify-rails but both assume a level of understanding a little beyond mine. Following your browserify instructions, where do you put the "Loading NPM modules" code? Where do you run "npm install" to actually install the module? What is no_content.es6.js? Is NoContent the name of the hypothetical NPM module you are loading?

Also, the browserify-rails documentation says to create a package.json file with "name": "something" in it. Is that where I'm supposed to specify the NPM module I want to use?

The NPM module I am trying to include in my react-rails app is 'react-data-grid'.

@lesliev
Copy link

lesliev commented Apr 8, 2016

@gauravtiwari, I've since proceeded based on some assumptions. I created a package.json file like this and ran npm install:

{
  "name": "testproject",
  "dependencies" : {
    "react-data-grid": "0.14.22",
    "browserify": "~10.2.4",
    "browserify-incremental": "^3.0.1"
  },
  "license": "MIT",
  "engines": {
    "node": ">= 0.10"
  }
}

This installed a huge amount of JS (55MB) under node_modules. I ran cd node_modules/react-data-grid && browserify index.js -o react_data_grid.js to try and get browserify to bundle the component.

This produced errors about react and react-dom being missing but since it seems they are included already I excluded them like this: browserify index.js -i react -i react-dom -o react_data_grid.js.

The result was a react_data_grid.js and a dist directory, both of which I copied to my Rails app's app/assets/javascripts/components directory.

Then I tried modifying application.js and components.js as you mention above. First I had to rename components.js to components.jsx because of the jsx content.

Loading a page then produced this error:

Browserify: /home/leslie/dev/git/hb/testproject/node_modules/.bin/browserifyinc --list --cachefile=/home/leslie/dev/git/hb/testproject/tmp/cache/browserify-rails/browserifyinc-cache.json -o "/home/leslie/dev/git/hb/testproject/tmp/cache/browserify-rails/output20160408-2868-18hl8dy" -
Completed 500 Internal Server Error in 1181ms (ActiveRecord: 5.4ms)


[Rollbar] Reporting exception: Error while running `/home/leslie/dev/git/hb/testproject/node_modules/.bin/browserifyinc --list --cachefile=/home/leslie/dev/git/hb/testproject/tmp/cache/browserify-rails/browserifyinc-cache.json -o "/home/leslie/dev/git/hb/testproject/tmp/cache/browserify-rails/output20160408-2868-18hl8dy" -`:

events.js:141
      throw er; // Unhandled 'error' event
      ^

Error: Cannot find module 'jquery-ujs' from '/home/leslie/dev/git/hb/testproject/app/assets/javascripts'
    at /home/leslie/dev/git/hb/testproject/node_modules/resolve/lib/async.js:46:17
...

My application.js looks like this:

//= require jquery
//= require jquery_ujs
//= require select2
//= require react
//= require react_ujs
//= require js-routes
//= require components
//= require_tree .

// Setup React in global scope
var React = window.React = global.React = require('react');
var ReactDOM = window.ReactDOM = global.ReactDOM = require('react-dom');
window.$ = window.jQuery = require('jquery');
require('jquery-ujs');

components.js.jsx looks like this:

var app = window.app = global.app = {};
var ReactDataGrid = require('./components/react_data_grid.js');
app.ReactDataGrid = ReactDataGrid;

//= require_tree ./components

(I moved the require_tree to the bottom, you don't mention it)

I've not tried to actually load the component on a page yet, I get this error as sprockets tries to compile the assets. I also tried reordering the requires in various ways by always get browserify complaining about certain files being missing.

Any ideas where I went wrong?

@gauravtiwari
Copy link

@awe2m2n2s You could also use rails-assets.org. which provides a lot of javascript packages bundled for Sprockets. React leaflet isn't available on rails-assets so I created it by pushing it to Bower. You could go to rails-assets.org and then search react-leaflet, select a version and then install it using your Gemfile. Then you could just include via Sprockets in your application.js.

source 'https://rails-assets.org' do
  gem 'rails-assets-react-leaflet'
end

@gauravtiwari
Copy link

@lesliev Is jquery-ujs installed via NPM? You have required it require('jquery-ujs');, but may be not installed. Check package.json please!

@gauravtiwari
Copy link

@awe2m2n2s It depends on how it works for you. React has a standalone browser package and could be used with Sprockets, but as you go deeper and start using libraries around React, you might start getting headaches and roadblocks, because not all of them are made to be compatible with Sprockets. You can of course make them if you want to.

Ideally, if you are doing a lot of work on client side and not just sprinkling javascript here and there, then it would make sense to use a workflow that's more integrated and efficient for front-end development. Now, that may mean going beyond traditional workflow. There is - Webpack, Browserify, Gulp, Grunt, Brunch, Bower and tons of other package managers. You could take a look at them and see what works for you, FYI, Webpack is most full-featured and popular nowadays. There is another implementation of React for Rails using Webpack - https://github.com/shakacode/react_on_rails

@awe2m2n2s
Copy link

You guys are truly awesome! damn thx so much and here are some remarks:

  • @rmosolgo: thanks so much! the "compiled" version within the asset-pipeline worked finally! all other's feel free to to see the solution at ReferenceError: exports is not defined PaulLeCam/react-leaflet#133
  • @gauravtiwari: I think you are right, it should work with the rails-assets.org and the asset-pipeline. Unfortunately, it isn't. I used your created gem - thanks so much for that - and got the same error I got with the gem 'rails-assets-PaulLeCam--react-leaflet', so I guess I am missing something there :(

@gauravtiwari
Copy link

@awe2m2n2s I see. Basically, you have to fork the react-leaflet and update the bower.json include dist/react-leaflet.js as the main file. Once you have done that you can update the package on rails-assets or just reinstall the gem and it should work. "main": ["./dist/react-leaflet.js"],

@jcmorrow
Copy link

@gauravtiwari thanks for being such a champ! I've spent several days toying around with different setups and I like yours a lot! I was just trying to imitate it but even with browserify running a babelify transformation it chokes on jsx, and throws Unexpected token errors.

error

How would you go about debugging something like this? I can't figure out if the problem is browserify, babelify, or my configuration! Thanks for any advice you might have.

@gauravtiwari
Copy link

@jcmorrow Are you providing right presets for transforming JSX? You may want to create a .babelrc file in the root directory with following content: { "presets": [ "es2015", "react", "stage-0" ] }

Then you have to install all 3 babel presets, explained here: https://babeljs.io/docs/plugins/preset-react/, after this when you run babelify it will pickup the .babelrc and transform the code using the presets provided.

@jcmorrow
Copy link

@gauravtiwari I had all of that except stage-0 (adding it doesn't seem to make a difference).

.babelrc:

{ "presets": [ "es2015", "react", "stage-0" ] }

application.rb:

config.browserify_rails.commandline_options = "-t [ babelify --presets [ es2015 ] --extensions .es6 ]"

package.json:

"dependencies": {
  "browserify": "^13.0.0",
  "browserify-incremental": "^3.1.1",
  "d3": "^3.5.16"
},
"devDependencies": {
  "babel-core": "^6.7.6",
  "babel-preset-es2015": "^6.6.0",
  "babel-preset-react": "^6.5.0",
  "babel-preset-stage-0": "^6.5.0",
  "babelify": "^7.2.0"
}

But still no jsx transform 😭

@gauravtiwari
Copy link

@jcmorrow Can you manually run and compile from command line or terminal? browserify script.js -o bundle.js -t [ babelify --presets [ es2015 react ] ]. You might need to reference the binary from local node_module/ installation if not globally installed.

@jcmorrow
Copy link

@gauravtiwari yeah I should have mentioned browserify on the command line works 100%.

@lesliev
Copy link

lesliev commented Apr 11, 2016

@gauravtiwari thanks for your help above, adding jquery-ujs to package.json did work. I guess I don't understand why this is even needed, I only require it because that's what you put in your original example application.js.

I also tried from scratch again without excluding react and react-dom when running browserify (I previously used -i react -i react-dom). That didn't work at all because now there's require('./emptyFunction') inside the browserified js and that package is missing. Adding emptyfunction to package.json has no effect.

I'd love to find (or be able to create) a complete guide on adding NPM's to react-rails projects.

@gauravtiwari
Copy link

@lesliev That's for ajax requests, jquery-ujs takes care of adding CSRF token to ajax requests so you don't get protect from forgery exceptions on Rails, plus a few more goodies using data-attrs. Could you push this repo somewhere for us to look?

@gauravtiwari
Copy link

@jcmorrow, @awe2m2n2s and @lesliev https://github.com/gauravtiwari/brunch_on_rails

Perhaps you would like to look at this setup and see if it works for you? It works without the gem and uses Brunch, which is like Webpack, but quite simple to setup and use. You could tweak it to make it work as you like without much constraints.

@gauravtiwari
Copy link

Example Rails 5 application running with browserify-rails and react-rails: https://github.com/gauravtiwari/browserify_on_rails

@gauravtiwari
Copy link

gauravtiwari commented Apr 15, 2016

And, finally using Webpack (https://webpack.github.io/docs/tutorials/getting-started/) without browserify-rails gem. Check out the rails 5 source code here with basic readme: https://github.com/gauravtiwari/webpack_on_rails

App uses the react-rails gem for providing view helper, mounting and rendering components on server and client. Performance wise webpack is much faster and provides same workflow as browserify-rails gem does.

@lesliev
Copy link

lesliev commented Apr 15, 2016

Awesome! Thanks so much for doing this. I'd continued by dropping the distribution JS into my app but I'll need a better solution in the long run.

@jcmorrow
Copy link

@gauravtiwari I like that last implementation a lot! I actually ended up using something similar a few days ago, though with Rails 4, not 5. As you say, it is much faster than browserify and gives much better error messages in my short experience.

Thanks for all your work on this!

@luccasmaso
Copy link

I have tried everything all of you said but it still duplicate importing react.
@gauravtiwari I've downloaded the https://github.com/gauravtiwari/browserify_on_rails code to see what I'm doing wrong, but as I suspected unfortunately the same duplicate occurred (Note de double console message):

capture d ecran 2016-04-18 a 11 09 33 pm

@gauravtiwari
Copy link

@luccasmaso try the updated repo: https://github.com/gauravtiwari/browserify_on_rails (Added a react plugin to test) and no duplication error. See show page (ignore the weird styles :)).

@Tectract
Copy link

Read all of this and I still have no idea how to 'require' javascript from node_modules/ into my react-rails app. Playing with browserify and webpack, the instructions are not clear. I have browserify installed but when I put #= require lodash into application.js.coffee, it crashes, lodash not found. Webpack, I couldn't even figure out where to start. Help?

@gauravtiwari
Copy link

gauravtiwari commented Apr 19, 2016

@Tectract - Browserify uses different workflow than sprockets so, you can't do #= require lodash - it won't work. Instead, where you need to use lodash - use var _ = require('lodash'); or with Babel you could do import _ from 'lodash, and then use _ variable.

If you want to make global lodash object, in applications.js attach it to window object like so: var _ = window._ = global._ = require('lodash');

For example: React is made global: https://github.com/gauravtiwari/browserify_on_rails/blob/master/app/assets/javascripts/application.js#L19

You could do same for lodash. If you just use it inside component, call it locally within component like so: https://github.com/gauravtiwari/browserify_on_rails/blob/master/app/assets/javascripts/components/pages/alert.es6.js#L2 (only with babel) or just use regular var Variable = require('my-node-lib')

@Tectract
Copy link

Tectract commented Apr 19, 2016

Thanks for your response. I'm trying to use rebass, a js-generated-css library, which I'm skeptical will work with browserify. I was just testing with lodash because it's easy to test. Have not succesfully loaded either NPM module into my react-rails stack yet.

Right now my site has a single React component, defined in view/view.js.cjsx, written in coffeescript. I'm using the sprockets-coffee-react gem to handle that coffeescript.

I try to include var _ = require('lodash'); in view.js.cjsx, no luck, var is a reserved word in coffeescript.

I try just _ = require('lodash'), no luck, component just simply does not load at all, it is undefined now because that line broke it.

I can't figure out how to get browserify to have any noticeable effect at all so far, everything just breaks when I try to include any NPM code. Maybe I installed it wrong? I have:

gem 'browserify-rails', '>= 0.9.1' in my gemfile, installed gem,
and I have: browserify and browserify-incremental in my package.json, installed

So maybe I should try webpack instead. Does this mean I will be compiling things from node_modules/ folders int minified sources and then copying those minified sources into app/assets/javascripts every time I start the server?

@gauravtiwari
Copy link

gauravtiwari commented Apr 19, 2016

@Tectract Browserify is fine for simple setup like this, first install lodash from npm: npm install --save lodash and then within your component: _ = require('lodash'

No everything will work as before, it just that now you can use npm modules with browserify-rails gem.

@gauravtiwari
Copy link

For rebass, you could do same and require as outlined in the docs at NPM: npm install rebass --save

@Tectract
Copy link

Tectract commented Apr 19, 2016

Sorry but this is not working for me, and I'm having a hard time troubleshooting it. I have done
npm install --save lodash

and I have added:
_ require('lodash')

Page loads without the view component. It is not defined now. Adding that line at the top has caused some sort of silent error and now the web console warns that "view" is undefined.

Here is the content of my view.js.cjsx right now:

@cjsx React.DOM

_ = require 'lodash'

@view = React.createClass
displayName: 'View'

render: ->

TopLevelView div here

@Tectract
Copy link

Huh, well this is interesting, it appears that the lodash/underscore functions are available for me now in view.js.cjsx, but something in here is causing the component to become undefined.

When I load the page that contains the view component now, it actually does console log out the results from an _.each function call, but the render doesn't complete and it tells me the view is undefined on the console. any ideas what's going on?

here is my view.cj.cjsx:

@cjsx React.DOM

_ = require('lodash');

@view = React.createClass
displayName: 'View'

render: ->
_.each [0,1], (num) =>
console.log(num)

\<div className='topLevelView'\>TopLevelView div here\</div\>

@Tectract
Copy link

ok got it working with browserify

in my application.js.coffee:
_ = window._ = global._ = require('lodash');

in view.js.cjsx:
render: ->
_.each [0,1], (num) =>
console.log(num)

<div className='topLevelView'>TopLevelView div here</div>

is happy now. Next will try rebass. Not sure if JS-generated-CSS is gonna work here, let's give it a shot...

@vinnyoodles
Copy link

Hey @gauravtiwari, sorry to piggyback on this issue but I'm having issue using browserify, react, and rails together.

This is my components.js file, but I'm getting this error when trying to import the react components.

Uncaught SyntaxError: Unexpected token import
// Component::Manifest
import APIConsole from './react/console/api_console.es6.js';
import ImagePopup from './react/table/image_popup.es6.js';
import Sidebar    from './react/sidebar/sidebar.es6.js';

// Setup app into global name space for server rendering
const app = window.app = global.app = {};

// Include into app namespace
app.APIConsole = APIConsole;
app.ImagePopup = ImagePopup;
app.Sidebar = Sidebar;

@vinnyoodles
Copy link

vinnyoodles commented May 23, 2016

I'm also getting this error, it doesn't state where this error is coming from but I think it is when trying to use the react components in the view files.

 Uncaught ReferenceError: app is not defined
<%= react_component('app.APIConsole', {:models => @models, :keys => @keys}.to_json) %>

@gauravtiwari
Copy link

gauravtiwari commented May 24, 2016

Hey @vinnyoodles Please check out this repo and see if you are missing anything. Btw, have you setup babelrc to transform es6 code?

https://github.com/gauravtiwari/browserify_on_rails/blob/master/.babelrc

@vinnyoodles
Copy link

I found the bug fix here http://www.we-edit.de/stackoverflow/question/babelify-browserify-rails-react-uncaught-syntaxerror-unexpected-token-import-34645619.html.

For some reason, the require('') is needed in the beginning of my component.js

require('');
// Component::Manifest
import APIConsole from './react/console/api_console.es6.js';
import ImagePopup from './react/table/image_popup.es6.js';
import Sidebar    from './react/sidebar/sidebar.es6.js';

const app = window.app = global.app = {};

// Expose components to global scope
app.APIConsole = APIConsole;
app.ImagePopup = ImagePopup;
app.Sidebar = Sidebar

@gauravtiwari
Copy link

@vinnyoodles Strange, I guess you are missing .babelrc or babel is not picking up proper transformer. Did you looked at the code I posted?

@vinnyoodles
Copy link

Yeah, I included the .babelrc file with the es6 transformation but I guess it wasn't looking for it. I probably set it up incorrectly since I've never used babel before

@aaronjmccoy
Copy link

aaronjmccoy commented Sep 23, 2016

@Tectract I'm trying to load rebass into react-rails as well, do you have a repo that is working up I could look at?

v9n pushed a commit to yeo/notyim that referenced this issue Jan 14, 2017
This is a giant beast. In future, we'll switch off this Asset Pipeline
completely. Meantime, we continuse to use it follow this references:

- reactjs/react-rails#413
- https://github.com/gauravtiwari/browserify_on_rails/

Basically, we use ES6 import on `components.js`. We then expose those
components into a global variable. Then sprocket include them and we use
`react-rails` to render those component.
@rmosolgo
Copy link
Member

webpacker is a great way to use react + npm + rails, it will be supported by this gem in the upcoming 2.0 release

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests