Compatibilidad con CSS-in-JS en Herramientas para desarrolladores

Alex Rudenko
Alex Rudenko

Este artículo habla sobre la compatibilidad con CSS en JS en Herramientas para desarrolladores, que está disponible desde Chrome 85, y, en general, lo que queremos decir con CSS en JS y cómo se diferencia del CSS común que ha sido compatible con Herramientas para desarrolladores durante mucho tiempo.

¿Qué es CSS en JS?

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

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

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

Veamos algunos ejemplos sobre cómo puedes insertar una hoja de estilo usando la API de CSSOM de manera similar a como lo hacen las bibliotecas 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 las Herramientas para desarrolladores, la función más usada cuando se trabaja con CSS es el panel Styles. En el panel Estilos, puedes ver las reglas que se aplican a un elemento determinado, editarlas y ver los cambios en la página en tiempo real.

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

Veamos en detalle los trabajos de edición de Styles 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 significa protocolo de 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 del documento y las analiza mediante el analizador de CSS del navegador. Luego, crea un índice que asocia cada regla de CSS con una posición en la fuente de la hoja de estilo.

Podrías preguntarte por qué necesita volver a analizar el CSS. El problema aquí es que, por razones de rendimiento, el navegador en sí no está relacionado con las posiciones de origen de las reglas CSS y, por lo tanto, no las almacena. Sin embargo, las Herramientas para desarrolladores necesita que las posiciones de la fuente sean compatibles con la edición de CSS. No queremos que los usuarios normales de Chrome paguen la penalización de rendimiento, pero sí queremos que los usuarios de Herramientas para desarrolladores tengan acceso a las posiciones de origen. Este enfoque de nuevo análisis 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 devueltas por el motor de estilo con el código fuente y proporciona una respuesta estructurada sobre las reglas de CSS para 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 en realidad 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 el fragmento necesita actualizarse.

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 la fuente 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 en JS en Herramientas para desarrolladores no funcionaba de inmediato: CSS-in-JS no tiene una fuente real almacenada en ningún lugar y las reglas de CSS viven en la memoria del navegador en estructuras de datos del CSSOM.

Cómo agregamos la compatibilidad con CSS en JS

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

El primer paso es compilar el texto fuente. El motor de estilo del navegador almacena las reglas 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 las reglas encontradas en una instancia de CSSStyleSheet y crea 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 los metadatos adicionales que requieren las 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 un CSSOMStyleSheetText que llama a CollectStyleSheetRules internamente. CSSOMStyleSheetText se invoca si la hoja de estilo no está intercalada o si es una hoja de estilo de recursos. Básicamente, 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 reglas adicionales que no están presentes en la fuente. Para manejar este caso, presentamos un método que permite fusionar esas reglas adicionales en el texto de origen. En este caso, 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 produjo 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 las sangrías originales es importante para el proceso de edición, ya que las posiciones del texto de origen de las reglas deben ser precisas.

Otro aspecto especial de las hojas de estilo CSS en JS es que la página las puede cambiar en cualquier momento. Si las reglas del CSSOM reales no estuvieran sincronizadas con la versión de texto, la edición no funcionaría. Para ello, presentamos el llamado sondeo, que permite que el navegador notifique a la parte del backend de Herramientas para desarrolladores cuando se muta 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 en JS ya funciona, pero queríamos mejorar la IU para indicar si se había construido una hoja de estilo. Agregamos un nuevo atributo llamado isConstructed al CSS.CSSStyleSheetHeader de CDP que el frontend utiliza para mostrar correctamente la fuente de una regla de CSS:

Hoja de estilo constructable

Conclusiones

Para recapitular nuestra historia, analizamos los casos de uso relevantes relacionados con CSS en JS que Herramientas para desarrolladores no admitía y vimos 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 de CSS de CSSOM tengan un texto de origen regular, lo que evita la necesidad de rediseñar completamente la edición de 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 navegadores de desarrollo predeterminados. Estos canales de vista previa te brindan acceso a las funciones más recientes de Herramientas para desarrolladores, prueban API de plataforma web de vanguardia y detectan problemas en tu sitio antes que los usuarios.

Comunicarse 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 otra cosa relacionada con Herramientas para desarrolladores.

  • Para enviarnos sugerencias o comentarios, accede a crbug.com.
  • Para informar un problema de Herramientas para desarrolladores, use Más opciones   Más   > Ayuda > Informar problemas de Herramientas para desarrolladores en Herramientas para desarrolladores.
  • Twittea a @ChromeDevTools.
  • Deja comentarios en nuestros videos de YouTube de Herramientas para desarrolladores o en videos de YouTube de las Sugerencias de las Herramientas para desarrolladores.