Rails, React,
Browserify

Packaging your React components


Using Node.js, I’ve grown accustomed to using CommonJS modules and I like them as a solution to dependency management and scope isolation in Javascript.

Using Rails, what you natively get is Sprockets, which I feel doesn’t provide the same robustness and flexibility CommonJS does.
So how could I continue using modules within Rails?

Browserify is a tool that lets us require modules in the browser just as we’d do in a Node.js application. It’s been wrapped into a gem called browserify-rails that makes it possible to use the require function in our Rails-served Javascript files.

This article will quickly go through the required steps to use browserify-rails along with react-rails, so that our React components can be packaged and required as CommonJS modules, while maintaining the ability to render them on the server.

I am assuming you have at least a basic knowledge of Rails, but will detail the Node.js/modules parts a bit more in case you are not familiar with those.


Rails & React

I won’t describe how to use Rails and React together, as I have already done so in my first article about Isomorphic React apps with Rails. Basically, you should use the react-rails gem and follow the instructions from the Readme, or read my article to get started.


Rails & Browserify

Browserify is an NPM package, and will allow us to use other NPM packages as well, making them available to our client-side scripts. Which means you are going to need Node.js on your development machine to make it all work.
Download and execute Node’s installer from the website before going further.

To use NPM with our Rails app, we need to make it an NPM package too, so that we can list our dependencies and manage them with the NPM command-line tool.
An NPM package is described by a package.json file, which you are going to create inside the root directory of your Rails application:

https://gist.github.com/olance/b75794d87555905a7087

This JSON file defines our package’s name, some basic metadata and the other packages we depend on for development, which for the moment only includes Browserify. The browserify-incremental tool is used by the newest version of the browserify-rails gem to make JS packaging faster when your source code changes.

Let’s install it within our app:

npm install

This command looks for package.json in the current directory, lists the declared dependencies and installs whichever one could not be found in the local node_modules directory.
As it is the first time we run it in our Rails app directory, it will create node_modules and install the browserify package there.

This is where the browserify-rails gem will look for it. This gem is indeed just a small wrapper around the original tool, and it will plug itself into the Sprockets post-processors system to bundle your Javascript files into “browserify’d” modules once they have been fully processed by the Assets Pipeline.

Add the gem to your Gemfile and run bundle install:

https://gist.github.com/olance/a4d3d333ff1011f00d93

You can now write Javascript modules. Let’s code a quick example.

The Greeter module

We’re going to write a very simple module that will publish two functions: sayHello and sayGoodbye.

Create the greeter.js file in app/assets/javascripts and copy the following code:

https://gist.github.com/olance/3aec27ecd456b80b9e2f

What’s important here is the assignment to module.exports. This is how you declare which functions, objects, variables (…) are exported by your module, and this is what browserify-rails will look for to decide whether it should run browserify on your file or not.

Note that the exported name might be different than the original function/object/… name from within the module code: here, we publish the hello function as sayHello and the goodbye function as sayGoodbye.

Here’s how you can require and use the module in your main application.js file:

https://gist.github.com/olance/f899a72a7aeb63bd0ceb

It couldn’t be easier! As you can see, you can still use the Sprockets require directive too.

What’s worth mentioning here, is how the use of CommonJS modules allow us to give a meaningful and contextual name to our imported pieces of code. The Greeter variable could have been named MyModule, and it wouldn’t have made any difference.
Also, you’ve probably realised that Greeter actually took the value we had assigned to module.exports in our module, which is a simple object. It means we can also import only the parts we really need from a module. For instance:

https://gist.github.com/olance/1b6ae42a90d00ad6ccd5

This time, we’ve only imported the sayHello function, which is renamed to hello within this part of the code.

Now that we know how to use Javascript modules within Rails, let’s see how we can package our React components.


Rails, React & Browserify

In order to make everything work together, we need to tweak a few things. But first, let’s list our goals:

  1. Make each React component a module that can be required from other modules/Javascript files.
  2. Maintain compatibility with the React UJS feature from react-rails, which mounts the appropriate React components on the client side when it finds specific tags output by the react_component view helper.
  3. Maintain compatibility with the server-side rendering feature of react-rails, which allows us to render React components on the server so that a complete HTML markup is served.

