Este artigo fala sobre o suporte ao 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 um sentido amplo, é uma abordagem para gerenciar o 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 rapidamente pelo app.
No contexto das ferramentas do desenvolvedor, CSS-in-JS significa que o conteúdo CSS é injetado na página usando APIs CSSOM. O CSS regular é injetado usando elementos <style>
ou <link>
e tem uma origem estática (por exemplo, um nó 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 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 pode injetar estilos usando APIs CSSOM em segundo plano, dependendo do modo de desenvolvimento e do navegador.
Vamos conferir alguns exemplos de como injetar uma folha de estilo usando a API CSSOM de forma 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; }');
Você também pode 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 Styles, você pode conferir quais regras se aplicam a um elemento específico, editá-las e conferir as mudanças na página em tempo real.
Antes do ano passado, o suporte a regras de CSS modificado usando APIs CSSOM era bastante limitado: você só podia ver as regras aplicadas, mas não editá-las. O principal objetivo do ano passado foi permitir a edição de regras CSS-in-JS usando o painel "Styles". Às vezes, também chamamos os estilos CSS-in-JS de "construídos" para indicar que eles foram criados usando APIs da Web.
Vamos nos aprofundar nos detalhes da edição de estilos no DevTools.
Mecanismo de edição de estilo no DevTools
Quando você seleciona um elemento no DevTools, o painel Styles é mostrado. O painel Styles emite um comando CDP chamado CSS.getMatchedStylesForNode para receber as regras de CSS que se aplicam ao elemento. O 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, ele cria um índice que associa cada regra CSS a uma posição na origem da folha de estilo.
Você pode perguntar: por que ele precisa analisar o CSS novamente? O problema aqui é que, por motivos de desempenho, o navegador não se preocupa 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 os usuários regulares 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 reanalisador aborda ambos os 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 fornecido. 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 as Ferramentas do desenvolvedor saibam 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. Lembra 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 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. Ele também atualiza a cópia da origem da folha de estilo que mantém e atualiza as posições da origem para a 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 a CSS-in-JS
Portanto, para oferecer suporte à edição de regras CSS-in-JS, decidimos que a melhor solução seria criar uma origem para folhas de estilo construídas que podem ser editadas usando o mecanismo descrito acima.
A primeira etapa é criar o texto de origem. O mecanismo de estilo do navegador armazena as regras CSS na classe CSSStyleSheet
. Essa é a classe que você pode criar instâncias em JavaScript, como discutido anteriormente. O código para criar o texto de origem é este:
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 envolve uma instância CSSStyleSheet e extrai outros metadados necessários pelas Ferramentas do desenvolvedor:
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 o 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 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, apresentamos um método para mesclar essas regras adicionais no texto de origem. Aqui, a ordem é importante porque as regras do 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 deve 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 precisam ser precisas.
Outro aspecto especial das 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, introduzimos uma sonda, que permite que o navegador notifique a parte de back-end do DevTools quando uma folha de estilo está sendo modificada. As folhas de estilo modificadas são sincronizadas durante a próxima chamada para CSS.getMatchedStylesForNode.
Com todas essas peças em vigor, a edição de CSS em JS já funciona, mas queríamos melhorar a interface para indicar se uma folha de estilos 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 de CSS:
Conclusões
Para recapitular nossa história, abordamos os casos de uso relevantes relacionados ao CSS-in-JS que o DevTools não oferecia e a solução para esses casos. O interessante dessa implementação é que conseguimos aproveitar a funcionalidade atual fazendo com que as regras CSS do CSSOM tivessem um texto de origem regular, evitando a necessidade de reprojetar completamente a edição de estilo no DevTools.
Para mais informações, consulte 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
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.
- Envie feedback e solicitações de recursos em crbug.com.
- Informe um problema do DevTools usando a opção Mais opções > Ajuda > Informar um problema do DevTools no DevTools.
- Envie um tweet para @ChromeDevTools.
- Deixe comentários nos vídeos Novidades do DevTools no YouTube ou Dicas do DevTools no YouTube.