gohugoio / hugo

The world’s fastest framework for building websites.
https://gohugo.io
Apache License 2.0
74.76k stars 7.46k forks source link

Make Hugo work with tailwindcss-jit #8343

Closed bep closed 1 year ago

bep commented 3 years ago

https://github.com/tailwindlabs/tailwindcss-jit

supjs commented 3 years ago

Would be nice if this could be added sometime soon. I think the JIT really improves the PostCSS / Tailwind workflow and speeds it up quite significantly.

metmac commented 3 years ago

Using @praveenjuge's workaround for now, but definitely stoked to see this come to Hugo.

sid-r-singh commented 3 years ago

Using @praveenjuge's workaround for now, but definitely stoked to see this come to Hugo.

I am also using this workaround. Let's hope to see the integration in Hugo soon.

mikewatkinsca commented 2 years ago

Noting that Tailwind 3.0 alpha has landed and JIT mode has replaced the classic engine; given few breaking changes (other than this one) expect Tailwind 3 will hit release within a couple of months. FYI

Tailwind 3.0.0 alpha 1 release

brycewray commented 2 years ago

Noting that Tailwind 3.0 alpha has landed and JIT mode has replaced the classic engine; given few breaking changes (other than this one) expect Tailwind 3 will hit release within a couple of months. FYI

Tailwind 3.0.0 alpha 1 release

Now released: https://tailwindcss.com/blog/tailwindcss-v3

fywk commented 2 years ago

Now released: https://tailwindcss.com/blog/tailwindcss-v3

Hi @brycewray, I've followed your guide on your blog to get JIT working. I couldn't get my site to build after upgrading to v3. I've altered npm scripts to get local server with watch mode working but no luck on building for production yet.

brycewray commented 2 years ago

Now released: https://tailwindcss.com/blog/tailwindcss-v3

Hi @brycewray, I've followed your guide on your blog to get JIT working. I couldn't get my site to build after upgrading to v3. I've altered npm scripts to get local server with watch mode working but no luck on building for production yet.

So that we don't go off-topic for this issue, please contact me with the "Reply via email" link on that specific page you mentioned, and let me know if your project is a public repo I can review. Thanks.

praveenjuge commented 2 years ago

Adding a note about the new standalone cli which has tailwindcss 3 as a self-contained executable. Maybe this will help here. https://tailwindcss.com/blog/standalone-cli

y1zhou commented 2 years ago

TailwindCSS v3.0.10 now allows piping data into the CLI mentioned by @praveenjuge above. Might be helpful!

nnooney commented 2 years ago

Hugo doesn't use the Tailwind CLI; instead, it expects to invoke Tailwind via the PostCSS module with a command roughly similar to cat tailwind.css | npx --no-install postcss --config ./postcss.config.js.

The error about stdin (mentioned in forums several times) occurs because of an interaction between PostCSS and the Tailwind module, not because of Hugo. I reported this issue in https://github.com/tailwindlabs/tailwindcss/issues/6894 which has been fixed in Tailwind v3.0.11.

The fix in the above issue involves ignoring stdin as a file name. I'm not sure what effect that has on the ability for Hugo to take advantage of the JIT compiler in Tailwind; but I can confirm that hugo server builds a static site with Tailwind v3.0.11.

praveenjuge commented 2 years ago

Yes after TailwindCSS v3.0.11, Hugo compiles the Tailwind PostCSS module successfully. But it doesn't watch for file changes yet. Not sure how to solve that.

icy-comet commented 2 years ago

Yes, indeed! As noticed by @praveenjuge, Hugo (v0.89.4+) does work with Tailwind JIT (v3.0.12) through postcss-cli (Hugo Pipes).

Gioni06 commented 2 years ago

Hi might have found a reasonably nice way to work with TailwindCSS 3.x using only Hugo Pipes. Works for me, but I'd love to validate the idea with a broader audience. I wrote a short article about it.

https://dev.to/jonas_duri/how-to-use-tailwindcss-30-without-external-npm-scripts-just-hugo-pipes-2lg9

Basically, I'm using resources.ExecuteAsTemplate and a random string to make Hugo re-compile tailwind on every save during development.

I'd love to hear your feedback

brycewray commented 2 years ago

@Gioni06 Nice! Looking forward to giving this a try.

Gioni06 commented 2 years ago

@Gioni06 Nice! Looking forward to giving this a try.

Thanks! This hack is so simple, I'm still thinking that it's somehow flawed, but I can't find anything :)

brycewray commented 2 years ago

@Gioni06 Do you have a public repo using this fix? Would be nice to see the whole thing, if possible.

nnooney commented 2 years ago

