Set up Tailwind CSS JIT in a Rails project to compile styles 20x faster

Topics

Learn how to switch to the latest and greatest just-in-time compilation feature from the freshly released Tailwind CSS 2.1 in a modern Rails app with styles managed by Webpacker. Only two small commits are required to adopt an entirely different development experience.

Tailwind CSS gained quite a lot of traction in the world of Ruby and Rails. We even have the official tailwindcss-rails gem blessed by DHH himself that you can use with good ol’ Asset Pipeline if, for some reason, you stay away from Webpacker, ES6 modules, and processing CSS with webpack.

I joined the Tailwind army about a year ago when I started working on a new demo application for AnyCable. I was amazed by the powerful yet simple development flow: fast prototyping via Tailwind Play, componentization via @apply, and the built-in PurgeCSS integration for smaller compiled CSS files.

Everything seemed perfect except for the one thing: a bit slower re-compilation in development (using webpack-dev-server) than I expected. The reason was that Webpack had to recompile all the Tailwind CSS styles whenever anything changed in the bundle. You can read more about the problem and the possible workaround in this article.

Tailwind team knew about the problem, and they came up with an elegant solution—they’ve built an entire just-in-time compiler for utility classes!

Tailwind CSS JIT is just out of the labs and still has the experimental status, but for my small demo project it has already proved to be useful. Take a look at the live reload times before and after switching to JIT:

Live reload with Tailwind CSS (no JIT)

2.5MB of CSS takes up to 4.5 seconds to compile, which is no way to live. Now see below for how much we can gain after switching to just-in-time compilation.

Live reload with Tailwind CSS JIT

Under 50 KB and the range of 100-200 ms! Now we are cooking with gas!

Now, let’s see how to make it happen in your code.

How to use Tailwind CSS JIT in a Rails app?

  • Commit 1: Upgrade to Webpacker 6 (still in beta). I followed the official guide. The only thing that I had to add was the ENV["WEBPACK_DEV_SERVER"] = "true" line in my bin/webpack-dev-server (it seems to be already fixed, but hasn’t been released yet).

  • Commit 2: Upgrade to Tailwind CSS 2.1 and PostCSS 8.0 using this guide. The final stroke is to turn on JIT mode in the tailwind.config.js:

 // tailwind.config.js
 module.exports = {
+   mode: 'jit',
    purge: {

That’s it! My development server is now reloading instantly.

System tests gotcha and workaround

I then tried to run my system tests that call asset:precompile, and the compilation froze forever. I tried to run bin/webpack and found that assets are successfully getting built, but the process doesn’t exit 🤔.

After digging through issues on GitHub, I found something relevant: Gulp task not finishing. It turned out that Tailwind JIT automatically watches for files when NODE_ENV=development and does this in the foreground (that’s why the process freezes). The solution is simple: add a TAILWIND_MODE=build environment variable in the context of your bundler. Luckily, with Webpacker, there’s a clear API to set that variable just in a context of a compiler and forget about it:

# config/application.rb
Webpacker::Compiler.env["TAILWIND_MODE"] = "build"

Now everything works! (And that single line is the only reason I wrote this short post 🙂)

Talk to us if you want to supercharge your digital products (including but not limited to Rails applications) and improve the development experience for your team with our help.

Join our email newsletter

Get all the new posts delivered directly to your inbox. Unsubscribe anytime.