loading...

PurgeCSS with Slim Templates

julianrubisch profile image julianrubisch Originally published at blog.minthesize.com on ・3 min read

I’ve been using Tailwind CSS for some of my Rails apps with great delight over the last few months. One downside of utility-first CSS frameworks, however, is the fact that they ship with a myriad of potentially useful classes of which your HTML might use but a few percent.

PurgeCSS (which is available as a PostCSS plugin) to the rescue! To simply quote the docs, it

analyzes your content and your css files. Then it matches the selectors used in your files with the one in your content files. It removes unused selectors from your css, resulting in smaller css files.

Install PurgeCSS

Okay, without further ado, let’s add it to the javascript package of our (webpacker enabled) Rails app:

$ bin/yarn add @fullhuman/postcss-purgecss

Since you probably do not want to enable it in your development environment - it does take some time to go through all your code and strip out the unused selectors every time - you’ll probably configure your postcss.config.js like this:

if (process.env.RAILS_ENV === "production") {
  environment.plugins.push(
    require("@fullhuman/postcss-purgecss")({
      content["./app/views/**/*.html.slim","./app/helpers/**/*.rb"],
      defaultExtractor: content => content.match(/[A-Za-z0-9-\_:./]+/g) || [],
      whitelist: collectWhitelist(),
      whitelistPatterns: [],
      whitelistPatternsChildren: [/trix/, /attachment/]
    })
   );
}

module.exports = environment;

Only, that doesn’t quite lead to the expected results with the Slim templating language. Slim is indentation-based and very terse, you basically write out CSS selectors in a tree:

.flex.justify-end.items-center
  .flex-1.text-base
  .flex-shrink.text-sm
  / ...

What’s more, Tailwind uses a lot of, let’s say unusual characters inside its CSS classes, such as /, :, and even .. Working with those in Slim is hard enough (and one reason I switched back to ERB for most Tailwind-based projects, but sometimes you just don’t have that choice), but getting the correct extractor regex pattern is some orders of magnitude harder.

Convert It Back To ERB

So, how can we help that? I decided to go with the naivest approach there possibly is, namely, converting Slim templates back to ERB before the webpacker assets are compiled. I was hesitant to try this until I realised slim offers a dedicated erb_converter for these purposes. So I wrote a custom script, and placed it in lib/scripts/slim_erb.rb:

require "slim/erb_converter"

open(Rails.root.join("tmp/compiled.html.erb"), "a+") do |compiled|
  Dir.glob(Rails.root.join("app/views/**/*.html.slim")).each do |slim_template|
    open slim_template do |f|
      slim_code = f.read
      erb_code = Slim::ERBConverter.new.call(slim_code)
      compiled.puts erb_code
    end
  end
end

This will concatenate all your Slim files into one large ERB file placed in your app’s tmp folder - which, since we only care about the selectors used in there - is just good enough. What remains to do, is to swap the content key in the PurgeCSS config:

"./app/views/**/*.html.slim" => "./tmp/*.html.erb"

In your deployment script, you just have to make sure you invoke this script as a Rails runner script (and optionally delete the temporary file):

$ bundle exec rails r lib/scripts/slim_erb.rb && bundle exec rails webpacker:compile

Fingers crossed 🤞🏻, your purged CSS should now contain all the selectors present in your view templates. Of course, the usual caveats about whitelisting third party libraries (like trix, for example), still apply.

Post Scriptum

I can foresee some objections as to how Tailwind allows for all types of sophisticated configurations (and might already have PurgeCSS included, depending on your version). I used Tailwind mainly as a stand-in for any kind of utility-first CSS framework. I’ve used this technique successfully with Semantic UI, Bootstrap, and others.

Discussion

pic
Editor guide
 

Slim added support for TailwindCSS oddities in version 4.1

 

That’s true. Note that I‘ve used tailwind as a stand-in for any similarly structured CSS framework though.

 

I did!

Going back to your article, I stopped using the dot syntax and use the classic declaration div class="…" instead of relying on slim's magic here.
Does this help with the issue you're addressing or am I missing something?

No, just then a key point on why to use slim at all is lost 😉

"No" I'm not missing anything, or "No" it doesn't help? 😀

Regarding the key point, nothing is lost that is not worth losing to me. On the contrary, I feel like I regained some lost flexibility.

  • I still save on all the closing markups + noisy <%=> erb syntax, which is the bulk of the heaviness.
  • I get to copy/paste classes between projects regardless of whether they use slim or not. Otherwise, you have to turn spaces into dots (and vice-versa).
  • When you use dom_id(object), it makes more sense to me to have it at the beginning of the line with div id=dom_id(object) class="…" data-… than after a long list of classes and hidden in the middle of other attributes declarations
  • It makes things a bit more clear and consistent (sometimes you still need a div without any class, or to declare classes with spaces because they are declared in a content_tag or text_field - back to the copy/paste issue).

Using the extractor from tailwindcss.com/docs/controlling-f... did the trick for me.