Aggiornamento dell'architettura DevTools: migrazione di DevTools a TypeScript

Tim van der Lippe
Tim van der Lippe

Questo post fa parte di una serie di post del blog che descrivono le modifiche che stiamo apportando all'architettura di DevTools e alla sua creazione.

A seguito della migrazione ai moduli JavaScript e alla migrazione ai componenti web, oggi continuiamo la nostra serie di post del blog sulle modifiche che stiamo apportando all'architettura di DevTools e alla sua creazione. Se non l'hai ancora visto, abbiamo pubblicato un video sul nostro lavoro di upgrade dell'architettura di DevTools al web moderno, con 14 suggerimenti su come apportare miglioramenti ai tuoi progetti web.

In questo post descriveremo il nostro percorso di 13 mesi che ci ha portato dal strumento di controllo dei tipi di Closure Compiler a TypeScript.

Introduzione

Date le dimensioni del codice sorgente di DevTools e la necessità di garantire sicurezza agli ingegneri che ci lavorano, l'utilizzo di un tipo di controllo è una necessità. A tal fine, DevTools ha adottato Closure Compiler nel 2013. L'adozione di Closure ha consentito agli ingegneri di DevTools di apportare modifiche in tutta sicurezza; il compilatore Closure eseguiva controlli di tipo per garantire che tutte le integrazioni di sistema fossero ben definite.

Tuttavia, con il passare del tempo, i controlli dei tipi alternativi sono diventati popolari nello sviluppo web moderno. Due esempi degni di nota sono TypeScript e Flow. Inoltre, TypeScript è diventato un linguaggio di programmazione ufficiale di Google. Sebbene questi nuovi controlli dei tipi siano diventati sempre più popolari, abbiamo anche notato che stavamo pubblicando regressioni che avrebbero dovuto essere rilevate da un controllo dei tipi. Pertanto, abbiamo deciso di rivalutare la nostra scelta di controllore dei tipi e di capire i passaggi successivi per lo sviluppo in DevTools.

Valutazione dei controlli di tipo

Poiché DevTools utilizzava già un controllore dei tipi, la domanda a cui dovevamo rispondere era:

Continuiamo a utilizzare Closure Compiler o eseguiamo la migrazione a un nuovo programma di controllo dei tipi?

Per rispondere a questa domanda, abbiamo dovuto valutare i controlli di tipo in base a diverse caratteristiche. Poiché il nostro utilizzo di un controllore dei tipi si concentra sulla fiducia degli ingegneri, l'aspetto più importante per noi è la correttezza del tipo. In altre parole: quanto è affidabile un controllore dei tipi per rilevare problemi reali?

La nostra valutazione si è concentrata sulle regressioni che avevamo rilasciato e sulla determinazione delle cause principali. Il presupposto è che, poiché utilizzavamo già Closure Compiler, Closure non avrebbe rilevato questi problemi. Pertanto, dobbiamo determinare se un altro tipo di controllore sarebbe stato in grado di farlo.

Correttezza del tipo in TypeScript

Poiché TypeScript era un linguaggio di programmazione ufficialmente supportato da Google e la sua popolarità stava aumentando rapidamente, abbiamo deciso di valutarlo per primo. TypeScript è stata una scelta interessante, in quanto il team di TypeScript stesso utilizza DevTools come uno dei suoi progetti di test per monitorare la compatibilità con il controllo dei tipi di JavaScript. L'output del test di riferimento di riferimento aveva mostrato che TypeScript stava rilevando una grande quantità di problemi di tipo, problemi che il compilatore Closure non rilevava necessariamente. Molti di questi problemi erano probabilmente la causa principale delle regressioni che stavamo rilasciando; questo, a sua volta, ci ha fatto credere che TypeScript potesse essere un'opzione praticabile per DevTools.

Durante la migrazione ai moduli JavaScript, avevamo già scoperto che Closure Compiler stava rilevando più problemi rispetto al passato. Il passaggio a un formato del modulo standard ha aumentato la capacità di Closure di comprendere la nostra base di codice e, di conseguenza, l'efficacia dei controlli dei tipi. Tuttavia, il team di TypeScript utilizzava una versione di riferimento di DevTools precedente alla migrazione dei moduli JavaScript. Di conseguenza, dovevamo capire se la migrazione ai moduli JavaScript avesse anche ridotto la quantità di errori rilevati dal compilatore TypeScript.

