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 -yNow you’re set to proceed. Install the packages you’ll need for the code to follow:
npm i -D npm-run-all rimraf sassThen, 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-allto run multiple scripts with one command. devsassis for development mode, and uses Sass to generate theassets/css/index.cssfile1 for Hugo Pipes to “see.”2prodsassis likedevsass, except for production, and thus we give itdevsass’s functionality plus minifying the generated CSS.3- And, just for good measure,
cleandeletes4 anypublicfolder 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.cssto your project’s top-level.gitignorefile, since there’s no need to track this temporarily generated file; in thebuildscript, theprodsasspart 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.cssreference tothemes/mytheme/assets/css/index.css. ↩︎As you can see in the
prod:hugoscript, 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 separatedevsassandprodsassscripts. However, with external CSS, you do need both of those scripts. ↩︎cleanuses therimrafpackage, a cross-platform version of therm -rfdeletion command from *n*x-like OSs such as Linux and macOS. Usingrimrafrather thanrm -rfprovides 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
hugoorhugo --gc --minify, the build on the host will fail because Hugo won’t get that generated CSS for processing in thescss.htmlpartial template described in this post. ↩︎
Latest commit (be74421b4) for page file:
2023-10-06 at 10:19:18 AM CDT.
Page history