Atualização da arquitetura do DevTools: migração do DevTools para o TypeScript

Tim van der Lippe
Tim van der Lippe

Esta postagem faz parte de uma série de postagens do blog (em inglês) que descrevem as mudanças que estamos fazendo na arquitetura do DevTools e como ela é criada.

Continuando nossa migração para módulos JavaScript e para Web Components, hoje continuamos nossa série de postagens do blog sobre as mudanças que estamos fazendo na arquitetura do Devtools e como ela é construída (links em inglês). Caso você ainda não tenha visto, postamos um vídeo sobre nosso trabalho de Upgrade da arquitetura do DevTools para a Web moderna, com 14 dicas sobre como melhorar seus projetos da Web.

Nesta postagem, vamos descrever nossa jornada de 13 meses mudando do closure Compiler para TypeScript (link em inglês).

Introdução

Considerando o tamanho da base de código do DevTools e a necessidade de dar confiança aos engenheiros que trabalham nela, usar um verificador de tipos é uma necessidade. Por isso, o DevTools adotou o closure Compiler em 2013. A adoção do closure permitiu que os engenheiros do DevTools fizessem mudanças com confiança; o closure compilador executa verificações de tipo para garantir que todas as integrações do sistema sejam bem tipadas.

No entanto, com o passar do tempo, os verificadores de tipos alternativos se tornaram populares no desenvolvimento moderno da Web. Dois exemplos importantes são o TypeScript e o Flow. Além disso, o TypeScript se tornou uma linguagem de programação oficial no Google. Embora esses novos verificadores de tipos tenham aumentado em popularidade, também notamos que estávamos enviando regressões que deveriam ter sido capturadas por um verificador de tipos. Por isso, decidimos reavaliar nossa escolha de verificador de tipo e descobrir as próximas etapas para o desenvolvimento no DevTools.

Como avaliar verificadores de tipo

Como o DevTools já estava usando um verificador de tipo, a pergunta que precisamos responder foi:

Seguimos usando o closure Compiler ou migramos para um novo verificador de tipos?

Para responder a essa pergunta, tivemos que avaliar os verificadores de tipo em várias características. Como o foco do verificador de tipo é a confiança do engenheiro, o aspecto mais importante para nós é a correção. Em outras palavras: qual é o nível de confiabilidade de um verificador de tipos na descoberta de problemas reais?

Nossa avaliação focou nas regressões enviadas e em determinar as causas raiz delas. A suposição aqui é que, como já estávamos usando o closure Compiler, o closure não teria detectado esses problemas. Portanto, teríamos que determinar se qualquer outro verificador de tipos seria capaz de fazer isso.

Correção de tipo no TypeScript

Como o TypeScript era uma linguagem de programação oficialmente aceita no Google e que estava crescendo rapidamente, decidimos avaliar o TypeScript primeiro. O TypeScript foi uma escolha interessante, porque a própria equipe usa o DevTools como um dos projetos de teste para acompanhar a compatibilidade com a verificação de tipo JavaScript. O resultado de teste de referência mostrou que o TypeScript estava detectando uma grande quantidade de problemas de tipo, problemas que o closure compilador não estava necessariamente detectando. Muitos desses problemas provavelmente eram a causa raiz das regressões que estamos lançando. Isso, por sua vez, nos fez acreditar que o TypeScript poderia ser uma opção viável para o DevTools.

Durante nossa migração para módulos JavaScript, já sabíamos que o closure Compiler estava descobrindo mais problemas do que antes. A mudança para um formato de módulo padrão aumentou a capacidade do closure de entender nossa base de código e, portanto, aumentou a eficácia dos verificadores de tipo. No entanto, a equipe do TypeScript estava usando uma versão de referência do DevTools que era anterior à migração dos módulos JavaScript. Portanto, tivemos que descobrir se a migração para módulos JavaScript também reduziu a quantidade de erros que o compilador TypeScript capturaria.

Como avaliar o TypeScript

O DevTools existe há mais de uma década e, por isso, se tornou um aplicativo da Web de tamanho considerável e rico em recursos. No momento em que esta postagem foi escrita, o DevTools tinha aproximadamente 150 mil linhas de código JavaScript próprio. Quando executamos o compilador TypeScript no nosso código-fonte, o grande volume de erros era impressionante. Descobrimos que, embora o compilador TypeScript estivesse emitindo menos erros relacionados à resolução do código (aproximadamente 2.000 erros), ainda havia mais 6.000 erros na nossa base de código relacionados à compatibilidade de tipos.

Isso mostrou que, embora o TypeScript fosse capaz de entender como resolver os tipos, ele encontrou uma quantidade significativa de incompatibilidades de tipo em nossa base de código. Uma análise manual desses erros mostrou que o TypeScript estava correto (na maioria das vezes). O TypeScript conseguiu detectar isso e o closure não era porque, muitas vezes, o compilador closure deduzia um tipo como Any, enquanto o TypeScript executava inferência de tipo com base em atribuições e inferia um tipo mais preciso. Dessa forma, o TypeScript foi realmente melhor em entender a estrutura dos nossos objetos e descobriu usos problemáticos.

