Hugo Module + Dart Sass: @use works when used directly, but fails when routed through another SCSS file

I’m seeing a strange behavior with Hugo Modules and Dart Sass and would like to understand whether this is expected behavior, a Hugo limitation, or a bug.

This issue was discovered while working on the following my theme’s PR: watch HikaeMe’s feat/side-nav-for-small-display branch. Since new users like me can only include up to two URLs in a post, I’m writing them in plain text.

Environment

  • Hugo Modules
  • Dart Sass
  • Bootstrap SCSS
  • Theme consumed as a Hugo Module

Problem

I have a SCSS entrypoint (styles.scss) that imports Bootstrap through an intermediate SCSS file.

Failing case

styles.scss

@use "vendor/bootstrap-min";

vendor/bootstrap-min.scss

@use "bootstrap/scss/bootstrap";

When the theme is consumed as a Hugo Module from GitHub, Hugo fails with:

TOCSS-DART: failed to transform ...
Can't find stylesheet to import.

The error indicates that Dart Sass cannot resolve:

@use "bootstrap/scss/bootstrap";

from within vendor/bootstrap-min.scss.

Working case

If I remove the intermediate file and import Bootstrap directly:

styles.scss

@use "bootstrap/scss/bootstrap";

Additional observations

  • The failing case works when the theme is loaded using: theme: HikaeMe in a yaml file
  • The failure only occurs when the theme is consumed as a Git-based Hugo Module.
  • bootstrap-min.scss contains nothing except:
@use "bootstrap/scss/bootstrap";

so the only difference is whether Bootstrap is imported directly from the entrypoint or through another SCSS file.

Minimal reproduction

You can try two examples with devcontainer.

Failing example:

Working example:

Question

Is this expected behavior of Dart Sass module resolution when used through Hugo Modules?

More specifically:

  1. Should @use "bootstrap/scss/bootstrap" resolve differently when it is referenced from an intermediate SCSS file versus directly from the entrypoint?
  2. Does Hugo Module mounting affect Sass load paths in a way that would explain this behavior?
  3. Is there a recommended pattern for wrapping third-party SCSS imports inside theme modules?

I think this is an issue you’ll almost certainly encounter in Bootstrap v6 which is not published yet, where @import has been deprecated and @use is now the primary method.

Any insight would be appreciated. Thanks!

This issue was discovered while working on the following PR:

I have to admit I gave up quite quickly because I somehow can’t find any layout files in the sasds-failure repo, but here is my initial thought of what might help or what might be going on:

  1. use "that config option where you tell sass to look in a specific folder for sass files when using use or import links and let it point to node_modules. I used this in one of my projects with node_modules/path/to/bootstrap/src and manually renamed all bootstrap links so that all the bootstrap/src/ paths were removed. This helped, but needed some commenting for the forgetting mind
  2. if the imported/used file is in a subdirectory, maybe the path is not right anymore? I remember giving up on Bootstrap and “new SASS” because they are still fully committed to the obsolete @import schtick. They will eventually release a new version and I hope it will all solve itself then.

What works for me with the latest Bootstrap version (non-public for a customer, but I’ll copy all needed particles here):

The partial where the CSS is created. Note the “includePaths” part.

{{ $sassTemplate := resources.Get "scss/theme.scss" }}
{{ $options := (dict
			"targetPath" "style.css"
			"outputStyle" "compressed"
			"enableSourceMap" true
			"includePaths" (slice "node_modules")
) }}
{{ $style := $sassTemplate | resources.ExecuteAsTemplate "css/theme.scss" . | css.Sass $options | resources.Minify }}
{{ $secureCSS := $style | resources.Fingerprint "sha512" }}

<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Nunito+Sans:ital,opsz,wght@0,6..12,200..1000;1,6..12,200..1000&display=swap" rel="stylesheet">

{{ if hugo.IsServer }}
  <link rel="stylesheet" href="/style.css">
{{ else }}
  <link rel="stylesheet" href="{{ $secureCSS.RelPermalink }}" integrity="{{ $secureCSS.Data.Integrity }}">
{{ end }}

The SASS theme file (under assets/scss/theme.scss) (note the @import and NOT @use):


// setup
@import "bootstrap/scss/functions";
@import "variables";
@import "bootstrap/scss/variables";
@import "bootstrap/scss/maps";
// ./bootstrap
... and so on.

in my modules configuration (config/_default/modules.toml) - (not sure if that is relevant, because it points to vendor and the sass generation uses node_modules directly… probably not):

[[mounts]]
source = "node_modules"
target = "assets/vendor"

[[mounts]]
source = "assets"
target = "assets"

This works with the latest Hugo and latest Bootstrap.

I understand that there might be ways to do it all via @use and with the latest SASS gimmicks, but as long as Bootstrap itself is not using the latest syntax you are preparing the next issue for when they finally do it. Get it working the old way for now and wait for Boostrap 6 or whatever the next larger version number is.

There is a issue open in the Bootstrap repo somewhere where someone with too much time on their hands lays out a solution to get the current Bootstrap working with the latest SASS features, but I stopped spending time on these things :wink:

I suspect you’re running into this:

https://github.com/golang/go/issues/71785

Rename your vendor directory to something like libs, exts, third-party, etc.