Las compensaciones de CSS-in-JS

Foto de Artem Bali.

Recientemente escribí una descripción general de nivel superior de CSS-in-JS, principalmente hablando de los problemas que este enfoque está tratando de resolver. Los autores de la biblioteca rara vez invierten tiempo en describir las compensaciones de su solución. A veces es porque son demasiado parciales, y otras simplemente no saben cómo los usuarios aplican la herramienta. Así que este es un intento de describir las compensaciones que he visto hasta ahora. Creo que es importante mencionar que soy el autor de JSS, por lo que debería considerarse parcial.

Impacto social

Hay una capa de personas que trabajan en la plataforma web y no conocen JavaScript. A esas personas se les paga por escribir HTML y CSS. CSS-in-JS ha tenido un gran impacto en el flujo de trabajo de los desarrolladores. Un cambio verdaderamente transformador nunca se puede hacer sin que algunas personas se queden atrás. No sé si CSS-in-JS tiene que ser la única forma, pero la adopción masiva es una clara señal de problemas con el uso de CSS en aplicaciones modernas.

Una gran parte del problema es nuestra incapacidad para comunicar con precisión los casos de uso en los que brilla CSS-in-JS y cómo usarlo adecuadamente para una tarea. Muchos entusiastas de CSS-in-JS han tenido éxito en la promoción de la tecnología, pero no muchos críticos hablaron sobre las compensaciones de una manera constructiva, sin tener cambios baratos en las herramientas. Como resultado, dejamos muchas compensaciones ocultas y no hicimos un gran esfuerzo para proporcionar la explicación y las soluciones.

CSS-in-JS es un intento de hacer que los casos de uso complejos sean más fáciles de manejar, ¡así que no lo empuje donde no es necesario!

Costo de tiempo de ejecución

Cuando CSS se genera a partir de JavaScript en tiempo de ejecución, en el navegador, hay una sobrecarga inherente. La sobrecarga de tiempo de ejecución varía de una biblioteca a otra. Este es un buen punto de referencia genérico, pero asegúrese de hacer sus propias pruebas. Las principales diferencias en el tiempo de ejecución aparecen dependiendo de la necesidad de tener un análisis CSS completo de cadenas de plantillas, cantidad de optimizaciones, detalles de implementación de estilos dinámicos, algoritmo de hash y costo de integración de marcos. *

Además de la posible sobrecarga de tiempo de ejecución, debe considerar 4 estrategias de agrupación diferentes, porque algunas bibliotecas CSS-in-JS admiten múltiples estrategias y depende del usuario aplicarlas. * *

Estrategia 1: solo generación de tiempo de ejecución

La generación de CSS en tiempo de ejecución es una técnica que genera una cadena CSS en JavaScript y luego la inyecta usando una etiqueta de estilo en el documento. Esta técnica produce una hoja de estilo, NO estilos en línea.

La compensación de la generación de tiempo de ejecución es la incapacidad de proporcionar contenido con estilo en la etapa inicial, ya que el documento comienza a cargarse. Este enfoque generalmente se adapta a aplicaciones sin contenido que pueden ser útiles de inmediato. Por lo general, tales aplicaciones requieren interacciones del usuario antes de que realmente puedan ser útiles para un usuario. A menudo, tales aplicaciones funcionan con contenido que es tan dinámico que se vuelve obsoleto tan pronto como lo cargue, por lo que debe establecer una tubería de actualización desde el principio, por ejemplo, Twitter. Además, cuando un usuario inicia sesión, no es necesario proporcionar HTML para SEO.

Si la interacción requiere JavaScript, el paquete debe cargarse antes de que la aplicación esté lista. Por ejemplo, puede mostrar el contenido de un canal predeterminado al cargar Slack en el documento, pero es probable que el usuario quiera cambiar el canal inmediatamente después. Entonces, si cargó el contenido inicial solo para tirarlo inmediatamente.

El rendimiento percibido de tales aplicaciones se puede mejorar con marcadores de posición y otros trucos para que la aplicación se sienta más instantánea de lo que realmente es. Dichas aplicaciones suelen tener muchos datos, por lo que no serán útiles tan rápido como un artículo.

Estrategia 2: generación de tiempo de ejecución con CSS crítico

CSS crítico es la cantidad mínima de CSS que se requiere para diseñar la página en su estado inicial. Se representa con una etiqueta de estilo en el encabezado del documento. Esta técnica se usa ampliamente con y sin CSS-in-JS. En ambos casos, es probable que cargue dos veces las reglas CSS, una vez como parte del CSS crítico y otra como parte del paquete JavaScript o CSS. El tamaño de CSS crítico puede ser bastante grande dependiendo de la cantidad de contenido. Por lo general, el documento no se almacenará en caché.

