DEV Community

Cover image for Sass 1.23 - La versión que cambió las reglas del juego
Adrian Benavente
Adrian Benavente

Posted on • Edited on

Sass 1.23 - La versión que cambió las reglas del juego

Se está por cumplir un año ya desde el lanzamiento de la versión que quizás introdujo los cambios más significativos en Sass como lenguaje desde la migración de su compilador de Ruby a Dart, y he notado que, sin embargo, este suceso ha pasado bastante desapercibido. Fuera del sitio oficial de Sass, los posteos en internet hablando acerca de esto, así como los videos en YouTube, son escasos por no decir nulos, y ni hablemos de encontrar algo en español. Es por eso que decidí ordenar de la forma más prolija y detallada posible pero sin agobiar al lector, las features más trascendentales que introduce Sass 1.23, y que, según sus desarrolladores, llegaron para quedarse.

Démosle la bienvenida al Module System

Sin lugar a dudas, el salto más importante que ha dado esta versión, y por el cual se la considera una versión bisagra es debido a la deprecación completa (aunque gradual) de la regla @import para la carga de archivos, cediendo el paso al nuevo Module System, cuya implementación gira en torno a la nueva at-rule @use.

Previo a esta versión, cuando usábamos @import, era imposible saber dónde habían sido definidas las funciones, mixins y variables (llamados "miembros" por convención, y así nos referiremos de ahora en adelante) que estábamos invocando dentro de un archivo (a los cuales a partir de ahora es correcto referirse como "namespaces"), ya que el solo hecho de hacer un @import los volvía disponibles dentro de todos los archivos que fueran llamados a continuación.

Esto era un dolor de cabeza para muchos desarrolladores, y un potencial problema en proyectos grandes donde podía llegar a ocurrir que se definieran dos miembros con el mismo nombre. Además, nos obligaba a prestar particular atención al orden de importación, cuando existían múltiples imports, si queríamos que determinados miembros estuvieran disponibles cuando los necesitábamos.

Con esto, de paso, Sass se despega por completo de la colisión confusa con el @import de CSS.

Cambios de la versión

Estos son algunos de los pricipales cambios y mejoras que introdujo la versión 1.23:

  • Importación de archivos como módulos mediante la regla @use. Mejora el encapsulamiento ya que los miembros de éste solo estarán disponibles dentro del archivo que los está usando, a diferencia de @import que tenía un scope global.
  // some-file.scss
  @import "some-module"; // ❌ disponible dentro de este archivo
  // y de todos los que se importen después 
Enter fullscreen mode Exit fullscreen mode
  // some-file.scss
  @use "some-module"; // ✅ disponible solo dentro de este archivo 
Enter fullscreen mode Exit fullscreen mode
  • Con la regla @forward, se podrá hacer que los miembros de otro archivo estén disponibles cuando el archivo actual sea importado con @use, creando de esta manera una API extendida. Sin embargo, el archivo que realiza el @forward no podrá acceder a los miembros de éste, a diferencia de @use, si no que actuará como un simple intermediario. Es decir que podemos hacer lo siguiente:
  // mixins.scss
  @mixin expand {
      display: block;
      width: 100%;
  }
Enter fullscreen mode Exit fullscreen mode
  // foo.scss
  @forward "mixins";

  .foo {
      align-items: center;
      background-color: peru;
      display: flex;
      flex-direction: column-reverse;
  }
Enter fullscreen mode Exit fullscreen mode
  // some-other-file.scss
  @use "foo";

  .expanded {
      @include foo.expand;
  }
Enter fullscreen mode Exit fullscreen mode

Mientras que esto nos daría un error:

  // palette.scss
  $colors: (
      primary: #007BFF,
      secondary: #6C757D,
      success: #DC3545
  );
Enter fullscreen mode Exit fullscreen mode
  // functions.scss
  @use "sass:map";
  @forward "palette";

  @function color($prop) {
      @return map.get(palette.$colors, $prop);
      // => Error: There is no module
      // with the namespace "palette".
  }
