Back to blog

Less Sass Please

Topics: css·precompilers

Share on

When I was planning the architecture for a new Angular app where I work I was faced with a dilemma...

The app was supposed to be extremely customizable. With a core codebase that should allow us to apply a theme layer that not only changes the brand colors but also can impact spacing, fonts, settings, images or even how the business logic may behave. This theme layer is to be placed in a different repository and loaded dynamically with our CI/CD flow.

Dynamic imports

I've always used Sass on several previous projects, so this I'd definitely stick with it again. But the first hurdle encountered was when I realized that Sass doesn't support dynamic imports. My idea was to have some sort of importing logic like this:

$client_path: "../../src/themes/awesome_client_1";

@import "#{$client_path}/styles";
// or
@use "#{$client_path}/styles";

Attempt to import things dynamically in SCSS.

Well... Sass doesn't support this. And during my research, I stumbled upon this heated discussion, which started in 2013, that pretty much stated that this would never be achieved in Sass.

⚔️ Fight!

Some people suggested using some sort of string substitution to achieve this, but I was avoiding these sorts of hacks until I had absolutely no other option.

And that's when I found the comment by user jslegers on that thread saying that Less actually supported this much-needed feature. So... off to check Less out.

@client_path: "../../src/themes/awesome_client_1";

@import "@{client_path}/styles";

Importing things dynamically in LESS.

By the way... did you know that even though Sass is perceived by most as being more popular, it actually has fewer stars than Less on Github? 🤯

After a few tests, I confirmed that the dynamic imports of Less worked perfectly for me, and I even learned some very interesting things about the differences between Sass and Less, that'll show you in the next sections.

What about optional imports?

Come on Sass! I'd really hope to be able to import things that I don't know yet if they'll exist.

That's how we can achieve this in Less:

// In generic codebase - Header component:
@import (optional) "@{client_path}/overrides/header.less";

Won't halt compilation if the file does not exist.

This proved important in my Angular application. Since the styles in Angular components are scoped to each component, I needed to add an import to a file that may not exist (thus, should not break the compilation) if the client design doesn't require style overrides.

At this point, I was already convinced I'd make the move to Less for this new project.

Declarative vs Imperative nature and its effects on Variable Overrides

The following article was written by one of the maintainers of the Less repositories, and he goes into a lot of detail into how both pre-processors differ. Definitely a must-read even if you're happy with your current choice.

Less: The World’s Most Misunderstood CSS Pre-processor

The article mentions that due to the declarative vs imperative nature of both languages the variable declaration order matters in Sass but not in Less.

At first, I thought this wasn't a very big deal until (on another more mature project) I tried to override some variables from a theme file and it didn't work.

"Why isn't this variable value changing?" I asked myself...

I had forgotten to add the !default declaration to the main variable and now I couldn't override it because of the order of imports.

In another situation, I was trying to use a variable not yet declared to override a different variable.

So you can already see that everything is more complicated and prone to errors than it should be.

Luckily Less ( 😍 ) doesn't have this problem because it evaluates variables declaratively. For this same reason, Less doesn't need default variable declarations either.

I actually had a whole set of examples explaining the problem and possible solutions while still using Sass, but it got very complicated and I decided to keep it out.

The bad part

Although Less lacks some features (that I honestly don't even miss that much) present in other frameworks, the worst part for me was adapting to the new syntax.

After a while, I ended up getting used and even enjoying it, although Prettier does not format its mixins correctly, and this is still driving me nuts!

.mq(
  desktop-only,
  {@carousel-arrows-spacing: @space-xs ; background-color: red; font-size:
    111px;},
  other_param
);

Media query mixin 👺: Not so much "prettier", is it?

Potential Solutions for these Sass traps

Not all is lost, though. There are some things you can do to mitigate some of these issues with Sass.

Using symlinks to fix dynamic imports

If on your generic code you point everything to a simple entry point (e.g. theme) you can create a symlink to the client theme repository and update this symlink when changing projects.

@import "theme/styles";

Theme is a symlink pointing to a client theme repo.

Hack your way into optional imports

The only way to fix this seems to be this ugly hack suggested here:

Optional Sass Imports

Variable overrides

Be careful of your order of imports, don't forget your !default declarations, and eat your vegetables kids!

Today's lesson: Use what suits your project better

I learned an important lesson during this search: Use the better tool for the job, not the shiniest!

Don't dismiss any technology (even older ones) just because you're used to something else. By researching a bit more about the problem you're trying to solve you might end up finding a tool that'll make your life easier and learn something new as a bonus.

Share on

Do you agree with me? Let's continue this discussion on the comment section below:

Sign in with your github account to be able to comment.