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

Actualización de la arquitectura de Herramientas para desarrolladores: Modernización de la infraestructura de CSS en Herramientas para desarrolladores

Esta entrada forma parte de una serie de entradas de blog en las que se describen los cambios que hacemos en la arquitectura de Herramientas para desarrolladores y su compilación. Explicaremos cómo funcionaba CSS en Herramientas para desarrolladores históricamente y cómo modernizamos nuestra CSS en Herramientas para desarrolladores como preparativo para la migración (finalmente) a una solución estándar web destinada a cargar CSS en archivos JavaScript.

Estado anterior de CSS en Herramientas para desarrolladores

Las Herramientas para desarrolladores implementaron CSS de dos maneras diferentes: una para los archivos CSS que se usan en la parte heredada de Herramientas para desarrolladores, otra para los componentes web modernos que se usan en Herramientas para desarrolladores.

La implementación de CSS en Herramientas para desarrolladores se definió hace muchos años y ahora está desactualizada. Herramientas para desarrolladores siguió usando el patrón module.json, y se realizó un gran esfuerzo para quitar estos archivos. El último bloqueador para quitar estos archivos es la sección resources, que se usa para cargar los archivos CSS.

Queríamos dedicar tiempo a explorar diferentes posibles soluciones que, con el tiempo, 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 de CSS.

Todos los archivos CSS que estaban en las Herramientas para desarrolladores se consideraron "heredados", ya que se cargaron con un archivo module.json, que se está quitando. Todos los archivos CSS tenían que estar enumerados 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 de acceso a sus contenidos. Para agregar estilos a las Herramientas para desarrolladores, deberás llamar a registerRequiredCSS con la ruta de acceso exacta al archivo que deseas cargar.

Llamada de registerRequiredCSS de ejemplo:

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

De esta manera, se recuperará el contenido del archivo CSS y se insertará como un elemento <style> en la página mediante la función appendStyle:

Función appendStyle que agrega CSS con un elemento de estilo 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 inicialmente usar CSS mediante etiquetas <style> intercaladas en los archivos componentes. Esto presentó sus propios desafíos:

  • Falta de compatibilidad con el resaltado de sintaxis. Los complementos que proporcionan resaltado de sintaxis para CSS intercalado no suelen ser tan buenos como las funciones de resaltado de sintaxis y autocompletar para CSS escrito en archivos .css.
  • Sobrecarga de rendimiento de compilación. CSS intercalado también significó que había dos pases para el análisis con lint: uno para archivos CSS y otro para CSS intercalado. Esta era una sobrecarga de rendimiento que podríamos quitar si todo el CSS se escribiera en archivos CSS independientes.
  • Desafío en la reducción. No se pudo reducir fácilmente el CSS intercalado, por lo que no se redujo ninguno de ellos. El tamaño del archivo de la compilación de lanzamiento de Herramientas para desarrolladores también aumentó con la CSS duplicada que introdujo 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 funcionara tanto con la infraestructura heredada como con los nuevos componentes web que se usan en Herramientas para desarrolladores.

Investigar posibles soluciones

El problema podría dividirse en dos partes diferentes:

  • Determinar cómo el sistema de compilación trabaja con los archivos CSS.
  • Descubrir cómo Herramientas para desarrolladores importan y usan los archivos CSS

Analizamos diferentes posibles soluciones para cada parte, las cuales se describen a continuación.

Cómo importar archivos CSS

El objetivo de importar y usar CSS en los archivos de TypeScript era apegarse a los estándares web lo más posible, aplicar la coherencia en todas las Herramientas para desarrolladores y evitar la duplicación de CSS 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 de CSS.

