Vernieuwing van de DevTools-architectuur: DevTools migreren naar TypeScript

Tim van der Lippe
Tim van der Lippe

Dit bericht maakt deel uit van een reeks blogposts waarin de wijzigingen worden beschreven die we aanbrengen in de architectuur van DevTools en hoe deze is gebouwd.

In vervolg op onze migratie naar JavaScript-modules en migratie naar Web Components , vervolgen we vandaag onze serie blogposts over de veranderingen die we aanbrengen in de architectuur van Devtools en hoe deze is gebouwd . (Als je het nog niet hebt gezien, hebben we een video geplaatst over ons werk over het upgraden van de architectuur van DevTools naar het moderne web , met 14 tips over hoe je verbeteringen kunt aanbrengen in je webprojecten.)

In dit bericht beschrijven we onze 13 maanden durende reis waarbij we overstapten van de typecontrole van Closure Compiler naar TypeScript .

Invoering

Gezien de omvang van de DevTools-codebasis en de noodzaak om vertrouwen te bieden aan de ingenieurs die eraan werken, is het gebruik van een typechecker een noodzaak . Met dat doel voor ogen adopteerde DevTools in 2013 de Closure Compiler. Door Closure toe te passen, konden DevTools-ingenieurs met vertrouwen veranderingen doorvoeren; de Closure-compiler voerde typecontroles uit om ervoor te zorgen dat alle systeemintegraties goed getypeerd waren.

Naarmate de tijd verstreek, werden alternatieve typecheckers echter populair in de moderne webontwikkeling. Twee opmerkelijke voorbeelden zijn TypeScript en Flow . Bovendien werd TypeScript een officiële programmeertaal bij Google. Hoewel deze nieuwe typecheckers in populariteit toenamen, merkten we ook dat we regressies verzendden die door een typechecker hadden moeten worden onderschept. Daarom hebben we besloten om onze keuze voor typecontrole opnieuw te evalueren en de volgende stappen voor de ontwikkeling op DevTools uit te zoeken.

Typecheckers evalueren

Omdat DevTools al een typecontrole gebruikte, was de vraag die we moesten beantwoorden:

Blijven we Closure Compiler gebruiken of migreren we naar een nieuwe typechecker?

Om deze vraag te beantwoorden, moesten we de typecheckers op verschillende kenmerken beoordelen. Omdat ons gebruik van een typecontrole zich richt op het vertrouwen van de ingenieur, is het belangrijkste aspect voor ons de correctheid van het type. Met andere woorden: hoe betrouwbaar is een typechecker in het ontdekken van echte problemen?

Onze evaluatie concentreerde zich op de regressies die we hadden verzonden en het bepalen wat de grondoorzaken daarvan zouden zijn. De veronderstelling hier is dat Closure deze problemen niet zou hebben opgemerkt, omdat we de Closure Compiler al gebruikten. We zouden dus moeten bepalen of een andere typecontrole dit had kunnen doen.

Typ correctheid in TypeScript

Omdat TypeScript een officieel ondersteunde programmeertaal bij Google was en snel in populariteit toenam, besloten we TypeScript eerst te evalueren. TypeScript was een interessante keuze, omdat het TypeScript-team zelf DevTools gebruikt als een van hun testprojecten om hun compatibiliteit met JavaScript-typecontrole te volgen. Hun basisreferentietestresultaten hadden aangetoond dat TypeScript een groot aantal typeproblemen opmerkte - problemen die de Closure-compiler niet noodzakelijkerwijs ontdekte. Veel van deze problemen waren waarschijnlijk de hoofdoorzaak van de regressies die we presenteerden; dit deed ons op zijn beurt geloven dat TypeScript een haalbare optie zou kunnen zijn voor DevTools.

Tijdens onze migratie naar JavaScript-modules hadden we al ontdekt dat Closure Compiler meer problemen aan het licht bracht dan voorheen. De overstap naar een standaardmoduleformaat had het vermogen van Closure om onze codebase te begrijpen vergroot en daardoor de effectiviteit van typecheckers vergroot. Het TypeScript-team gebruikte echter een basisversie van DevTools die dateerde van vóór de migratie van JavaScript-modules. Daarom moesten we uitzoeken of de migratie naar JavaScript-modules ook het aantal fouten had verminderd dat de TypeScript-compiler zou opvangen.

