CSS en TypeScript con extracto de vainilla | Programar Plus

vanilla-extract es una nueva biblioteca CSS-in-TypeScript independiente del marco. Es una forma liviana, robusta e intuitiva de escribir sus estilos. vanilla-extract no es un marco CSS prescriptivo, sino una pieza flexible de herramientas para desarrolladores. Las herramientas de CSS han sido un espacio relativamente estable en los últimos años con PostCSS, Sass, módulos CSS y componentes con estilo que salieron antes de 2017 (algunos mucho antes) y siguen siendo populares en la actualidad. Tailwind es una de las pocas herramientas que ha revolucionado las herramientas de CSS en los últimos años.

extracto de vainilla tiene como objetivo sacudir las cosas de nuevo. Fue lanzado este año y tiene la ventaja de poder aprovechar algunas tendencias recientes, que incluyen:

  • Desarrolladores de JavaScript cambiando a TypeScript
  • Compatibilidad del navegador con propiedades personalizadas de CSS
  • Estilo utilitario primero

Hay un montón de innovaciones inteligentes en el extracto de vainilla que creo que lo convierten en un gran problema.

Tiempo de ejecución cero

Las bibliotecas CSS-in-JS generalmente inyectan estilos en el documento en tiempo de ejecución. Esto tiene beneficios, incluida la extracción de CSS crítica y el estilo dinámico.

Pero como regla general, un archivo CSS separado será más eficaz. Esto se debe a que el código JavaScript necesita pasar por un análisis/compilación más costoso, mientras que un archivo CSS separado se puede almacenar en caché mientras que el protocolo HTTP2 reduce el costo de la solicitud adicional. Además, las propiedades personalizadas ahora pueden proporcionar mucho estilo dinámico de forma gratuita.

Entonces, en lugar de inyectar estilos en el tiempo de ejecución, el extracto de vainilla se asemeja a Linaria y astroturf. Estas bibliotecas le permiten crear estilos usando funciones de JavaScript que se extraen en el momento de la compilación y se usan para construir un archivo CSS. Aunque escribe extracto de vainilla en TypeScript, no afecta el tamaño general de su paquete de JavaScript de producción.

Mecanografiado

Una gran propuesta de valor del extracto de vainilla es que te pones a escribir. Si es lo suficientemente importante como para mantener el resto de su base de código segura, ¿por qué no hacer lo mismo con sus estilos?

TypeScript proporciona una serie de beneficios. Primero, hay autocompletar. Si escribe “fo”, entonces, en un editor compatible con TypeScript, obtendrá una lista de opciones de fuente en un menú desplegable: fontFamily, fontKerning, fontWeight, o cualquier otra cosa que coincida, para elegir. Esto hace que las propiedades CSS sean detectables desde la comodidad de su editor. Si no puede recordar el nombre de fontVariant pero sepa que comenzará con la palabra “fuente”, la escribe y se desplaza por las opciones. En VS Code, no necesita descargar ninguna herramienta adicional para que esto suceda.

Esto realmente acelera la creación de estilos:

También significa que su editor está mirando por encima de su hombro para asegurarse de que no esté cometiendo errores de ortografía que puedan causar errores frustrantes.

Los tipos de extracto de vainilla también brindan una explicación de la sintaxis en su definición de tipo y un enlace a la documentación de MDN para la propiedad CSS que está editando. Esto elimina un paso de buscar frenéticamente en Google cuando los estilos se comportan de forma inesperada.

Imagen de VSCode con el cursor sobre la propiedad fontKerning y una ventana emergente que describe lo que hace la propiedad con un enlace a la documentación de Mozilla para la propiedad

Escribir en TypeScript significa que está usando nombres de mayúsculas y minúsculas para las propiedades CSS, como backgroundColor. Esto podría ser un pequeño cambio para los desarrolladores que usan sintaxis CSS regular, como background-color.

integraciones

vanilla-extract proporciona integraciones de primera clase para todos los paquetes más nuevos. Aquí hay una lista completa de las integraciones que admite actualmente:

  • paquete web
  • esconstruir
  • Vite
  • manto de nieve
  • SiguienteJS
  • gatsby

También es completamente independiente del marco. Todo lo que necesita hacer es importar nombres de clases de vanilla-Extract, que se convierten en una cadena en el momento de la compilación.

Uso

Para usar extracto de vainilla, escribe un .css.ts archivo que sus componentes pueden importar. Las llamadas a estas funciones se convierten en cadenas de nombre de clase con hash y ámbito en el paso de compilación. Esto puede sonar similar a los módulos CSS, y no es una coincidencia: uno de los creadores de vanilla-Extract, Mark Dalgleish, también es cocreador de los módulos CSS.