I think I understand how this works. By adding a source of randomness to the output filename of the template, Hugo will always regenerate the content in server mode. The uniqueness doesn't need to come from a hash function, we just want a different output filename for resources.ExecuteAsTemplate each time the server triggers a rebuild of the content.

With the following diff, I'm able to confirm that Hugo does pass the sources to TailwindCSS each time a file changes.

 {{ $styles := resources.Get "css/tw.css" }}
 {{ $styles = $styles | resources.PostCSS }}
 {{ if  hugo.IsProduction  }}
 {{ $styles = $styles | minify | fingerprint | resources.PostProcess }}
+{{ else if .Site.IsServer }}
+{{ $styles = $styles | resources.ExecuteAsTemplate (printf "tailwind.dev.%v.css" now.UnixMilli) .}}
 {{ end }}
 <link href="{{ $styles.RelPermalink }}" rel="stylesheet" />

I'm using a timestamp rather than a random source to generate a unique filename. I can confirm that the styles are rebuilt every time a change is detected by the server (even if I don't change any styles). Nice find @Gioni06!

Edit 2022-01-21: as mentioned in the responses below, this solution requires nodejs v14 or later and Hugo v0.88.0 or later.

Gioni06 commented 2 years ago

@nnooney Thanks! I'm glad that you can confirm the idea :)

mukhtharcm commented 2 years ago

Can anyone leave a sample repo for this?

nnooney commented 2 years ago

Take a look at bep/hugo-starter-tailwind-basic for setting TailwindCSS up. While that repo uses Tailwind 2.0, you can add the modification above to layouts/_default/baseof.html to get it working with Tailwind 3.0.

istr commented 2 years ago

Confirmed that @nnooney 's proposal works, given you have nodejs v14 or later, see https://github.com/tailwindlabs/tailwindcss/issues/6894#issuecomment-1017929657

inwardmovement commented 2 years ago

@nnooney

{{ $styles = $styles | resources.ExecuteAsTemplate (printf "tailwind.dev.%v.css" now.UnixMilli) .}}

Any idea why I have this error?

execute of template failed: template: partials/styles.html:6:81:
executing "partials/styles.html" at <now>:
can't evaluate field UnixMilli in type time.Time

https://github.com/inwardmovement/inwardmovement.github.io/blob/tailwind/layouts/partials/styles.html#L7

earthboundkid commented 2 years ago

Your version of Hugo is too old. t.UnixMilli() was added in Go 1.17, and your version of Hugo is from before then.

jmooring commented 2 years ago

Your version of Hugo is too old

You need v0.88.0 or later

inwardmovement commented 2 years ago

Oh crap, thanks

istr commented 2 years ago

When using either @Gioni06 's or @nnooney 's rebuild trigger, I would recommend that you put this into a separate partial that is included using partialCached. Otherwise the build times grow for larger sites due to excessive rebuilds. With a separate partial this could be reduced to one rebuild per cycle (or per cycle and language).

For example in head.html

{{ partialCached "css.html" . }}

and in css.html

  {{- $styles := resources.Get "css/style.css" }}
  {{- $styles = $styles | resources.PostCSS }}
  {{- if .Site.IsServer }}
  {{- $styles = $styles | resources.ExecuteAsTemplate (printf "styles.dev.%v.css" now.UnixMilli) .}}
  {{- end }}
  {{- $styles = $styles | minify | fingerprint | resources.PostProcess }}
  {{- $cdnBase := urls.Parse (index (where .Sites "LanguageCode" "cdn") 0).BaseURL }}
  {{- $stylesURL := printf "//%s%s" $cdnBase.Host $styles.RelPermalink }}
  <link rel="preload" href="{{ $stylesURL }}" integrity="{{ $styles.Data.Integrity }}" as="style" crossorigin>
  <link rel="stylesheet" href="{{ $stylesURL }}" integrity="{{ $styles.Data.Integrity }}" crossorigin>

(Note that I always want fingerprinting / minification in dev mode (ymmv); and that I "abuse" a pseudo-language to render styles to a different CDN host in that example)

mukhtharcm commented 2 years ago

Do anybody know why I'm getting two files as output, one named stdin and the other the CSS file with random string as above mentioned.

But both files content is same. It's the content of my styles.css file

@tailwind base;
@tailwind components;
@tailwind utilities;

Screenshot from dev tools below

Screenshot_20220123-154223827 Screenshot_20220123-154216337

earthboundkid commented 2 years ago

@mukhtharcm ask in the support section of the Hugo Discourse forum. This is a place for tracking progress on implementing fixes, not supporting individual users.

Gioni06 commented 2 years ago

@Gioni06 Do you have a public repo using this fix? Would be nice to see the whole thing, if possible.