TypeScript evalueren

DevTools bestaat al meer dan tien jaar en is uitgegroeid tot een webapplicatie van aanzienlijk formaat en met veel functies. Op het moment dat deze blogpost wordt geschreven, bevat DevTools ongeveer 150.000 regels first-party JavaScript-code. Toen we de TypeScript-compiler op onze broncode draaiden, was het enorme aantal fouten overweldigend. We kwamen erachter dat, hoewel de TypeScript-compiler minder fouten met betrekking tot coderesolutie (~2.000 fouten) produceerde, er nog steeds nog eens 6.000 fouten aanwezig waren in onze codebase met betrekking tot typecompatibiliteit.

Hieruit bleek dat hoewel TypeScript in staat was om te begrijpen hoe typen moesten worden opgelost, het een aanzienlijk aantal type-incompatibiliteiten in onze codebase vond. Een handmatige analyse van deze fouten had aangetoond dat TypeScript (meestal) correct was. De reden dat TypeScript deze wel kon detecteren en Closure niet, was omdat de Closure-compiler vaak afleidde dat een type een Any was, terwijl TypeScript type-inferentie zou uitvoeren op basis van toewijzingen en een nauwkeuriger type zou afleiden. Als zodanig was TypeScript inderdaad beter in het begrijpen van de structuur van onze objecten en ontdekte het problematische gebruik .

Een belangrijk voordeel hiervan is dat het gebruik van de Closure-compiler in DevTools het frequente gebruik van @unrestricted omvatte. Door een klasse te annoteren met @unrestricted worden de strikte eigenschapscontroles van de Closure-compiler voor die specifieke klasse effectief uitgeschakeld, wat betekent dat een ontwikkelaar een klassedefinitie naar believen kan uitbreiden zonder typeveiligheid. We konden geen enkele historische context vinden over waarom het gebruik van @unrestricted gangbaar was in de DevTools-codebase, maar het had ertoe geleid dat de Closure-compiler in een minder veilige werkingsmodus werd uitgevoerd voor grote delen van de codebase.

Een kruisanalyse van onze regressies met de typefouten die TypeScript ontdekte, toonde ook een overlap aan, wat ons deed geloven dat TypeScript deze problemen had kunnen voorkomen (op voorwaarde dat de typen zelf correct waren).

any

Op dit punt moesten we beslissen tussen het verbeteren van ons Closure Compiler-gebruik of het migreren naar TypeScript. (Aangezien Flow noch bij Google noch in Chromium werd ondersteund, moesten we van die optie afzien.) Op basis van discussies met en aanbevelingen van Google-technici die aan de JavaScript/TypeScript-tooling werkten, hebben we ervoor gekozen om voor de TypeScript-compiler te kiezen. (We hebben onlangs ook een blogpost gepubliceerd over het migreren van Puppeteer naar TypeScript .)

De belangrijkste redenen voor de TypeScript-compiler waren de verbeterde typecorrectheid, terwijl andere voordelen onder meer de ondersteuning van TypeScript-teams intern bij Google en de kenmerken van de TypeScript-taal waren, zoals interfaces (in tegenstelling tot typedefs in JSDoc).

Door voor de TypeScript-compiler te kiezen, moesten we aanzienlijk investeren in de DevTools-codebase en de interne architectuur ervan. Daarom schatten we dat we minstens een jaar nodig hadden voor de migratie naar TypeScript (gepland voor het derde kwartaal van 2020).

Het uitvoeren van de migratie

De grootste vraag die bleef: hoe gaan we migreren naar TypeScript? We hebben 150.000 regels code en die kunnen we niet in één keer migreren. We wisten ook dat het uitvoeren van TypeScript op onze codebase duizenden fouten zou blootleggen.

We hebben meerdere opties geëvalueerd:

  1. Verkrijg alle TypeScript-fouten en vergelijk ze met een "gouden" uitvoer . Deze aanpak zou vergelijkbaar zijn met wat het TypeScript-team heeft. Het grootste nadeel van deze aanpak is het hoge aantal merge-conflicten, aangezien tientallen engineers in dezelfde codebase werken.
  2. Stel alle problematische typen in op any . Dit zou er in wezen voor zorgen dat TypeScript fouten onderdrukt. Wij hebben voor deze optie niet gekozen, omdat ons doel voor de migratie typecorrectheid was, die door onderdrukking zou worden ondermijnd.
  3. Herstel alle TypeScript-fouten handmatig. Hiervoor moeten duizenden fouten worden hersteld, wat tijdrovend is.

