Puppeteer migreren naar TypeScript

Het DevTools-team is een grote fan van TypeScript. Nieuwe code in DevTools wordt er zelfs in geschreven en we zijn bezig met een grote migratie van de hele codebase naar typecontrole door TypeScript. Je vindt meer informatie over die migratie in onze presentatie op de Chrome Dev Summit 2020. Het was daarom een logische stap om ook de codebase van Puppeteer te migreren naar TypeScript.

De migratie plannen

Toen we de migratie planden, wilden we in kleine stappen vooruitgang kunnen boeken. Zo blijft de overhead van de migratie laag (je werkt maar aan een klein deel van de code tegelijk) en blijft het risico ook laag. Als er iets misgaat met een van de stappen, kun je deze makkelijk terugzetten. Puppeteer heeft veel gebruikers en een defecte release zou voor veel van hen problemen veroorzaken. Daarom was het essentieel dat we het risico op schadelijke wijzigingen tot een minimum beperkten.

We hadden ook het geluk dat Puppeteer een robuuste set unittests heeft die alle functionaliteit dekken. Dit betekende dat we er zeker van konden zijn dat we geen code braken tijdens de migratie en dat we geen wijzigingen in onze API introduceerden. Het doel van de migratie was om deze af te ronden zonder dat Puppeteer-gebruikers zelfs maar beseften dat we waren gemigreerd. De tests waren een essentieel onderdeel van die strategie. Als we geen goede testdekking hadden gehad, hadden we die toegevoegd voordat we verdergingen met de migratie.

Het is riskant om codewijzigingen door te voeren zonder tests, maar wijzigingen waarbij je hele bestanden of de hele codebase aanpast, zijn extra riskant. Als je mechanische wijzigingen aanbrengt, kun je makkelijk een stap overslaan. Meerdere keren hebben de tests een probleem gevonden dat zowel de implementeerder als de beoordelaar over het hoofd had gezien.

We hebben vooraf tijd geïnvesteerd in onze CI-instellingen (Continuous Integration). We hebben gemerkt dat CI-runs voor pull-verzoeken onbetrouwbaar waren en vaak mislukten. Dit gebeurde zo vaak dat we de gewoonte hadden ontwikkeld om onze CI te negeren en de pull-verzoeken toch samen te voegen, in de veronderstelling dat de fout een eenmalig probleem in CI was in plaats van een probleem in Puppeteer.

Na wat algemeen onderhoud en een speciale periode om enkele regelmatige testfouten op te lossen, hebben we de test in een veel consistentere geslaagde staat gebracht, waardoor we naar CI kunnen luisteren en weten dat een fout een echt probleem aangeeft. Dit werk is niet glamoureus en het is frustrerend om eindeloze CI-uitvoeringen te bekijken, maar het was essentieel dat onze testsuite betrouwbaar werkte, gezien het aantal pull-verzoeken dat de migratie veroorzaakte.

Eén bestand kiezen en landen

Op dit moment was onze migratie klaar en hadden we een robuuste CI-server vol tests om ons te beschermen. In plaats van een willekeurig bestand te kiezen, hebben we een klein bestand geselecteerd om te migreren. Dit is een nuttige oefening omdat u hiermee het geplande proces kunt valideren dat u gaat uitvoeren. Als het werkt voor dit bestand, is je aanpak geldig. Als het niet werkt, kun je terug naar de tekentafel.

Door bestand voor bestand te werken (en met regelmatige Puppeteer-releases, zodat niet alle wijzigingen in dezelfde npm-versie werden geleverd) bleef het risico beperkt. We hebben DeviceDescriptors.js als eerste bestand gekozen, omdat het een van de meest eenvoudige bestanden in de codebasis was. Het kan een beetje teleurstellend zijn om al dit voorbereidende werk te doen en zo'n kleine wijziging aan te brengen, maar het doel is niet om meteen grote wijzigingen aan te brengen, maar om voorzichtig en methodisch bestand voor bestand te werk te gaan. De tijd die je besteedt aan de validatie van de aanpak, bespaart je later tijd tijdens de migratie als je die ingewikkeldere bestanden tegenkomt.

