functionsasssass-maps

Cutting down repeating code in Sass functions


I'm working on an HTML boilerplate that uses a lot of Sass to create a maintainable and easy to use code base. Part of that are a few functions:

// Returns the scale value found at $key.
@function get-type-scale($key) {
    $value: map-get($type-scale, $key);

    @if $value != null {
        @return $value;
    }

    @else {
        @warn "Unfortunately, `#{$key}` is not defined in the `$type-scale` map.";
    }
}

// Returns the line-height value found at $key.
@function get-line-height($key) {
    $value: map-get($line-heights, $key);

    @if $value != null {
        @return $value;
    }

    @else {
        @warn "Unfortunately, `#{$key}` is not defined in the `$line-heights` map.";
    }
}

// etc... I have 2 more functions like this where only the $map changes.

These functions then get called in a few mixins, like this:

// Sets font-size and line-height based on the $level.
@mixin set-type($level: 0) {
    font-size: get-type-scale($level);
    line-height: get-line-height($level);
}

While this works just fine, I don't like the fact that I'm repeating a lot of code in the functions. I've tried writing a generic function that receives a map name as a parameter, but I cannot use interpolation in map-get().

Is there a way to make the function code more elegant and as DRY as possible?

I appreciate any insights. Cheers!


Solution

  • I've tried writing a generic function that receives a map name as a parameter, but I cannot use interpolation in map-get().

    Unfortunately, it is not possible to create variable names from the names of other variables at all (only from its values). Furthermore, a variable only knows its value(s) and not its name which is another issue we are facing when it comes to define the variable warning messages.

    I come up with a little improvement that reduces the amount of duplicated code and keeps the ease of use of the high-level function calls. Criticism of having to pass a third variable to the generic function is justified, but I simply couldn't find a clean way to avoid it.

    $line-heights: (
      0: 1em,
      1: 2em
    );
    
    $type-scale: (
      0: 1em,
      1: 2em
    );
    
    // Returns the scale value found at $key.
    @function get-type-scale($key) {
        @return get-value-or-warn($type-scale, $key, 'type-scale');
    }
    
    // Returns the line-height value found at $key.
    @function get-line-height($key) {
        @return get-value-or-warn($line-heights, $key, 'line-heights');
    }
    
    @function get-value-or-warn($map, $key, $map-name) {
        $value: map-get($map, $key);
        
        @if $value != null {
            @return $value;
        }
    
        @else {
            @warn "Unfortunately, `#{$key}` is not defined in the `$#{$map-name}` map.";
        }
    }
    
    // Sets font-size and line-height based on the $level.
    @mixin set-type($level: 0) {
        font-size: get-type-scale($level);
        line-height: get-line-height($level);
    }