Ondanks de grote verwachte inspanningen hebben we voor optie 3 gekozen. Er waren nog meer redenen waarom we voor deze optie kozen: het stelde ons bijvoorbeeld in staat alle code te auditen en eens in de tien jaar alle functionaliteit te beoordelen, inclusief de implementatie ervan. . Vanuit zakelijk perspectief leverden we geen nieuwe waarde, maar handhaafden we eerder de status quo. Dit maakte het moeilijker om optie 3 als de juiste keuze te rechtvaardigen.

Door TypeScript te adopteren waren we er echter sterk van overtuigd dat we toekomstige problemen konden voorkomen , vooral rond regressies. Als zodanig was het argument minder "we voegen nieuwe bedrijfswaarde toe", en meer "we zorgen ervoor dat we de verkregen bedrijfswaarde niet verliezen".

JavaScript-ondersteuning van de TypeScript-compiler

Nadat we de buy-in veilig hadden gesteld en een plan hadden ontwikkeld om zowel de Closure- als de TypeScript-compiler op dezelfde JavaScript-code te laten draaien , zijn we begonnen met enkele kleine bestanden. Onze aanpak was grotendeels bottom-up: begin met de kerncode en ga omhoog in de architectuur totdat we de panelen op hoog niveau bereiken.

We konden ons werk parallelliseren door preventief @ts-nocheck toe te voegen aan elk afzonderlijk bestand in DevTools. Het proces van het "repareren van TypeScript" zou bestaan ​​uit het verwijderen van de @ts-nocheck -annotatie en het oplossen van eventuele fouten die TypeScript zou vinden. Dit betekende dat we er zeker van waren dat elk bestand was gecontroleerd en dat zoveel mogelijk typeproblemen waren opgelost.

Over het algemeen werkte deze aanpak met weinig problemen. We kwamen verschillende bugs tegen in de TypeScript-compiler, maar de meeste waren onduidelijk:

  1. Een optionele parameter met een functietype dat any retourneert, wordt zoals vereist behandeld: #38551
  2. Een eigenschapstoewijzing aan een statische methode van een klasse breekt declaratie: #38553
  3. Bij de declaratie van een subklasse met een constructor zonder args en een superklasse met een constructor args wordt de onderliggende constructor weggelaten: #41397

Deze bugs benadrukken dat de TypeScript-compiler in 99% van de gevallen een solide basis is om op voort te bouwen. Ja, deze obscure bugs veroorzaakten soms problemen voor DevTools, maar meestal waren ze zo duister dat we er gemakkelijk omheen konden werken.

Het enige probleem dat enige verwarring had veroorzaakt, was de niet-deterministische uitvoer van .tsbuildinfo bestanden: #37156 . Bij Chromium vereisen we dat twee builds van dezelfde Chromium-commit resulteren in exact dezelfde uitvoer. Helaas ontdekten onze Chromium-build-ingenieurs dat de .tsbuildinfo uitvoer niet-deterministisch was: crbug.com/1054494 . Om dit probleem te omzeilen, moesten we het .tsbuildinfo bestand (dat in wezen JSON bevat) aap-patchen en nabewerken om een ​​deterministische uitvoer te retourneren: https://crrev.com/c/2091448 Gelukkig heeft het TypeScript-team het probleem opgelost upstream-probleem en we konden onze tijdelijke oplossing al snel verwijderen. Bedankt aan het TypeScript-team voor hun ontvankelijkheid voor bugrapporten en het snel oplossen van deze problemen!

Over het algemeen zijn we tevreden over de (type)correctheid van de TypeScript-compiler. We hopen dat Devtools als groot open-source JavaScript-project heeft bijgedragen aan het versterken van JavaScript-ondersteuning in TypeScript.

Het analyseren van de nasleep

We hebben goede vooruitgang kunnen boeken bij het oplossen van deze typefouten en het langzaam vergroten van de hoeveelheid code die door TypeScript wordt gecontroleerd. In augustus 2020 (9 maanden na deze migratie) deden we echter een check-in en ontdekten dat we met ons huidige tempo onze deadline niet zouden halen. Een van onze technici heeft een analysegrafiek gebouwd om de voortgang van "TypeScriptification" (de naam die we aan deze migratie hebben gegeven) weer te geven.