Valutazione di TypeScript

DevTools esiste da oltre un decennio e nel tempo è diventato un'applicazione web di dimensioni e funzionalità notevoli. Al momento della stesura di questo post del blog, DevTools contiene circa 150.000 righe di codice JavaScript proprietario. Quando abbiamo eseguito il compilatore TypeScript sul nostro codice sorgente, il volume di errori era insopportabile. Abbiamo scoperto che, sebbene il compilatore TypeScript emettesse meno errori relativi alla risoluzione del codice (~2000 errori), nel nostro codebase erano ancora presenti altri 6000 errori relativi alla compatibilità dei tipi.

Ciò ha dimostrato che, sebbene TypeScript fosse in grado di capire come risolvere i tipi, ha trovato una quantità significativa di incompatibilità di tipo nel nostro codice di base. Un'analisi manuale di questi errori ha dimostrato che TypeScript era (nella maggior parte dei casi) corretto. Il motivo per cui TypeScript è stato in grado di rilevarli e Closure no è che spesso il compilatore Closure deduceva che un tipo fosse un Any, mentre TypeScript eseguiva l'inferenza del tipo in base alle assegnazioni e deduceva un tipo più preciso. Di conseguenza, TypeScript è stato effettivamente in grado di comprendere meglio la struttura dei nostri oggetti e ha scoperto utilizzi problematici.

Un aspetto importante da tenere presente è che l'utilizzo del compilatore Closure in DevTools includeva l'uso frequente di @unrestricted. L'annotazione di una classe con @unrestricted disattiva in modo efficace i controlli rigorosi delle proprietà del compilatore Closure per quella classe specifica, il che significa che uno sviluppatore può aumentare una definizione di classe a piacere senza la sicurezza del tipo. Non è stato possibile trovare un contesto storico che spieghi perché l'utilizzo di @unrestricted era prevalente nel codice di DevTools, ma questo ha comportato l'esecuzione del compilatore Closure in una modalità di funzionamento meno sicura per ampie parti del codice.

Un'analisi incrociata delle nostre regressioni con gli errori di tipo rilevati da TypeScript ha mostrato anche una sovrapposizione, il che ci ha portato a credere che TypeScript avrebbe potuto evitare questi problemi (a condizione che i tipi stessi fossero corretti).

Effettuare una chiamata any

A questo punto, dovevamo decidere se migliorare l'utilizzo di Closure Compiler o eseguire la migrazione a TypeScript. Poiché Flow non era supportato né da Google né da Chromium, abbiamo dovuto rinunciare a questa opzione. In base alle discussioni e ai consigli degli ingegneri di Google che lavorano sugli strumenti JavaScript/TypeScript, abbiamo scelto il compilatore TypeScript. Di recente abbiamo anche pubblicato un post del blog sulla migrazione di Puppeteer a TypeScript.

I motivi principali per cui abbiamo scelto il compilatore TypeScript sono stati la correzione dei tipi migliorata, mentre altri vantaggi includevano l'assistenza dei team TypeScript interni a Google e le funzionalità del linguaggio TypeScript, come interfaces (a differenza di typedefs in JSDoc).

La scelta del compilatore TypeScript ha comportato un investimento significativo nella base di codice di DevTools e nella sua architettura interna. Di conseguenza, abbiamo stimato che ci sarebbe voluto almeno un anno per eseguire la migrazione a TypeScript (obiettivo previsto per il terzo trimestre del 2020).

Eseguire la migrazione

La domanda più importante che rimane: come eseguiremo la migrazione a TypeScript? Abbiamo 150.000 righe di codice e non possiamo eseguirne la migrazione in un'unica volta. Sapevamo inoltre che l'esecuzione di TypeScript nel nostro codebase avrebbe rilevato migliaia di errori.

Abbiamo valutato diverse opzioni:

  1. Ottieni tutti gli errori di TypeScript e confrontali con un output "gold". Questo approccio sarebbe simile a quello del team di TypeScript. Il principale svantaggio di questo approccio è l'elevata incidenza di conflitti di unione, poiché decine di ingegneri lavorano nella stessa base di codice.
  2. Imposta tutti i tipi problematici su any. In sostanza, TypeScript eliminerebbe gli errori. Non abbiamo scelto questa opzione perché il nostro obiettivo della migrazione era la correttezza del tipo, che la soppressione avrebbe minato.
  3. Correggi manualmente tutti gli errori di TypeScript. Ciò richiederebbe la correzione di migliaia di errori, il che richiede tempo.

