Cuándo y cómo admitir múltiples versiones de Sass

El otro día, estaba revisando el código Sass del sistema de cuadrícula de Jeet, por el simple hecho de hacerlo. Después de algunos comentarios sobre el repositorio de GitHub, entendí que los mantenedores de Jeet aún no estaban listos para pasar a Sass 3.3. De hecho, es más exacto decir Jeet usuarios no está listo para pasar a Sass 3.3, según la cantidad de problemas abiertos cuando Jeet comenzó a usar las funciones de Sass 3.3. 

De todos modos, el punto es que Jeet no puede obtener todas las cosas geniales y brillantes de Sass 3.3. O puede?

* -existe Funciones

Si conoce la versión 3.3 que se trajo a Sass, es posible que sepa que se han agregado un par de funciones auxiliares al núcleo, destinadas a ayudar a los desarrolladores de marcos a admitir varias versiones de Sass al mismo tiempo:

  • global-variable-existencia ($ nombre): comprueba si existe una variable en el ámbito global
  • variable-existe ($ nombre): comprueba si existe una variable en el alcance actual
  • la función existe ($ nombre): comprueba si una función existe en el alcance global
  • mixin-existe ($ nombre): comprueba si existe una mezcla en el ámbito global

También hay una característica-existe ($ nombre) función, pero realmente no estoy seguro de lo que hace ya que los documentos son bastante evasivos al respecto. Incluso eché un vistazo al código de la función, pero no hace más quebool (Sass.has_feature? (feature.value)), lo cual no ayuda mucho.

De todos modos, tenemos un par de funciones que pueden verificar si existe una función, una mezcla o una variable, y eso es bastante bueno. Tiempo de seguir adelante.

Detección de la versión de Sass

Bien, nuevas funciones, muy bien. Pero, ¿qué sucede cuando usamos una de esas funciones en un entorno Sass 3.2.x? Averigüemos con un pequeño ejemplo..

// Definiendo una variable $ my-awesome-variable: 42; // En otro lugar en el código $ hace-mi-impresionante-variable-existe: variable-existe ('mi-impresionante-variable'); // Sass 3.3 -> 'true' // Sass 3.2 -> 'variable-existencia (' my-awesome-variable ')' 

Como puede ver en los resultados, Sass 3.2 no falla ni lanza ningún error. Analiza variable-existe ('my-awesome-variable') como una cuerda, así que básicamente "variable-existe ('my-awesome-variable')". Para verificar si estamos tratando con un valor booleano o una cadena, podemos escribir una prueba muy simple:

$ return-type: type-of ($ hace-mi-impresionante-variable-existe); // Sass 3.3 -> 'bool' // Sass 3.2 -> 'string' 

Ahora podemos detectar la versión de Sass desde el código. ¿Qué tan impresionante es eso? En realidad, no detectamos exactamente la versión de Sass; más bien, encontramos una manera de definir si estamos ejecutando Sass 3.2 o Sass 3.3, pero eso es todo lo que necesitamos en este caso.

Mejora progresiva

Veamos la mejora progresiva de las funciones de Sass. Por ejemplo, podríamos usar herramientas nativas si están disponibles (Sass 3.3), o recurrir a herramientas personalizadas si no lo están (Sass 3.2). Eso es lo que le sugerí a Jeet con respecto a la reemplazar-nth () Función, que se utiliza para reemplazar un valor en un índice específico.

Así es como nosotros podría hazlo:

@function replace-nth ($ list, $ index, $ value) // Si 'set-nth' existe (Sass 3.3) @if function-existe ('set-nth') == true @return set- nth ($ list, $ index, $ value);  // Si no es Sass 3.2 $ resultado: (); $ index: if ($ index < 0, length($list) + $index + 1, $index); @for $i from 1 through length($list)  $result: append($result, if($i == $index, $value, nth($list, $i)));  @return $result;  

Y entonces supongo que eres como ...  ¿cuál es el punto de hacer esto si podemos hacer que funcione para Sass 3.2 de todos modos? Buena pregunta. Yo diría que actuación. En nuestro caso, set-nth es una función nativa de Sass 3.3, lo que significa que funciona en Ruby, lo que significa que es mucho más rápido que una función Sass personalizada. Básicamente, las manipulaciones se realizan en el lado de Ruby en lugar del compilador de Sass.

Otro ejemplo (todavía de Jeet) sería un marcha atrás Función, invirtiendo una lista de valores. Cuando lancé SassyLists por primera vez, no había Sass 3.3, por lo que revertir una lista significaría crear una nueva lista, retrocediendo sobre la lista inicial, agregando valores a la nueva. Hizo bien el trabajo. Sin embargo, ahora que tenemos acceso a la set-nth función de Sass 3.3, hay una forma mucho mejor de revertir una lista: intercambiando índices.

