Como migrar o Puppeteer para o TypeScript

Somos grandes fãs do TypeScript na equipe do DevTools. Tanto que o novo código no DevTools está sendo criado nele, e estamos no meio de uma grande migração de toda a base de código para ser verificada pelo TypeScript. Saiba mais sobre essa migração na nossa palestra no Chrome Dev Summit 2020. Portanto, fazia todo o sentido migrar a base de código do Puppeteer para o TypeScript também.

Como planejar a migração

Ao planejar a migração, queríamos fazer progresso em pequenas etapas. Isso reduz o overhead da migração, já que você trabalha apenas em uma pequena parte do código por vez, e também diminui o risco. Se algo der errado em uma das etapas, é fácil reverter. O Puppeteer tem muitos usuários, e um lançamento com falhas causaria problemas para muitos deles. Por isso, era fundamental manter o risco de alterações interruptivas no mínimo.

Também tivemos a sorte de o Puppeteer ter um conjunto robusto de testes de unidade que cobrem todas as funcionalidades. Isso significava que podíamos ter certeza de que não estávamos quebrando o código durante a migração, mas também que não estávamos introduzindo mudanças na nossa API. O objetivo da migração era concluí-la sem que os usuários do Puppeteer percebessem que tínhamos migrado, e os testes foram uma parte essencial dessa estratégia. Se não tivéssemos uma boa cobertura de testes, teríamos adicionado isso antes de continuar com a migração.

Fazer qualquer mudança no código sem testes é arriscado, mas as mudanças em que você mexe em arquivos inteiros ou em toda a base de código são especialmente arriscadas. Ao fazer mudanças mecânicas, é fácil esquecer uma etapa, e em várias ocasiões os testes detectaram um problema que passou despercebido pelo implementador e pelo revisor.

Uma coisa em que investimos tempo desde o início foi a configuração de integração contínua (CI). Percebemos que as execuções de CI em relação a solicitações de pull eram instáveis e falhavam com frequência. Isso acontecia com tanta frequência que tínhamos o hábito de ignorar a CI e mesclar as solicitações de pull de qualquer maneira, presumindo que a falha era um problema isolado na CI, e não um problema no Puppeteer.

Depois de uma manutenção geral e de um tempo dedicado a corrigir alguns flakes de teste regulares, conseguimos deixá-lo em um estado de aprovação muito mais consistente, permitindo que ouçamos a CI e saibamos que uma falha estava indicando um problema real. Esse trabalho não é glamoroso, e é frustrante assistir a execuções infinitas de CI, mas foi fundamental ter nosso conjunto de testes funcionando de maneira confiável devido ao número de solicitações de pull que a migração estava gerando.

Escolher e soltar um arquivo

Neste ponto, tínhamos nossa migração pronta para ser executada e um servidor de CI robusto cheio de testes para nos proteger. Em vez de analisar qualquer arquivo, escolhemos um arquivo pequeno para migrar. Esse é um exercício útil porque permite validar o processo planejado que você está prestes a realizar. Se funcionar nesse arquivo, sua abordagem é válida. Caso contrário, você pode voltar à fase de planejamento.

Além disso, analisar arquivo por arquivo (e com lançamentos regulares do Puppeteer, para que todas as mudanças não fossem enviadas na mesma versão do npm) manteve o risco baixo. Escolhemos DeviceDescriptors.js como o primeiro arquivo porque ele era um dos mais simples na base de código. Pode parecer um pouco decepcionante fazer todo esse trabalho de preparação e implementar uma mudança tão pequena, mas o objetivo não é fazer mudanças enormes imediatamente, mas sim proceder com cautela e metodicamente arquivo por arquivo. O tempo gasto validando a abordagem definitivamente economiza tempo mais tarde na migração, quando você se deparar com esses arquivos mais complicados.

Prove o padrão e repita

Felizmente, a mudança em DeviceDescriptors.js foi feita na base de código, e o plano funcionou como esperado. Nesse momento, você está pronto para trabalhar e seguir em frente, que é exatamente o que fizemos. Usar um rótulo do GitHub é uma ótima maneira de agrupar todas as solicitações de pull, e achamos isso útil para acompanhar o progresso.

Migre e melhore depois

Para cada arquivo JavaScript individual, nosso processo foi:

  1. Renomeie o arquivo de .js para .ts.
  2. Execute o compilador do TypeScript.
  3. Corrija os problemas.
  4. Crie a solicitação de pull.

A maior parte do trabalho nessas solicitações de pull iniciais foi extrair interfaces TypeScript para estruturas de dados existentes. No caso da primeira solicitação de pull que migrou DeviceDescriptors.js, conforme discutimos anteriormente, o código passou de:

module.exports = [
  { 
    name: 'Pixel 4',
    … // Other fields omitted to save space
  }, 
  …
]

E se tornou:

interface Device {
  name: string,
  …
}

const devices: Device[] = [{name: 'Pixel 4', …}, …]

module.exports = devices;

Como parte desse processo, trabalhamos em cada linha da base de código para verificar se havia problemas. Como em qualquer base de código que existe há alguns anos e cresceu ao longo do tempo, há áreas de oportunidade para refatorar o código e melhorar a situação. Especialmente com a mudança para o TypeScript, vimos lugares em que uma pequena reestruturação do código nos permitiria confiar mais no compilador e ter uma melhor segurança de tipos.

Ao contrário do que parece, é muito importante resistir a fazer essas mudanças imediatamente. A meta da migração é colocar a base de código no TypeScript. Durante uma migração grande, pense sempre no risco de causar falhas no software e nos usuários. Ao manter as mudanças iniciais mínimas, mantivemos esse risco baixo. Depois que o arquivo foi mesclado e migrado para TypeScript, pudemos fazer mudanças subsequentes para melhorar o código e aproveitar o sistema de tipos. Defina limites rígidos para a migração e tente não ultrapassá-los.

Migrar os testes para testar nossas definições de tipo

Depois que todo o código-fonte foi migrado para o TypeScript, pudemos focar nos testes. Nossos testes tinham ótima cobertura, mas foram todos escritos em JavaScript. Isso significa que uma coisa que eles não testaram foram nossas definições de tipo. Um dos objetivos de longo prazo do projeto (em que ainda estamos trabalhando) é enviar definições de tipo de alta qualidade prontas para uso com o Puppeteer, mas não tínhamos nenhuma verificação no nosso codebase sobre as definições de tipo.

Ao migrar os testes para TypeScript (seguindo o mesmo processo, arquivo por arquivo), encontramos problemas com nosso TypeScript que, de outra forma, teriam sido deixados para os usuários encontrarem para nós. Agora, nossos testes não apenas cobrem todas as funcionalidades, mas também funcionam como uma verificação de qualidade do TypeScript.

Já nos beneficiamos muito do TypeScript como engenheiros que trabalham na base de código do Puppeteer. Junto com nosso ambiente de CI muito melhorado, isso nos permitiu aumentar a produtividade ao trabalhar com o Puppeteer e fazer com que o TypeScript detectasse bugs que, de outra forma, teriam entrado em uma versão do npm. Estamos animados para enviar definições de TypeScript de alta qualidade e permitir que todos os desenvolvedores que usam o Puppeteer também se beneficiem desse trabalho.

Baixar os canais de visualização

Use o Chrome Canary, Dev ou Beta como navegador de desenvolvimento padrão. Esses canais de pré-lançamento 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 dos 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 aos DevTools.