Nonostante l'elevato impegno previsto, abbiamo optato per l'opzione 3. Abbiamo scelto questa opzione per altri motivi: ad esempio, ci ha permesso di eseguire l'audit di tutto il codice e di effettuare una revisione decennale di tutte le funzionalità, inclusa la relativa implementazione. Dal punto di vista commerciale, non stavamo offrendo un nuovo valore, ma piuttosto mantenendo lo status quo. Di conseguenza, è stato più difficile giustificare l'opzione 3 come scelta corretta.

Tuttavia, adottando TypeScript, eravamo fermamente convinti di poter prevenire problemi futuri, in particolare quelli relativi alle regressioni. Di conseguenza, l'argomento non era tanto "stiamo aggiungendo nuovo valore all'attività", quanto piuttosto "ci assicuriamo di non perdere il valore dell'attività ottenuto".

Supporto di JavaScript del compilatore TypeScript

Dopo aver ottenuto l'approvazione e aver sviluppato un piano per eseguire sia il compilatore Closure sia quello TypeScript sullo stesso codice JavaScript, abbiamo iniziato con alcuni file di piccole dimensioni. Il nostro approccio è stato principalmente dal basso verso l'alto: abbiamo iniziato dal codice di base e siamo risaliti nell'architettura fino a raggiungere i pannelli di alto livello.

Abbiamo potuto parallelizzare il nostro lavoro aggiungendo preventivamente @ts-nocheck a ogni singolo file in DevTools. La procedura per "correggere TypeScript" consiste nel rimuovere l'annotazione @ts-nocheck e risolvere gli errori rilevati da TypeScript. Ciò significava che eravamo certi che ogni file fosse stato controllato e che fossero stati risolti il maggior numero possibile di problemi relativi ai tipi.

In generale, questo approccio ha funzionato con pochi problemi. Abbiamo riscontrato diversi bug nel compilatore TypeScript, ma la maggior parte era oscura:

  1. Un parametro facoltativo con un tipo di funzione che restituisce any viene considerato obbligatorio: #38551
  2. L'assegnazione di una proprietà a un metodo statico di un'interruzione della dichiarazione della classe: #38553
  3. La dichiarazione di una sottoclasse con un costruttore senza argomenti e una superclasse con un costruttore con argomenti omette il costruttore secondario: #41397

Questi bug mostrano che, nel 99% dei casi, il compilatore TypeScript è una base solida su cui costruire. Sì, a volte questi bug oscuri causavano problemi a DevTools, ma il più delle volte erano abbastanza oscuri da poterli aggirare facilmente.

L'unico problema che ha causato un po' di confusione è stato l'output non deterministico dei file .tsbuildinfo: #37156. In Chromium, è necessario che qualsiasi coppia di build dello stesso commit di Chromium generi lo stesso output esatto. Purtroppo, i nostri ingegneri di compilazione di Chromium hanno scoperto che l'output di .tsbuildinfo non era deterministico: crbug.com/1054494. Per risolvere il problema, abbiamo dovuto eseguire il monkey-patch del file .tsbuildinfo (che contiene essenzialmente JSON) e sottoporlo a post-elaborazione per restituire un output deterministico: https://crrev.com/c/2091448 Per fortuna, il team di TypeScript ha risolto il problema a monte e presto siamo riusciti a rimuovere la nostra soluzione alternativa. Grazie al team di TypeScript per aver preso in considerazione le segnalazioni di bug e aver risolto i problemi tempestivamente.

Nel complesso, siamo soddisfatti dell'accuratezza (del tipo) del compilatore TypeScript. Ci auguriamo che Devtools, in quanto grande progetto JavaScript open source, abbia contribuito a consolidare il supporto di JavaScript in TypeScript.

Analizzare le conseguenze