Yes, I just prototyped something together. Note that this already includes the proposed changes from @nnooney

https://github.com/Gioni06/hugo-pipes-tailwind-3

bojanvidanovic commented 2 years ago

@bep any updates on official support?

jacobian commented 2 years ago

@bojanvidanovic nagging the maintainer for updates can get really frustrating. If he had updates, he'd post 'em in this thread. I know patience is hard, but remember that the person on the other end here is a volunteer doing this in his free time, and please try to be respectful of that.

bojanvidanovic commented 2 years ago

You are totally right, and I'm really sorry for this comment if it sounded disrespectful. Nagging the maintainer really wasn't my intention. I understand the level of work that goes into maintaining a library of this size, so putting the pressure never helps.

imomaliev commented 2 years ago

UPDATE: this does not work reliably. For now use original hack. More in https://github.com/gohugoio/hugo/issues/8343#issuecomment-1040031194

Big thanks to @Gioni06, @nnooney and @istr for helping with creating this hack. I think I can take it one step further by using hugo_stats.json as hash in css filename.

I've written short article about this with my full thought process. Here is TLDR

First, we need to enable writeStats in our configuration.

diff --git a/path/to/config.toml b/path/to/config.toml
--- a/path/to/config.toml
+++ b/path/to/config.toml
 languageCode = "en-us"
 title = "Blog"

+[build]
+# Used for hashing styles in development to work with TailwindCSS's jit
+writeStats = true
+

After that, we need to update our head.html to read and hash hugo_stats.json

diff --git a/path/to/head.html b/path/to/head.html
--- a/path/to/head.html
+++ b/path/to/head.html
     {{- $css := resources.Get "css/main.css" | resources.PostCSS }}
     {{- if hugo.IsProduction }}
-        {{- $css = $css | resources.Minify | resources.Fingerprint | resources.PostProcess }}
-    {{- else if .Site.IsServer }}
-        {{- $css = $css | resources.ExecuteAsTemplate (printf "main.%v.css" now.UnixMilli) . }}
+        {{- $css = $css | minify | fingerprint | resources.PostProcess }}
+    {{- else if (and (.Site.IsServer) (fileExists "hugo_stats.json"))  }}
+        {{- $stats_hash := getJSON "hugo_stats.json" | printf "%v" | sha256 }}
+        {{- $css = $css | resources.ExecuteAsTemplate (printf "main.%v.css" $stats_hash) . }}

Here we're using getJSON and converting its contents to string via print and hashing this value with sha256

and don't forget using partialCache

diff --git a/path/to/baseof.html b/path/to/baseof.html
--- a/path/to/baseof.html
+++ b/path/to/baseof.html
 <!DOCTYPE html>
  <html lang="en">
-    {{- partial "head.html" . -}}
+    {{- partialCached "head.html" . -}}
imomaliev commented 2 years ago

UPDATE solution I suggested in https://github.com/gohugoio/hugo/issues/8343#issuecomment-1038989247 doesn't work consistently. I tried multiple things but it sometimes works and sometimes breaks, as @mukhtharcm mentioned in https://dev.to/mukhtharcm/comment/1m4do you may have to save 2 times for styles to regenerate properly. I will be converting to original hack in my project. My suspicion is that this due to when hugo_stats.json is generated during build.

mukhtharcm commented 2 years ago

@imomaliev but I think saving two times is better than waiting for full rebuilds when markdown files change :smile:

imomaliev commented 2 years ago

@mukhtharcm is there a problem with original hack https://github.com/gohugoio/hugo/issues/8343#issuecomment-1019383325 for your usecase?

mukhtharcm commented 2 years ago

Nothing much. I'm thankful to everyone on this thread for all the solutions. The problem was, even if I was editing markdown files, the CSS was being rebuilt and It was comparatively slow. like (14s vs 320ms)

imomaliev commented 2 years ago

The speed increase in my solution may be due to usage of partialCached instead of partial for head.html

mukhtharcm commented 2 years ago

No. It's not @imomaliev . I was already using partialCached . It is because as you told in the post, CSS only rebuilds when there is an actual change in hugo_stats.json. So It'll not completely rebuild when there is just a markdown file change.

danielsoto-dev commented 2 years ago

Hellow! I just started yo work with HUGO and tailwind, i wanted to know if is normal that on every change the solution generates a new .css even in dev mode, i was wondering id this behavior is necessary, because it doesnt make sense to me to have that many files

CleanShot 2022-05-05 at 09 19 44@2x

