Como modernizar a infraestrutura de CSS no DevTools

Atualização da arquitetura do DevTools: como modernizar a infraestrutura de CSS no DevTools

Este post faz parte de uma série de posts no blog que descrevem as mudanças que estamos fazendo na arquitetura do DevTools e como ele é criado. Vamos explicar como o CSS funcionava no DevTools e como modernizamos o CSS no DevTools para preparar a migração para uma solução padrão da Web para carregar CSS em arquivos JavaScript.

Estado anterior do CSS no DevTools

O DevTools implementou o CSS de duas maneiras diferentes: uma para arquivos CSS usados na parte legada do DevTools e outra para os componentes modernos da Web que estão sendo usados no DevTools.

A implementação de CSS no DevTools foi definida há muitos anos e agora está desatualizada. As Ferramentas do desenvolvedor continuaram usando o padrão module.json, e foi necessário um grande esforço para remover esses arquivos. O último bloqueador para a remoção desses arquivos é a seção resources, que é usada para carregar arquivos CSS.

Queríamos passar um tempo explorando diferentes soluções em potencial que poderiam se transformar em scripts de módulo CSS. O objetivo era remover a dívida técnica causada pelo sistema legado, mas também facilitar o processo de migração para scripts de módulo CSS.

Todos os arquivos CSS que estavam no DevTools eram considerados "legados" porque eram carregados usando um arquivo module.json, que está em processo de remoção. Todos os arquivos CSS precisavam estar listados em resources em um arquivo module.json no mesmo diretório do arquivo CSS.

Exemplo de um arquivo module.json restante:

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

Esses arquivos CSS preenchem um mapa de objetos global chamado Root.Runtime.cachedResources como um mapeamento de um caminho para o conteúdo. Para adicionar estilos ao DevTools, você precisa chamar registerRequiredCSS com o caminho exato do arquivo que você quer carregar.

Exemplo de chamada registerRequiredCSS:

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

Isso recuperaria o conteúdo do arquivo CSS e o inseriria como um elemento <style> na página usando a função appendStyle:

Função appendStyle que adiciona CSS usando um elemento de estilo in-line:

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);

Quando apresentamos os componentes modernos da Web (usando elementos personalizados), decidim inicialmente usar CSS com tags <style> inline nos próprios arquivos de componentes. Isso trouxe seus próprios desafios:

  • Falta de suporte para destaque de sintaxe. Os plug-ins que oferecem destaque de sintaxe para CSS inline geralmente não são tão bons quanto os recursos de destaque de sintaxe e preenchimento automático para CSS escritos em arquivos .css.
  • Crie um overhead de desempenho. O CSS inline também exigia duas passagens para inspeção: uma para arquivos CSS e outra para CSS inline. Esse era um custo de desempenho que poderíamos remover se todo o CSS fosse escrito em arquivos CSS independentes.
  • Desafio na minificação. Não foi possível reduzir o CSS inline com facilidade. Por isso, nenhum CSS foi reduzido. O tamanho do arquivo do build de lançamento do DevTools também aumentou pelo CSS duplicado introduzido por várias instâncias do mesmo componente da Web.

O objetivo do meu projeto de estágio era encontrar uma solução para a infraestrutura CSS que funcionasse com a infraestrutura legada e os novos componentes da Web usados no DevTools.

Pesquisando possíveis soluções

O problema pode ser dividido em duas partes diferentes:

  • Descobrir como o sistema de build lida com arquivos CSS.
  • Descobrir como os arquivos CSS são importados e usados pelo DevTools.

Analisamos diferentes soluções possíveis para cada parte, que são descritas abaixo.

Como importar arquivos CSS

O objetivo de importar e usar CSS nos arquivos TypeScript era ficar o mais próximo possível dos padrões da Web, aplicar consistência em todas as ferramentas do DevTools e evitar CSS duplicado no HTML. Também queríamos escolher uma solução que permitisse migrar nossas mudanças para novos padrões de plataforma da Web, como scripts de módulo CSS.