Bewijs het patroon en herhaal

Gelukkig is de wijziging naar DeviceDescriptors.js doorgevoerd in de codebase en werkte het plan zoals we hadden gehoopt. Op dat moment ben je klaar om aan de slag te gaan en dat is precies wat we hebben gedaan. Met een GitHub-label kun je alle pull-verzoeken groeperen. We vonden dat handig om de voortgang bij te houden.

Laat het migreren en verbeter het later

Voor elk afzonderlijk JavaScript-bestand was ons proces:

  1. Wijzig de naam van het bestand van .js naar .ts.
  2. Voer de TypeScript-compiler uit.
  3. Los eventuele problemen op.
  4. Maak het pull-verzoek.

Het meeste werk in deze eerste pull-verzoeken was om TypeScript-interfaces voor bestaande gegevensstructuren te extraheren. In het geval van het eerste pull-verzoek dat DeviceDescriptors.js migreerde, zoals we eerder hebben besproken, ging de code van:

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

En werd:

interface Device {
  name: string,
  …
}

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

module.exports = devices;

Als onderdeel van dit proces hebben we elke regel van de codebase doorgewerkt om problemen te vinden. Zoals bij elke codebase die al een paar jaar bestaat en in de loop van de tijd is gegroeid, zijn er mogelijkheden om de code te refactoren en de situatie te verbeteren. Vooral met de overstap naar TypeScript zagen we plaatsen waar we met een kleine herstructurering van de code meer op de compiler konden vertrouwen en betere typeniveaus konden krijgen.

Het is misschien niet wat u verwacht, maar het is heel belangrijk dat u deze wijzigingen niet meteen doorvoert. Het doel van de migratie is om de codebase in TypeScript te krijgen. Tijdens een grote migratie moet je altijd nadenken over het risico dat de software en je gebruikers schade oplopen. Door de eerste wijzigingen minimaal te houden, hebben we dat risico laag gehouden. Nadat het bestand was samengevoegd en gemigreerd naar TypeScript, konden we vervolgwijzigingen aanbrengen om de code te verbeteren en het typesysteem te benutten. Stel strikte grenzen in voor je migratie en probeer binnen die grenzen te blijven.

De tests migreren om onze typedefinities te testen

Nadat we de hele broncode naar TypeScript hadden gemigreerd, konden we ons richten op onze tests. Onze tests hadden een goede dekking, maar waren allemaal geschreven in JavaScript. Dit betekende dat ze onze typedefinities niet testten. Een van de langetermijndoelen van het project (waar we nog steeds aan werken) is om kant-en-klare typedefinities van hoge kwaliteit te leveren met Puppeteer, maar we hadden geen controles in onze codebase over onze typedefinities.

Door de tests naar TypeScript te migreren (volgens hetzelfde proces, bestand voor bestand), hebben we problemen met onze TypeScript gevonden die anders door gebruikers zouden zijn gevonden. Nu dekken onze tests niet alleen al onze functionaliteit, maar fungeren ze ook als kwaliteitscontrole van onze TypeScript.

We hebben al veel voordeel gehad van TypeScript als technici die aan de Puppeteer-codebase werken. In combinatie met onze sterk verbeterde CI-omgeving kunnen we productiever werken aan Puppeteer en kan TypeScript bugs opsporen die anders in een npm-release terecht zouden zijn gekomen. We zijn blij dat we TypeScript-definities van hoge kwaliteit kunnen leveren, zodat alle ontwikkelaars die Puppeteer gebruiken, ook van dit werk kunnen profiteren.

Download de voorbeeldkanalen

Overweeg Chrome Canary, Dev of Bèta te gebruiken als standaard ontwikkelingsbrowser. Via deze voorbeeldkanalen heb je toegang tot de nieuwste DevTools-functies, kun je geavanceerde webplatform-API's testen en kun je problemen op je site vinden voordat je gebruikers dat doen.

Contact opnemen met het Chrome DevTools-team

Gebruik de volgende opties om de nieuwe functies, updates of iets anders met betrekking tot DevTools te bespreken.