Evan Stern
November 16, 20204 min read

This is a very specific use case, I'm aware, but what happens when you combine have a project that uses TailwindCSS, Gatsby, and PurgeCSS and then combine that with a 3rd party dependency that is also written using Tailwind classes?

The answer: strange stuff happens.

The Setup

Let's say you have a Gatsby project that is set up to use TailwindCSS. I won't go into detail about how you'd set such a project up as you can find articles about that easily. You'll most likely want to purge any unused CSS from Tailwind at build time. There are two common ways of doing this.

Using Tailwind to purge the CSS files

Probably the easiest way to purge your CSS files is to use Tailwind's built in purge flag. This can be done by editing your tailwind.config.js file and simply adding something like this:

// tailwind.config.js

module.exports = {
  purge: ['./src/**/*.tsx'],
};

The above code snippet will cover purging your CSS in any of the matched files.

(By the way, in Tailwind 2.0 the above strategy will be replaced by something called "purge layers". You can read about it here)

Using PurgeCSS

The built in Tailwind setup works pretty well for most use cases but doesn't allow for any customization for things like custom prefixes or ignoring patterns.

If you need more than what the out of the box solution offers, you'll be going with PurgeCSS.

There are a number of ways to do this but my preference is to use gatsby-plugin-purgecss. You can set that up like so:

// gatsby-config.js

module.exports = {
  // ...
  plugins: [
    // ...
    {      resolve: 'gatsby-plugin-purgecss',      options: {        tailwind: true,      },    },  ],
};

Adding a Dependency That Uses Tailwind

Now that we're purging our CSS, what happens if we throw a monkey wrench into the mix and import a dependency that uses Tailwind classes to style itself?

Why would this happen? Well, I ran into this because MachineServant packages up common components and exports them to bit.dev for consumption by other projects we manage. Since Tailwind is commonly used in all our projects it was easy to style things using Tailwind classes. As long as there is a stylesheet around that can resolve those class names, everything works just fine.

The Dependency

Let's assume that we are going to import a very simple dependency defined as such:

// @mycomponents/button.tsx
import React from 'react';

type ButtonProps = {
  label: string;
};

export const Button: React.FC = ({ label }) => (
  <button className="rounded-lg bg-blue-800 text-white py-2 px-4 hidden sm:block">
    {label}
  </button>
);

Notice that we are using the classes hidden sm:block. This should hide the button by default (in mobile, for example) and then show it for any screen size sm or larger.

We import it into our project somewhere and then use it:

// App.tsx
import React from 'react';
import { Button } from '@mycomponents/button.tsx';
export const App: React.FC = () => (
  <main>
    <Button label="My Button" />
  </main>
);

So, What Happens?

What happens depends on whether or not you build the project.

In Development

In development mode, this works just fine. We have a button, it shows up for any screen size sm or larger and it is hidden otherwise. Everything looks and works just like you'd expect.

In Production

In a production build, however, the button is just simply not visible no matter what the screen size is.

What Is Happening?

Basically, what's happening is that PurgeCSS is purging the sm:block selector when it runs during a production build. This means that all that is ever computed is the hidden class.

Why does this happen? Because PurgeCSS doesn't know about your dependencies and it isn't looking at them as source files when determining what should or shouldn't be purged.

How Do We Fix It?

It took some digging into the gatsby-plugin-purgecss documentation to figure it out but you can solve this by telling the plugin what content you want to include in the purge calculations.

In our example, we have a dependency named @mycomponents/button, so we need to tell PurgeCSS about it.

You can do that by extending the default content option in the gatsby-plugin-purgecss definition in the gatsby-config.js file like so:

// gatsby-config.js

module.exports = {
  // ...
  plugins: [
    // ...
    {
      resolve: 'gatsby-plugin-purgecss',
      options: {
        tailwind: true,
        content: [          // This is the default configuration, make sure to include it          path.join(process.cwd(), 'src/**/!(*.d).{ts,js,jsx,tsx}'),          // This is the dependency          path.join(            process.cwd(),            'node_modules/@mycomponents/button/**/!(*.d).{ts,js,jsx,tsx}'          ),        ],      },
    },
  ],
};

Essentially, just add a path to the content option list that points to the component that needs to be scanned when PurgeCSS does its thing.

Obviously, the string you add to content can be any glob pattern you like. This is a very specific example but you could create more inclusive glob patterns to grab more in one definition if you'd like.

Conclusion

I'm relatively certain that there are other ways to make this work than what I just outlined. But, I did a lot of searching and didn't see any other solutions. Hopefully, this will help someone out who gets stuck on this like I did.



MachineServant

MachineServant