How to use TailwindCSS 3.0 without external NPM scripts, just Hugo pipes
Tags: hugo, tailwindcss, postcss
Published: 2022-01-16 19:20:35

---
title: How to use TailwindCSS 3.0 without external NPM scripts, just Hugo pipes
published: true
description: How to use TailwindCSS 3.0 without external NPM scripts, just Hugo pipes
tags: ["Hugo", "TailwindCSS", "PostCSS"]
---

*tldr;* Here is a working demo: https://github.com/Gioni06/hugo-pipes-tailwind-3

TailwindCSS is a utility-first CSS framework for rapid UI development and as of version 3, the JIT mode is the new default. Among other benefits, it renders a CSS file, that only contains the code, that your site is actually using -- nothing more!

## 1. What changed since TailwindCSS 3

Since version 3, TailwindCSS only uses the JIT compiler to do its magic. That means, that utility classes will be generated on demand instead of purging unused classes afterward. The thing with TailwindCSS is, that only the compilation result changes, not the input file. For this reason, Hugo Pipes won't work beyond the initial render pass. This behavior is perfectly fine for a production build, but it doesn't detect changes when running a Hugo development server in "watch" mode.

```go
{{/* Doesn't work in "watch" mode */}}
{{ $style := resources.Get "tailwind.css" | resources.PostCSS | resources.Minify }}
```

## 2. How to use TailwindCSS without external NPM scripts, just Hugo Pipes

First up, we use TailwindCSS as a PostCSS plugin. This is key to using Hugo's build-in `resources.PostCSS` pipe. You can read how to setup PostCSS with TailwindCSS here: https://tailwindcss.com/docs/using-with-preprocessors#using-post-css-as-your-preprocessor

To make work in both production and development the "Hugo Way", I came up with a little hack. Rather than using the input file as a regular CSS resource, we can tell Hugo to treat it like a template file using the `resources.ExecuteAsTemplate` pipe.

To ensure that the result always changes, we can create a random string and pass it into the CSS input template. Also, the final CSS file has the generated random string as part of its file name. 

Note that this is only necessary during development. In production, a single render pass by Hugo is all it takes to work with TailwindCSS.

This is the complete code that I came up with.

```go
{{ if .Site.IsServer }}
    {{ $seed := "weqklrjfmnk213409ufasdfhnlk3j4bladsfsl" }}
    {{ $random := delimit (shuffle (split (md5 $seed) "" )) "" }}
    {{
    $style := resources.Get "tailwind.css"
    | resources.PostCSS
    | resources.ExecuteAsTemplate (printf "tailwind.dev.%s.css" $random) $random
    }}
    <link rel="stylesheet" href="{{ $style.RelPermalink }}">
{{ else }}
    {{
    $style := resources.Get "tailwind.css"
        | resources.PostCSS
        | resources.Minify
    }}
    <link rel="stylesheet" href="{{ $style.RelPermalink }}">
{{ end }}
```

## 3. Conclusions

In this article, we discussed how to use TailwindCSS without external NPM scripts by using Hugo pipes. First, we discussed how to use the JIT mode in TailwindCSS 3.0. Next, I showed how to use Hugo pipes to treat the input file like a template so that it always triggers a re-compilation when running Hugo in "watch" mode.

## 4. References and further reading 

- https://github.com/gohugoio/hugo/issues/8343
- https://gohugo.io/hugo-pipes/postcss/
- https://tailwindcss.com/docs/using-with-preprocessors#using-post-css-as-your-preprocessor
- https://www.brycewray.com/posts/2021/11/making-tailwind-jit-work-hugo/