DEV Community

loading...
Cover image for How Do PostCSS + Tailwind Work? Part 1: Shrinking the Build

How Do PostCSS + Tailwind Work? Part 1: Shrinking the Build

zev profile image Zev Averbach ・4 min read

I love when a library or utility has an intuitive API that you can just hit the ground running with, or when it has a massive audience so you can Stack Overflow your way to success. However, at least in web land things can be complex and counterintuitive enough that I'd like surpass "well-crafted StackOverflow search" familiarity with them.

One such technology is Tailwind CSS. I don't fancy myself a designer, and to be honest I've never gone deep with CSS: To be more honest, my best trick is "float: right;" which usually sends me down an increasingly fiddly path. I love Tailwind UI because everything looks so modern right out of the box, so I don't have to fiddle very much.

However, the class lists in Tailwind UI's components are necessarily verbose, and I'm eager to start using Tailwind CSS the recommended way: By building (compiling?) pre-processing my Tailwind-using projects' CSS. My understanding is that this 1) reduces the build size because it only includes the Tailwind classes you use, and 2) allows you to make your components/HTML readable again by abstracting (@apply-ing) many class names into one.

For this post I'm going to focus on reducing the build size of the CSS.

Foundations

Tailwind is a PostCSS plugin, it turns out. The docs point in every direction but its npm page, which puzzled me at first: The reason for this is that it's a tool — a parser — which

...parses CSS into an abstract syntax tree (AST); passes that AST through any number of “plugin” functions; and then converts that AST back into a string, which you can output to a file.
-- It's Time for Everyone to Learn About PostCSS, David Clark

Thus, the plugins are where the action happens.

Experiment #1: postcss-cli

Both the PostCSS and Tailwind docs start immediately giving pointers about how to set yourself up in various bundlers. However, I'd like to hold off on that if possible and see if I can run the Tailwind plugin on its own. A brief, down-the-page section in the PostCSS README mentions postcss-cli which can be used like so:

$ postcss --use <plugin-name> [-c options.json] -o main.css css/*.css
Enter fullscreen mode Exit fullscreen mode

First things first:

$ npm install postcss postcss-cli tailwindcss
Enter fullscreen mode Exit fullscreen mode

I don't quite know what sort of content to feed into the Tailwind plugin, but let's try the bare minimum:

/* hi.css */

@tailwind base;
@tailwind components;
@tailwind utilities;
Enter fullscreen mode Exit fullscreen mode

That's from the 'install' docs of Tailwind. As you might guess, @tailwind is not valid CSS, so I'm thinking this will get pre-processed when I do

$ postcss --use tailwindcss -o main.css hi.css
Enter fullscreen mode Exit fullscreen mode

Things are not working as expected on my Mac — it can't find the CLI to invoke it — but I Googled my way to npx postcss --use ..., which does work. Oh, technology.

What this produces is what I assume is the entire codebase of those three "modules" referred to in hi.css (fun fact, @tailwind <thing> is called an "At-rule").

It's nearly 172,000 lines long! And indeed, from experimenting it looks like, as of today "base" is about 540 lines of code, "components" is 220, and "utilities" is 171,000!

It looks like "components" is dedicated exclusively to the class name "container" on various screen sizes and with different size prefixes. "base" doesn't define any classes, just sets some defaults for every conceivable HTML tag ("Preflight", they call it).

Experiment #2: postcss.config.js

I'm thinking that if I create this file (from the Tailwind docs again),

// postcss.config.js

module.exports = {
  plugins: {
    tailwindcss: {},
  },
}
Enter fullscreen mode Exit fullscreen mode

maybe I won't have to do --use when using the CLI?

$ postcss -o main.css hi.css
Enter fullscreen mode Exit fullscreen mode

Yes! This prevents the need for specifying which plugin(s) to invoke.

Shrink the Build

Going back to those 172,000 lines of 'rendered' Tailwind CSS code: Let's get Tailwind's help in whittling them down.

First stop, $ npx tailwindcss-cli@latest init which creates a tailwind.config.js. According to the docs we have to make this alteration to get unused CSS purged:

// tailwind.config.js

module.exports = {
  purge: [
    './src/**/*.html',
    './src/**/*.svelte',
  ],
  theme: {},
  variants: {},
  plugins: [],
}
Enter fullscreen mode Exit fullscreen mode

You can add *.jsx, etc. if you're working with other frameworks.

Experiment #3: How Small Will It Get?

First let's run the purge with no HTML/Svelte files whatsoever:

$ export NODE_ENV=production && npx tailwindcss-cli@latest build src/hi.css -o build/main.css
Enter fullscreen mode Exit fullscreen mode

Woot! This produces a main.css of only about 370 lines, which seems to be a subset of @reset.

Experiment #4: Adding One Line To The Build

Now I'll add an index.html that uses one Tailwind class:

<!--src/index.html-->

<div class="bg-blue-400"></div>
Enter fullscreen mode Exit fullscreen mode

Running build produces another compact main.css, but this time it seems to include all of @base as well as .bg-blue-400. main.css is only 11kb, whereas the non-purged version from the beginning is 3.5mb!

Next Time

In the next post we'll explore how to customize and extract classes in Tailwind, as well as integrating it with a bundler (probably Snowpack!).

Discussion

pic
Editor guide