Uma desvantagem importante é que o uso do closure compilador no DevTools incluiu o uso frequente do @unrestricted. Anotar uma classe com @unrestricted desativa efetivamente as verificações estritas de propriedade do compilador closure para essa classe específica, o que significa que um desenvolvedor pode aumentar uma definição de classe à vontade sem segurança de tipo. Não foi possível encontrar nenhum contexto histórico sobre por que o uso de @unrestricted era predominante na base de código do DevTools, mas isso resultou na execução do closure compilador em um modo de operação menos seguro para grandes partes da base do código.

Uma análise cruzada das nossas regressões com os erros de tipo descobertos pelo TypeScript também mostrou uma sobreposição, o que nos levou a acreditar que o TypeScript poderia ter evitado esses problemas (desde que os tipos em si estivessem corretos).

Fazendo chamada para any

Neste ponto, tivemos que decidir entre melhorar nosso uso do closure Compiler ou migrar para o TypeScript. Como o fluxo não tinha suporte no Google nem no Chromium, tivemos que renunciar a essa opção. Com base em discussões e recomendações dos engenheiros do Google que trabalham nas ferramentas JavaScript/TypeScript, optamos pelo compilador TypeScript. Também publicamos recentemente uma postagem do blog sobre como migrar o Puppeteer para o TypeScript (em inglês).

Os principais motivos para o compilador TypeScript foram a melhoria da precisão do tipo, enquanto outras vantagens incluíam o suporte das equipes do TypeScript internamente no Google e os recursos da linguagem TypeScript, como interfaces (em vez de typedefs no JSDoc).

Escolher o compilador TypeScript significava que tínhamos que investir significativamente na base de código do DevTools e na arquitetura interna dele. Por isso, precisávamos de pelo menos um ano para migrar para o TypeScript (com previsão para o terceiro trimestre de 2020).

Como executar a migração

A maior pergunta que resta é como migrar para o TypeScript? Temos 150.000 linhas de código e não podemos migrá-las de uma só vez. Também sabíamos que a execução do TypeScript na nossa base de código revelaria milhares de erros.

Avaliamos várias opções:

  1. Consiga todos os erros do TypeScript e compare-os com uma saída "dourada". Essa abordagem seria semelhante à da equipe do TypeScript. A maior desvantagem dessa abordagem é a alta ocorrência de conflitos de mesclagem, já que dezenas de engenheiros trabalham na mesma base de código.
  2. Defina todos os tipos problemáticos como any. Isso basicamente faria com que o TypeScript suprimisse erros. Não escolhemos essa opção, porque nosso objetivo para a migração era a correção do tipo que a supressão prejudicaria.
  3. Corrija todos os erros do TypeScript manualmente. Isso envolveria a correção de milhares de erros, o que é demorado.

Apesar do grande esforço esperado, optamos pela opção 3. Escolhemos essa opção por outros motivos: por exemplo, ela nos permitiu auditar todo o código e fazer uma revisão por década de todas as funcionalidades, incluindo a implementação. De uma perspectiva de negócios, não estávamos proporcionando um novo valor, mas mantendo o status quo. Por isso, ficou mais difícil justificar a opção 3 como a correta.

No entanto, ao adotar o TypeScript, acreditamos que poderíamos evitar problemas futuros, especialmente em torno de regressões. Dessa forma, o argumento foi menos "estamos adicionando um novo valor comercial" e mais "garantimos que não vamos perder valor comercial obtido".

Suporte a JavaScript do compilador TypeScript

Depois de garantir a adesão e desenvolver um plano para executar os compiladores closure e TypeScript no mesmo código JavaScript, começamos com alguns arquivos pequenos. Nossa abordagem foi principalmente de baixo para cima: começar com o código principal e avançar na arquitetura até chegar aos painéis de alto nível.

Conseguimos carregar nosso trabalho em paralelo adicionando @ts-nocheck antecipadamente a cada arquivo no DevTools. O processo de "correção do TypeScript" seria remover a anotação @ts-nocheck e resolver todos os erros encontrados pelo TypeScript. Isso significava que estávamos confiantes de que cada arquivo tinha sido verificado e que o maior número possível de problemas de tipo tinha sido resolvido.

Em geral, essa abordagem funcionou com alguns problemas. Encontramos vários bugs no compilador do TypeScript, mas a maioria deles era obscura:

  1. Um parâmetro opcional com um tipo de função que retorna any é tratado como obrigatório: #38551.
  2. Uma atribuição de propriedade a um método estático de uma declaração quebra a declaração: 38553 (link em inglês).
  3. A declaração de uma subclasse com um construtor no-args e uma superclasse com um construtor args omite o construtor filho: #41397.

Esses bugs destacam que, no caso dos 99% dos casos, o compilador TypeScript é uma base sólida para a criação. Sim, esses bugs obscuros às vezes causavam problemas para o DevTools, mas na maioria das vezes eles eram obscuros o suficiente para que pudéssemos contorná-los facilmente.

