Update, 2022-03-09: Things changed dramatically the day after I originally wrote this, so there’s a sequel you’ll definitely want to read after this one.
General note: This site’s appearance, configuration, hosting, and other basic considerations will change over time. As a result, certain content on this page could be at variance with what you’re currently seeing on the site, but the two were consistent when this post originally appeared.
As promised at the end of my previous post (please read that first if you haven’t already, and then come back here), I used what I learned in that exercise to try an experiment which worked — and it constitutes excellent news for Hugo users who prefer to style their sites with Sass.
First, as almost always seems necessary, I’ll provide some back story.
The LibSass problem
In October, 2020, the Sass project deprecated the LibSass implementation on which Hugo Pipes depends to provide Sass support. Two key points in the deprecation announcement were:
- Going forward, LibSass will receive no additional feature updates, but rather only fixes for major bugs and security issues. As a result, since LibSass has received no feature updates since November, 2018, LibSass users — and LibSass-dependent apps like Hugo — are now nearly three-and-a-half years behind the curve where Sass features are concerned.
- All LibSass users should switch to Dart Sass.
On many if not most other static site generators (SSGs), moving to Dart Sass is a fairly simple matter (other than whatever Sass code changes it might require, of course): one needs only to use the standard Sass package. However, since LibSass is baked into Hugo, the only answer appears to be in the form of Embedded Dart Sass, present in one’s path
. And, while that’s doable on a website developer’s personal device, getting it into a hosting vendor’s build process is another matter, one which remains unsolved at this writing.
A stab in the dark
While running through the Tailwind-fix part of the previous article, I got to thinking: why not just use that standard Sass package with Hugo? I’d seen no other articles or forum comments about doing it that way. In either case, Tailwind or standard Sass, you’re using an npm package so, if it’s good enough for the proverbial goose . . .
I gave it a shot, and am delighted to tell you that it works!
Be advised that, unlike the Tailwind-3-on-Hugo workaround, this one for Dart-Sass-on-Hugo needs package.json
scripting. While some might find that a bother, I prefer to put it in the category of “Let’s avoid criticizing the talking dog’s grammar, but rather just be glad he can frickin’ talk in the first place.” And, on the good side: this method, unlike the Tailwind-3-on-Hugo solution, doesn’t require creating a specially fingerprinted CSS file just so Hugo will refresh the development server when files change, since the package.json
script has both Sass and Hugo constantly refreshing as needed.
So, let’s get to the nerdy goodness, shall we?
Setting up Dart Sass in your Hugo project
Node, npm
, and package.json
First of all, if you don’t even use node modules in any of your projects (Hugo or otherwise), you may need to install npm
. From here on, I’ll assume you’ve already done so.
If your Sass-using Hugo project has no package.json
file as yet, go into the project and run this npm
command to create that file:
npm init -y
Now you’re set to proceed. Install the packages you’ll need for the code to follow:
npm i -D npm-run-all rimraf sass
Then, open package.json
and make its scripts
object look as follows:
"scripts": {
"clean": "rimraf public",
"devsass": "sass --no-source-map assets/scss/index.scss assets/css/index.css",
"prodsass": "sass --no-source-map assets/scss/index.scss assets/css/index.css --style=compressed",
"start": "NODE_ENV=development npm-run-all clean devsass --parallel dev:*",
"build": "NODE_ENV=production npm-run-all clean prodsass prod:hugo",
"dev:sass": "npm run devsass -- --watch",
"dev:hugo": "hugo server",
"prod:hugo": "hugo --gc --minify"
},
What do all those scripts do? While the following explanation doesn’t cover the scripts in order, its sequence may make it easier to understand their interaction:
- The scripts near the bottom that start with either
dev:
orprod:
make Sass and Hugo, respectively, do their usual thing in the appropriate mode, whether development or production. They’re called by . . . - . . .
start
(for development) andbuild
(for production), with each usingnpm-run-all
to run multiple scripts with one command. devsass
is for development mode, and uses Sass to generate theassets/css/index.css
file1 for Hugo Pipes to “see.”2prodsass
is likedevsass
, except for production, and thus we give itdevsass
’s functionality plus minifying the generated CSS.3- And, just for good measure,
clean
deletes4 anypublic
folder that a previous Hugo build might have left behind. This obviously is irrelevant for production — although definitely quite relevant for development — but I always include it to avoid occasional flashes of weirdness. It doesn’t hurt anything and, besides, Ya Nevah Know.
To run this in development, type npm run start
in your terminal. For the build command at your host, set it to npm run build
.5 But, before you do either, there’s one more thing to do, and that’s giving Hugo the necessary templating for all of this to work.
The scss.html
partial
As in the two posts about Tailwind-3-on-Hugo, you’ll want to create a separate partial template (“partial”) — we’ll name it scss.html
— and call it with the partialCached
function. This will make both development and production far less taxing on your system and the host’s. The scss.html
partial will contain the SCSS/CSS-handling you’d otherwise do elsewhere (perhaps the site-wide baseof.html
or a head.html
partial), where you’ll replace that code with:
{{ partialCached "scss.html" . }}
Almost done! Now we finish up by offering two different versions of the scss.html
partial: one for external CSS, and one for internal CSS. You simply use whichever version best fits the way you like to style your site.
First, the version for external CSS:
scss.html
{{ $styles := resources.Get "css/index.css" }}
{{ if hugo.IsProduction }}
{{ $styles = $styles | resources.Minify | fingerprint }}
{{ end }}
<link href="{{ $styles.RelPermalink }}" rel="stylesheet" />
Then, the version for internal CSS:
scss.html
{{ $styles := resources.Get "css/index.css" }}
{{ if hugo.IsProduction }}
{{ with $styles }}
<style>{{- .Content | safeCSS -}}</style>
{{ end }}
{{ else }}
<link href="{{ $styles.RelPermalink }}" rel="stylesheet" />
{{ end }}
Note: In case it would help, I’ve also put up a minimal demo repo and site based on this code.
The fight for mindshare
These workarounds for Tailwind-JIT-on-Hugo and Dart-Sass-on-Hugo may seem awfully kludgy, especially to those Hugo users who typically eschew any dealings with extra software dependencies. Still, these methods (or, one would hope, better ones yet to come from brighter folks than I) could play a significant role in Hugo’s prospects for at least the near future, if not beyond.
To have its best chance of attracting both new and seasoned developers in this competitive market, Hugo should be compatible with tools to which devs are already committed or, at least, attracted. Perhaps solutions along the lines of those described in this series of posts can help Hugo compete more effectively for web dev mindshare, especially against trendier, JavaScript-based SSGs which seamlessly use both Tailwind-with-JIT and Dart Sass.
Incidentally, you may want to add
./assets/css/index.css
to your project’s top-level.gitignore
file, since there’s no need to track this temporarily generated file; in thebuild
script, theprodsass
part generates it at the host on each build. ↩︎This file location within the scripts assumes you’re not using a theme. If you are, adjust this accordingly. For example, with a theme named
mytheme
, you’d change eachassets/css/index.css
reference tothemes/mytheme/assets/css/index.css
. ↩︎As you can see in the
prod:hugo
script, we’re already minifying the generated HTML in production. Thus, if you’re using internal CSS, you can get by with just onedevsass
-like script, rather than separatedevsass
andprodsass
scripts. However, with external CSS, you do need both of those scripts. ↩︎clean
uses therimraf
package, a cross-platform version of therm -rf
deletion command from *n*x-like OSs such as Linux and macOS. Usingrimraf
rather thanrm -rf
provides the same action for users of all platforms, even Windows. ↩︎If you neglect this and leave the Hugo repo’s build command as, say, the more standard choices of
hugo
orhugo --gc --minify
, the build on the host will fail because Hugo won’t get that generated CSS for processing in thescss.html
partial template described in this post. ↩︎
Latest commit (08cb79e2
) for page file:
2023-04-17 at 6:18:22 PM CDT.
Page history