How do PostCSS and PostProcess work together?

Why do the hugo doc instructions suggest putting the resources.PostCSS call in the template but outside of the if hugo.isProduction conditional where the resources.PostProcess call is? Shouldn’t they be together? Why would you want to run PostCSS in development mode without also using PostProcess?

{{ $css := resources.Get "css/main.css" }}
{{ $css = $css | resources.PostCSS }}
{{ if hugo.IsProduction }}
  {{ $css = $css | minify | fingerprint | resources.PostProcess }}
{{ end }}
<link href="{{ $css.RelPermalink }}" rel="stylesheet" />

The PostCSS setup may affect the rendering of your site (depending on plugins), so it makes sense to preview those effects when running hugo server.

PostProcess should not affect the rendering of your site. To improve performance, only do it when necessary (i.e., hugo.IsProduction).

You’re talking about having other postcss node modules to run other than purgecss (which as configured in the docs, only actually runs when the environment is production). So those other modules might not depend on being in a production environment so you’d want to have those run also.

But in the case where you only use purgecss and you only care to run it in production mode, then it would be ok and make sense to include the PostCSS call in the conditional?

So what is the point of PostProcess (i know the docs say that it delays the processing of that resource until the whole html is built) when PostCSS is called before it? How does the delay work or help in that case? Because when PostCSS is called, and in production mode, it will execute purgecss based on hugo_stats.json which seems to get built early enough for it to work. I guess the syntax and the timing of everything is what’s not clicking.

Sure, whatever you’d prefer.

The hugo_stats.json file is not complete until all pages have been rendered.

Marking a resource with resources.PostProcess delays any transformations to after the build, typically because one or more of the steps in the transformation chain depends on the result of the build (e.g. files in public).

1 Like

Ok, so here’s my mental model of what’s happening then. As pages are built, their tags, classes, ids are added to the hugo_stats.json. So not everything might be added until the entire build completes.

And the PostProcess tells Hugo to not perform any transformations on the resource thus marked until everything else has been built. Which means hugo “looks ahead” to see if any resource is marked PostProcess. If it is, even if it’s on another code line, it does everything that’s non-transformative until it hits the transformation call, then everything after that including that are put on hold until after the end of the build. So then I assume in the templates where the state exists, there is a placeholder for the output of the transformation to be filled in when it’s done.

So you could do PostCSS without the PostProcess and it will do the transformation on that resource at that moment but depending on the transformation, it might be working with incomplete data.

Two reasons:

1 It’s relatively slow
2 There are some gotchas with it when running with it in the server, I don’t remember the exact details

The 2 only reasons why I implemented it was to get CSS purging and CSS prefixing, both of which is not available in the “Go world”-

1 Like

Thinking about it, we would still need to “post process” it even if we had the tools in Go.

Yea that makes sense. So do you think my mental model of the process is accurate or not quite?

That’s correct. It works on the content in /public – which has placeholders for .RelPermalink etc. Which means that you currently cannot do e.g. {{ .RelPermalink | upper }} e.g. I’ve been thinking on improving this into a more generally useful construct here:

Is Go/Hugo executed in a procedural manner? or line by line? if so, or even if not, how does hugo know that i’ve called PostProcess on a resource which was piped in after having been assigned value by some previous call to some resource transformation, if the transformation code is reached first?

{{ $css := resources.Get "css/main.css" }}
{{ $css = $css | resources.PostCSS }}
{{ if hugo.IsProduction }}
  {{ $css = $css | minify | fingerprint | resources.PostProcess }}
{{ end }}
<link href="{{ $css.RelPermalink }}" rel="stylesheet" />

Also, I noticed that if there isn’t a use of the resource like $css.RelPermalink, then the code doesn’t even seem to execute… at least I think that’s what I’ve seen. I’ll have to double check that.

How is … that possible?

A given template is executed in sequence. The tricky part is the concurrency (as the same template may be executed in parallel); me must make sure that a given transformation is performed once only, which is some sort of locking behind the scene.

I mean I’m reading through the template.

  1. I come across a resource transformation (resources.PostCSS for example)
  2. I execute that line
  3. I come across a later line that says to PostProcess the input (which happens to be the resource from before)

If lines of the template code are executed line by line, top to bottom and in a certain piping order, then how does it know to wait to process some resource until post build when the code telling it to transform the resource comes earlier?

I may be completely wrong with how templates are executed. Above is my simple understanding of typical procedural code exectution.

OK, that’s where the “Hugo pipes” gets into the picture:

  • A chain isn’t executed (transformed) until you use it (e.g. .RelPermalink); if you don’t use it, it will never be transformed.
  • Calling resources.PostProcess is just creating a new Resource copy marked for later execution.

I see. So when you use the resource, then it transforms… So does it look at the transformation call stack in reverse order and that’s how it knows to post process (if it’s called) before postcss?

Yea, it’s backed by a Go slice, and the actual transformation (e.g. scss process → minify → fingerprint) can be made extremely effective because of this.

Great to know. That’s interesting and clever.

So slightly related, when you pipe commands (Not Hugo Pipes, just typical pipe syntax) into other commands, is the stack built from right to left or left to right? It didn’t matter in the end but I’m curious how the execution of a pipe chain works

It’s from left to right. The result of the left side is passed as the last argument to the right side.

This topic was automatically closed 2 days after the last reply. New replies are no longer allowed.