O único problema que causou alguma confusão foi a saída não determinista dos arquivos .tsbuildinfo: #37156 (link em inglês). No Chromium, exigimos que dois builds da mesma confirmação do Chromium resultem exatamente na mesma saída. Infelizmente, os engenheiros de criação do Chromium descobriram que a saída do .tsbuildinfo não era determinista: crbug.com/1054494. Para resolver esse problema, tivemos que fazer o monkey-patch do arquivo .tsbuildinfo (que basicamente contém JSON) e pós-processá-lo para retornar uma saída determinista: https://crrev.com/c/2091448. Felizmente, a equipe do TypeScript resolveu o problema upstream e logo conseguimos remover nossa solução alternativa. Agradecemos à equipe do TypeScript por receber relatórios de bugs e corrigir esses problemas rapidamente.

Em geral, estamos satisfeitos com a exatidão (tipo) do compilador TypeScript. Esperamos que o Devtools, como um grande projeto de código aberto em JavaScript, tenha ajudado a solidificar o suporte a JavaScript no TypeScript.

Análise das consequências

Conseguimos progredir na resolução desses erros de tipo e aumentar lentamente a quantidade de código verificada pelo TypeScript. No entanto, em agosto de 2020 (9 meses após essa migração), fizemos uma verificação e descobrimos que não atingiríamos nosso prazo com nosso ritmo atual. Um dos nossos engenheiros criou um gráfico de análise para mostrar o progresso de "TypeScriptification" (nome que demos a essa migração).

Progresso da migração do TypeScript

Progresso da migração do TypeScript: acompanhamento das linhas de código restantes que precisam ser migradas

As estimativas de zero linhas restantes variam de julho de 2021 a dezembro de 2021, quase um ano após nosso prazo. Após conversarmos com a gerência e outros engenheiros, concordamos em aumentar a quantidade de engenheiros que trabalham na migração para o suporte ao compilador TypeScript. Isso foi possível porque projetamos a migração para ser carregável em paralelo, de modo que vários engenheiros trabalhando em vários arquivos diferentes não entrassem em conflito uns com os outros.

Nesse ponto, o processo de TypeScriptification se tornou um esforço de toda a equipe. Com a ajuda extra, conseguimos finalizar a migração no final de novembro de 2020, 13 meses após o início e mais de um ano antes da nossa estimativa inicial.

No total, 18 engenheiros enviaram 771 listas de mudanças (semelhante a uma solicitação de envio). Nosso bug de rastreamento (https://crbug.com/1011811) tem mais de 1.200 comentários (quase todos eles são postagens automáticas de listas de mudanças). Nossa planilha de rastreamento tinha mais de 500 linhas para todos os arquivos a serem digitados, o responsável e a lista de mudanças em que eles estavam "digitados".

Como reduzir o impacto do desempenho do compilador TypeScript

O maior problema com o qual estamos lidando atualmente é o desempenho lento do compilador TypeScript. Dado o número de engenheiros que criam o Chromium e o DevTools, esse gargalo é caro. Infelizmente, não foi possível identificar esse risco antes da nossa migração. Só quando migramos a maioria dos arquivos para o TypeScript descobrimos um aumento perceptível no tempo gasto nos builds do Chromium: https://crbug.com/1139220 (link em inglês).

Informamos esse problema diretamente à equipe do compilador do Microsoft TypeScript, mas, infelizmente, ela determinou esse comportamento como intencional. Esperamos que eles reconsiderem a questão. Enquanto isso, estamos trabalhando para reduzir o máximo possível o impacto da lentidão no desempenho do Chromium.

Infelizmente, as soluções disponíveis hoje nem sempre são adequadas para colaboradores que não são Googlers. Como as contribuições de código aberto para o Chromium são muito importantes (especialmente as da equipe do Microsoft Edge), estamos procurando ativamente alternativas que funcionem para todos os colaboradores. No entanto, ainda não encontramos uma solução alternativa adequada.

Estado atual do TypeScript no DevTools

No momento, removemos o verificador de tipo do compilador closure da nossa base de código e usamos apenas o compilador TypeScript. Podemos escrever arquivos criados em TypeScript e usar recursos específicos do TypeScript (como interfaces, genéricos etc.), o que nos ajuda no dia a dia. Estamos mais confiantes de que o compilador TypeScript capturará erros de tipo e regressões, que é o que esperávamos que acontecesse quando começamos a trabalhar nessa migração. Essa migração, como muitas outras, foi lenta, variada e muitas vezes desafiadora, mas, à medida que rendemos os benefícios, acreditamos que valeu a pena.

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é-lançamento oferecem acesso aos recursos mais recentes do DevTools, testam as APIs modernas de plataformas da Web e encontram problemas no site antes dos usuários.

Entrar em contato com a equipe do Chrome DevTools

Use as opções a seguir para discutir os novos recursos e mudanças na publicação ou qualquer outra coisa relacionada 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.
  • Envie um tweet em @ChromeDevTools.
  • Deixe comentários nos nossos vídeos do YouTube sobre a ferramenta DevTools ou nos vídeos do YouTube com dicas sobre o DevTools.