Compatibilidad con CSS-in-JS en Herramientas para desarrolladores

Alex Rudenko
Alex Rudenko

Este artículo trata sobre la compatibilidad con CSS en JS en Herramientas para desarrolladores que se lanzó desde Chrome 85 y, en general, qué entendemos por CSS-in-JS y cómo se diferencia del CSS normal que ha sido compatible con Herramientas para desarrolladores por mucho tiempo.

¿Qué es CSS-in-JS?

La definición de CSS-in-JS es bastante imprecisa. En términos generales, es un enfoque para administrar el código CSS con JavaScript. Por ejemplo, podría significar que el contenido CSS se define mediante JavaScript y que la app genera sobre la marcha el resultado final de CSS.

En el contexto de Herramientas para desarrolladores, CSS-in-JS significa que el contenido de CSS se inserta en la página mediante las API de CSSOM. El código CSS regular se inyecta utilizando elementos <style> o <link>, y tiene una fuente estática (p.ej., un nodo del DOM o un recurso de red). Por el contrario, CSS-in-JS a menudo no tiene una fuente estática. Un caso especial aquí es que el contenido de un elemento <style> se puede actualizar con la API de CSSOM, lo que provoca que la fuente no esté sincronizada con la hoja de estilo CSS real.

Si usas cualquier biblioteca CSS-in-JS (p.ej., styled-component, Emotion o JSS), la biblioteca podría insertar estilos con las APIs de CSSOM de forma interna según el modo de desarrollo y el navegador.

Veamos algunos ejemplos de cómo insertar una hoja de estilo usando la API de CSSOM similar a lo que hacen las bibliotecas de CSS-in-JS.

// Insert new rule to an existing CSS stylesheet
const element = document.querySelector('style');
const stylesheet = element.sheet;
stylesheet.replaceSync('.some { color: blue; }');
stylesheet.insertRule('.some { color: green; }');

También puedes crear una hoja de estilo completamente nueva:

// Create a completely new stylesheet
const stylesheet = new CSSStyleSheet();
stylesheet.replaceSync('.some { color: blue; }');
stylesheet.insertRule('.some { color: green; }');

// Apply constructed stylesheet to the document
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];

Compatibilidad con CSS en Herramientas para desarrolladores

En Herramientas para desarrolladores, la función más usada cuando se trabaja con CSS es el panel Styles. En el panel Estilos, puedes ver qué reglas se aplican a un elemento determinado, editar las reglas y ver los cambios en la página en tiempo real.

Antes del año pasado, la compatibilidad de las reglas de CSS modificadas con las API de CSSOM era bastante limitada: solo podías ver las reglas aplicadas, pero no podías editarlas. El objetivo principal que teníamos el año pasado era permitir la edición de reglas CSS-in-JS mediante el panel Styles. En ocasiones, también llamamos a los estilos CSS-in-JS "constructed" para indicar que se construyeron utilizando API web.

Analicemos los detalles del trabajo de edición de Estilos en Herramientas para desarrolladores.

Mecanismo de edición de estilo en Herramientas para desarrolladores

Mecanismo de edición de estilo en Herramientas para desarrolladores

Cuando seleccionas un elemento en Herramientas para desarrolladores, se muestra el panel Estilos. El panel Styles emite un comando de CDP llamado CSS.getMatchedStylesForNode para obtener las reglas de CSS que se aplican al elemento. CDP es la sigla en inglés de protocolo para Herramientas para desarrolladores de Chrome y es una API que permite que el frontend de Herramientas para desarrolladores obtenga información adicional sobre la página inspeccionada.

Cuando se invoca, CSS.getMatchedStylesForNode identifica todas las hojas de estilo en el documento y las analiza mediante el analizador de CSS del navegador. A continuación, crea un índice que asocia cada regla CSS con una posición en la fuente de la hoja de estilo.

Podrías preguntarte por qué se debe volver a analizar el CSS. El problema es que, por razones de rendimiento, el navegador en sí no se preocupa por las posiciones de origen de las reglas CSS y, por lo tanto, no las almacena. Sin embargo, Herramientas para desarrolladores necesita las posiciones de origen para admitir la edición de CSS. No queremos que los usuarios habituales de Chrome paguen la sanción de rendimiento, pero queremos que los usuarios de Herramientas para desarrolladores tengan acceso a las posiciones de origen. Este enfoque de volver a analizar aborda ambos casos de uso con desventajas mínimas.

A continuación, la implementación de CSS.getMatchedStylesForNode le solicita al motor de estilo del navegador que proporcione reglas de CSS que coincidan con el elemento determinado. Por último, el método asocia las reglas que muestra el motor de estilo con el código fuente y proporciona una respuesta estructurada sobre las reglas de CSS, de modo que Herramientas para desarrolladores sepa qué parte de la regla es el selector o las propiedades. Permite que las Herramientas para desarrolladores editen el selector y las propiedades de forma independiente.

Ahora, veamos la edición. ¿Recuerdas que CSS.getMatchedStylesForNode muestra posiciones de origen para cada regla? Eso es fundamental para la edición. Cuando cambias una regla, Herramientas para desarrolladores emite otro comando de CDP que realmente actualiza la página. El comando incluye la posición original del fragmento de la regla que se está actualizando y el texto nuevo con el que se debe actualizar el fragmento.

En el backend, cuando se maneja la llamada de edición, Herramientas para desarrolladores actualiza la hoja de estilo de destino. También actualiza la copia de la fuente de la hoja de estilo que mantiene y actualiza las posiciones de origen para la regla actualizada. En respuesta a la llamada de edición, el frontend de Herramientas para desarrolladores recupera las posiciones actualizadas del fragmento de texto que se acaba de actualizar.

