Como modernizar a infraestrutura de CSS no DevTools

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

Esta postagem faz parte de uma série de postagens do blog (em inglês) que descrevem as mudanças que estamos fazendo na arquitetura do DevTools e como ela é criada. Explicaremos como o CSS funcionava no DevTools e como modernizamos nosso CSS nesse método para preparar (em algum momento) 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 os 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. O DevTools manteve o uso do padrão module.json (link em inglês), e houve um grande esforço para remover esses arquivos. O último obstáculo para a remoção desses arquivos é a seção resources, que é usada para carregar arquivos CSS.

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

Todos os arquivos CSS que estavam no DevTools foram considerados "legados" porque foram 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.

Um exemplo de um arquivo module.json restante:

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

Esses arquivos CSS preencheriam um mapa de objetos global chamado Root.Runtime.cachedResources como um mapeamento de um caminho para o conteúdo. Para adicionar estilos ao DevTools, chame registerRequiredCSS com o caminho exato para o 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 (que usam elementos personalizados), decidimos inicialmente usar o CSS com tags <style> inline nos próprios arquivos de componentes. Isso apresentou seus próprios desafios:

  • Falta de suporte ao destaque de sintaxe. Os plug-ins que oferecem destaque de sintaxe para CSS inline não costumam ser tão bons quanto os recursos de destaque de sintaxe e preenchimento automático para CSS escritos em arquivos .css.
  • Sobrecarga de desempenho da criação. Além disso, o CSS inline precisava de duas passagens para inspeção: uma para arquivos CSS e outra para CSS inline. Essa sobrecarga de desempenho poderia ser removida se todos os CSS fossem escritos em arquivos CSS independentes.
  • Desafio na minificação. Não foi possível reduzir facilmente o CSS inline. Por isso, nenhum CSS foi reduzido. O tamanho do arquivo do build de lançamento do DevTools também foi aumentado pelo CSS duplicado introduzido por várias instâncias do mesmo componente da Web.

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

Pesquisar possíveis soluções

O problema pode ser dividido em duas partes:

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

Analisamos diferentes possíveis soluções para cada parte e elas estão descritas abaixo.

Importar arquivos CSS

O objetivo com a importação e utilização de CSS nos arquivos TypeScript era permanecer o mais próximo possível dos padrões da Web, aplicar consistência em todo o DevTools e evitar CSS duplicado no HTML. Também queríamos escolher uma solução que tornasse possível migrar nossas mudanças para os novos padrões de plataforma da Web, como os Scripts de módulo CSS.

Por esses motivos, as declarações @import e as tags não parecem ser adequadas para o DevTools. Elas 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 de módulo CSS seria mais difícil porque as importações teriam que ser explicitamente adicionadas e tratadas de forma diferente de 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 pudéssemos adicioná-lo ao Shadow Dom (o DevTools usa o Shadow DOM há 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á-lo facilmente no arquivo TypeScript. Consideramos Rollup e webpack como bundlers em potencial para fazer essa transformação. O DevTools já usa o Rollup no build de produção, mas adicionar um bundler ao build de produção pode gerar possíveis 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, exploramos a opção de usar o sistema de compilação GN atual para fazer essa transformação para nós.

A nova infraestrutura de uso de CSS no DevTools

A nova solução envolve o uso de adoptedStyleSheets para adicionar estilos a um Shadow DOM específico enquanto usa o sistema de build GN para gerar objetos CSSStyleSheet que podem ser adotados por uma document ou uma 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 do adoptedStyleSheets tem vários benefícios, incluindo:

  • Está em processo de tornar-se um padrão moderno da Web
  • Impede CSS duplicado
  • Aplica estilos apenas a um Shadow DOM e isso evita problemas causados por nomes de classe ou seletores de ID duplicados em arquivos CSS
  • Facilidade de migração para padrões futuros da Web, como scripts de módulo CSS e declarações de importação

A única ressalva para a solução foi que as instruções import exigiam que o arquivo .css.js fosse importado. Para permitir que a GN gere um arquivo CSS durante a criação, escrevemos o script generate_css_js_files.js. Agora, o sistema de build processa cada arquivo CSS e os transforma em um arquivo JavaScript que, por padrão, exporta um objeto CSSStyleSheet. Isso é ótimo, já que podemos importar o arquivo CSS e adotá-lo facilmente. Além disso, agora também podemos reduzir 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.

Como migrar código legado usando regras ESLint

Embora os componentes da Web pudessem ser migrados manualmente com facilidade, o processo de migração dos usos legados do registerRequiredCSS era mais complexo. As duas principais funções 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 sua base de código. Isso foi útil, já que o ESLint já analisa o código em uma Árvore de sintaxe abstrata(link em inglês) AST) e poderíamos consultar os nós de chamada específicos que eram chamadas para o registro de 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 valiam a pena ser capturados e quais deveriam ser migrados manualmente. Também queríamos garantir que pudéssemos informar um usuário quando um arquivo .css.js importado não estiver sendo gerado automaticamente pelo sistema de build, o que evita erros de arquivo não encontrado no ambiente de execução.

Uma desvantagem de usar as regras ESLint para a migração é que não era possível alterar o arquivo de build GN necessário no sistema. Essas alterações tiveram que ser feitas manualmente pelo usuário em cada diretório. Embora isso exigisse mais trabalho, essa era uma boa maneira de confirmar que cada arquivo .css.js importado é realmente gerado pelo sistema de build.

No geral, o uso de regras ESLint para essa migração foi muito útil, porque migramos rapidamente o código legado para a nova infraestrutura. Além disso, ter o AST prontamente disponível significava que também poderíamos lidar com vários casos extremos na regra e corrigi-los de modo automático e confiável usando a API de correção 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 do registerRequiredCSS também foi migrada para o novo sistema. Só falta remover o máximo 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

Use o Chrome Canary, Dev ou Beta como seu navegador de desenvolvimento padrão. Esses canais de pré-lançamento oferecem acesso aos recursos mais recentes do DevTools, testam as APIs modernas de plataformas da Web e encontram problemas no site antes dos usuários.

Entrar em contato com a equipe do Chrome DevTools

Use as opções a seguir para discutir os novos recursos e mudanças na publicação ou qualquer outra coisa relacionada ao DevTools.

  • Envie uma sugestão ou feedback em crbug.com.
  • Informe um problema do DevTools em Mais opções   Mais   > Ajuda > Informar problemas no DevTools.
  • Envie um tweet em @ChromeDevTools.
  • Deixe comentários nos nossos vídeos do YouTube sobre a ferramenta DevTools ou nos vídeos do YouTube com dicas sobre o DevTools.