#rails

Working with Rails, Webpack and Heroku

Kashyap's avatar

Kashyap

Note: This Rails version targeted in the post is one before webpacker got added. The folder structure and procedure might differ if you're using a newer version of Rails. But the context does help.

Deployment of a Rails app that uses Webpack to Heroku is tricky because it requires both Node.js and Ruby. After various unsuccessful attempts, I had a solution that worked without customizing Heroku buildpacks. If you know how Heroku buildpacks work, skip to the summary for the TL;DR version.

A little bit of background first. The application I'm working on uses React for writing interactive components on the front-end. Rails handles routing and database-related functions. The React components are written in ES6 syntax that gets transpiled to the more widely supported ES5 API via the Babel transpiler. ESLint is used for code linting, and Redux for handling complex state transitions of React components. All the modular JavaScript components are bundled into a single file using Webpack.

It's worth pointing out that there's already a well supported React gem for use with Rails react-rails, but we chose to use npm instead. We wanted to leverage the latest version(s) of React, Redux, ESLint, and we felt that using the npm-based package management workflow works best in this case.

This is the application directory structure we decided to go with:

app/
lib/
config/
... (usual rails directories)
webpack/
  |-- package.json
  |-- components
  |-- reducers

Initially the package.json required by npm was placed inside the webpack/ directory, but this made deployment to Heroku a bit complicated. The Webpack asset compilation step bundles all the JavaScript files into a single file called react_bundle.js and places it under the app/assets/javascripts directory. This file is in turn loaded into Rails' asset pipeline. So the prerequisite for the asset pipeline to work properly is that a react_bundle.js file must be present in app/assets/javascripts by the time assets:precompile step is run during deployment.

Heroku buildpacks

Heroku uses setup scripts they call buildpacks for figuring out the type of application, and to run the necessary build steps. For example, Heroku runs build steps for a Rack-based application if it finds a Gemfile in the root directory. If it finds a Railties gem in the Gemfile specs, it will trigger the Rails related build steps. At a simplified level, these are the steps that are run for a Rack application:

  1. Check for Gemfile and Gemfile.lock in the root directory. If it's present, assume this is a Ruby application and start Ruby related build steps.
  2. Check to see if specs in Gemfile.lock has Rack gem as a dependency.
  3. Install necessary Ruby version
  4. Install bundler and install all dependencies
  5. Run a web process based on a Procfile or the default web process

In the same way, Heroku figures out the build steps for a Node.js application based on the presence of a package.json file in the root directory. So what if we need both npm related steps and Ruby related steps? We need to keep both the Gemfile and package.json in the root directory. That's step 1.

Multiple Heroku Buildpacks

Heroku supports having multiple buildpacks for a single application. If we use both Node.js and Ruby buildpacks, we need a way to tell Heroku to run the npm-related steps first, and only then to run the Ruby related steps. This is to ensure that the react_bundle.js file is present under app/assets/javascripts when Rails' asset pipeline commands are run. The Heroku command line provides a way configure the build packs and specify an order to them:

heroku buildpacks:clear
heroku buildpacks:set heroku/nodejs
heroku buildpacks:add heroku/ruby --index 2

The --index argument means the heroku/ruby buildpack is inserted after the heroku/nodejs buildpack. This completes step 2.

Post build hook on Heroku

Once the buildpacks are setup, we need a way to run a Webpack command right after the Node.js setup is completed, and just before the Ruby/Rails related setup starts. Heroku's Node.js buildpack provides a way for users to run a custom script right after the normal Node.js build steps are completed. For this, a command must be specified in package.json under the scripts section with a heroku-postbuild key. Here is the relevant section from package.json:

"scripts": {
  "webpack:deploy": "webpack --config=webpack/webpack.config.js -p",
  "heroku-postbuild": "npm run webpack:deploy"
},

This completes step 3. With these three steps, I was able to setup a workable Webpack + Rails scaffold on Heroku.

Summary

  1. Put the package.json in the root folder of the Rails application.
  2. Setup multiple buildpacks on Heroku via Heroku's command line utility, with heroku/nodejs in the first position and heroku/ruby second.
  3. Add the webpack bundling command in package.json's scripts section with heroku-postbuild as the key.