Gridsome, Tailwind, and other PostCSS

2020 March 6

In this post I'll describe how to setup PostCSS plugins to work with Tailwind and Gridsome. We start by creating a new project with  gridsome create gridsome-tailwind-postcss. We will start by setting up Tailwind, move on to add PurgeCSS and finally configure other interesting PostCSS plugins to work with everything.

I set up a starter repository with every step on Github.

Setup Tailwind

The Gridsome documentation already does a good job at explaining how to add Tailwind. This part of the post is based on this documentation and provided here so you don't have to go back and forth between multiple sources. The steps to setup Tailwind are :

  1. Install Tailwind with npm or yarn
  2. Create a main.css file
  3. Import the file in the project
  4. Create a tailwind.config.js file
  5. Update gridsome.config.js

1. Install Tailwind

npm install tailwindcss

2. main.css

Create a file main.css in the /src/css directory with the following content:

@tailwind base;

@tailwind components;

@tailwind utilities;

3. Import main.css

In main.js import the newly created css file:

import '~/css/main.css'

import DefaultLayout from '~/layouts/Default.vue'

export default function (Vue, { router, head, isClient }) {
  // Set default layout as a global component
  Vue.component('Layout', DefaultLayout)
}

4. Tailwind configuration file

Create the TailwindCSS configuration file in /src. You can generate it with:

npx tailwind init

This generates a minimal tailwind.config.js at the root of your project:

module.exports = {
    theme: {
        extend: {}
    },
    variants: {},
    plugins: []
}

5. Update gridsome.config.js

We need to inform Gridsome that we are using TailwindCSS. To do so we add an import to TailwindCSS and update the configuration css.loaderOptions.postcss.

const tailwind = require("tailwindcss");

const postcssPlugins = [tailwind()];

module.exports = {
  siteName: "Gridsome",
  plugins: [],
  css: {
    loaderOptions: {
      postcss: {
        plugins: postcssPlugins
      }
    }
  }
};

6. Testing our config

In the existing src/pages/Index.vue we add a button styled with TailwindCSS to verify that the setup is working properly :

<button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
 Button
</button>

Then we start (or restart) the server with gridsome develop.

Setup PurgeCSS

PurgeCSS allows us to remove unused CSS. It is particularly powerful when using Tailwind. To setup PurgeCSS the steps are:

  1. Install PurgeCSS
  2. Create a purgecss.config.js file
  3. Update gridsome.config.js

1. Install PurgeCSS

npm install -D @fullhuman/postcss-purgecss

2. PurgeCSS config file

Create a file named purgecss.config.js in the root of the project with the configuration below:

module.exports = {
    content: [
        './src/**/*.vue',
        './src/**/*.js',
        './src/**/*.jsx',
        './src/**/*.html',
        './src/**/*.pug',
        './src/**/*.md',
    ],
    whitelist: [
        'body',
        'html',
        'img',
        'a',
        'g-image',
        'g-image--lazy',
        'g-image--loaded',
    ],
    extractors: [
        {
            extractor: content => content.match(/[A-z0-9-:\\/]+/g),
            extensions: ['vue', 'js', 'jsx', 'md', 'html', 'pug'],
        },
    ],
}

3. Update the Gridsome configuration

Update gridsome.config.js to import purgecss and add it to the postcss plugins. Start by adding purgecss:

const purgecss = require('@fullhuman/postcss-purgecss')

You only really need purgeCSS in production, to add the plugin conditionnaly you can use the value of process.env.NODE_ENV and check if it is equal to 'production'. The conditionnal import looks like this:

if (process.env.NODE_ENV === 'production') postcssPlugins.push(purgecss(require('./purgecss.config.js')))

Your file should now look like this:

const tailwind = require('tailwindcss')
const purgecss = require('@fullhuman/postcss-purgecss')

const postcssPlugins = [
    tailwind(),
]

if (process.env.NODE_ENV === 'production') postcssPlugins.push(purgecss(require('./purgecss.config.js')))