Por estos motivos, las sentencias @import y las etiquetas no resultaron adecuadas para Herramientas para desarrolladores. No serían uniformes con las importaciones en el resto de Herramientas para desarrolladores y generarían destellos de contenido sin estilo (FOUC). La migración a secuencias de comandos de módulos de CSS sería más difícil porque las importaciones tendrían que agregarse explícitamente y manejarse de manera diferente a como lo harían con 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>`

Posibles soluciones usando @import o <link>.

En cambio, decidimos encontrar una forma de importar el archivo CSS como un objeto CSSStyleSheet para que podamos agregarlo a Shadow Dom (DevTools usa Shadow DOM durante unos años) usando su propiedad adoptedStyleSheets.

Opciones de agrupador

Necesitábamos una manera de convertir los archivos CSS en un objeto CSSStyleSheet para poder manipularlo con facilidad en el archivo de TypeScript. Consideramos Rollup y webpack como posibles agrupadores para realizar esta transformación por nosotros. Herramientas para desarrolladores ya usa Rollup en su compilación de producción, pero agregar cualquiera de los agrupadores a la compilación de producción podría tener problemas de rendimiento potenciales cuando trabajes con nuestro sistema de compilación actual. Nuestra integración con el sistema de compilación GN de Chromium dificulta la creación de paquetes, por lo que los agrupadores no suelen integrarse bien con el sistema de compilación de Chromium actual.

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

La nueva infraestructura para usar CSS en Herramientas para desarrolladores

La nueva solución implica el uso de adoptedStyleSheets para agregar estilos a un Shadow DOM específico mientras se usa el sistema de compilación de GN para generar objetos CSSStyleSheet que se pueden adoptar con document o 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];
  }
}

Usar adoptedStyleSheets tiene varios beneficios, como los siguientes:

  • Está en proceso de convertirse en un estándar web moderno
  • Impide el uso de CSS duplicado.
  • Aplica estilos solo a un Shadow DOM y evita los problemas causados por nombres de clase o selectores de ID duplicados en archivos CSS
  • Es fácil de migrar a estándares web futuros, como secuencias de comandos de módulos de CSS y aserciones de importación.

La única salvedad para esta solución es 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 los transforma en un archivo JavaScript que exporta un objeto CSSStyleSheet de forma predeterminada. Esto es muy útil, ya que podemos importar el archivo CSS y adoptarlo fácilmente. Además, ahora también podemos reducir la compilación de producción con facilidad y, así, reducir 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 generado iconButton.css.js a partir de la secuencia de comandos.

Migra 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 diseños heredados fueron registerRequiredCSS y createShadowRootWithCoreStyles. Decidimos que, como 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. Herramientas para desarrolladores ya usa una serie de reglas personalizadas específicas para su base de código. Esto fue útil, ya que ESLint ya analiza el código en un objeto Abstract Syntax Tree(abbr. AST) y podríamos consultar los nodos de llamada particulares que eran llamadas para registrar CSS.

El mayor problema que encontramos al escribir las reglas de ESLint de migración fue la captura de casos extremos. Queríamos asegurarnos de lograr el equilibrio adecuado entre saber qué casos extremos valía la pena capturar y cuáles deberían migrarse de forma manual. También queríamos asegurarnos de avisarle 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 en el tiempo de ejecución de los archivos.

Una desventaja de usar reglas ESLint para la migración era que no podíamos cambiar el archivo de compilación GN requerido en el sistema. El usuario tenía que realizar estos cambios manualmente en cada directorio. Si bien esto requería más trabajo, era una buena manera de confirmar que cada archivo .css.js que se importaba sea generado por el sistema de compilación.

En general, el uso de reglas de ESLint para esta migración fue muy útil, ya que pudimos migrar rápidamente el código heredado a la infraestructura nueva y tener el AST disponible nos permitió manejar varios casos extremos en la regla y corregirlos de manera automática y confiable con la API de corrección de ESLint.

¿Qué debes hacer a continuación?

Hasta ahora, todos los componentes web de las Herramientas para desarrolladores de Chromium 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 sistema nuevo. Solo falta quitar tantos archivos module.json como sea posible y, luego, migrar esta infraestructura actual para implementar secuencias de comandos del módulo de CSS en el futuro.

Descarga los canales de vista previa

Considera usar Canary, Dev o Beta de Chrome como tu navegador de desarrollo predeterminado. Estos canales de vista previa te brindan acceso a las funciones más recientes de Herramientas para desarrolladores, prueba APIs de plataformas web de vanguardia y encuentra problemas en tu sitio antes que tus usuarios.

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

Usa las siguientes opciones para analizar las nuevas funciones y los cambios en la publicación, o cualquier otra cosa relacionada con Herramientas para desarrolladores.

  • Envíanos tus sugerencias o comentarios a través de crbug.com.
  • Informa un problema en Herramientas para desarrolladores mediante Más opciones   Más   > Ayuda > Informar problemas con Herramientas para desarrolladores en Herramientas para desarrolladores.
  • Envía un tweet a @ChromeDevTools.
  • Deja comentarios en los videos de YouTube de las Novedades de las Herramientas para desarrolladores o en las sugerencias de Herramientas para desarrolladores (videos de YouTube).