This is the code that im using: {{ $styles := resources.Get "css/main.css" }} {{ $styles = $styles | resources.PostCSS }} {{ if hugo.IsProduction }} {{ $styles = $styles | minify | fingerprint | resources.PostProcess }} {{ else }} {{ $styles = $styles | resources.ExecuteAsTemplate (printf "css/index.dev.%v.css" now.UnixMilli) .}} {{ end }} <link href="{{ $styles.RelPermalink }}" rel="stylesheet" />

An this is the layout, i thought maybe i did something wrong but after installing the sample project https://github.com/Gioni06/hugo-pipes-tailwind-3, it seems that the problem also occurs.

kenmorse commented 2 years ago

It seems like Hugo Pipes and particularly PostCSS are so close to making this all work fairly smoothly in development (server) mode. @cryptic-code states:

However, Hugo doesn’t trigger a CSS rebuild on a template change and simply rebuilds only the changed template/file (which is the desired behaviour with most tools). But since Tailwind v3+ pretty much requires a rebuild on every template change, this becomes a problem in development.

I tried disabling file caches 2, and the --disableFastRender flag, but that doesn’t do the trick.

If there was a Hugo option to force a post-processing operation like PostCSS to run with every file change (including template files, partials), would that resolve this issue?

earthboundkid commented 2 years ago

As another way to get it working in dev mode, would just using the CDN in browser JavaScript version work?

I guess the downside of that is you can't use third party plugins and you'd need to copy the Tailwind config, since it's at root level and not in an assets folder.

icy-comet commented 2 years ago

If there was a Hugo option to force a post-processing operation like PostCSS to run with every file change (including template files, partials), would that resolve this issue?

Introducing this as an opt-in feature would certainly solve the development problems for small to medium sites. @kenmorse

As for the argument regarding very large sites and/or the time it takes, it's not a Hugo-related or a Hugo-specific problem. It's the Tailwind's JIT mode that requires a re-scan and a re-build of CSS files whenever a class is added/removed/modified in the source files. Provided that one doesn't need a third-party plugin to add/modify Tailwind styles, @carlmjohnson already suggested Tailwind's Play CDN to be a better fit for those concerned. Hugo Pipes and the local JIT compiler can always be used for production builds (which already works).

kenmorse commented 2 years ago

As another way to get it working in dev mode, would just using the CDN in browser JavaScript version work?

@carlmjohnson – yes, that could be a great development alternative for some situations, given the caveats. Running hugo server and Tailwind in watch mode seems to work too. It seems the Hugo post operation pipe (PostCSS) should work, but it just needs something to tell it to always fire in some situations.

ivanduka commented 2 years ago

Something like resources.Get "css/index.css" | resources.PostCSS (dict "alwaysRebuild" true) would be awesome where "alwaysRebuild" true triggers rebuild on changing any watched file, not only to the CSS file.

ivanduka commented 2 years ago

Hellow! I just started yo work with HUGO and tailwind, i wanted to know if is normal that on every change the solution generates a new .css even in dev mode, i was wondering id this behavior is necessary, because it doesnt make sense to me to have that many files CleanShot 2022-05-05 at 09 19 44@2x This is the code that im using: `{{ $styles := resources.Get "css/main.css" }} {{ $styles = $styles | resources.PostCSS }} {{ if hugo.IsProduction }} {{ $styles = $styles | minify | fingerprint | resources.PostProcess }} {{ else }} {{ $styles = $styles | resources.ExecuteAsTemplate (printf "css/index.dev.%v.css" now.UnixMilli) .}} {{ end }}

` An this is the layout, i thought maybe i did something wrong but after installing the sample project https://github.com/Gioni06/hugo-pipes-tailwind-3, it seems that the problem also occurs.

It works as expected. With your code you 'trick' Hugo Pipes to create a new css file every time there is an update to any watched file.

ahaltindis commented 1 year ago

Probably not very Hugo way but I am using tailwindcss CLI which keeps me away from Node.js/npm. Even though it has more pieces to put together, seems a bit more straightforward.

I have this simple bash script which runs hugo server and tailwindcss watcher together:

#!/bin/bash

$(cd themes/{theme} && tailwindcss -i assets/css/input.css -o assets/css/output.css --watch) &

hugo server 

For production, hooking tailwindcli before hugo build command.

istr commented 1 year ago

@bep I think that this could now be closed, as it is implemented in 0.112.*?

The documentation explicitly states that

The build.cachebusters configuration option was added to support development using Tailwind 3.x’s JIT compiler

https://gohugo.io/getting-started/configuration/#configure-cache-busters

The reference implementation works like a charm: https://github.com/bep/hugo-starter-tailwind-basic

Or did I miss something that is still WIP?

earthboundkid commented 1 year ago

This issue should have been closed when Hugo 112 released.

bep commented 1 year ago

Closing, this is fixed (and actually works very well after a few tweaks).