HTML & CSS - - By Hugo Giraudel

How to Use Warnings and Errors in Sass Effectively

The following is a short extract from our book, Jump Start Sass, written by Hugo Giraudel and Miriam Suzanne. It’s the ultimate beginner’s guide to Sass. SitePoint Premium members get access with their membership, or you can buy a copy in stores worldwide.

Our incredible journey through Sass is slowly coming to an end, and so far you’ve been doing great! There’s one technical chapter left before we look at project architecture, and then you’ll be fully equipped to write Sass code in your own projects.

Now we’re going to look at warnings and errors. Both form a one-way communication system between the program (in this case, Sass) and the developer (you). If you’re wondering about the point of errors in the CSS world, remember that you already know the answer. Whenever you forget a semicolon or use a function incorrectly, Sass throws an error at you, explaining what you’ve done wrong and how you can fix it, thankfully! It would be a real pain to have to dig into the code to figure out what’s gone wrong.

Sass has long provided a way to emit warnings from stylesheets, but it’s only recently added support to throw errors as well—and for good reason! Over the last few years, Sass has allowed authors to build complex systems to abstract difficult or repetitive patterns and concepts, such as grids. These systems must be able to communicate with authors, stopping the compilation process with a custom error message if anything ever goes wrong.

Both warnings and errors are emitted in the current output channel. When compiling Sass by hand or by using a tool through a command line interface (CLI) such as Grunt or Gulp, the output stream is the console. For tools that include a user interface, such as Codekit or Prepros, it’s likely that they catch and display warnings and errors as part of their interface. Online playgrounds such as CodePen and SassMeister manage to catch errors but not warnings, so don’t be alarmed if you’re unable to test them in there.

Warnings

As has been stated, the ability to emit warnings in Sass is not new. It’s possible to display messages or the value of any SassScript expression to the standard output stream through the @warn directive.

A warning has no impact on the compilationprocess; it does not prevent compiling to pursue or change it in any way. Its only purpose is to display a message in the console.

There are a lot of reasons to use warnings in Sass. Here are a couple, but you’re likely to find your own:

  • informing the user of an assumption made about the code in order to avoid surprise and hard-to-track bugs
  • advising about a deprecated function or mixin as part of a library or framework

Sending a warning is dead simple to do: start with the @warn directive, then state whatever it is. Warnings are usually made to provide some information and context, so they often feature a sentence explaining the situation. That being said, you don’t have to use a string; you can warn with a number, a list, a map—whatever. Here, we print a string:

@warn 'Uh-oh, something looks weird.';

Using a regular CLI client, this warning will emit the following output:

WARNING: Uh-oh, something looks weird.
         on line 1 of /Users/hgiraudel/jump-start-sass/warning.scss

Hey, that’s nice, isn’t it? Although this warning is far from helpful. It says that something looks weird but does not say what, why, or what can be done to stop it from looking weird. We’ll discuss how we can improve on warnings further on.

Let’s move on to a more serious example now that we know how to use he feature. Imagine we have a Sass custom function that attempts to convert a pixel value in em unit:

@function px-to-em($value, $base-font-size: 16px) {
  @return ($value / $base-font-size) * 1em;
}

// Usage
.foo {
  font-size: px-to-em(42px); // 2.625em
}

All good. Now, what happens when passing a unitless number—such as 42—to the function? Maybe you’ve guessed it, but as it’s not quite obvious I’ll give you the answer:

2.625em/px isn’t a valid CSS value.

This happens because you’re trying to perform a calculation between incompatible units (px and em). What we could do to circumvent this issue is assume the unitless value be expressed in pixels and convert it first:

@function px-to-em($value, $base-font-size: 16px) {
  @if unitless($value) {
    @warn 'Assuming value `#{$value}` to be in pixels; attempting to convert it.';
    $value: $value * 1px;

  }

  @return ($value / $base-font-size) * 1em;
}

The function is expecting a value expressed in pixels. We can still make it work with a unitless value; however, we cannot be sure that this is the expected behavior. We can only assume that it’s good enough.

Because we’re assuming what is the correct behavior for our function, it’s important to let the developer know what we’re doing and why. Otherwise it could lead to bugs that are hard to track, which is not
what you should be aiming for.

Another practical example would be to warn against the usage of a deprecated function or mixin. You might have already heard of or used Bourbon, a lightweight mixin library for Sass. Bourbon is actively maintained, and sometimes requires removing helpers from the library. To avoid suddenly breaking a person’s ode, Bourbon warns about future deprecations way before it actually removes mixins:

@mixin inline-block {
  display: inline-block;

  @warn 'The `inline-block` mixin is deprecated and will be removed in the next major version release.';
}

Clever! People who still use the inline-block mixin from Bourbon are aware that the library will remove it completely in the next version, so they know to start updating their codebase to remove the mixin.