Voortgang van TypeScript-migratie

Voortgang van TypeScript-migratie: bijhouden van resterende coderegels die moeten worden gemigreerd

Schattingen wanneer we nul resterende lijnen zouden bereiken, varieerden van juli 2021 tot december 2021, bijna een jaar na onze deadline. Na gesprekken met het management en andere technici kwamen we overeen om het aantal technici dat werkt aan de migratie naar ondersteuning voor de TypeScript-compiler te vergroten. Dit was mogelijk omdat we de migratie zo hadden ontworpen dat deze parallelleerbaar was, zodat meerdere technici die aan meerdere verschillende bestanden werkten geen conflicten met elkaar zouden veroorzaken.

Op dit punt werd het TypeScriptification-proces een teambrede inspanning. Met de extra hulp konden we onze migratie eind november 2020 afronden, 13 maanden na de start, en ruim een ​​jaar eerder dan onze aanvankelijke schatting voorspelde.

In totaal waren er 771 wijzigingslijsten (vergelijkbaar met een Pull Request) ingediend door 18 ingenieurs . Onze trackingbug ( https://crbug.com/1011811 ) heeft meer dan 1200 reacties (bijna allemaal geautomatiseerde berichten uit wijzigingslijsten). Ons trackingblad bevatte meer dan 500 rijen voor alle te typen bestanden, hun rechtverkrijgende en in welke wijzigingslijst ze waren “getypt”.

Het beperken van de impact van de prestaties van de TypeScript-compiler

Het grootste probleem waar we momenteel mee te maken hebben, zijn de trage prestaties van de TypeScript-compiler. Gezien het aantal ingenieurs dat Chromium en DevTools bouwt, is dit knelpunt kostbaar. Helaas konden we dit risico vóór onze migratie niet identificeren, en pas op het punt waarop we de meeste bestanden naar TypeScript hadden gemigreerd, ontdekten we een merkbare toename in de tijd die werd besteed aan Chromium-builds: https://crbug .com/1139220

We hebben dit probleem stroomopwaarts gerapporteerd aan het Microsoft TypeScript-compilerteam, maar helaas hebben zij vastgesteld dat dit gedrag opzettelijk is. We hopen dat ze dit probleem zullen heroverwegen, maar in de tussentijd werken we eraan om de trage prestatie-impact aan de Chromium-kant zoveel mogelijk te beperken.

Helaas zijn de oplossingen die vandaag de dag voor ons beschikbaar zijn, niet altijd geschikt voor niet-Google-bijdragers. Omdat de open-sourcebijdragen aan Chromium erg belangrijk zijn (vooral die van het Microsoft Edge-team), zijn we actief op zoek naar alternatieven die voor alle bijdragers werken. Op dit moment hebben we echter nog geen geschikte alternatieve oplossing gevonden.

Huidige status van TypeScript in DevTools

Op dit moment hebben we de typecontrole van de Closure-compiler uit onze codebase verwijderd en vertrouwen we uitsluitend op de TypeScript-compiler. We kunnen TypeScript-geschreven bestanden schrijven en gebruik maken van TypeScript-specifieke functies (zoals interfaces, generieke programma's, enz...), wat ons dagelijks helpt. We hebben er steeds meer vertrouwen in dat de TypeScript-compiler typefouten en regressies zal onderkennen, en dat is precies wat we hoopten dat zou gebeuren toen we voor het eerst aan deze migratie begonnen te werken. Deze migratie verliep, net als zovele, langzaam, genuanceerd en vaak uitdagend, maar nu we er de vruchten van plukken, denken we dat het de moeite waard was.

Download de voorbeeldkanalen

Overweeg om Chrome Canary , Dev of Beta te gebruiken als uw standaard ontwikkelingsbrowser. Met deze voorbeeldkanalen krijgt u toegang tot de nieuwste DevTools-functies, kunt u geavanceerde webplatform-API's testen en kunt u problemen op uw site opsporen voordat uw gebruikers dat doen!

Neem contact op met het Chrome DevTools-team

Gebruik de volgende opties om de nieuwe functies, updates of iets anders gerelateerd aan DevTools te bespreken.