style()

Puede crear una clase CSS de alcance automático utilizando el style() función. Pasa los estilos del elemento y luego exporta el valor devuelto. Importe este valor en algún lugar de su código de usuario y se convertirá en un nombre de clase con ámbito.

// title.css.ts
import {style} from "@vanilla-extract/css";

export const titleStyle = style({
  backgroundColor: "hsl(210deg,30%,90%)",
  fontFamily: "helvetica, Sans-Serif",
  color: "hsl(210deg,60%,25%)",
  padding: 30,
  borderRadius: 20,
});
// title.ts
import {titleStyle} from "./title.css";

document.getElementById("root").innerHTML = `<h1 class="${titleStyle}">Vanilla Extract</h1>`;

Las consultas de medios y los pseudoselectores también se pueden incluir dentro de las declaraciones de estilo:

// title.css.ts
backgroundColor: "hsl(210deg,30%,90%)",
fontFamily: "helvetica, Sans-Serif",
color: "hsl(210deg,60%,25%)",
padding: 30,
borderRadius: 20,
"@media": {
  "screen and (max-width: 700px)": {
    padding: 10
  }
},
":hover":{
  backgroundColor: "hsl(210deg,70%,80%)"
}

Estos style las llamadas a funciones son una ligera abstracción sobre CSS: todos los nombres y valores de propiedades se asignan a las propiedades y valores de CSS con los que está familiarizado. Un cambio al que hay que acostumbrarse es que los valores a veces se pueden declarar como un número (p. ej. padding: 30) que por defecto es un valor de unidad de píxel, mientras que algunos valores deben declararse como una cadena (p. ej. padding: "10px 20px 15px 15px").

Las propiedades que van dentro de la función de estilo solo pueden afectar a un único nodo HTML. Esto significa que no puede usar el anidamiento para declarar estilos para los hijos de un elemento, algo a lo que podría estar acostumbrado en Sass o PostCSS. En su lugar, debe diseñar a los niños por separado. Si un elemento hijo necesita diferentes estilos basados ​​en el padre, puede usar el selectors propiedad para agregar estilos que dependen del padre:

// title.css.ts
export const innerSpan = style({
  selectors:{[`${titleStyle} &`]:{
    color: "hsl(190deg,90%,25%)",
    fontStyle: "italic",
    textDecoration: "underline"
  }}
});
// title.ts
import {titleStyle,innerSpan} from "./title.css";
document.getElementById("root").innerHTML = 
`<h1 class="${titleStyle}">Vanilla <span class="${innerSpan}">Extract</span></h1>
<span class="${innerSpan}">Unstyled</span>`;

O también puede usar la API de creación de temas (que veremos a continuación) para crear propiedades personalizadas en el elemento principal que consumen los nodos secundarios. Esto puede parecer restrictivo, pero se ha dejado intencionalmente de esta manera para aumentar la capacidad de mantenimiento en bases de código más grandes. Significa que sabrá exactamente dónde se han declarado los estilos para cada elemento de su proyecto.

Tematización

Puedes usar el createTheme función para construir variables en un objeto TypeScript:

// title.css.ts
import {style,createTheme } from "@vanilla-extract/css";

// Creating the theme
export const [mainTheme,vars] = createTheme({
  color:{
    text: "hsl(210deg,60%,25%)",
    background: "hsl(210deg,30%,90%)"
  },
  lengths:{
    mediumGap: "30px"
  }
})

// Using the theme
export const titleStyle = style({
  backgroundColor:vars.color.background,
  color: vars.color.text,
  fontFamily: "helvetica, Sans-Serif",
  padding: vars.lengths.mediumGap,
  borderRadius: 20,
});

Luego, el extracto de vainilla le permite hacer una variante de su tema. TypeScript lo ayuda a garantizar que su variante use todos los mismos nombres de propiedad, por lo que recibe una advertencia si olvida agregar el background propiedad al tema.

Imagen de VS Code donde se muestra un tema declarado pero falta la propiedad de fondo, lo que provoca una gran cantidad de líneas onduladas rojas para advertir que la propiedad se ha olvidado

Así es como podrías crear un tema normal y un modo oscuro:

// title.css.ts
import {style,createTheme } from "@vanilla-extract/css";

