CSS and webpack

Let's learn about CSS and webpack in this lesson!

Building CSS in webpack

In this lesson, we’re going to focus on using CSS in webpack.

For more details on exactly what our tools do when we load them, see Webpacker.

Initially, CSS was a little tricky to load from Webpacker, but there have been continual improvements and it’s now about the same complexity as using Sprockets.

The default Webpacker setup allows us to use plain CSS as well as Sass and SCSS. Sass and SCSS are valuable extensions to basic CSS, including an easier syntax for variables and nested selectors. The SCSS version of the syntax is very similar to the original CSS, while Sass is a minimalist syntax that is similar to Haml. We’ll be using SCSS for examples in the book, though I expect that most of them will also work as plain CSS.

If you look at our codebase so far, we’ve been using the Bulma CSS framework, but we’ve been importing it directly in our layout using a direct link in our application.html.erb layout file. Now we’re going to import the Bulma CSS framework via Webpacker.

Bulma can be imported as a node module:

$ yarn add bulma

We’re also going to need one other library to get the types right for this:

yarn add @types/webpack-env -D

This library will make sure we are using the correct version of require, which we will need in a page or two in order to load static images.

The way that this works in Webpacker changed in Webpacker 5.0. To make this work now, we only need one line of actual SCSS code, which is the line that imports Bulma. We’ve put it in app/javascript/packs/application.scss. The filename is significant, because Webpacker will pack all the application.* files into a single pack, so by naming our file application.scss, it’s included in with the existing application pack.

Here are the contents of the file:

@import '~bulma';

@import is a Sass statement that imports a file, ~bulma. In this case, the tilde character is a shortcut that means look in the node_modules directory.

At this point, we have a little bit of ambiguity. For me, in my sample app, this configuration works as-is. According to the current Webpacker docs, however, you need to separately add the stylesheet to the application layout using stylesheet_pack_tag. I don’t think that’s still true, but if this setup doesn’t work for you, try adding this line to the layout.

<%= stylesheet_pack_tag("application") %>

And we’re set.

We can now add custom CSS rules to the application.scss file, or we can import other files into the application.scss file. Webpacker doesn’t have any opinions on how you structure your custom CSS. As with JavaScript packs, it’s pretty common to have the top page be basically a manifest of other pages and to then split those other pages such that each file has a relatively focused batch of CSS rules, one for say, button styling, or one for typography, or colors.

Webpack processes CSS using a tool called PostCSS. PostCSS provides our SASS/SCSS parsing, but it can also do a lot of other post-processing of CSS, which we’re not using at this point, but you should check the docs for features that could be useful on larger projects.

Adding CSS and assets to webpack

There are a lot of static file assets, such as images or fonts, that you might want to use via Webpacker in your code or CSS. To get those assets into our pack file we have to import them, and then Webpacker gives us some helper methods to be able to access the files. We need helper methods here because bundling the images into the pack changes the name of the file, so we can’t just use the raw file name.

First we need to import the static files. To do so, we add the following code, to our application.ts. This code is boilerplate from the Webpacker gem, which I removed along the way; we can put it back now.

const images = require.context("../images", true)
const imagePath = name => images(name, true)

The first line is the important one: it uses the webpack require.context method to add the entire ../images directory into our pack—that’s the relative name for the app/javascript/images directory. One important piece here is that the app/javascript/images directory must actually exist, or TypeScript will fail to compile.

Any files placed inside that subdirectory are then available to the pack when the pack is compiled. We can do separate require.context calls for other directories if we want to add items like fonts, or video, or whatever.

The last line creates a method called imagePath that you can then export or use in other JavaScript code to get the name of the file.

At this point, we have a few ways to access any image file in that images subdirectory.

First, From JavaScript code, we can use that imagePath method we just defined, which looks like imagePath("./arrow.png") (you need the ./ for the method to work).

Second, From our SCSS files, we can reference the image directly, relative to the file we are in, and webpack will handle the reference correctly. So if we wanted to use an image as a background image in a style defined in our packs/application.scss file, it’d look like background-image: url("../images/arrow.svg");. We can directly reference the image file relative to application.scss.

Third, inside a Rails view, Webpacker defines two helper methods: asset_pack_path, which takes in the location of a file in the file system and returns its Webpacker name, and image_pack_tag, which is a replacement for image_tag, as in image_pack_tag("media/images/arrow.png"). The file paths need to be prefixed with media/ for some reason that the existing documentation does not make clear.

Get hands-on with 1400+ tech skills courses.