Uma experiência de depuração aprimorada
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. Mesmo que algumas dessas mudanças sejam demonstradas no Angular, elas também podem ser aplicadas a outros frameworks. A equipe do Chrome DevTools incentiva outras estruturas a adotar as novas APIs do console e pontos de extensão do mapa de origem, para que elas 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 ver apenas o código, não o do framework abaixo 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 origens de terceiros, como código da estrutura ou código gerado pelo bundler. Agora, quando um framework usa essa extensão, os autores evitam automaticamente códigos que não querem ver ou seguir 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 de 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 origens de terceiros conhecidas nesse mapa de origem. Ao analisar o mapa de origem, o Chrome DevTools usará isso para descobrir quais seções do código devem ser ignoradas.
Confira abaixo um mapa de origem para um arquivo out.js
gerado. Há dois sources
originais que contribuíram para a geração do 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 para ambas as fontes originais, e o Chrome DevTools exibiria esses arquivos por padrão no Debugger:
- Como arquivos na árvore de origem.
- Como resultado na caixa de diálogo "Quick Open".
- Como locais de frames de chamada mapeados em stack traces de erro enquanto pausados em um ponto de interrupção e durante o passo.
Há uma informação adicional que agora pode ser incluída nos mapas de origem para identificar qual dessas origens é um código próprio 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 adicionadas 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 todos 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ê for um desenvolvedor de framework ou bundler, verifique se os mapas de origem gerados durante o processo de compilação incluem esse campo para se conectar a esses novos recursos no Chrome DevTools.
x_google_ignoreList
no Angular
A partir do Angular v14.1.0, o conteúdo das pastas node_modules
e webpack
foi marcado como “para ignorar”.
Isso foi conseguido com uma mudança no angular-cli
, criando um plug-in que se conecta ao módulo Compiler
do webpack.
O plug-in webpack (link em inglês) que nossos engenheiros criaram hooks no estágio PROCESS_ASSETS_STAGE_DEV_TOOLING
e preenche o campo x_google_ignoreList
nos mapas de origem para os recursos finais que o webpack gera e o navegador carrega.
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 até aqui", mas, muitas vezes, isso é da perspectiva da máquina, e não necessariamente de algo que corresponda à perspectiva do desenvolvedor ou do modelo mental 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
. Nesses casos, isso é feito por padrão. Assim, os desenvolvedores já podem inspecioná-los. Mas, em projetos mais complexos, isso não é tão simples quanto isso, especialmente ao usar um framework com mecanismos de agendamento mais avançados — por exemplo, um que executa 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.
API Async Stack Tagging
Sem a inclusão de tags assíncronas, os stack traces do código que são executados de maneira assíncrona de maneiras complexas pelos frameworks aparecem sem conexão com o código em que foram programados.
Com a inclusão de tag Async Stack, é 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. Sua 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 pode ser usada posteriormente 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" serão exibidas no stack trace em sequência.
As tarefas podem ser executadas quantas vezes você quiser, e a carga útil de trabalho pode ser diferente entre cada execução. A pilha de chamadas no site de agendamento será lembrada até que o objeto da tarefa seja coletado da lixeira.
API Async Stack Tagging no Angular
No Angular, foram feitas alterações 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 chegaram ao NgZone 0.11.8 do Angular usando solicitações de envio #46693 e 46958.
Frames de chamada amigáveis
Os frameworks geralmente geram códigos de todos os tipos de linguagens de modelo ao construir um projeto, como modelos Angular ou JSX que transformam código HTML em JavaScript simples que é executado no navegador. Às vezes, esses tipos de funções geradas recebem nomes pouco amigáveis, como nomes com uma única letra depois de minificados ou alguns nomes obscuros ou desconhecidos, mesmo quando não são.
No Angular, não é incomum ver frames de chamadas com nomes como AppComponent_Template_app_button_handleClick_1_listener
nos stack traces.
Para resolver isso, o Chrome DevTools agora oferece suporte para renomear essas 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 vai mostrar 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 implementadas 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 explorando maneiras de incluir nomes de funções no campo “nomes” dos mapas de origem e fazer referência a esses nomes nos mapeamentos entre o código gerado e o código original.
Por exemplo, se uma função para um listener de eventos for gerada e seu nome for incompatível ou removido durante a minificação, os mapas de origem agora poderão incluir o nome mais simples para essa 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). O Chrome DevTools usará esses nomes para renomear os frames de chamada nos rastreamentos de pilha.
No futuro
Usar o Angular como piloto de teste para conferir se nosso trabalho tem sido uma experiência incrível. 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.