En los últimos años los preprocesadores de CSS se han hecho muy populares porque permiten escribir CSS de forma programática, lo cuál ha supuesto un importante salto adelante en flexibilidad y potencia a la hora de declarar estilos, siendo Sass el más conocido y usado de entre éstos, y aunque actualmente existen muchas otras formas de estilar nuestras aplicaciones web, como CSS-in-JS en alguno de sus múltiples sabores, PostCSS o CSS modules, el uso de SASS sigue estando muy extendido, como revela el último The State of CSS 2021, y por tanto, sigue siendo una herramienta con largo recorrido.
Module System
En 2019 Sass anunció Module System, ****un cambio notable en core de su
herramienta que dejaría atrás el uso de @import y algunas funciones globales,
y que la implementación llamada Dart Sass
(basada en JavaScript y en la que
se trabajaba desde 2016)
ya soportaba el nuevo sistema de módulos, por lo que desde la organización,
empezaron a fomentar su uso por delante de
LibSass y RubySass.
A pesar de existir retrocompatibilidad y de que podían cohexisitir ambos
enfoques, tras un período de transición -para dar tiempo a los autores a
realizar este cambio-, se abandonaría definitivamente @import y los viejos
módulos internos de SASS, a más tardar el 1 de octubre de 2022.
Por entonces, LibSass estaba muy extendida (como dependencia de node-sass) pero no soportaba totalmente el nuevo sistema de módulos, y además, acabaría deprecada, por lo que el cambio acabaría siendo forzado.
¿Y cómo me afecta esto?
Pues depende. Significa que vamos a tener un montón de legacy code que tendríamos que migrar y que puede hacerse siguiendo esta guía de migración, pero, si algo está funcionando, ¿por qué cambiarlo?. Pero también significa que cuando uses SASS en tus nuevos desarrollos, tendrás que utilizar el nuevo sistema de módulos, y por tanto tendrás que conocer las nuevas reglas.
@use y @forward
Lo más destacable de esta versión son la aparición de estas dos nuevas reglas,
que vienen para reemplazar a @import y que van a eliminar el ámbito
global en el que vivían las utilidades.
Hasta ahora, incluíamos en un única hoja de estilos (en un índice), el resto de
ficheros parciales que estilaban otros componentes o partes de la aplicación,
así como ciertas utilidades; variables, mixins, placeholders y funciones, a las
que todos esos parciales tenían acceso y podían utilizar, pero con el nuevo
sistema de módulos, cuando un parcial quiera acceder a alguna utilidad, tendrá
que importarla explícitamente, de manera que tendremos más control, y de un
simple vistazo sabremos qué código que no pertenece a ese parcial se está
utilizando y de dónde viene, algo muy parecido a los import de JavaScript.
Pero no sólo eso, sino que existen otras muchas ventajas.
👉 Con “miembros” nos referimos a variables, funciones y mixins
Beneficios
- Localización. Es mucho más fácil saber qué se está utilizando en cada hoja de estilos y de dónde viene al tener que estar declarado explícitamente al principio del archivo
- Desaparece el ámbito global. Al no existir en un espacio virtual común
para nuestras variables, mixins y funciones, se evitarán colisiones por
nombres repetidos entre nuestro código y el de terceros, por ejemplo,
podríamos tener un mixin llamado
rowque existiese también con el mismo nombre en una librería de un tercero que estemos usando - Encapsulación. Los archivos importados se encapsulan usando un namespace por lo que evitará colisiones dentro del mismo archivo
- Evitar duplicaciones. Con
@import, podías importar el mismo archivo tantas veces como quisieras, más por error que a propósito, afectando el CSS resultante, pero con@usey@forwardtenemos protección frente a estos despistes - Desambiguación. Al dejar de utilizarse
@importdesaparece también la ambigüedad que existía, ya que@importes una regla de CSS - Privacidad. Podemos declarar miembros privados que no podrán ser utilizados desde fuera de ese archivo, o al importar un archivo, podemos declarar cuáles exactamente queremos importar o cuáles que no se importen
- Flexibilidad con librerías de terceros. Podemos configurar librerías de terceros sin tener que sobreescribirlas, un punto interesante si somos nosotros los autores de una libería
@use
@use 'theme';
.text {
color: theme.$primary-color;
}
Se declara al principio del archivo en el que se quiere tener acceso a esos miembros (variables, mixins y funciones) para poder utilizarlos.
Por defecto, están encapsulados con el nombre del fichero, de modo que si
queremos acceder a la variable $primary-color de theme.scss tendremos que
hacerlo como theme.$primay-color. Esto nos permite usar en el mismo fichero
dos o más $primary-color ****si vienen de archivos diferentes.
Pero además, podemos renombrar los namespaces usando as
@use 'theme' as defaultTheme;
@use '../3rd-party-library/theme' as externalTheme;
.box {
color: defaultTheme.$primary-color;
background-color: externalTheme.$primary-color;
}
También podemos eliminar el namespace usando *, aunque en este caso, si
hubiese coincidencias entre variables, de diferentes importaciones, SASS
lanzaría un error
@use 'theme' as *;
@use '../3rd-party-library/theme' as *;
.box {
color: $primary-color; // desde theme
background-color: $primary-color; // desde 3rd-party-library/theme
}
Además, en la declaración de los miembros, podemos harcerlos privados, es decir,
sólo para uso interno y que no se tenga acceso a éste desde fuera, prefijándolo
con - o _
// _palette.scss
$primary-color: '#282828' // variable accesible
@mixin _reset-list {
// mixin no accesible desde el exterior
margin: 0;
padding: 0;
list-style: none;
}
@forward
Sirve para traer a un fichero el contenido de otro, por ejemplo, el índice en el
que importamos parciales a través de @import.
Como @use, tenemos protección ante duplicidad de código si tratamos de
llamarlo dos veces, pero a diferencia de éste, @forward no añade namespaces.
// Ajustes generales
@forward 'base/reset';
@forward 'base/reset'; // no duplicamos código
@forward 'base/common';
// Componentes comunes
@forward '../components/';
Otra similitud con @use es la posibilidad de manejar la privacidad de los
miembros, pero en este caso se indica en el momento de utilizar @forward
utilizando claúsulas para declarar qué queremos importar o cuáles queremos
excluir, usando show o hide
@forward 'hero' show $hero-color; // importando SÓLO la variable $hero-color
@forward 'utils' hide triangle; // importando TODO menos el mixin triangle()
Módulos internos (built-in)
Algunos de los módulos internos y utilidades de SASS han cambiado, porque son
nuevos, han sido renombrado o eliminados, así no dejes de consultarlos en la
documentación oficial, pero lo
más importante es que ahora tendrás que acceder a los módulos de forma explícita
con @use y que podrás renombrarlos
@use 'sass:math' as builtInSassMathModule;
@use 'sass:color';
@use 'theme';
body {
&::before {
content: builtInSassMathModule.random();
color: color.alpha(theme.$color);
}
}
Configuración de librerías de terceros
Con @use podemos configurar librerías de terceros sin tener que sobreescribir
sus valores, usando with en el momento de la importación
$custom-breakpoints: (
mobile: 375px,
tablet-small: 640px,
tablet: 780px,
tablet-large: 920px,
desktop: 1180px,
wide: 1400px,
);
$custom-show-breakpoints: (mobile, tablet, desktop, wide);
@forward '../node_modules/sass-mq/mq' with (
$breakpoints: $custom-breakpoints,
$show-breakpoints: $custom-show-breakpoints
);
Conclusiones
A pesar de que el sistema de módulos traía muchas ventajas, no se produjo una
transición masiva y sigue habiendo mucho código que usa @import, probablemente
porque se juntaron varios factores al mismo tiempo, como por ejemplo:
- El auge del shadow-dom y la posibilidad de encapsular estilos en componentes reutilizables
- CSS-in-JS
- Styled-components
- Emotion
- JSS
- PostCSS
- CSS modules
- Tailwind (y otras aproximaciones de utilidades)
Así que, debido al amplio abanico de posibilidades que se iba abriendo y que LibSass seguía disponible y no suponía ningún cambio, siguió siendo muy popular (como muestra npm trends), pero con el anuncio de que LibSass desaparecerá y el cambio será obligatorio para los nuevos desarrollos, si queremos seguir usando SASS hay que empezar a acoger el cambio y no aplazar más lo inevitable, por eso os dejamos un enalce a un repositorio con una estructura básica scratcheada y con algunos comentarios para que podáis probarlo :)
Aquí el enlace al repositorio para cacharrear un poquito con los diferentes tipos de importaciones
Nota al pie
En un post anterior sobre
las funciones min, max y clamp, explicábamos que para usar usar estas
funciones en Sass teníamos que entrecomillarlas y utilizar unquote() porque
Sass las confundía con las suyas propias, ya que compartían nombres.
.element-c {
width: unquote("min(500px, 98%)");
}
.element-d {
font-size: unquote("max(min(10%, ((1.25vw + 10px) * 0.5), 5vw), 1rem)");
}
Pero si ya has leído el artículo mencionado al principio de esta entrada te podrás imaginar por qué ya no supone un problema y por qué con dart-sass podemos utilizarlas de forma normal.
.element-c {
width: min(500px, 98%);
}
.element-d {
font-size: max(min(10%, ((1.25vw + 10px) * 0.5), 5vw), 1rem);
}
Estas funciones pertenecen al módulo ‘math’, y como por defecto al importar un
módulo con @use sus miembros están encapsulados, ya no hay colisión entre la
función min nativa de CSS y la propia de Sass.
@use 'sass:math';
.element-c {
width: min(500px, 98%); /* nativa CSS */
}
.element-e {
width: math.min(500px, 300px); /* propia de Sass */
}