Feasibility for Hugo internal purgecss functionality?

Would it be feasible to write a Hugo internal package to purge css files, which acts as a replacement for PurgeCSS functionality?

The current implementation of writeStats parses the build html for tags, ids and class names, but not for element attributes. The Tailwindcss-forms plugin uses the type=„“ attribute of input elements, which are purged from the css file under the current setup. see issue Collect attributes in hugo_stats.json · Issue #7560 · gohugoio/hugo · GitHub

It would be a somewhat minor rewrite to add parsing for input element attributes to Hugo writeStats, but I think that would be just a work around.

These writeStates are handed down to PurgeCSS for further handling. When running the purgecss command there is a noticeable time delay when purging from a fairly big css file, an example would be the css file generated by TailwindCSS with over 10.000 LOC. I think there could be a major speed improvement, if done natively with go and Hugo, instead of an external JS/Node process.

And wouldn’t it be nice, to include the purge command into the Hugo pipes pipeline?


{{ $css := resources.Get "css/main.css" }}

{{ $css = $css | resources.PostCSS }}

{{ if hugo.IsProduction }}

{{ $css = $css | purge | minify | fingerprint | resources.PostProcess }}

{{ end }}

<link href="{{ $css.RelPermalink }}" rel="stylesheet" />

I could not find a go package which replicates PurgeCSS, or at least handles a subset of the functionality of purging unused css from a file.

I did found some go packages which implement parsing of css files, This could be a starting point if combined with go html parsing.

Gorilla CSS3 tokenizer: GitHub - gorilla/css: A CSS3 tokenizer.

Lexer and Parser for Web formats: GitHub - tdewolff/parse: Go parsers for web formats

CSS selectors for use with the parse trees produced by the html package: GitHub - andybalholm/cascadia: CSS selector library in Go

Any thoughts on this and how to best proceed?

This would be fantastic if we could do in native Go, but I don’t think there is a library that does it, and I suspect that creating such a library (even the basic one) may be a lot of work. I know that Tailwind project has created their own purger for the JIT project. That is in JavaScript, but could maybe get some sense of “how hard”.

You are right, recreating the complete JIT compiler of Tailwind might be a task too big for starters. But I think this is not necessary, as the main reason for the JIT project is reducing compile times of the Tailwind framework under Webpack while developing. A problem Hugo does not have (at least in my experience).

Lightning fast build times . Tailwind can take 3–8s to initially compile using our CLI, and upwards of 30–45s in webpack projects because webpack struggles with large CSS files.

There even seems to be a downside for the JIT compiler:

  • Advanced PurgeCSS options like safelist aren’t supported yet since we aren’t actually using PurgeCSS.

Maybe one step after the other. Replicating the functionality of PurgeCSS, which “only” deals with diffing a tree of html files against a css file, should be doable. I am still trying to grok the inner workings of PurgeCSS. It is interesting to see that they test their package against a precompiled css file of Bootstrap (150kb) and Tailwind (2.74MB).

A minimal API for the go package I have in mind would be:
p := NewPurger(options)
p.purge (content: single html file, css: file string) purged css file { … }
p.purgeFS (content: html file system path, css: file string) purged css file { … }

Thank you, nice find. From a first glance this would need some some optimization though, as

attribute selectors are ignored

Indeed. It is only a starting place. I haven’t evaluated beyond that.