Enter fullscreen mode Exit fullscreen mode

Es posible, de ser necesario, decidir de manera selectiva qué variables, mixins o funciones de determinado namespace estarán disponibles, de la siguiente manera:

  // src/_list.scss
  $horizontal-list-gap: 2em;

  @mixin list-reset {
    margin: 0;
    padding: 0;
    list-style: none;
  }

  @mixin list-horizontal {
    @include reset;

    li {
      display: inline-block;
      margin: {
        left: -2px;
        right: $horizontal-list-gap;
      }
    }
  }
Enter fullscreen mode Exit fullscreen mode
  // bootstrap.scss
  @forward "src/list" hide list-reset, $horizontal-list-gap;
Enter fullscreen mode Exit fullscreen mode

...o de forma inversa con show:

  @forward "src/list" show list-horizontal;
Enter fullscreen mode Exit fullscreen mode

Y también algo que es muy interesante es que podemos agregar un prefijo para todos los miembros del namespace:

  // src/_list.scss
  @mixin reset {
    margin: 0;
    padding: 0;
    list-style: none;
  }
Enter fullscreen mode Exit fullscreen mode
  // bootstrap.scss
  @forward "src/list" as list-*;
Enter fullscreen mode Exit fullscreen mode
  // styles.scss
  @use "bootstrap";

  li {
    @include bootstrap.list-reset;
  }
Enter fullscreen mode Exit fullscreen mode
  • La at-rule @extend se limita al scope del archivo actual cuando se utiliza @use. A diferencia de antes, que al usar @import el @extend quedaba disponible globalmente, en ocasiones haciendo difícil predecir qué reglas estábamos extendiendo.

  • Usando los modificadores de acceso - ó _ podemos hacer que un miembro sea privado, lo que significa que solo podrá ser utilizado dentro del propio archivo que lo define: $_private-var: value ó $-private-var: value.

  • Namespaces personalizados mediante el uso de as. Por ejemplo:

  @use "functions" as fn;

  .button {
      background-color: fn.color("success");
  }
Enter fullscreen mode Exit fullscreen mode

...o como top-level module (usar con precaución):

  @use "functions" as *;

  .button {
      background-color: color("success");
  }
Enter fullscreen mode Exit fullscreen mode

Esta última forma tiene la limitación de que, si usamos otro módulo como "top-level module" y este contiene, también, una función llamada color(), el compilador arrojará un error.

  • También podemos importar un módulo sobreescribiendo el valor de una o más de sus variables. Para ello, primero tenemos que asignarle un valor por defecto al momento de definirla:
  // bootstrap.scss
  $paragraph-margin-bottom: 1rem !default;

  p {
    margin-top: 0;
    margin-bottom: $paragraph-margin-bottom;
  }
Enter fullscreen mode Exit fullscreen mode

Y luego llamar el archivo de esta manera, utilizando la cláusula with que recibirá un mapa con las variables que queramos pisar:

  @use "bootstrap" with (
    $paragraph-margin-bottom: 1.2rem
  );
Enter fullscreen mode Exit fullscreen mode
  • Las funciones del core de Sass ahora son accedidas a través de módulos nativos: sass:color, sass:list, sass:map, sass:math, sass:meta, sass:selector, y sass:string. Las cargaremos en nuestra hoja de estilos con @use (por ej: @use "sass:map") y las usaremos como cualquier otro módulo, con la sintáxis namespace.function(). Esto se hizo para evitar la colisión con funciones nativas de CSS, lo que les permitirá, en el futuro, agregar nuevas de manera segura.

  • Se incorporaron, además, algunos mixins al core, que vienen a solucionar algunas malas prácticas del pasado, como los @import anidados, tal es el caso de meta.load-css($url, $with: ()) que sirve para cargar una hoja de estilos de manera dinámica, recibiendo dos parámetros, el primero $url es un string con la url de dicho CSS, y el segundo $with: () es opcional y deberá ser un mapa con la configuración, igual al del ejemplo anterior solo que en este caso las variables no llevan $ adelante. También tenemos meta.module-variables() y meta.module-functions() para acceder a las variables o funciones respectivamente de un determinado módulo. Están disponibles de la misma manera que las built-in functions: @use "sass:meta".

