Suporte a CSS-in-JS no DevTools

Alex Rudenko
Alex Rudenko

Este artigo fala sobre o suporte a CSS-in-JS no DevTools que chegou 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 em JS é bastante vaga. Em termos gerais, é uma abordagem para gerenciar código CSS usando JavaScript. Por exemplo, isso pode significar que o conteúdo do CSS é definido usando JavaScript e a saída CSS final é gerada imediatamente pelo app.

No contexto do DevTools, CSS-in-JS significa que o conteú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ó do DOM ou um recurso de rede). Em contrapartida, o CSS em JS geralmente não tem uma fonte 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 dessincronizada com a folha de estilo CSS real.

Se você usar qualquer biblioteca CSS-in-JS (por exemplo, styled-component, Emotion, JSS), a biblioteca pode injetar estilos usando APIs CSSOM internamente, 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 estão fazendo.

// 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 ver quais regras se aplicam a um elemento específico, além de editá-las e ver as alterações na página em tempo real.

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

Vamos nos aprofundar nos detalhes do trabalho de edição de estilos no DevTools.

Mecanismo de edição de estilo no DevTools

Mecanismo de edição de estilo no DevTools

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

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

Você pode perguntar: por que ele precisa analisar o CSS novamente? O problema é que, por motivos de desempenho, o próprio navegador não está preocupado com as posições de origem das regras CSS e, portanto, não as armazena. Mas 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 a penalidade de desempenho, mas queremos que os usuários do DevTools tenham acesso às posições de origem. Essa abordagem de reaná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 em questão. Por fim, o método associa as regras retornadas pelo mecanismo de estilo ao código-fonte e fornece uma resposta estruturada sobre regras 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 falar sobre a edição. Lembre-se de que CSS.getMatchedStylesForNode retorna posições de origem para cada regra? 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. Além disso, atualiza a cópia da fonte da folha de estilo que mantém e as posições de origem da regra atualizada. Em resposta à chamada de edição, o front-end do DevTools recupera as posições atualizadas para o fragmento de texto que acabou de ser atualizado.

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

Como adicionamos suporte para CSS-in-JS

Portanto, para oferecer suporte à edição de regras de CSS em JS, decidimos que a melhor solução seria criar uma fonte para folhas de estilo construídas que possa ser editada com 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 podem ser criadas a partir de JavaScript, como 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 com base nas regras encontradas em uma instância CSSStyleSheet e cria uma única string a partir dela. Esse método é invocado quando uma instância da classe InspectorStyleSheet é criada. A classe InspectorStyleSheet encapsula uma instância CSSStyleSheet e extrai metadados adicionais 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 será invocado se a folha de estilo não estiver in-line ou se não for uma folha de estilo de recursos. Basicamente, esses dois snippets já permitem a edição básica das folhas de estilo que são criadas usando o construtor new CSSStyleSheet().

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

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

Em seguida, a página inseriu algumas regras novas usando a API JS, produzindo 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 e recuo originais é importante para o processo de edição porque as posições das regras no texto de origem devem ser precisas.

Outro aspecto especial para folhas de estilo CSS-in-JS é que elas podem ser alteradas pela página a qualquer momento. Se as regras do CSSOM reais estivessem fora de sincronia com a versão em texto, a edição não funcionaria. Para isso, apresentamos uma sondagem, que permite que o navegador notifique a parte de back-end do DevTools quando uma folha de estilo for modificada. As folhas de estilo modificadas sã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 construída. Adicionamos um novo atributo chamado isConstructed ao CSS.CSSStyleSheetHeader do CDP que o front-end usa para exibir corretamente a origem de uma regra CSS:

Folha de estilo que pode ser construída

Conclusões

Para recapitular nossa história aqui, analisamos os casos de uso relevantes relacionados ao CSS-in-JS que o DevTools não ofereceu suporte 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 comum, evitando a necessidade de reestruturar 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 seu navegador de desenvolvimento padrão. Esses canais de pré-visualização dão acesso aos recursos mais recentes do DevTools, testam APIs modernas da plataforma Web e encontram problemas no seu site antes que os usuários o façam!

Entrar em contato com a equipe do Chrome DevTools

Use as opções a seguir para discutir os novos recursos e mudanças na postagem ou qualquer outro assunto relacionado ao DevTools.

  • Envie uma sugestão ou feedback pelo site crbug.com.
  • Para informar um problema do DevTools, use Mais opções   Mais   > Ajuda > Informar problemas do DevTools no DevTools.
  • Tuíte em @ChromeDevTools.
  • Deixe comentários nos vídeos do YouTube sobre as novidades do DevTools ou nos vídeos do YouTube com dicas sobre o DevTools.