Abbiamo fatto buoni progressi nella risoluzione di questi errori di tipo e nell'aumento graduale della quantità di codice controllata da TypeScript. Tuttavia, ad agosto 2020 (9 mesi dopo l'inizio della migrazione), abbiamo effettuato un controllo e abbiamo scoperto che non saremmo riusciti a rispettare la scadenza con il nostro attuale ritmo. Uno dei nostri tecnici ha creato un grafico di analisi per mostrare l'avanzamento della "TypeScriptificazione" (il nome che abbiamo dato a questa migrazione).

Avanzamento della migrazione a TypeScript

Avanzamento della migrazione a TypeScript: monitoraggio delle righe di codice rimanenti di cui è necessaria la migrazione

Le stime relative al momento in cui non avremmo più linee disponibili andavano da luglio 2021 a dicembre 2021, quasi un anno dopo la scadenza. Dopo aver discusso con il management e altri ingegneri, abbiamo deciso di aumentare il numero di ingegneri che si occupano della migrazione al supporto del compilatore TypeScript. Ciò è stato possibile perché abbiamo progettato la migrazione in modo che fosse parallelizzabile, in modo che più ingegneri che lavoravano su più file diversi non entrassero in conflitto tra loro.

A questo punto, il processo di TypeScriptificazione è diventato un impegno a livello di team. Con l'aiuto aggiuntivo, siamo riusciti a completare la migrazione alla fine di novembre 2020, 13 mesi dopo l'inizio e più di un anno prima della data prevista dalla nostra stima iniziale.

In totale, sono stati inviati 771 elenchi di modifiche (simili a una richiesta pull) da 18 ingegneri. Il nostro bug di monitoraggio (https://crbug.com/1011811) ha oltre 1200 commenti (quasi tutti post automatici provenienti dai changelist). Il nostro foglio di monitoraggio conteneva oltre 500 righe per tutti i file da convertire in TypeScript, il relativo assegnatario e il changelist in cui sono stati "convertiti in TypeScript".

Mitigazione dell'impatto delle prestazioni del compilatore TypeScript

Il problema più grande che stiamo affrontando al momento è la lentezza del compilatore TypeScript. Dato il numero di ingegneri che sviluppano Chromium e DevTools, questo collo di bottiglia è costoso. Purtroppo, non siamo stati in grado di identificare questo rischio prima della migrazione e solo dopo aver eseguito la migrazione della maggior parte dei file a TypeScript abbiamo scoperto un notevole aumento del tempo impiegato per le build di Chromium: https://crbug.com/1139220

Abbiamo segnalato il problema al team di compilatori TypeScript di Microsoft, ma purtroppo hanno stabilito che questo comportamento è intenzionale. Ci auguriamo che riconsiderino il problema, ma nel frattempo stiamo lavorando per ridurre al minimo l'impatto del rallentamento delle prestazioni lato Chromium.

Purtroppo, le soluzioni attualmente a nostra disposizione non sono sempre adatte ai collaboratori non Google. Poiché i contributi open source a Chromium sono molto importanti (in particolare quelli del team di Microsoft Edge), stiamo cercando attivamente alternative che funzionino per tutti i collaboratori. Tuttavia, al momento non abbiamo trovato una soluzione alternativa adatta.

Stato attuale di TypeScript in DevTools

Al momento abbiamo rimosso il controllore dei tipi del compilatore Closure dal nostro codice di base e ci basiamo esclusivamente sul compilatore TypeScript. Siamo in grado di scrivere file creati in TypeScript e di utilizzare funzionalità specifiche di TypeScript (come interfacce, generici e così via), che ci aiutano quotidianamente. Abbiamo aumentato la certezza che il compilatore TypeScript rileverà errori di tipo e regressioni, che è ciò che speravamo accadesse quando abbiamo iniziato a lavorare a questa migrazione. Questa migrazione, come molte altre, è stata lenta, complessa e spesso impegnativa, ma ora che stiamo raccogliendo i frutti, riteniamo che sia valsa la pena.

Scaricare i canali di anteprima

Valuta la possibilità di utilizzare Chrome Canary, Dev o Beta come browser di sviluppo predefinito. Questi canali di anteprima ti consentono di accedere alle funzionalità più recenti di DevTools, di testare API di piattaforme web all'avanguardia e di trovare i problemi sul tuo sito prima che lo facciano gli utenti.

Contatta il team di Chrome DevTools

Utilizza le seguenti opzioni per discutere di nuove funzionalità, aggiornamenti o qualsiasi altro argomento relativo a DevTools.