The Difference between @warn and @debug

You may or may not be familiar with the @debug directive, which prints the value of a SassScript expression to the standard output stream in the same fashion as @warn. You might be wondering why there are two features performing the same task, and what could possibly be the difference between the two.

Well, there are two major differences between warning about a value and debugging a value. The first one is that warnings can be turned off using the quiet option. Debugs, on the other hand, will always be printed so that you remember to remove them when you’re done using them.

The second difference is that warnings come with a stack trace—a report of the active stack framesat a certain point in time during the execution of a program. As aresult, you know from where they’re being emitted. Debugs only print thevalue, along with the line they were called in, but they offer no extrainformation.

The @debug directive can really come in handywhen you want to know what’s inside a variable, for instance:

@debug $base-font-size;

Watch AtoZ: Sass

Learn Sass letter by letter

Errors

Warnings and errors behave fairly similarly in Sass, so learningabout errors is going to be a breeze now that you are perfectly familiarwith warnings! The only difference between an error and a warning is—as you might have guessed—that the error stops the compilation process.

Using errors can be handy when validating parameters from mixins and functions, for instance. In the previous section, this worked even when the given argument was not exactly as expected, but we cannot (and should not) always do this. Most of the time, if arguments are invalid, it is better to throw an error so that the stylesheet author can fix the problem.

You can throw an error using the @error directive. As for warnings, you can pass anything to this directive—not necessarily a string, although it usually makes more sense to provide a clear context. The argument (what you give to the @error directive) will be printed in the standard output stream, as well as a stack trace to give more insight about the problem. The compilation process will stop immediately.

Let’s start with a Gandalf-approved error:

@error 'YOUUUUU! SHALL NOT. PASS.';

The output might depend on how you compile your stylesheets, as some tools catch and enhance the errors a certain way. Using the standard sass Ruby binary (gem), here’s how it looks:

Error: YOUUUUU! SHALL NOT. PASS.
        on line 1 of /Users/hgiraudel/jump-start-sass/error.scss
  Use --trace for backtrace.

With the trace option, you can have the full
stack trace from Sass itself, which isn’t that useful unless there’s an
actual bug somewhere in the preprocessor. Hence why it is hidden as a
default.

Time to take a look at a real practical example. Let’s start by writing a small function to help accessing deeplynested values in maps, map-deep-get(..):

@function map-deep-get($map, $keys...) {
  @each $key in $keys {
    $map: map-get($map, $key);

    @if (type-of($map) == 'null') {
      @return $map;
    }
  }

  @return $map;
}

Let’s enhance it with custom errors. But first, consider the following map and map-deep-get(..) call:

$map: (
  'foo': (
    'bar': (
      'baz': 42
    )
  )
);

$value: map-deep-get($map, 'foo', 'bar', 'baz', 'qux');

As you may have noticed, the map lacks having a qux key nested in baz. Indeed, baz is not even associated with a map; instead, it is mapped to a number (42). If we try to execute this code, it will yield:

Error: 42 is not a map for `map-get`
        on line 1 of /Users/hgiraudel/jump-start-sass/error.scss

Sass tries to perform a map-get(..) on 42 and emits an error because it cannot be done. While the error message is correct, it’s not very helpful. What would be helpful is to know the name of the key that caused the issue. We can do that!

We already check whether $map is null to perform an early return so as to avoid a compilation error if a key doesn’t exist. We can perform a second check to ensure that the map is actually a map, or we throw a meaningful error:

@function map-deep-get($map, $keys...) {
  @each $key in $keys {
    $map: map-get($map, $key);

    // If `$map` does not contain the next key, return `null`
    @if type-of($map) == 'null' {
      @return $map;
    }
    
    // If `$map` is not a map, throw an error
    @if type-of($map) != 'map' {
      @error 'Key `#{$key}` is not associated with a map but a #{type-of($map)} (`#{$map}`).';
    }
  }

  @return $map;
}

If we run our previous snippet again, here’s the output:

Error: Key `baz` is not associated with a map but a number (`42`).
        on line 1 of /Users/hgiraudel/jump-start-sass/error.scss

That’s much better! It’s now easy to fix our map and/or our function call thanks to the helpful error message.

Wrapping Things Up

In this chapter, we learned how we can use Sass to emit warnings and throw errors in the standard output stream. This is usually the console, but it might vary depending on the way one compiles stylesheets.

Warnings are helpful to emit non-critical messages to stylesheet authors—especially for framework and library authors—such as deprecation warnings or code assumptions. On the other hand, errors are used to prevent the compilation from pursuing, making it clear that the code needs to be fixed before going any further.

All in all, warnings and errors are especially useful inside functions and mixins in order to validate user input, ensuring the stylesheets are being compiled as expected.