¿Es seguro de usar?

Desde el lanzamiento de esta versión, ya ha pasado casi un año y han salido 3 versiones menores posteriormente (actualmente se encuentra en la 1.26), por lo que es 100% seguro de utilizar en cualquier proyecto nuevo. Sin embargo, hemos de esperar que no nos funcionen las nuevas features en proyectos de más de un año de antigüedad.

Si bien esta versión es completamente retro-compatible, los desarrolladores avisan que @import será eventualmente removido por completo de Sass, pero que dejarán transcurrir un tiempo prudencial para que tengamos chance de migrar.

Por suerte, Sass pone a nuestra disposición una herramienta de migración automatizada, a través de línea de comandos: sass-migrator. Existen varias formas de instalarla, las cuales se detallan aquí.

Migrando nuestro código

Seguiremos las instrucciones para Sass: Migrator de la página oficial, y una vez que tengamos instalada la CLI, simplemente navegamos a la carpeta de nuestro proyecto y hacemos:

sass-migrator module --migrate-deps <path/to/style.scss>
Enter fullscreen mode Exit fullscreen mode

El flag --migrate-deps lo que hace es decirle que migre no solo el archivo específico que le estamos indicando si no además todos los archivos que pudiera estar importando.

Podemos pasarle el flag --verbose para que la consola nos de output sobre los cambios que va realizando sobre los archivos, y el flag --dry-run si queremos que corra el comando de manera "simulada" sin ejecutar realmente los cambios, lo cual tiene sentido usándolo en combinación con el anterior.

Si bien lo anterior bastará para la mayoría de los casos, hay un último flag que puede llegar a ser muy útil para quienes desarrollen alguna biblioteca: --forward, que acepta los valores none(valor por defecto), all y prefixed. Si elegimos --forward=all permitiremos que el usuario de nuestra biblioteca pueda acceder a toda la API (excepto miembros privados) con un solo @use, ya que lo que hará es un @forward de todos los archivos. En el caso de prefixed, básicamente hace un @forward únicamente de los miembros a los que hayamos puesto un prefijo, el cual debemos explicitar de manera obligatoria con el flag adicional --remove-prefix; la herramienta eliminará automáticamente este prefijo. Antes de los módulos, se solía agregar estos prefijos para evitar colisiones de nombres. El ejemplo del sitio de Sass, lo grafica a la perfección:

$ cat style.scss
@import "theme";

@mixin app-inverted {
  color: $app-bg-color;
  background-color: $app-color;
}
Enter fullscreen mode Exit fullscreen mode
$ sass-migrator --migrate-deps module --remove-prefix=app- style.scss
$ cat style.scss
@use "theme";

@mixin inverted {
  color: theme.$bg-color;
  background-color: theme.$color;
}
Enter fullscreen mode Exit fullscreen mode

Debo decir que he realizado unas cuantas migraciones y, en todos los casos, la herramienta es bastante efectiva e inteligente al realizar su labor. En algúna ocasión he tenido que refactorizar un poco a mano pero han sido cosas mínimas que no me han llevado más de unos minutos. Aun así, es recomendable primero probar siempre con --dry-run --verbose, para nuestra tranquilidad.

Referencias:

Invitame un café en cafecito.app

Top comments (2)

Collapse
 
itsjuanit profile image
Juan Ignacio

No estaba enterado de estas features, muy bueno leer todo y en español, gracias por el post!

Collapse
 
dinael profile image
Dinael URDANETA

Gracias por compartir esta información, había leído lo de @use pero simplemente me pareció una forma más fina de un @import sin más.

Como dices en la introducción esto a pasado por debajo de mesa sin mucha repercusión pero creo que es un problema de "marketing" un cambio así debería haber llevado a una versión 2 por ejemplo, algo que sólo por ver el cambio de versión te pongas a investigar.