DEV Community

Cover image for How to use TailwindCSS 3.0 without external NPM scripts, just Hugo pipes
Jonas Duri
Jonas Duri

Posted on • Edited on

How to use TailwindCSS 3.0 without external NPM scripts, just Hugo pipes

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.

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

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.

{{ 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 }}
Enter fullscreen mode Exit fullscreen mode

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

Top comments (2)

Collapse
 
ivanduka profile image
Ivan Duka

Excellent article, thanks!
There is a downside, though - the resources folder is polluted now with the useless junk and cannot be checked in to Git anymore. It is in Git for me because image resizing is a CPU-costly operation, and I prefer to store the computed results in Git to avoid recalculation.

Collapse
 
jonas_duri profile image
Jonas Duri

Thanks ;) You are totally right, that is a real downside if you rely on this folder to be under version control