The Self-Aware Sass Mixin

I did a talk the other night at Up Front Berlin about some use cases for features in Sass from the past year. The slides are here; but I wanted to publish some notes so this post is one part of those notes.

My talk focused on the map data-type, and specifically on combining maps with directives such as @content, @at-root, and unique-id() in interesting ways. One of these is a mixin design pattern that solves some problems with using @extendand placeholder (silent) selectors in Sass:

  1. having placeholder extensions appear somewhere way up in the top of your code, because that's where you imported them; and
  2. having to think about choosing @include vs. @extend in the first place.

This "self-aware" mixin works by creating (and extending) a placeholder selector for itself dynamically, the first time it is included, and uses a map to keep records of these includes, so when it is subsequently included with the same arguments it can extend the corresponding placeholder rather than repeating code.

In the following code, some of the mixin's arguments are used for this self-check, while others are considered to be optional and specific to each inclusion. In the resulting CSS you can see that 'common' styles have been extended (selectors combined) while specific styles have been output the normal way (selectors stand alone).

SCSS source and tests:

// THE SELF-AWARE MIXIN

// define global map to store info about this mixin
$my-mixin-info: ();

// define mixin with any format of arguments
@mixin my-mixin($pos1, $pos2, $map: (), $rest...) {

  // capture some or all of the arguments,
  // according to whatever repetitions you want to avoid
  $my-args: ($pos1, $pos2, $map);

  // look up these arguments as a key in the global map
  $id: map-get($my-mixin-info, $my-args);

  // if they return an 'ID'...
  @if $id { 

    // extend that ID
    @extend %#{$id}; 

    // output any specific stuff
    specific: inspect($rest);
  }

  // otherwise...
  @else {

    // create the ID
    $id: unique-id();

    // merge the ID in to the mixin's map against the ARGS
    $my-mixin-info: map-merge($my-mixin-info, ($my-args: $id)) !global;

    // create a placeholder named after the ID at root
    @at-root {
      %#{$id} {

        // output the stuff you want to not repeat
        common: inspect($my-args);
      }    
    }

    // extend that placeholder
    @extend %#{$id};

    // output any specific stuff
    specific: inspect($rest);
  }
}

.test {
  @include my-mixin(1, 2, (), 4, 5);
}

.test2 {
  @include my-mixin(1, 2);
}

.test3 {
  @include my-mixin(1, 2, (), 6, 7);
}

CSS output:

.test {
  specific: 4, 5;
}
.test, .test2, .test3 {
  common: 1, 2, ();
}

.test2 {
  specific: ();
}

.test3 {
  specific: 6, 7;
}