Modernización de la infraestructura de CSS en Herramientas para desarrolladores

Actualización de la arquitectura de DevTools: Modernización de la infraestructura de CSS en DevTools

Esta publicación forma parte de una serie de publicaciones de blog en las que se describen los cambios que estamos realizando en la arquitectura de DevTools y cómo se compila. Explicaremos cómo funcionaba el CSS en DevTools históricamente y cómo modernizamos nuestro CSS en DevTools para prepararnos para migrar (en algún momento) a una solución estándar web para cargar CSS en archivos JavaScript.

Estado anterior del CSS en DevTools

DevTools implementó CSS de dos maneras diferentes: una para los archivos CSS que se usan en la parte heredada de DevTools y otra para los componentes web modernos que se usan en DevTools.

La implementación de CSS en DevTools se definió hace muchos años y ahora está desactualizada. DevTools se aferró al uso del patrón module.json y se hizo un gran esfuerzo para quitar estos archivos. El último bloqueador para quitar estos archivos es la sección resources, que se usa para cargar archivos CSS.

Queríamos dedicar tiempo a explorar diferentes soluciones potenciales que, en última instancia, podrían transformarse en secuencias de comandos de módulos de CSS. El objetivo era quitar la deuda técnica causada por el sistema heredado, pero también facilitar el proceso de migración a las secuencias de comandos de módulos CSS.

Los archivos CSS que se encontraban en DevTools se consideraban "heredados", ya que se cargaban con un archivo module.json, que se está quitando. Todos los archivos CSS debían aparecer en resources en un archivo module.json en el mismo directorio que el archivo CSS.

Ejemplo de un archivo module.json restante:

{
  "resources": [
    "serviceWorkersView.css",
    "serviceWorkerUpdateCycleView.css"
  ]
}

Luego, estos archivos CSS propagarían un mapa de objetos global llamado Root.Runtime.cachedResources como una asignación de una ruta a su contenido. Para agregar estilos a DevTools, debes llamar a registerRequiredCSS con la ruta de acceso exacta al archivo que deseas cargar.

Ejemplo de llamada registerRequiredCSS:

constructor() {
  
  this.registerRequiredCSS('ui/legacy/components/quick_open/filteredListWidget.css');
  
}

Esto recuperaría el contenido del archivo CSS y lo insertaría como un elemento <style> en la página con la función appendStyle:

Función appendStyle que agrega CSS con un elemento de diseño intercalado:

const content = Root.Runtime.cachedResources.get(cssFile) || '';

if (!content) {
  console.error(cssFile + ' not preloaded. Check module.json');
}

const styleElement = document.createElement('style');
styleElement.textContent = content;
node.appendChild(styleElement);

Cuando presentamos los componentes web modernos (con elementos personalizados), decidimos, en un principio, usar CSS a través de etiquetas <style> intercaladas en los archivos de componentes. Esto presentó sus propios desafíos:

  • Falta de compatibilidad con el resaltado de sintaxis. Los complementos que proporcionan resaltado de sintaxis para el CSS intercalado no suelen ser tan buenos como las funciones de resaltado de sintaxis y autocompletado para el CSS escrito en archivos .css.
  • Genera una sobrecarga de rendimiento. El CSS intercalado también significaba que se necesitaban dos pases para el linting: uno para los archivos CSS y otro para el CSS intercalado. Esta era una sobrecarga de rendimiento que podíamos quitar si todo el CSS se escribía en archivos CSS independientes.
  • Desafío en la reducción. El CSS intercalado no se pudo reducir fácilmente, por lo que no se redujo ninguno. El tamaño del archivo de la compilación de lanzamiento de DevTools también aumentó debido al CSS duplicado que introdujeron varias instancias del mismo componente web.

El objetivo de mi proyecto de pasantía era encontrar una solución para la infraestructura de CSS que funcione con la infraestructura heredada y los nuevos componentes web que se usan en DevTools.

Investigar posibles soluciones

El problema se podría dividir en dos partes diferentes:

  • Descubrir cómo el sistema de compilación controla los archivos CSS
  • Descubrir cómo DevTools importa y utiliza los archivos CSS

Analizamos diferentes soluciones potenciales para cada parte, que se describen a continuación.

Cómo importar archivos CSS

El objetivo de importar y usar CSS en los archivos de TypeScript era ceñirse lo más posible a los estándares web, aplicar la coherencia en DevTools y evitar el CSS duplicado en nuestro HTML. También queríamos poder elegir una solución que permitiera migrar nuestros cambios a nuevos estándares de plataformas web, como las secuencias de comandos de módulos CSS.

Por estos motivos, las instrucciones @import y las etiquetas no parecían ser la opción adecuada para DevTools. No serían uniformes con las importaciones en el resto de DevTools y generarían un destello de contenido sin diseño (FOUC). La migración a las secuencias de comandos de módulos CSS sería más difícil porque las importaciones tendrían que agregarse de forma explícita y tratarse de manera diferente a como lo harían con las etiquetas <link>.

const output = LitHtml.html`
<style> @import "css/styles.css"; </style>
<button> Hello world </button>`
const output = LitHtml.html`
<link rel="stylesheet" href="styles.css">
<button> Hello World </button>`

Soluciones potenciales con @import o <link>

En su lugar, optamos por encontrar una forma de importar el archivo CSS como un objeto CSSStyleSheet para poder agregarlo al Shadow DOM (DevTools usa Shadow DOM desde hace un par de años) con su propiedad adoptedStyleSheets.

Opciones del empaquetador

