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.