Placeholder vs Mixins, o como no repetirse

13/11/2014
Placeholder vs Mixins, o como no repetirse

Con el concepto de reutilización en mente (Don't Repeat Yourself y en un intento de facilitar y modularizar las hojas de estilo, Sass implementó una serie de mecanismos que nos permiten establecer una serie de 'funciones' dentro de nuestros estilos. 

Mixins

Estos Mixins, como se llaman, no dejan de ser métodos que introducen propiedades CSS allí dónde se les incluye, de forma que no es necesario repetir ese código para cada uno de los selectores a los que debemos aplicar ese estilo. Esto sin mencionar que podemos mantener nuestros estilos con caracter semántico evitando clases como ".pull-right".

=pull-right
  float: right
.firma
  +pull-right

Claro que si lo que necesitamos es algo diferente según el caso, podemos ampliar su uso utilizando parámetros:

=colorize($color:#888)
  font-size: 1.5em
  font-family: sans-serif
  color: $color
  text-decoration: none
  &:hover
    color: darken($color,10%)
.new-link
  +colorize(#AD141E)
.section-title
  +colorize()
  font-weight: bold

Esto compila en:

.new-link {
  font-size: 1.5em;
  font-family: sans-serif;
  color: #AD141E;
  text-decoration: none;
}
.new-link:hover {
  color: #7f0f16;
}
.section-title {
  font-size: 1.5em;
  font-family: sans-serif;
  color: #888;
  text-decoration: none;
  font-weight: bold;
}
.section-title:hover {
  color: #6f6f6f;
}

Espera, espera, un momento: a pesar de que gran parte del código es exactamente igual entre uno y otro, éste se repite. Es normal, puesto que un mixin es sencillamente eso: no repetirte en el código que escribes a costa de repetir en el código final. Sin embargo, esta pérdida de limpieza y eficiencia tiene fácil solución. 

Placeholder

Un Placeholder es muy fácil de entender: es una clase en sass que no aparecerá en el CSS compilado, pero que podemos usar para heredar o extender. 

%colorize
  font-size: 1.5em
  font-family: sans-serif
  text-decoration: none
.new-link
  @extend %colorize
.section-title
  @extend %colorize
  font-weight: bold

Como podéis ver aquí falta algo. En nuestro mixin había parámetros, pero en los placeholder estos no tienen cabida. La solución a esta serie de problemas no es una cosa o la otra, la solución está en complementar ambas herramientas:

%colorize
  font-size: 1.5em
  font-family: sans-serif
  text-decoration: none
=colorize($color:#888)
  @extend %colorize
  color: $color
  &:hover
    color: darken($color,10%)
.new-link
  +colorize(#AD141E)
.section-title
  +colorize()
  font-weight: bold

El resultado es lo más limpio a lo que podemos aspirar:

.new-link, .section-title {
  font-size: 1.5em;
  font-family: sans-serif;
  text-decoration: none;
}
.new-link {
  color: #AD141E;
}
.new-link:hover {
  color: #7f0f16;
}
.section-title {
  color: #888;
  font-weight: bold;
}
.section-title:hover {
  color: #6f6f6f;
}

Un código compilado en el que las propiedades compartidas están unidas, y por separado sólo aquellas que son específicas para cada caso.

Placeholder y mixin en Media Queries

Uno de los problemas que tenemos con esta técnica y el uso de placeholder es que desde la versión 3.3.0 de Sass, hacer @extend dentro de un media-query se ha convertido en algo complicado. Si quisieráis hacer esto en el ejemplo anterior

  @media screen and (min-width 600px) {
        +colorize(green)
  }

... sería imposible. Sass os informará amablemente con un mensaje tal que así:

You may not @extend an outer selector from within @media. You may only @extend selectors within the same directive. From "@extend %colorize" on line 7.

Esto ocurre porque la directiva @extend sólo funciona con elementos definidos en tu mismo entorno (dentro del mismo media-query en el que haces el @extend).

Actualmente no hay una solución oficial para esto, aunque sí que hay opciones para bordear este pequeño problema y solucionarlo. Algunas van desde la solución de Hugo Giraudiel, encapsulando el placeholder a su vez en un mixin, lo cual sigue manteniendo el código compilado limpio, pero dando la opción de cargarlo como mixin o como placeholder, según sea necesario.

@mixin myMixin($extend: true) {
  @if $extend {
    @extend %myMixin;
  }
  @else {
    // Mixin core
  }
}
%myMixin {
  @include myMixin($extend: false);
}

Existen otras opciones más complejas, como la de crear un grupo de mixins con los puntos de ruptura e iterar en ellos, generando un extend por cada uno y cargando los @extend sin problema. Sin embargo, estas soluciones, por su complejidad, pueden no ser utiles excepto si el proyecto tiene gran envergadura y el uso de media-query y placeholder va a ser muy intensivo.

Rendimiento

Existen argumentos en contra del uso de placeholder, por supuesto. Hay quien argumenta que el tiempo de compilación es mayor con el uso de @extend, y es cierto. Un centenar de mixins compila antes que unas pocas decenas de selectores con @extend. 

Otros argumentan que, aunque el resultado sea más limpio, el peso que se gana al evitar ese código repetido es mínimo y despreciable, puesto que hoy en día las hojas de estilo se comprimen con gzip (y este además funciona mejor cuanto más cadenas de texto repetidas encuentre). Aunque esto, como mínimo, dependerá del caso.