Points 2. and 3. might be clearer to you if you have read my previous article.

We can already feel quite confident about the first goal: we’ve seen that using browserify-rails will allow us to package any Javascript code into modules. However, what about JSX code?

JSX files to JS modules

This is the first tweak to our current configuration: JSX files need to be transformed to Javascript before being bundled as modules, otherwise we’ll end up with some illegal characters/identifiers in our code.

Thankfully, Browserify comes in with a very powerful feature: transformers. A transformer is similar to an engine in the Sprockets jargon: it takes a file in, transforms it and outputs it so that it can be processed further.
As you have probably guessed already, there is a browserify transformer that will take care of JSX files for us. It’s an NPM package called reactify.

Let’s add it as a dependency in our package.json file:

https://gist.github.com/olance/f9c3860226275a583326
npm install

Once it’s installed, we need to tell browserify-rails about that extra option we’d like it to use when running browserify. This is done by adding the following line inside config/application.rb:

https://gist.github.com/olance/cdc3404bac576d19422c

There are actually two options here. The first one tells browserify to use the reactify tranformer. The second one tells browserify to treat files with the .js.jsx extensions as modules, so that we can require such files without specifying their extension all the time.

Our first requirement is met: React components can now fully be bundled into modules.

Components in the global scope

To fulfill our two other goals, there’s just one thing to know: React will look for our components in the global scope.

Say we’re trying to render a <Page /> component. In a browser, React will try to use window.Page as a React component and mount it. On the server, where window doesn’t exist, it would rather look for something like global.Page.

The simplest solution to this is to declare components as global variables by omitting the var keyword:

https://gist.github.com/olance/398118fbca41c49d1bda

This will make MyComponent available to the global scope, be it window, global or anything else. But I don’t like polluting the global scope without at least showing that I know what I am doing. I’d really prefer writing something like this:

https://gist.github.com/olance/8c29a097d1853a697298

For this to work, we’ll need another tweak.

In react-rails, the server-side rendering code only declares a global variable that represents the global scope. Thus, any component assigned to a property on the window variable won’t be found.
On the other end, browsers use window to represent the global scope and do not usually define global.

To work around this, the dummy app from the react-rails test suite manually declares the global variable so that it will take the value of window if it is not defined already:

https://gist.github.com/olance/ce301ebfe00e606dac58

A component can then be created on the global variable and be accessible both to the server (which defines global) and the client (which defines window).

So this is a way to go. However, I’d rather have nothing to write, so I have sent a pull request to fix this on the gem side by defining global, window and self in the code that serves as the context for server-side rendering.

Until the PR is merged or rejected (hopefully for a good reason), I will personally use this line in my Gemfile to avoid the above workaround:

https://gist.github.com/olance/61a15caf568cc85142a2

Feel free to choose whichever solution makes sense to you!

Update
My pull request has been merged on the 16th of September 2014, you can switch to using:


Organizing our components

Now that we’ve solved everything we had to solve, let’s put all the pieces together!

It really comes down to how we should organize our React components. I guess there’s no perfect answer to that, and certainly not a single one, so here’s the most reasonable solution I could think of. Suggestions are welcome ☺

I think what should be kept in mind, is that react-rails need components to be available on the global scope, but not all of them: only those “root” components that we are going to use with the react_component helper within our views.

Knowing that, here is what I would suggest:

  • Create each component in its own module, along with its direct children components that couldn’t be used anywhere else. Only export the main component though.
  • Use require to pull external components in a component’s module when they’re needed as children.
  • In app/assets/javascripts/components.js, only require the top-level components of your app and assign them to properties of window (or global, depending on the solution you chose earlier).
  • In app/assets/javascripts/application.js, which is supposed to be your main Javascript file, remove the “require_tree .” directive and replace it with require components.
    You would otherwise ask Sprockets to copy in each and every of your components individually, unnecessarily duplicating a lot of code, as browserify would in turn bundle each component with a copy of its own requirements.

Following those few rules will allow both server- and client-side rendering of your React components, while letting you isolate your components code and organize them at will.

You can find an example project built along the lines of this article on my Github account: https://github.com/olance/rails-react-browserify-example
The commits will show you the steps taken to set up the Rails app from scratch!

Show your support

Clapping shows how much you appreciated Olivier Lance’s story.