Integrating React and Webpack with Rails without Gems

reactwebpackrails

Note: the strategy discussed in this post is one I later made into a gem called react_rails_webpack that you can find here.

Part 1: Learning to Like React, Webpack, and NPM

I’ve been trying to learn some deeper Javascript lately. I’ve been learning about the Node and npm ecosystems, and I’ve settled on React for my first Javascript framework.

To get started with React, I began with Henrik Joreteg’s excellent Human Javascript video tutorial. One of the things I got attached to learning this way was the idea that I could require CSS files from my Javascript via Webpack. It seems counterintuitive, but the thing I love about this is it allows me to go even further with one of the things I liked about Volt: being able to put all my files related to a particular visual widget (model, view, and related assets) under the same component directory.

Something I’ve come to dislike about Rails is that the folder structure for assets is completely separate from that of the views. This makes it so that to be able to easily find styles for my views, I have to maintain parallel but separate directory structures in views and assets. To me, this feels painfully not DRY.

By contrast, if I can require a style file in a React component file, I can also put the style and function code in the same folder. This means that literally all of the code of any sort that is relevant to a particular component is in the same place. If I want to reuse a component in another app, I can copy the containing folder over and require the component, and I’m done. I don’t have to separately copy other files like assets. I don’t have to painstakingly maintain parallel directory structures or require assets via a labyrinthine require statement winding up and down the folder structure of the app. All of the knowledge about a component is in the same place. All of the relevant code is in the same place.

Part 2: Researching Tools for React and Rails Integration

There are other things I like about Webpack as well. Babel integration for writing in ES6 and hot reloading with React and Redux, for example. So when I started looking at how to get React integrated with Rails, I looked for something that would provide these features out of the box, and would allow me to further explore and extend my toolsets by installing other npm packages.

I found that the most popular gem, react-rails, didn’t provide this. A newer gem, react_on_rails, promised Webpack integration and looked more promising, so I gave it a shot. I found for my purposes, though, that adding and customizing the npm packages I needed was still pretty complicated. I also couldn’t understand why it took so much work to add additional npm packages using react_on_rails when it was so simple in a Javascript-only app, and when, ultimately, most of what was going on was the Javascript, etc, files were being compiled into asset files in the Rails asset pipeline.

It was at this point that I realized there might be another solution: I might be able to set up an integration myself.

Part 3: Doing React and Rails Integration Myself

In the Human Javascript video tutorials, you build an app that relies heavily on an npm package called hjs-webpack. hjs-webpack does the webpack configuration for you (setting up, among other things, Javascript asset compilation and a hot-reloading development server), and compiles a whole front-end app into three files: a big javascript file, a big css file, and a tiny index.html file that contains only head, body, and a div for React to target. hjs-webpack was designed for static front-end apps, but I saw no reason why I couldn’t just compile the javascript and css files it creates into the Rails assets folder and let Rails take integrating the assets from there.

To this end, I setup a “client” folder in my Rails application root with a basic hjs-webpack app configured to output to “/app/assets/webpack”. I had to make a minor change to hjs-webpack — I added a configuration option to prevent webpack from minifying Javascript, so that the compiled files still have understandable variable names for when things go wrong (code for my version of hjs-webpack with this config option added is here). I also wrote some Ruby and Javascript code to facilitate a little bit of magic React component creation and props passing in Ruby.

I can add a React component to a Rails view like this:

<%= react_component(:ComponentName) %>

Or with props:

<%= react_component(:ComponentName, { content: ‘Hello, world!’ }) %>

The react_component helper implementation is very simple. It looks like this:

require 'json'

module ReactHelper
  # Props should be a Ruby hash
  def react_component(component_name, props = {})
    content_tag(
      :div,
      nil,
      class: 'react-component-target',
      data: {
        componentName: component_name,
        componentProps: props.to_json
      }
    )
  end
end

All it does is create an HTML element with a class of “react-component-target”, and two data attributes: one for the component name, and another for any component props (which it converts from a Ruby hash to a JSON object). Javascript handles the rest.

Here’s the Javascript file responsible for rendering the React components created with this helper:

import React from 'react'
import ReactDOM from 'react-dom'

import ComponentName from './components/ComponentName/ComponentName'

const components = {
  'ComponentName': ComponentName
}

const reactTargetDivs = document.getElementsByClassName('react-component-target')

let componentName, componentProps

Array.prototype.forEach.call(reactTargetDivs, function (targetDiv) {
  componentName = targetDiv.getAttribute('data-componentname')
  componentProps = JSON.parse(targetDiv.getAttribute('data-componentprops'))
  ReactDOM.render(
    React.createElement(
       components[componentName],
       componentProps
     ),
    targetDiv
  )
})

It works like this: I add each component I want to be able to use the Ruby helper for to the “components” object so that it can be looked up by its name. The script finds all HTML elements with the “react-component-target” class, and for each one, it grabs the component name and props from data attributes, and calls React.createElement with the corresponding component class and the props.

By doing things this way, and telling hjs-webpack to pipe the results into a Rails asset folder, I’ve found I can easily add and take advantage of any npm packages that I could’ve used in a standalone Javascript app. I can also run a hot reloading webpack dev server just like with standalone apps (for when I want to debug a React component outside the context of Rails).

I don’t have any idea how to go from here to server-side rendering, which is a drawback of not using one of the established gems, but server-side rendering isn’t something I need, and outside of that, this approach has worked best for my needs so far.

Thanks to Henrik Joreteg for his excellent Javascript tutorial and hjs-webpack. Thanks to the react_on_rails team for some of the inspiration in terms of how to accomplish this integration.

For anyone trying this at home

  • Don’t forget to require the assets in the app/assets/webpack folder appropriately (and/or add the app/assets/webpack folder to the Rails asset paths in application.rb)

  • The Javascript for setting up the React components must be executed after the HTML for the React components it’s targeting is rendered. Right now I’m doing this by putting the script tag for the relevant Javascript at the bottom of the application layout, but there’s probably a better way.

  • Changes to your Javascript will show up right away if you’re working with the webpack dev server, but you will need to run the webpack command to get the latest version of your Javascript code compiled into Rails assets and visible to the rest of your application.