Enabling Modern JavaScript in Rails with Webpack(er)

Tingshen Chen
Nov 6, 2018 · 8 min read
Image for post
Image for post

JavaScript: Rails’ neglected sibling

Rails, on the other hand, is commonly known for its preference for convention over configuration. That meant it had strong opinions on how JavaScript should be handled. Specifically, Rails had its own asset packaging library called Sprockets, which was not pre-configured to resolve NPM dependencies. Instead, jQuery and a small number of popular JavaScript libraries wrapped by Gems are available. On top of that, the language that shipped with Rails was CoffeeScript, which was losing favor over the rising popularity of ES6 syntax.

Enter Webpacker

Fortunately, the Rails community had also identified the need for Webpack. They introduced the Webpacker gem, which was compatible for Rails 4.2+. Out of the box, Webpacker generates configurations that applies the best practices for development, staging, and production environments. This worked immediately with our deploy pipeline since it was preconfigured to follow Rails conventions. Specifically, it hooks up the build process to rake asset:precompile. This saved us an immense amount of time fumbling with configuration files, allowing us to focus our attention on refactoring and migrating our code.

The Migration Process

Ideally, it would have been nice to have blocked off a significant chunk of time to work on the migration. In reality, we still had lots of features to ship and it’s not always easy to justify spending time on a project that doesn’t immediately pay dividends to the company’s success. Nevertheless, I believed it was important to keep our tech debt in check and progress towards a modern stack. Therefore, we took an incremental approach in our migration, loading two application bundles for a period of time. To save time, we focused on a pure refactor, settling on less than ideal changes on the first pass so that we didn’t blow up any particular pull request.

module.exports = {
test: /(\.module)?\.js(\.jsx)?\.coffee$/,
exclude: /node_modules/,
use: ['babel-loader', 'coffee-loader']
}
const context = require.context(‘global’);
context.keys().forEach(context);

Immediate Improvements

Once migration is complete, there are some quick wins to be gained. Webpack’s production configuration will automatically minify your application bundle and apply hashes to the names of your assets to invalidate cached assets in browsers. You can take this a step further by setting up a separate chunk for your vendor and entry bundle, since the vendor file changes infrequently.

function initReactRailsContext() {
const componentRequireContext = require.context(‘containers’, true);
ReactRailsUJS.useContext(componentRequireContext);
// convert the component names passed into react_component
// from pascal-case to snake-case for the file names to be resolved
const originalGetConstructor = ReactRailsUJS.getConstructor;

ReactRailsUJS.getConstructor = function (reqctx) {
return originalGetConstructor(_.snakeCase(reqctx));
};
return componentRequireContext;
}
const componentRequireContext = initReactRailsContext();
if (module.hot) {
module.hot.accept(componentRequireContext.id, () => {
initReactRailsContext();
ReactRailsUJS.mountComponents();
});
}

Square Corner Blog

Buying and selling sound like simple things - and they…

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store