Dwight Watson's blog

Replacing Rails Asset Pipeline with Webpacker

This blog post was originally published a little while ago. Please consider that it may no longer be relevant or even accurate.

I'll admit that I'm still relatively new to Rails Webpacker, but on a new project I've decided to completely replace the Sprockets asset pipeline and instead handle all my assets through Webpacker. As a fellow convention over configuration fan I've tried my best to interpret what the convention for structuring a Webpacker app is - I think it's still early days and even the Rails team haven't worked it out yet. I suspect it's something the community will nut out over time. Nonetheless, here is how I've replaced the asset pipeline in my app.

If you're starting fresh, run rails new blank --skip-sprockets --webpack to kick off a new Rails app without Sprockets and with Webpacker. If you need to add Webpacker to an existing project then read up on the docs. Then remove some gems from your 'Gemfile' you're not going to need - sass-rails, uglifier & coffee-rails. You can also ditch app/assets as you're not going to be using that anymore.

First, let's take a look at the default application.js generated by Webpacker.

/* eslint no-console:0 */
// This file is automatically compiled by Webpack, along with any other files
// present in this directory. You're encouraged to place your actual application logic in
// a relevant structure within app/javascript and only use these pack files to reference
// that code so it'll be compiled.
//
// To reference this file, add <%= javascript_pack_tag 'application' %> to the appropriate
// layout file, like app/views/layouts/application.html.erb

console.log("Hello World from Webpacker");

If you read the comment it's pretty clear that the app/javascript/packs directory is meant only to be the entry point to your packs, and to put your actual app in app/javascript. This leads us onto the directory structure.

Directory structure

This is how I've organised my Webpacker app - for an example app called blog. You'll see that I've nested my actual JavaScript app in app/javascript/blog rather than just app/javascript and there's a reason for this. First, it means I can have multiple apps in the same project if I need them (rather than having all their code side-by-side). Second, It allows me to have a really pack entry point which I'll show next.

blog
+-- app
| +-- javascript
| | +-- blog
| | | +-- fonts
| | | +-- images
| | | +-- styles
| | | +-- index.js
| | +-- packs
| | | +-- application.js

Now let's take a look at my app/javascript/packs/application.js file - the entry point to my pack. It's stupidly simple:

import "blog";

This will go import and run app/javascript/blog/index.js which will be the entry point to the rest of my JavaScript application. This keeps the package entry point as simple as possible and then lets you work your magic in your own app.

Keep in mind you aren't going to call your directory (and filename) blog as well, you'll match the name of your Rails app for simplicity. Otherwise of course you can call it whatever you like.

Now, use javascript_pack_tag to reference your app.

javascript_pack_tag 'application', 'data-turbolinks-track': 'reload'

In development

I'm just popping this in here because it seems to have confused some of the people I've spoken to on the issue. When in development run bin/webpack-dev-server - this will watch changes to your app and rebuild when required, pushing changes to the browser. When you're ready to compile you can call bin/webpack or rails assets:precompile but you're likely to leave that up to your server.

Turbolinks and Rails UJS

The next thing to tackle (if you're using either of these features) is to get Turbolinks and Rails UJS back up and running. In the asset pipeline it's as simple calling //= require but it's a little different when using them as modules. First, install both the NPM packages.

yarn add rails-ujs turbolinks

Then we need to import and start them up, in our app/javascript/blog/index.js file (or whatever the name of your app is).

import Rails from "rails-ujs";
import Turbolinks from "turbolinks";

Rails.start();
Turbolinks.start();

As you can see they use the same signature to kick them off after you've imported them. Pretty easy.

Environment Variables

You can access your environment variables from your scripts as they are compiled. I used to append .erb to the filename and then do something like <%= ENV['X_ENV_VAR'] %> but there is a better way. Environment variables are exposed to process.env.

export const STRIPE_API_KEY = process.env.STRIPE_API_KEY;

Stylesheets

You can simply import the CSS or Sass stylesheets you want to use. I've been popping my Sass entry point in app/javascript/blog/styles/app.scss and then keeping everything I need in the styles directory. Then it's easy to import in your app's JavaScript.

import "./styles/app.scss";

While on this topic, it's worth learning about the ~ character when using imports as it will start a lookup from the node_modules directory. So if you wanted to import something like Bootstrap it's as easy as the following.

@import '~bootstrap/scss/bootstrap';

Boom - Bootstrap 4 ready to go!

Images

Images work in a similar sort of way to stylesheets - you will need to import them in your JavaScript before you can use them. I place my images in app/javascript/blog/images and then have a app/javascript/blog/images/index.js file that goes and imports each of the images in the directory. For example, it would look something like this.

import "./logo.svg";
import "./menu-open.svg";
import "./menu-close.svg";

Keep in mind this won't actually include the images in your stylesheets, it'll just pull them in through Webpacker so they're available to your app. You can then use the asset_pack_path helper in your views to reference the files. So, if you wanted to use one of those images you could do something like...

= image_tag asset_pack_path('logo.svg')

In addition you can reference your images in your CSS/Sass and Webpacker will automatically hook them up for you. Generally the root path will be the entry point to your CSS so you shouldn't need to use absolute paths or anything funky.

Conclusion

This is pretty much a work in progress, it's more of a snapshot of how I'm using Webpacker now. I tried to find other posts about how people are using Webpacker but there's not much out there at the moment. I'm really interested to see what the standard use of Webpacker will look like, or if we'll even come to some sort of standard at all.

A blog about Laravel & Rails by Dwight Watson;

Picture of Dwight Watson

Follow me on Twitter, or GitHub.