Necesitábamos una forma de convertir archivos CSS en un objeto CSSStyleSheet para poder manipularlo fácilmente en el archivo TypeScript. Consideramos Rollup y webpack como posibles empaquetadores para realizar esta transformación por nosotros. DevTools ya usa Rollup en su compilación de producción, pero agregar cualquiera de los empaquetadores a la compilación de producción podría tener posibles problemas de rendimiento cuando se trabaja con nuestro sistema de compilación actual. Nuestra integración con el sistema de compilación GN de Chromium dificulta el empaquetado y, por lo tanto, los empaquetadores suelen no integrarse bien con el sistema de compilación actual de Chromium.

En su lugar, exploramos la opción de usar el sistema de compilación GN actual para que realice esta transformación por nosotros.

La nueva infraestructura para usar CSS en DevTools

La nueva solución implica usar adoptedStyleSheets para agregar estilos a un Shadow DOM en particular mientras se usa el sistema de compilación GN para generar objetos CSSStyleSheet que pueden ser adoptados por un document o un ShadowRoot.

// CustomButton.ts

// Import the CSS style sheet contents from a JS file generated from CSS
import customButtonStyles from './customButton.css.js';
import otherStyles from './otherStyles.css.js';

export class CustomButton extends HTMLElement{
  
  connectedCallback(): void {
    // Add the styles to the shadow root scope
    this.shadow.adoptedStyleSheets = [customButtonStyles, otherStyles];
  }
}

El uso de adoptedStyleSheets tiene varios beneficios, como los siguientes:

  • Está en proceso de convertirse en un estándar web moderno.
  • Evita el CSS duplicado
  • Aplica estilos solo a un Shadow DOM, lo que evita cualquier problema causado por nombres de clase duplicados o selectores de ID en archivos CSS.
  • Fácil de migrar a futuros estándares web, como las secuencias de comandos de módulos CSS y las aserciones de importación

La única salvedad de la solución era que las sentencias import requerían que se importara el archivo .css.js. Para permitir que GN genere un archivo CSS durante la compilación, escribimos la secuencia de comandos generate_css_js_files.js. El sistema de compilación ahora procesa cada archivo CSS y lo transforma en un archivo JavaScript que, de forma predeterminada, exporta un objeto CSSStyleSheet. Esto es excelente, ya que podemos importar el archivo CSS y adoptarlo fácilmente. Además, ahora también podemos reducir la compilación de producción fácilmente, lo que ahorra el tamaño del archivo:

const styles = new CSSStyleSheet();
styles.replaceSync(
  // In production, we also minify our CSS styles
  /`${isDebug ? output : cleanCSS.minify(output).styles}
  /*# sourceURL=${fileName} */`/
);

export default styles;

Ejemplo de iconButton.css.js generado a partir de la secuencia de comandos.

Cómo migrar código heredado con reglas de ESLint

Si bien los componentes web se podían migrar fácilmente de forma manual, el proceso para migrar los usos heredados de registerRequiredCSS era más complejo. Las dos funciones principales que registraron estilos heredados fueron registerRequiredCSS y createShadowRootWithCoreStyles. Decidimos que, dado que los pasos para migrar estas llamadas eran bastante mecánicos, podíamos usar reglas de ESLint para aplicar correcciones y migrar automáticamente el código heredado. DevTools ya usa una serie de reglas personalizadas específicas para la base de código de DevTools. Esto fue útil porque ESLint ya analiza el código en un árbol de sintaxis abstracta(abreviatura de AST) y podríamos consultar los nodos de llamada específicos que eran llamadas para registrar CSS.

El mayor problema que enfrentamos cuando escribimos las reglas de ESLint de migración fue capturar casos extremos. Queríamos asegurarnos de encontrar el equilibrio adecuado entre saber qué casos extremos valía la pena capturar y cuáles debían migrarse de forma manual. También queríamos asegurarnos de poder informarle a un usuario cuando el sistema de compilación no genera automáticamente un archivo .css.js importado, ya que esto evita que se produzcan errores de archivo no encontrado durante el tiempo de ejecución.

Una desventaja de usar reglas de ESLint para la migración fue que no pudimos cambiar el archivo de compilación GN requerido en el sistema. El usuario debía realizar estos cambios de forma manual en cada directorio. Si bien esto requirió más trabajo, fue una buena manera de confirmar que el sistema de compilación genera cada archivo .css.js que se importa.

En general, usar las reglas de ESLint para esta migración fue muy útil, ya que pudimos migrar rápidamente el código heredado a la nueva infraestructura y tener el AST disponible de inmediato significó que también pudimos controlar varios casos extremos en la regla y corregirlos automáticamente de forma confiable con la API de corrector de ESLint.

¿Qué debo hacer?

Hasta el momento, todos los componentes web de Chromium DevTools se migraron para usar la nueva infraestructura de CSS en lugar de usar estilos intercalados. La mayoría de los usos heredados de registerRequiredCSS también se migraron para usar el nuevo sistema. Solo queda quitar tantos archivos module.json como sea posible y, luego, migrar esta infraestructura actual para implementar secuencias de comandos de módulos de CSS en el futuro.

Descarga los canales de vista previa

Considera usar Chrome Canary, Dev o Beta como tu navegador de desarrollo predeterminado. Estos canales de versión preliminar te brindan acceso a las funciones más recientes de DevTools, te permiten probar las APIs de plataformas web de vanguardia y te ayudan a encontrar problemas en tu sitio antes que tus usuarios.

Comunícate con el equipo de Chrome DevTools

Usa las siguientes opciones para hablar sobre las funciones nuevas, las actualizaciones o cualquier otro tema relacionado con DevTools.