Por esses motivos, as instruções @import e as tags não parecem ser a melhor opção para o DevTools. Eles não seriam uniformes com as importações no restante do DevTools e resultariam em um Flash Of Unstyled Content (FOUC). A migração para os scripts do módulo CSS seria mais difícil porque as importações teriam que ser explicitamente adicionadas e tratadas de forma diferente do que fariam com tags <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>`

Possíveis soluções usando @import ou <link>.

Em vez disso, optamos por encontrar uma maneira de importar o arquivo CSS como um objeto CSSStyleSheet para que ele possa ser adicionado ao Shadow Dom (o DevTools usa o Shadow DOM por alguns anos) usando a propriedade adoptedStyleSheets.

Opções do Bundler

Precisávamos de uma maneira de converter arquivos CSS em um objeto CSSStyleSheet para que pudéssemos manipulá-los facilmente no arquivo TypeScript. Consideramos o Rollup e o webpack como possíveis agrupadores para fazer essa transformação. O DevTools já usa o Rollup no build de produção, mas adicionar qualquer bundler ao build de produção pode causar problemas de desempenho ao trabalhar com nosso sistema de build atual. Nossa integração com o sistema de build GN do Chromium dificulta o agrupamento e, portanto, os bundlers tendem a não se integrar bem ao sistema de build atual do Chromium.

Em vez disso, analisamos a opção de usar o sistema de build atual do GN para fazer essa transformação.

A nova infraestrutura de uso de CSS no DevTools

A nova solução envolve o uso de adoptedStyleSheets para adicionar estilos a um DOM sombra específico e usar o sistema de build do GN para gerar objetos CSSStyleSheet que podem ser adotados por um document ou um 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];
  }
}

O uso de adoptedStyleSheets tem vários benefícios, incluindo:

  • Está em andamento para se tornar um padrão moderno da Web
  • Evita CSS duplicado
  • Aplica estilos apenas a um DOM sombra, evitando problemas causados por nomes de classe duplicados ou seletores de ID em arquivos CSS.
  • Migração fácil para futuros padrões da Web, como scripts de módulo CSS e importações de atribuição

A única ressalva à solução era que as instruções import exigiam que o arquivo .css.js fosse importado. Para permitir que o GN gere um arquivo CSS durante a criação, escrevemos o script generate_css_js_files.js. O sistema de build agora processa todos os arquivos CSS e os transforma em um arquivo JavaScript que exporta um objeto CSSStyleSheet por padrão. Isso é ótimo, porque podemos importar o arquivo CSS e adotá-lo com facilidade. Além disso, agora também podemos minimizar o build de produção com facilidade, economizando o tamanho do arquivo:

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;

Exemplo de iconButton.css.js gerado pelo script.

Migrar código legado usando regras ESLint

Embora os componentes da Web pudessem ser migrados manualmente com facilidade, o processo de migração de usos legados do registerRequiredCSS era mais complexo. As duas funções principais que registravam estilos legados eram registerRequiredCSS e createShadowRootWithCoreStyles. Decidimos que, como as etapas para migrar essas chamadas eram bastante mecânicas, poderíamos usar as regras ESLint para aplicar correções e migrar automaticamente o código legado. O DevTools já usa várias regras personalizadas específicas para a base de código do DevTools. Isso foi útil porque o ESLint já analisa o código em uma árvore de sintaxe abstrata (abreviatura de AST) e poderíamos consultar os nós de chamada específicos que eram chamadas para registrar CSS.

O maior problema que enfrentamos ao programar as regras ESLint de migração foi a captura de casos extremos. Queríamos ter o equilíbrio certo entre saber quais casos extremos valem a pena capturar e quais devem ser migrados manualmente. Também queríamos garantir que pudéssemos informar um usuário quando um arquivo .css.js importado não estivesse sendo gerado automaticamente pelo sistema de build, porque isso evitava erros de arquivo não encontrado durante a execução.

Uma desvantagem de usar regras ESLint para a migração era que não era possível alterar o arquivo de build GN necessário no sistema. Essas mudanças precisavam ser feitas manualmente pelo usuário em cada diretório. Embora isso exigisse mais trabalho, foi uma boa maneira de confirmar que todos os arquivos .css.js importados são realmente gerados pelo sistema de build.

No geral, o uso das regras do ESLint para essa migração foi muito útil, porque conseguimos migrar rapidamente o código legado para a nova infraestrutura. Além disso, ter o AST prontamente disponível significa que também podemos lidar com vários casos extremos na regra e corrigi-los automaticamente usando a API fixer do ESLint.

E agora?

Até agora, todos os componentes da Web no Chromium DevTools foram migrados para usar a nova infraestrutura CSS em vez de estilos inline. A maioria dos usos legados de registerRequiredCSS também foram migrados para o novo sistema. Tudo o que resta é remover o maior número possível de arquivos module.json e migrar essa infraestrutura atual para implementar scripts de módulo CSS no futuro.

Fazer o download dos canais de visualização

Considere usar o Chrome Canary, Dev ou Beta como seu navegador de desenvolvimento padrão. Esses canais de visualização dão acesso aos recursos mais recentes do DevTools, permitem testar APIs de plataforma da Web de última geração e ajudam a encontrar problemas no seu site antes que os usuários.

Entre em contato com a equipe do Chrome DevTools

Use as opções a seguir para discutir os novos recursos, atualizações ou qualquer outra coisa relacionada ao DevTools.