export const [mainTheme,vars] = createTheme({
  color:{
    text: "hsl(210deg,60%,25%)",
    background: "hsl(210deg,30%,90%)"
  },
  lengths:{
    mediumGap: "30px"
  }
})
// Theme variant - note this part does not use the array syntax
export const darkMode = createTheme(vars,{
  color:{
    text:"hsl(210deg,60%,80%)",
    background: "hsl(210deg,30%,7%)",
  },
  lengths:{
    mediumGap: "30px"
  }
})
// Consuming the theme 
export const titleStyle = style({
  backgroundColor: vars.color.background,
  color: vars.color.text,
  fontFamily: "helvetica, Sans-Serif",
  padding: vars.lengths.mediumGap,
  borderRadius: 20,
});

Luego, usando JavaScript, puede aplicar dinámicamente los nombres de clase devueltos por vanilla-extract para cambiar de tema:

// title.ts
import {titleStyle,mainTheme,darkMode} from "./title.css";

document.getElementById("root").innerHTML = 
`<div class="${mainTheme}" id="wrapper">
  <h1 class="${titleStyle}">Vanilla Extract</h1>
  <button onClick="document.getElementById('wrapper').className="${darkMode}"">Dark mode</button>
</div>`

¿Cómo funciona esto debajo del capó? Los objetos que declaras en el createTheme se convierten en propiedades personalizadas de CSS adjuntas a la clase del elemento. Estas propiedades personalizadas se codifican para evitar conflictos. El CSS de salida para nuestro mainTheme ejemplo se ve así:

.src__ohrzop0 {
  --color-brand__ohrzop1: hsl(210deg,80%,25%);
  --color-text__ohrzop2: hsl(210deg,60%,25%);
  --color-background__ohrzop3: hsl(210deg,30%,90%);
  --lengths-mediumGap__ohrzop4: 30px;
}

Y la salida CSS de nuestro darkMode el tema se ve así:

.src__ohrzop5 {
  --color-brand__ohrzop1: hsl(210deg,80%,60%);
  --color-text__ohrzop2: hsl(210deg,60%,80%);
  --color-background__ohrzop3: hsl(210deg,30%,10%);
  --lengths-mediumGap__ohrzop4: 30px;
}

Entonces, todo lo que necesitamos cambiar en nuestro código de usuario es el nombre de la clase. Aplica el darkmode nombre de clase al elemento padre, y el mainTheme las propiedades personalizadas se intercambian por darkMode unos.

API de recetas

El style y createTheme Las funciones brindan suficiente poder para diseñar un sitio web por sí mismas, pero vanilla-extract proporciona algunas API adicionales para promover la reutilización. La API de Recetas le permite crear un montón de variantes para un elemento, que puede elegir en su marcado o código de usuario.

Primero, debe instalarse por separado:

npm install @vanilla-extract/recipes

Así es como funciona. importas el recipe función y pasar un objeto con las propiedades base y variants:

// button.css.ts
import { recipe } from '@vanilla-extract/recipes';

export const buttonStyles = recipe({
  base:{
    // Styles that get applied to ALL buttons go in here
  },
  variants:{
    // Styles that we choose from go in here
  }
});

Dentro base, puede declarar los estilos que se aplicarán a todas las variantes. Dentro variants, puede proporcionar diferentes formas de personalizar el elemento:

// button.css.ts
import { recipe } from '@vanilla-extract/recipes';
export const buttonStyles = recipe({
  base: {
    fontWeight: "bold",
  },
  variants: {
    color: {
      normal: {
        backgroundColor: "hsl(210deg,30%,90%)",
      },
      callToAction: {
        backgroundColor: "hsl(210deg,80%,65%)",
      },
    },
    size: {
      large: {
        padding: 30,
      },
      medium: {
        padding: 15,
      },
    },
  },
});

Luego puede declarar qué variante desea usar en el marcado:

// button.ts
import { buttonStyles } from "./button.css";

<button class=`${buttonStyles({color: "normal",size: "medium",})}`>Click me</button>

¡Y vanilla-extract aprovecha TypeScript dando autocompletado para sus propios nombres de variantes!

Puede nombrar sus variantes como desee y poner las propiedades que desee en ellas, así:

// button.css.ts
export const buttonStyles = recipe({
  variants: {
    animal: {
      dog: {
        backgroundImage: 'url("./dog.png")',
      },
      cat: {
        backgroundImage: 'url("./cat.png")',
      },
      rabbit: {
        backgroundImage: 'url("./rabbit.png")',
      },
    },
  },
});

Puede ver cómo esto sería increíblemente útil para construir un sistema de diseño, ya que puede crear componentes reutilizables y controlar las formas en que varían. Estas variaciones se vuelven fácilmente detectables con TypeScript: todo lo que necesita escribir es CMD/CTRL + Space (en la mayoría de los editores) y obtiene una lista desplegable de las diferentes formas de personalizar su componente.

Utilidad primero con Sprinkles

