Suporte a CSS-in-JS no DevTools

Alex rudenko
Alex Rudenko

Este artigo aborda o suporte a CSS-in-JS no DevTools que foi lançado desde o Chrome 85 e, em geral, o que queremos dizer com CSS-in-JS e como ele é diferente do CSS normal que tem suporte do DevTools há muito tempo.

O que é CSS-in-JS?

A definição de CSS-in-JS é bastante vaga. Em termos gerais, é uma abordagem para gerenciar código CSS usando JavaScript. Por exemplo, isso pode significar que o conteúdo de CSS é definido usando JavaScript e a saída de CSS final é gerada imediatamente pelo aplicativo.

No contexto do DevTools, CSS em JS significa que o conteúdo do CSS é injetado na página usando as APIs CSSOM. O CSS normal é injetado usando elementos <style> ou <link> e tem uma fonte estática (por exemplo, um nó DOM ou um recurso de rede). Por outro lado, o CSS em JavaScript geralmente não tem uma origem estática. Um caso especial aqui é que o conteúdo de um elemento <style> pode ser atualizado usando a API CSSOM, fazendo com que a origem fique fora de sincronia com a folha de estilo CSS real.

Se você usar qualquer biblioteca CSS-in-JS (por exemplo, styled-component, Emotion, JSS), a biblioteca poderá injetar estilos usando as APIs CSSOM em segundo plano, dependendo do modo de desenvolvimento e do navegador.

Vamos ver alguns exemplos de como injetar uma folha de estilo usando a API CSSOM de maneira semelhante ao que as bibliotecas CSS-in-JS fazem.

// 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; }');

Também é possível criar uma folha de estilo completamente nova:

// 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];

Suporte a CSS no DevTools

No DevTools, o recurso mais usado ao lidar com CSS é o painel Styles. No painel Estilos, você pode conferir quais regras se aplicam a determinado elemento, editá-las e conferir as mudanças na página em tempo real.

Antes do ano passado, o suporte para regras CSS modificadas com as APIs CSSOM era bastante limitado: só era possível ver as regras aplicadas, mas não editá-las. O principal objetivo que tínhamos no ano passado era permitir a edição de regras de CSS em JavaScript usando o painel "Estilos". Às vezes, também chamamos os estilos CSS em JS de "constructed" para indicar que eles foram construídos usando APIs da Web.

Vamos conferir os detalhes do funcionamento de edição de estilos no DevTools.

Mecanismo de edição de estilos no DevTools

Mecanismo de edição de estilos no DevTools

Quando você seleciona um elemento no DevTools, o painel Styles é mostrado. O painel Estilos emite um comando CDP chamado CSS.getMatchedStylesForNode para receber regras de CSS que se aplicam ao elemento. CDP significa "Chrome DevTools Protocol" e é uma API que permite que o front-end do DevTools receba mais informações sobre a página inspecionada.

Quando invocado, CSS.getMatchedStylesForNode identifica todas as folhas de estilo no documento e as analisa usando o analisador de CSS do navegador. Em seguida, cria um índice que associa cada regra CSS a uma posição na origem da folha de estilo.

Você pode se perguntar: por que ele precisa analisar o CSS de novo? O problema aqui é que, por motivos de desempenho, o navegador não está preocupado com as posições de origem das regras de CSS e, portanto, não as armazena. No entanto, o DevTools precisa das posições de origem para oferecer suporte à edição de CSS. Não queremos que usuários comuns do Chrome paguem pela penalidade de desempenho, mas queremos que os usuários do DevTools tenham acesso às posições de origem. Essa nova análise aborda os dois casos de uso com desvantagens mínimas.

Em seguida, a implementação de CSS.getMatchedStylesForNode solicita que o mecanismo de estilo do navegador forneça regras de CSS que correspondam ao elemento especificado. Por fim, o método associa as regras retornadas pelo mecanismo de estilo ao código-fonte e fornece uma resposta estruturada sobre as regras do CSS para que o DevTools saiba qual parte da regra é o seletor ou as propriedades. Ela permite que o DevTools edite o seletor e as propriedades de forma independente.

Agora vamos saber mais sobre a edição. Lembre-se de que CSS.getMatchedStylesForNode retorna posições de origem para todas as regras? Isso é crucial para a edição. Quando você muda uma regra, o DevTools emite outro comando do CDP que atualiza a página. O comando inclui a posição original do fragmento da regra que está sendo atualizada e o novo texto com que o fragmento precisa ser atualizado.