Esto explica por qué la edición de CSS-in-JS en Herramientas para desarrolladores no funcionó de inmediato: CSS-in-JS no tiene una fuente real almacenada en ningún lugar y las reglas de CSS se encuentran en la memoria del navegador en las estructuras de datos del CSSOM.

Cómo agregamos compatibilidad con CSS-in-JS

Por lo tanto, para admitir la edición de reglas CSS en JS, decidimos que la mejor solución sería crear una fuente para hojas de estilo construidas que se puedan editar utilizando el mecanismo existente descrito anteriormente.

El primer paso es compilar el texto fuente. El motor de estilo del navegador almacena las reglas de CSS en la clase CSSStyleSheet. Esa clase es aquella cuyas instancias puedes crear desde JavaScript como se explicó anteriormente. El código para compilar el texto fuente es el siguiente:

String InspectorStyleSheet::CollectStyleSheetRules() {
  StringBuilder builder;
  for (unsigned i = 0; i < page_style_sheet_->length(); i++) {
    builder.Append(page_style_sheet_->item(i)->cssText());
    builder.Append('\n');
  }
  return builder.ToString();
}

Itera en las reglas que se encuentran en una instancia de CSSStyleSheet y compila una sola string a partir de ella. Este método se invoca cuando se crea una instancia de la clase InspectorStyleSheet. La clase InspectorStyleSheet une una instancia de CSSStyleSheet y extrae metadatos adicionales que son necesarios para Herramientas para desarrolladores:

void InspectorStyleSheet::UpdateText() {
  String text;
  bool success = InspectorStyleSheetText(&text);
  if (!success)
    success = InlineStyleSheetText(&text);
  if (!success)
    success = ResourceStyleSheetText(&text);
  if (!success)
    success = CSSOMStyleSheetText(&text);
  if (success)
    InnerSetText(text, false);
}

En este fragmento, vemos que CSSOMStyleSheetText llama a CollectStyleSheetRules de forma interna. Se invoca CSSOMStyleSheetText si la hoja de estilo no está intercalada ni si es una hoja de estilo de recurso. En esencia, estos dos fragmentos ya permiten la edición básica de las hojas de estilo que se crean con el constructor new CSSStyleSheet().

Un caso especial son las hojas de estilo asociadas con una etiqueta <style> que se mutaron con la API de CSSOM. En este caso, la hoja de estilo contiene el texto de origen y las reglas adicionales que no están presentes en la fuente. Para manejar este caso, presentamos un método que combina esas reglas adicionales en el texto de origen. Aquí, el orden es importante porque las reglas CSS se pueden insertar en el medio del texto fuente original. Por ejemplo, imagina que el elemento <style> original contenía el siguiente texto:

/* comment */
.rule1 {}
.rule3 {}

Luego, la página insertó algunas reglas nuevas con la API de JS, lo que generó el siguiente orden de reglas: .rule0, .rule1, .rule2, .rule3, .rule4. El texto de origen resultante después de la operación de combinación debería ser el siguiente:

.rule0 {}
/* comment */
.rule1 {}
.rule2 {}
.rule3 {}
.rule4 {}

La preservación de los comentarios y la sangría originales es importante para el proceso de edición porque las posiciones del texto de origen de las reglas deben ser precisas.

Otro aspecto especial de las hojas de estilo CSS-in-JS es que la página puede modificarlas en cualquier momento. Si las reglas reales del CSSOM no se sincronizan con la versión de texto, no funcionará la edición. Para ello, presentamos el llamado sondeo, que permite que el navegador notifique a la parte del backend de Herramientas para desarrolladores cuando se está mutando una hoja de estilo. Las hojas de estilo mutadas se sincronizan durante la siguiente llamada a CSS.getMatchedStylesForNode.

Con todas estas piezas en su lugar, la edición de CSS-in-JS ya funciona, pero queríamos mejorar la IU para indicar si se creó una hoja de estilo. Agregamos un atributo nuevo llamado isConstructed al CSS.CSSStyleSheetHeader de CDP que el frontend utiliza para mostrar correctamente la fuente de una regla de CSS:

Hoja de estilo construible

Conclusiones

Para resumir nuestra historia, repasamos los casos de uso relevantes relacionados con CSS-in-JS que no fueron admitidos en Herramientas para desarrolladores y repasamos la solución para admitir esos casos de uso. La parte interesante de esta implementación es que pudimos aprovechar la funcionalidad existente haciendo que las reglas CSS de CSSOM tengan un texto fuente regular, lo que evita la necesidad de volver a diseñar por completo la edición del estilo en Herramientas para desarrolladores.

Para obtener más información, consulta nuestra propuesta de diseño o el error de seguimiento de Chromium que hace referencia a todos los parches relacionados.

Descarga los canales de vista previa

Considera usar Chrome Canary, Dev o Beta como tu navegador de desarrollo predeterminado. Estos canales de vista previa te brindan acceso a las funciones más recientes de Herramientas para desarrolladores, prueban las API de vanguardia de la plataforma web y te permiten encontrar problemas en tu sitio antes que los usuarios.

Cómo comunicarte con el equipo de Herramientas para desarrolladores de Chrome

Usa las siguientes opciones para hablar sobre las nuevas funciones y los cambios en la publicación, o cualquier otro aspecto relacionado con Herramientas para desarrolladores.

  • Envíanos una sugerencia o un comentario a través de crbug.com.
  • Informa un problema en Herramientas para desarrolladores con Más opciones   Más   > Ayuda > Informar problemas de Herramientas para desarrolladores en esta herramienta.
  • Envía un tweet a @ChromeDevTools.
  • Deje comentarios en las Novedades de los videos de YouTube de Herramientas para desarrolladores o en las sugerencias de Herramientas para desarrolladores los videos de YouTube.