module.exports = {
    siteName: 'Gridsome',
    plugins: [],
    css: {
        loaderOptions: {
            postcss: {
                plugins: postcssPlugins,
            },
        },
    },
}

Great! PurgeCSS is now completely set up !

CSSNext

One interesting PostCSS plugin is CSSNext which gives us the ability to use future CSS specification today. Think of it of the Babel of CSS.

Install CSSNext

 npm install postcss-cssnext

Update the Gridsome config

We add the PostCSS plugin to the Gridsome config as usual:

const tailwind = require("tailwindcss");
const cssnext = require("postcss-cssnext")
const purgecss = require("@fullhuman/postcss-purgecss");

const postcssPlugins = [cssnext(), tailwind()];

if (process.env.NODE_ENV === "production")
  postcssPlugins.push(purgecss(require("./purgecss.config.js")));

module.exports = {
  siteName: "Gridsome",
  plugins: [],
  css: {
    loaderOptions: {
      postcss: {
        plugins: postcssPlugins
      }
    }
  }
};

The order in which we load the plugins is important. Tailwind is not capable of reading next generation CSS so we need to transform it for Tailwind first.

Testing our setup

If you are using VSCodium (or VSCode), you can use the plugin PostCSS Language Support to get syntax highlighting in your CSS files. Let's test some features of CSSNext in our basic project. In main.css add some css between @tailwind components and @tailwind utilities:

@tailwind base;
@tailwind components;

:root {
    --mainColor: red;
  }

button {
  & a {
      color: var(--mainColor)
  }

  @nest &.custom {
      background-color: pink
  }
}
@tailwind utilities;

We want @tailwind utilities last to avoid overriding tailwind utilities. Here we use a variable --mainColor set to red, and some nesting. Let's update our Index.vue to make use of our new CSS:

<button class="custom font-bold py-2 px-4 rounded"><a>Button</a></button>

Now if we start our server we should see a pink button with a red text. We managed to use CSSNext with tailwind!

Importing files

Now let's say we have created multiple themes and split them across different files to better structure our project, our css folder could look like this:

main.css
base.css
theme-pink.css
theme-blue.css

If we want to import the css from theme-pink.css into our project we can simply use an @import statement in our main.css file:

@tailwind base;
@tailwind components;

@import "./theme-pink.css";

@tailwind utilities;

PostCSS Preset Env

CSSNext has been deprecated by its original author. A fork has been created named postcss-preset-env. Let's see how we can migrate our code to this new plugin. First we uninstall CSSNext:

npm uninstall postcss-cssnext

We install the new plugin:

npm install postcss-preset-env

And we update the configuration in gridsome.config.js. By default, nesting is not activated, we configure the plugin to activate nesting with the following code:

presetEnv({
  /* use stage 3 features + css nesting rules */
  stage: 3,
  features: {
    'nesting-rules': true
  }
})

The first part of our config file now looks like this:

const presetEnv = require("postcss-preset-env");

const postcssPlugins = [
  presetEnv({
    /* use stage 3 features + css nesting rules */
    stage: 3,
    features: {
      "nesting-rules": true
    }
  }),
  tailwind()
];

We can try and run the server and everything should be working properly.

Using Tailwind utilities

What if we want to use Tailwind utilities directly in our css ? To demonstrate this use case let's modify our Index.vue file to add some more HTML.

    <pre>
      <code>
        if (x==1) {
          print("Hello")
        }
      </code>
    </pre>

    <p class="subtitle">
      This gray text is semibold and uppercase
    </p>

In our file theme-pink.css we would like to add some CSS so that the code block has a dark pink background and the subtitle class is gray and uppercase. We can use the directive @apply followed by Tailwind utilities to do just that:

pre {
  @apply bg-pink-900 rounded-t;

  & code {
    @apply text-gray-300;
  }
}

.subtitle {
  @apply text-gray-600 font-semibold uppercase bg-pink-200 rounded-b;
}

If we now run our server we can see that our modifications were taken into account.