Sprinkles es un marco de utilidad primero construido sobre extracto de vainilla. Así es como lo describen los documentos de extracto de vainilla:

Básicamente, es como crear su propia versión de Tailwind, Styled System, etc., sin tiempo de ejecución y con seguridad de tipos.

Entonces, si no eres fanático de nombrar cosas (todos tenemos pesadillas de crear un outer-wrapper div y luego nos damos cuenta de que necesitamos envolverlo con un . . . outer-outer-wrapper ) Las chispas pueden ser su forma preferida de usar extracto de vainilla.

La API de Sprinkles también debe instalarse por separado:

npm install @vanilla-extract/sprinkles

Ahora podemos crear algunos bloques de construcción para que los usen nuestras funciones de utilidad. Vamos a crear una lista de colores y longitudes declarando un par de objetos. Los nombres de las claves de JavaScript pueden ser los que queramos. Los valores deberán ser valores CSS válidos para las propiedades CSS para las que planeamos usarlos:

// sprinkles.css.ts
const colors = {
  blue100: "hsl(210deg,70%,15%)",
  blue200: "hsl(210deg,60%,25%)",
  blue300: "hsl(210deg,55%,35%)",
  blue400: "hsl(210deg,50%,45%)",
  blue500: "hsl(210deg,45%,55%)",
  blue600: "hsl(210deg,50%,65%)",
  blue700: "hsl(207deg,55%,75%)",
  blue800: "hsl(205deg,60%,80%)",
  blue900: "hsl(203deg,70%,85%)",
};

const lengths = {
  small: "4px",
  medium: "8px",
  large: "16px",
  humungous: "64px"
};

Podemos declarar a qué propiedades CSS se aplicarán estos valores usando el defineProperties función:

  • Pasarle un objeto con un properties propiedad.
  • En properties, declaramos un objeto donde las claves son las propiedades CSS que el usuario puede configurar (estas deben ser propiedades CSS válidas) y los valores son los objetos que creamos anteriormente (nuestras listas de colors y lengths).
// sprinkles.css.ts
import { defineProperties } from "@vanilla-extract/sprinkles";

const colors = {
  blue100: "hsl(210deg,70%,15%)"
  // etc.
}

const lengths = {
  small: "4px",
  // etc.
}

const properties = defineProperties({
  properties: {
    // The keys of this object need to be valid CSS properties
    // The values are the options we provide the user
    color: colors,
    backgroundColor: colors,
    padding: lengths,
  },
});

Luego, el paso final es pasar el valor de retorno de defineProperties al createSprinkles y exporte el valor devuelto:

// sprinkles.css.ts
import { defineProperties, createSprinkles } from "@vanilla-extract/sprinkles";

const colors = {
  blue100: "hsl(210deg,70%,15%)"
  // etc.
}

const lengths = {
  small: "4px",
  // etc. 
}

const properties = defineProperties({
  properties: {
    color: colors,
    // etc. 
  },
});
export const sprinkles = createSprinkles(properties);

Luego podemos comenzar a diseñar dentro de nuestros componentes en línea llamando al sprinkles en el atributo class y eligiendo que opciones queremos para cada elemento.

// index.ts
import { sprinkles } from "./sprinkles.css";
document.getElementById("root").innerHTML = `<button class="${sprinkles({
  color: "blue200",
  backgroundColor: "blue800",
  padding: "large",
})}">Click me</button>
</div>`;

La salida de JavaScript contiene una cadena de nombre de clase para cada propiedad de estilo. Estos nombres de clase coinciden con una sola regla en el archivo CSS de salida.

<button class="src_color_blue200__ohrzop1 src_backgroundColor_blue800__ohrzopg src_padding_large__ohrzopk">Click me</button>

Como puede ver, esta API le permite diseñar elementos dentro de su marcado usando un conjunto de restricciones predefinidas. También evita la difícil tarea de encontrar nombres de clases para cada elemento. El resultado es algo que se parece mucho a Tailwind, pero que también se beneficia de toda la infraestructura que se ha construido alrededor de TypeScript.

La API de Sprinkles también le permite escribir condiciones y abreviaturas para crear estilos receptivos usando clases de utilidad.

Terminando

vanilla-extract se siente como un gran paso nuevo en las herramientas de CSS. Se ha pensado mucho en convertirlo en una solución robusta e intuitiva para el estilo que utiliza toda la potencia que proporciona la escritura estática.

Otras lecturas

  • documentación de extracto de vainilla
  • Charla de Mark sobre el extracto de vainilla
  • extracto de vainilla Discord
  • Ejemplos de código en CodeSandbox

(Visited 3 times, 1 visits today)