Para comparar el rendimiento entre ambas implementaciones, intenté revertir el alfabeto latino (una lista de 26 elementos) 500 veces. Los resultados fueron, más o menos:

  • entre 2s y 3s con el "enfoque 3.2" (utilizando adjuntar)
  • nunca más de 2s con el "enfoque 3.3" (usando set-nth)

La diferencia sería aún mayor con una lista más larga, simplemente porque intercambiar índices es mucho más rápido que agregar valores. Así que, una vez más, traté de ver si podíamos aprovechar al máximo los dos mundos. Aquí está lo que se me ocurrió:

@function reverse ($ list) // Si 'set-nth' existe (Sass 3.3) @if function-existir ('set-nth') == true @for $ i desde 1 hasta el piso (longitud ($ list) / 2) $ list: set-nth (set-nth ($ list, $ i, nth ($ list, - $ i)), - $ i, nth ($ list, $ i));  @return $ list;  // Si no es Sass 3.2 $ resultado: (); @for $ i from length ($ list) * -1 a -1 $ result: append ($ result, nth ($ list, abs ($ i)));  @return $ resultado;  

Una vez más, estamos aprovechando al máximo Sass 3.3 mientras seguimos admitiendo Sass 3.2. Esto es bastante bueno, ¿no te parece? Por supuesto, podríamos escribir la función al revés, tratando con Sass 3.2 primero. No hace absolutamente ninguna diferencia en absoluto..

@function reverse ($ list) // Si 'set-nth' no existe (Sass 3.2) @if function-existe ('set-nth')! = true $ result: (); @for $ i from length ($ list) * -1 a -1 $ result: append ($ result, nth ($ list, abs ($ i)));  @return $ resultado;  // De lo contrario, Sass 3.3 @ para $ i desde 1 hasta el piso (longitud (lista $) / 2) $ lista: set-nth (set-nth ($ lista, $ i, nth ($ lista, - $ i )), - $ i, nth ($ list, $ i));  @return $ list;  

Nota: para comprobar si estamos ejecutando Sass 3.2 en el último ejemplo, podríamos haber probado function-existencia ("set-nth") == unquote ('function-existencia ("set-nth")') también, pero eso es bastante largo y propenso a errores.

Almacenando la versión de Sass en una variable

Para evitar verificar las características existentes varias veces, y como solo tratamos con dos versiones diferentes de Sass aquí, podemos almacenar la versión de Sass en una variable global. Aquí es cómo lo hice:

$ sass-version: if (function-existir ("function-existir") == verdadero, 3.3, 3.2); 

Te daré eso es un poco complicado. Permítame explicarle lo que está pasando aquí. Estamos usando el Si() Función ternaria, diseñada así:

  • el primer argumento de la Si() la función es la condición; se evalúa a cierto o falso
  • si la condición se evalúa a cierto, devuelve el segundo argumento
  • de lo contrario devuelve el tercer argumento

Nota: Sass 3.2 es un tipo de buggy con la función ternaria. Evalúa los tres valores, no solo el que debe devolverse. Esto a veces puede llevar a algunos errores inesperados..

Ahora, echemos un vistazo a lo que está pasando con Sass 3.3:

  • la función-existe ('función-existe') devoluciones cierto porque obviamente la función existe () existe
  • entonces la función-existe ('function-existir') == verdadero es como true == true cual es cierto
  • asi que $ sass-version se establece en 3.3

Y si estamos ejecutando Sass 3.2:

  • la función-existe ('función-existe') No es una función sino una cadena, así que básicamente "función-existe ('función-existe')"
  • la función-existe ('function-existir') == verdadero es falso
  • asi que $ sass-version se establece en 3.2

Si eres un tipo de persona, puedes envolver esto en una función..

@function sass-version () @return if (function-existir ("function-existir") == verdadero, 3.3, 3.2);  

Entonces úsalo de esta manera:

@if sass-version () == 3.3 // Sass 3.3 @if sass-version () == 3.2 // Sass 3.2 @if sass-version () < 3.3  // Sass 3.2  

Por supuesto, podríamos haber comprobado la existencia de otra función 3.3 como llamada() o map-get () pero potencialmente podría haber una versión de Sass donde * -existe Se implementan funciones, pero no llamada() o mapas, así que siento que es mejor verificar la existencia de un * -existe función. Y ya que usamos la función existe, vamos a probar este!

Al futuro!

