Uma experiência de depuração melhorada
Nos últimos meses, a equipe do Chrome DevTools colaborou com a equipe do Angular para lançar melhorias na experiência de depuração no Chrome DevTools. Pessoas de ambas as equipes trabalharam juntas e tomaram medidas para permitir que os desenvolvedores depurem e criem perfis de aplicativos da Web a partir da perspectiva de autoria: em termos de linguagem de origem e estrutura do projeto, com acesso a informações familiares e relevantes para eles.
Esta postagem examina os bastidores para entender quais mudanças no Angular e no Chrome DevTools foram necessárias para fazer isso. Embora algumas dessas mudanças sejam demonstradas pelo Angular, elas também podem ser aplicadas a outros frameworks. A equipe do Chrome DevTools incentiva outros frameworks a adotar as novas APIs do console e os pontos de extensão do mapa de origem para que eles também possam oferecer uma melhor experiência de depuração aos usuários.
Código de lista de ignorados
Ao depurar aplicativos usando o Chrome DevTools, os autores geralmente querem apenas o código deles, não o do framework ou alguma dependência escondida na pasta node_modules
.
Para isso, a equipe do DevTools introduziu uma extensão para mapas de origem, chamada x_google_ignoreList
. Essa extensão é usada para identificar fontes de terceiros, como código de framework ou código gerado pelo bundler. Quando um framework usa essa extensão, os autores evitam automaticamente o código que não querem ver ou percorrer sem precisar configurar isso manualmente antes.
Na prática, o Chrome DevTools pode ocultar automaticamente o código identificado como tal nos stack traces, na árvore de origens e na caixa de diálogo Quick Open, além de melhorar o comportamento de percorrer e retomar o depurador.
A extensão do mapa de origem x_google_ignoreList
Nos mapas de origem, o novo campo x_google_ignoreList
se refere à matriz sources
e lista os índices de todas as fontes de terceiros conhecidas nesse mapa. Ao analisar o mapa de origem, as Ferramentas para desenvolvedores do Chrome vão usar isso para descobrir quais seções do código devem ser ignoradas.
Confira abaixo um mapa de origem para um arquivo gerado out.js
. Há dois sources
originais que contribuíram para gerar o arquivo de saída: foo.js
e lib.js
. O primeiro é algo que um desenvolvedor de sites criou e o segundo é uma estrutura que eles usaram.
{
"version" : 3,
"file": "out.js",
"sourceRoot": "",
"sources": ["foo.js", "lib.js"],
"sourcesContent": ["...", "..."],
"names": ["src", "maps", "are", "fun"],
"mappings": "A,AAAB;;ABCDE;"
}
O sourcesContent
está incluído nas duas fontes originais, e o Chrome DevTools exibiria esses arquivos por padrão no Debugger:
- Como arquivos na árvore de origem.
- Como resultados na caixa de diálogo "Abrir rapidamente".
- Como locais de quadro de chamada mapeados em rastreamentos de pilha de erros enquanto pausado em um ponto de interrupção e durante a execução.
Agora é possível incluir mais uma informação nos mapas de origem para identificar quais delas são códigos próprios ou de terceiros:
{
...
"sources": ["foo.js", "lib.js"],
"x_google_ignoreList": [1],
...
}
O novo campo x_google_ignoreList
contém um único índice que se refere à matriz sources
: 1. Isso especifica que as regiões mapeadas para lib.js
são, na verdade, códigos de terceiros que precisam ser adicionados automaticamente à lista de ignorados.
Em um exemplo mais complexo, mostrado abaixo, os índices 2, 4 e 5 especificam que as regiões mapeadas para lib1.ts
, lib2.coffee
e hmr.js
são códigos de terceiros que precisam ser adicionados automaticamente à lista de ignorados.
{
...
"sources": ["foo.html", "bar.css", "lib1.ts", "baz.js", "lib2.coffee", "hmr.js"],
"x_google_ignoreList": [2, 4, 5],
...
}
Se você é um desenvolvedor de framework ou bundler, verifique se os mapas de origem gerados durante o processo de build incluem esse campo para se conectar a esses novos recursos no Chrome DevTools.
x_google_ignoreList
no Angular
A partir da Angular v14.1.0, o conteúdo das pastas node_modules
e webpack
foi marcado como “para ignorar”.
Isso foi feito com uma mudança no angular-cli
criando um plug-in que se conecta ao módulo Compiler
do webpack
O plug-in do webpack criado pelos nossos engenheiros se conecta ao estágio PROCESS_ASSETS_STAGE_DEV_TOOLING
e preenche o campo x_google_ignoreList
nos mapas de origem dos recursos finais gerados pelo webpack e carregados pelo navegador.
const map = JSON.parse(mapContent) as SourceMap;
const ignoreList = [];
for (const [index, path] of map.sources.entries()) {
if (path.includes('/node_modules/') || path.startsWith('webpack/')) {
ignoreList.push(index);
}
}
map[`x_google_ignoreList`] = ignoreList;
compilation.updateAsset(name, new RawSource(JSON.stringify(map)));
Stack traces vinculados
Os Stack Traces respondem à pergunta “como cheguei aqui”, mas muitas vezes isso é da perspectiva da máquina e não necessariamente algo que corresponda à perspectiva do desenvolvedor ou ao modelo mental dele do tempo de execução do aplicativo. Isso é especialmente verdadeiro quando algumas operações são programadas para acontecer de maneira assíncrona mais tarde: ainda pode ser interessante saber a "causa raiz" ou o lado da programação dessas operações, mas isso é exatamente algo que não fará parte de um stack trace assíncrono.
Internamente, o V8 tem um mecanismo para acompanhar essas tarefas assíncronas quando os primitivos de programação padrão do navegador são usados, como setTimeout
. Isso é feito por padrão nesses casos, para que os desenvolvedores possam inspecionar. Mas, em projetos mais complexos, não é tão simples assim, especialmente quando se usa uma estrutura com mecanismos de programação mais avançados, por exemplo, que realiza rastreamento de zona, enfileiramento de tarefas personalizadas ou que divide atualizações em várias unidades de trabalho executadas ao longo do tempo.
Para resolver isso, o DevTools expõe um mecanismo chamado "API Async Stack Tagging" no objeto console
, que permite que os desenvolvedores de framework indiquem os locais em que as operações estão programadas e onde são executadas.
A API Async Stack Tagging
Sem a inclusão de tags de pilha assíncrona, os rastreamentos de pilha para código executado de forma assíncrona de maneira complexa por frameworks aparecem sem conexão com o código em que foram programados.
Com a inclusão de tags de pilha assíncrona, é possível fornecer esse contexto, e o stack trace fica assim:
Para fazer isso, use um novo método console
(link em inglês) chamado console.createTask()
, fornecido pela API Async Stack Tagging. A assinatura é a seguinte:
interface Console {
createTask(name: string): Task;
}
interface Task {
run<T>(f: () => T): T;
}
Invocar console.createTask()
retorna uma instância Task
que você pode usar mais tarde para executar o código assíncrono.
// Task Creation
const task = console.createTask(name);
// Task Execution
task.run(f);
As operações assíncronas também podem ser aninhadas, e as "causas raiz" vão ser mostradas no stack trace em sequência.
As tarefas podem ser executadas várias vezes, e o payload do trabalho pode ser diferente em cada execução. A pilha de chamadas no site de programação será lembrada até que o objeto da tarefa seja coletado da lixeira.
A API Async Stack Tagging no Angular
No Angular, foram feitas mudanças no NgZone, o contexto de execução do Angular que persiste em tarefas assíncronas.
Ao programar uma tarefa, ele usa console.createTask()
quando disponível. A instância Task
resultante é armazenada para uso posterior. Ao invocar a tarefa, o NgZone usará a instância Task
armazenada para executá-la.
Essas mudanças foram lançadas no NgZone 0.11.8 do Angular por meio dos pull requests #46693 e #46958.
Frames de chamada amigáveis
Os frameworks geralmente geram código de todos os tipos de linguagens de modelagem ao criar um projeto, como modelos Angular ou JSX que transformam código com aparência de HTML em JavaScript simples que é executado no navegador. Às vezes, esses tipos de funções geradas recebem nomes que não são muito amigáveis, como nomes de uma única letra após a minificação ou nomes obscuros ou desconhecidos, mesmo quando não são.
No Angular, não é incomum encontrar frames de chamada com nomes como AppComponent_Template_app_button_handleClick_1_listener
em rastros de pilha.
Para resolver esse problema, o Chrome DevTools agora oferece suporte à renomeação dessas funções usando mapas de origem. Se um mapa de origem tiver uma entrada de nome para o início de um escopo de função (ou seja, o parêntese esquerdo da lista de parâmetros), o frame de chamada precisará exibir esse nome no stack trace.
Frames de chamada amigáveis no Angular
A renomeação de frames de chamadas no Angular é um esforço contínuo. Esperamos que essas melhorias sejam lançadas gradualmente ao longo do tempo.
Ao analisar os modelos HTML que os autores escreveram, o compilador Angular gera o código TypeScript, que é transformado em código JavaScript que o navegador carrega e executa.
Como parte desse processo de geração de código, os mapas de origem também são criados. No momento, estamos procurando maneiras de incluir nomes de funções no campo "names" dos mapas de origem e fazer referência a esses nomes nos mapeamentos entre o código gerado e o original.
Por exemplo, se uma função para um listener de eventos for gerada e o nome dela for removido ou não for amigável durante a minificação, os mapas de origem agora poderão incluir o nome mais amigável dessa função no campo "names", e o mapeamento para o início do escopo da função agora poderá se referir a esse nome (ou seja, o parêntese esquerdo da lista de parâmetros). As Ferramentas do desenvolvedor do Chrome vão usar esses nomes para renomear frames de chamada em stack traces.
No futuro
Usar o Angular como piloto de teste para verificar nosso trabalho foi uma experiência maravilhosa. Queremos saber a opinião dos desenvolvedores de frameworks e enviar feedback sobre esses pontos de extensão.
Há mais áreas que gostaríamos de explorar. Em particular, como melhorar a experiência de criação de perfil no DevTools.