No back-end, ao processar a chamada de edição, o DevTools atualiza a folha de estilo de destino. Também atualiza a cópia da fonte da folha de estilo que mantém e atualiza as posições de origem da regra atualizada. Em resposta à chamada de edição, o front-end do DevTools recebe as posições atualizadas do fragmento de texto que acabou de ser atualizado.

Isso explica por que a edição de CSS-in-JS no DevTools não funcionou imediatamente: o CSS em JS não tem uma fonte real armazenada em lugar nenhum e as regras de CSS residem na memória do navegador nas estruturas de dados do CSSOM.

Como adicionamos suporte para CSS em JavaScript

Assim, para oferecer suporte à edição de regras CSS-in-JS, decidimos que a melhor solução seria criar uma fonte para as folhas de estilo construídas que possam ser editadas usando o mecanismo existente descrito acima.

A primeira etapa é criar o texto de origem. O mecanismo de estilo do navegador armazena as regras de CSS na classe CSSStyleSheet. Essa classe é aquela cujas instâncias você pode criar a partir do JavaScript, conforme discutido anteriormente. O código para criar o texto de origem é o seguinte:

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

Ela itera as regras encontradas em uma instância de CSSStyleSheet e cria uma única string a partir dela. Esse método é invocado quando uma instância da classe InspectorStyleSheet é criada. A classe InspectorStyleSheet une uma instância do CSSStyleSheet e extrai outros metadados exigidos pelo DevTools:

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

Neste snippet, vemos CSSOMStyleSheetText que chama CollectStyleSheetRules internamente. CSSOMStyleSheetText é invocado se a folha de estilo não estiver inline ou se uma folha de estilo de recurso for usada. Basicamente, esses dois snippets já permitem a edição básica das folhas de estilo criadas usando o construtor new CSSStyleSheet().

Um caso especial são as folhas de estilo associadas a uma tag <style> que foram modificadas usando a API CSSOM. Nesse caso, a folha de estilo contém o texto de origem e regras adicionais que não estão presentes na origem. Para lidar com esse caso, introduzimos um método para mesclar essas regras adicionais no texto de origem. Aqui, a ordem é importante porque as regras de CSS podem ser inseridas no meio do texto original. Por exemplo, imagine que o elemento <style> original contivesse o seguinte texto:

/* comment */
.rule1 {}
.rule3 {}

Então, a página inseriu algumas novas regras usando a API JS que produziu a seguinte ordem de regras: .rule0, .rule1, .rule2, .rule3, .rule4. O texto de origem resultante após a operação de mesclagem será o seguinte:

.rule0 {}
/* comment */
.rule1 {}
.rule2 {}
.rule3 {}
.rule4 {}

A preservação dos comentários originais e do recuo é importante para o processo de edição, pois as posições das regras no texto de origem precisam ser precisas.

Outro aspecto especial para folhas de estilo CSS em JS é que elas podem ser alteradas pela página a qualquer momento. Se as regras do CSSOM não estiverem sincronizadas com a versão do texto, a edição não vai funcionar. Para isso, apresentamos a chamada sondagem, que permite ao navegador notificar a parte de back-end do DevTools quando uma folha de estilo estiver sofrendo mutação. As folhas de estilo modificadas são então sincronizadas durante a próxima chamada para CSS.getMatchedStylesForNode.

Com todas essas peças no lugar, a edição de CSS em JS já funciona, mas queríamos melhorar a interface para indicar se uma folha de estilo foi criada. Adicionamos um novo atributo chamado isConstructed ao CSS.CSSStyleSheetHeader do CDP que o front-end usa para mostrar corretamente a origem de uma regra CSS:

Folha de estilo edificável

Conclusões

Para recapitular nossa história aqui, analisamos os casos de uso relevantes relacionados ao CSS-in-JS que não eram compatíveis com o DevTools e explicamos a solução para esses casos de uso. A parte interessante dessa implementação é que conseguimos aproveitar a funcionalidade existente fazendo com que as regras de CSS do CSSOM tenham um texto de origem regular, evitando a necessidade de rearquitetar completamente a edição de estilo no DevTools.

Para mais informações, confira nossa proposta de design ou o bug de rastreamento do Chromium, que faz referência a todos os patches relacionados.

Fazer o download dos canais de visualização

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

Entrar em contato com a equipe do Chrome DevTools

Use as opções abaixo para discutir os novos recursos e mudanças na postagem ou qualquer outro assunto relacionado 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.
  • Publique no Twitter em @ChromeDevTools.
  • Deixe comentários nos vídeos do YouTube sobre o que há de novo ou nos vídeos do YouTube de dicas sobre o DevTools.