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”.

1 Like

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.