Sin CSS crítico, una aplicación de una sola página con mucho contenido estático y tiempo de ejecución CSS-in-JS tendrá que mostrar marcadores de posición en lugar de contenido. Esto es malo porque podría haber sido útil para un usuario mucho antes, mejorando la accesibilidad en dispositivos de gama baja y para conexiones de bajo ancho de banda.

Con CSS crítico, la generación de CSS en tiempo de ejecución se puede hacer en una etapa posterior, sin bloquear la interfaz de usuario en la fase inicial. Sin embargo, tenga en cuenta que en los dispositivos móviles de gama baja, que tienen aproximadamente más de 5 años, la generación de CSS a partir de JavaScript puede tener un impacto negativo en el rendimiento. Depende en gran medida de la cantidad de CSS que se genere y de la biblioteca utilizada, por lo que no se puede generalizar.

La compensación de esta estrategia es el costo de la extracción de CSS crítico y el costo de la generación de CSS en tiempo de ejecución.

Estrategia 3: solo extracción en tiempo de construcción

Esta estrategia es la predeterminada en la web sin CSS-in-JS. Algunas bibliotecas CSS-in-JS le permiten extraer CSS estático en el momento de la compilación. * En este caso, no hay sobrecarga de tiempo de ejecución, CSS se representa en la página usando una etiqueta de enlace. El costo de la generación CSS se paga una vez por adelantado.

Hay 2 compensaciones principales aquí:

  1. No puede usar algunas de las API dinámicas que CSS-in-JS ofrece en tiempo de ejecución, porque no tiene acceso al estado. A menudo, aún no puede usar las propiedades personalizadas de CSS, ya que no son compatibles con todos los navegadores y no pueden ser rellenadas por naturaleza en tiempo de compilación. En este caso, tendrá que hacer soluciones para temas dinámicos y estilos basados ​​en estado. *
  2. Sin CSS crítico y con un caché vacío, bloqueará la primera pintura, hasta que se cargue su paquete CSS. Un elemento de enlace en la cabecera del documento bloquea la representación de HTML.
  3. Especificidad no determinista con división de paquetes basada en páginas en aplicaciones de una sola página. *

Estrategia 4: extracción en tiempo de compilación con CSS crítico

Esta estrategia tampoco es exclusiva de CSS-in-JS. La extracción estática completa con CSS crítico ofrece el mejor rendimiento cuando se trabaja con una aplicación más estática. Este enfoque todavía tiene las compensaciones antes mencionadas de un CSS estático, excepto que la etiqueta de enlace de bloqueo se puede mover al final del documento.

Hay 4 estrategias principales de renderizado CSS. Solo 2 de ellos son específicos de CSS-in-JS y ninguno de ellos se aplica a todas las bibliotecas.

Accesibilidad

CSS-in-JS puede disminuir la accesibilidad cuando se usa de manera incorrecta. Esto sucederá cuando se implemente un sitio de contenido en gran parte estático sin extracción de CSS crítico para que el HTML no se pueda pintar antes de cargar y evaluar el paquete de JavaScript. Esto también puede suceder cuando se procesa un archivo CSS enorme utilizando una etiqueta de enlace de bloqueo en el encabezado del documento, que es el problema actual más popular con la incrustación tradicional y no es específico de CSS-in-JS.

Los desarrolladores deben asumir la responsabilidad de la accesibilidad. Todavía existe una fuerte idea equivocada de que una conexión a Internet inestable es un problema de los países económicamente débiles. Tendemos a olvidar que tenemos problemas de conectividad todos los días cuando ingresamos a un sistema ferroviario subterráneo o a un edificio grande. Una conexión móvil estable sin cables es un mito. ¡Ni siquiera es fácil tener una conexión WiFi estable, por ejemplo, una red WI-FI de 2,4 GHz puede recibir interferencias de un horno microondas!

El costo de CSS crítico con renderizado del lado del servidor

Para obtener la extracción de CSS crítico para CSS-in-JS, necesitamos SSR. SSR es un proceso de generar el HTML final para un estado dado de una aplicación en el servidor. De hecho, puede ser un proceso bastante complejo y costoso. Requiere una cierta cantidad de ciclos de CPU en el servidor para cada solicitud HTTP.