Sass 3.3 es la primera versión para implementar. * -existe funciones, por lo que tenemos que comprobar si * -existe ($ param)en realidad devuelve un valor booleano o se analiza como una cadena, lo que es una especie de hacky.

Ahora, digamos que Sass 3.4 será lanzado mañana con un unicornio() Función, trayendo maravillas y arco iris al mundo. La función para detectar la versión de Sass probablemente se vería así:

@function sass-version () @ si la función existe ('unicornio') == verdadero @return 3.4;  @else si la función existe ('unicornio') == false @return 3.3;  @else @return 3.2;  

Unicornio napolitano de Erin Hunting

Y luego si Sass 3.5 trae una arco iris() función, actualizarías sass-version () de esta manera:

@function sass-version () @ si la función existe ('rainbow') == true @return 3.5;  @else si la función existe ('unicornio') == verdadero y la función-existe ('arco iris') == falso @return 3.4;  @else si la función existe ('unicornio') == false @return 3.3;  @else @return 3.2;  

Y así.

Hablando de unicornios y arco iris ...

Que sería De Verdad impresionante sería la capacidad de importar un archivo dentro de una declaración condicional. Desafortunadamente, esto no es posible en este momento. Dicho esto, está programado para Sass 4.0, así que no perdamos la esperanza todavía..

De todos modos, imagina que podríamos importar un archivo basado en el resultado del sass-version () función. Esto haría que sea muy fácil realizar funciones de polyfill Sass 3.3 para Sass 3.2.

Por ejemplo, podríamos tener un archivo que incluya todas las versiones Sass 3.2 de las funciones de mapas que usen listas bidimensionales (como lo que hizo Lu Nelson con Sass-List-Maps) e importarlo solo cuando se trate de Sass 3.2, así:

// Desafortunadamente, esto no funciona :( @ sass-version () < 3.3  @import "polyfills/maps";  

Entonces, podríamos usar todas esas funciones (como map-get) en nuestro código sin preocuparse por la versión Sass. Sass 3.3 usaría funciones nativas, mientras que Sass 3.2 usaría polyfills. 

Pero eso no funciona.

Uno podría venir con la idea de definir funciones en una declaración condicional, en lugar de importar un archivo completo. Entonces, podríamos definir funciones relacionadas con el mapa solo si aún no existen (en otras palabras: Sass 3.2). Desafortunadamente, esto tampoco funciona: las funciones y los mixins no se pueden definir en una directiva.

Las funciones pueden no estar definidas dentro de las directivas de control u otras combinaciones..

Lo mejor que podemos hacer en este momento es definir una versión Sass 3.2 y una versión Sass 3.3 en cada función, como hemos visto en la parte superior de este artículo. Pero no solo es más complicado de mantener, sino que también requiere que todas las funciones nativas de Sass 3.3 estén envueltas en una función personalizada. Echa un vistazo a nuestro reemplazar-enésimo función de antes: no podemos nombrarlo set-nth (), o va a ser infinitamente recursivo cuando se usa Sass 3.3. Así que tenemos que encontrar un nombre personalizado (en este caso reemplazar-enésimo).

Ser capaz de definir funciones o importar archivos dentro de directivas condicionales permitiría mantener las características nativas tal como están, al mismo tiempo que genera polyfills para versiones anteriores de Sass. Desafortunadamente, no podemos. Eso apesta.

Mientras tanto, supongo que podríamos usar esto para avisar al usuario cuando esté usando un compilador obsoleto de Sass. Por ejemplo, si su biblioteca / framework Sass / lo que sea que use Sass, podría agregar esto a la parte superior de su hoja de estilo principal:

@if sass-version () < 3.3  @warn "You are using a version of Sass prior to 3.3. Unfortunately for you, Sass 3.3 is required for this tool to work. Please make sure to upgrade your Sass compiler.";  

Ahí. En caso de que el código se bloquee porque está utilizando características no compatibles como mapas y cosas, se le advertirá al usuario por qué cuando él o ella comprueba la salida.

Pensamientos finales

Hasta ahora, Sass ha sido bastante lento para moverse en el punto de vista de las versiones. Recuerdo haber leído en alguna parte que los mantenedores de Sass estaban deseando avanzar un poco más rápido, lo que significa que podríamos encontrarnos con múltiples versiones de Sass en un futuro cercano..

Aprender a detectar la versión de Sass y hacer uso de * -existe La función será, en mi opinión, un día importante, al menos para algunos proyectos (marcos, sistemas de grillas, bibliotecas ...). Hasta entonces, sigan chupando chicos.!

Otras lecturas

  • Sass 3.3 (Maptastic Maple) por John W. Long
  • Sass 3.3 se lanza en el blog de Sass