CSS-in-JS generalmente aprovecha el hecho de que está enganchado a la canalización de representación HTML. * Sabe qué HTML se procesó y qué CSS necesita para que pueda producir la cantidad mínima absoluta. El CSS crítico agrega una sobrecarga adicional a la representación HTML en el servidor porque ese CSS también debe compilarse en una cadena CSS final. Sin embargo, en algunos escenarios, es difícil o incluso imposible almacenar en caché en el servidor.

Renderizar caja negra

Debe ser consciente de cómo una biblioteca CSS-in-JS que está utilizando procesa su CSS. Por ejemplo, las personas a menudo no son conscientes de cómo Styled Components y Emotion implementan estilos dinámicos. Los estilos dinámicos son una sintaxis que permite el uso de funciones de JavaScript dentro de la declaración de estilos. Esas funciones aceptan accesorios y devuelven un bloque CSS.

Para mantener la especificidad del orden de origen coherente, las dos bibliotecas mencionadas anteriormente generan una nueva regla CSS si contiene una declaración dinámica y el componente se actualiza con nuevos accesorios. Para demostrar lo que quiero decir, creé este sandbox. En JSS decidimos tomar una compensación diferente, lo que nos permite actualizar las propiedades dinámicas sin generar nuevas reglas CSS. *

Curva de aprendizaje empinada

Para las personas que están familiarizadas con CSS, pero que son nuevas en JavaScript, la cantidad inicial de trabajo para ponerse al día con CSS-in-JS podría ser bastante grande.

No necesita ser un desarrollador profesional de JavaScript para escribir CSS-in-JS, hasta el punto en que se involucra una lógica compleja. No podemos generalizar la complejidad del estilo, ya que realmente depende del caso de uso. En los casos en que CSS-in-JS se vuelve complejo, es probable que la implementación con CSS vainilla sea aún más compleja.

Para el estilo básico CSS-in-JS, uno necesita saber cómo declarar variables, cómo usar cadenas de plantillas e interpolar valores de JavaScript. Si se usa la notación de objeto, uno debe saber cómo trabajar con objetos JavaScript y la sintaxis basada en objetos específicos de la biblioteca. Si el estilo dinámico está involucrado, uno necesita saber cómo usar las funciones y condicionales de JavaScript.

En general, hay una curva de aprendizaje, no podemos negarla. Sin embargo, esta curva de aprendizaje generalmente no es mucho más grande que aprender Sass. De hecho, creé este curso de egghead para demostrar esto.

Sin interoperabilidad

La mayoría de las librerías CSS-in-JS no son interoperables. Esto significa que los estilos escritos con una biblioteca no se pueden representar con una biblioteca diferente. Prácticamente significa que no puede cambiar su aplicación completa fácilmente de una implementación a otra. También significa que no puede compartir fácilmente su interfaz de usuario en NPM sin incluir su biblioteca CSS-in-JS de elección en el paquete del consumidor a menos que tenga una extracción estática en tiempo de construcción para su CSS.

Hemos comenzado a trabajar en el formato ISTF que se supone que soluciona este problema, pero desafortunadamente aún no hemos tenido tiempo de llevarlo a un estado listo para producción. *

Creo que compartir componentes de interfaz de usuario agnósticos de framework reutilizables en el dominio público sigue siendo un problema generalmente difícil de resolver.

Riesgos de seguridad

Es posible introducir fugas de seguridad con CSS-in-JS. Al igual que con cualquier aplicación del lado del cliente, siempre debe escapar de la entrada del usuario antes de representarla.

Este artículo le dará más información y algunos ejemplos de desfiguración.

Nombres de clase ilegibles

Algunas personas todavía piensan que es importante que mantengamos nombres de clase legibles en la web. Actualmente, muchas bibliotecas CSS-in-JS proporcionan nombres de clase significativos basados ​​en el nombre de la declaración o el nombre del componente en modo de desarrollo. Algunos de ellos incluso le permiten personalizar la función del generador de nombres de clase.

Sin embargo, en el modo de producción, la mayoría de ellos generan nombres más cortos para una carga útil más pequeña. Esta es una compensación que el usuario de la biblioteca tiene que hacer y personalizar la biblioteca si es necesario.

Conclusión

Existen compensaciones, y probablemente ni siquiera las mencioné todas. Pero la mayoría de ellos no se aplican universalmente a todos los CSS-in-JS. Dependen de la biblioteca que use y de cómo la use.

* Tomará un artículo dedicado para explicar esta oración. Avísame en Twitter (@ oleg008) sobre cuál te gustaría leer más.