Come forse saprai, Chrome DevTools è un'applicazione web scritta utilizzando HTML, CSS e JavaScript. Nel corso degli anni, DevTools è diventato più ricco di funzionalità, più intelligente e più esperto della piattaforma web più ampia. Sebbene DevTools sia cresciuto nel corso degli anni, la sua architettura è in gran parte simile a quella originale quando faceva ancora parte di WebKit.
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 modalità di creazione. Spiegheremo come ha funzionato storicamente DevTools, quali erano i vantaggi e le limitazioni e cosa abbiamo fatto per alleviarle. Pertanto, approfondiamo i sistemi di moduli, come caricare il codice e come abbiamo finito per utilizzare i moduli JavaScript.
All'inizio non c'era niente
Sebbene l'attuale panorama frontend offra una serie di sistemi di moduli con strumenti appositamente progettati, oltre al formato dei moduli JavaScript ora standardizzato, nessuno di questi esisteva al momento della creazione di DevTools. DevTools è basato su codice inizialmente rilasciato in WebKit più di 12 anni fa.
Il primo riferimento a un sistema di moduli in DevTools ha origine nel 2012: l'introduzione di un elenco di moduli con un elenco di origini associato.
Questo faceva parte dell'infrastruttura Python utilizzata all'epoca per compilare e creare DevTools.
Una modifica successiva ha estratto tutti i moduli in un file frontend_modules.json
separato (commit) nel 2013 e poi in file module.json
separati (commit) nel 2014.
Un file module.json
di esempio:
{
"dependencies": [
"common"
],
"scripts": [
"StylePane.js",
"ElementsPanel.js"
]
}
Dal 2014, il pattern module.json
viene utilizzato in DevTools per specificare i moduli e i file di origine.
Nel frattempo, l'ecosistema web si è evoluto rapidamente e sono stati creati più formati di moduli, tra cui UMD, CommonJS e i moduli JavaScript eventualmente standardizzati.
Tuttavia, DevTools si è bloccato al formato module.json
.
Sebbene DevTools continuasse a funzionare, l'utilizzo di un sistema di moduli non standardizzato e univoco presentava alcuni svantaggi:
- Il formato
module.json
richiedeva strumenti di compilazione personalizzati, simili ai moderni bundler. - Non era presente l'integrazione con l'IDE, il che richiedeva strumenti personalizzati per generare file comprensibili dalle IDE moderne (lo script originale per generare file jsconfig.json per VS Code).
- Funzioni, classi e oggetti sono stati inseriti nell'ambito globale per consentire la condivisione tra i moduli.
- I file erano dipendenti dall'ordine, il che significa che l'ordine in cui erano elencati i file
sources
era importante. Non c'era alcuna garanzia che il codice su cui fai affidamento venisse caricato, a parte il fatto che una persona fisica lo avesse verificato.
Nel complesso, quando abbiamo valutato lo stato attuale del sistema dei moduli in DevTools e negli altri formati di moduli (più ampiamente utilizzati), abbiamo capito che il pattern module.json
creava più problemi di quanti ne risolvesse e che era il momento di pianificarne la transizione.
I vantaggi degli standard
Tra i sistemi di moduli esistenti, abbiamo scelto i moduli JavaScript come destinazione della migrazione. Al momento della decisione, i moduli JavaScript erano ancora disponibili dietro un flag in Node.js e una grande quantità di pacchetti disponibili su NPM non aveva un bundle di moduli JavaScript che potevamo utilizzare. Nonostante ciò, abbiamo concluso che i moduli JavaScript erano l'opzione migliore.
Il vantaggio principale dei moduli JavaScript è che si tratta del formato del modulo standardizzato per JavaScript.
Quando abbiamo elencato gli svantaggi di module.json
(vedi sopra), ci siamo resi conto che quasi tutti erano correlati all'utilizzo di un formato di moduli unico e non standardizzato.
Scegliere un formato per i moduli non standardizzato significa che dobbiamo investire tempo noi stessi nella creazione di integrazioni con gli strumenti di creazione utilizzati dai nostri manutentori.
Queste integrazioni erano spesso fragili e non supportavano le funzionalità, richiedevano tempi di manutenzione aggiuntivi e a volte causavano bug impercettibili che alla fine venivano inviati agli utenti.
Poiché i moduli JavaScript erano lo standard, gli IDE come VS Code, i tipi di controllo come Closure Compiler/TypeScript e gli strumenti di compilazione come Rollup/minifier erano in grado di comprendere il codice sorgente che abbiamo scritto.
Inoltre, quando un nuovo gestore entra a far parte del team DevTools, non deve perdere tempo per imparare un formato module.json
proprietario, mentre è probabile che abbia già familiarità con i moduli JavaScript.
Ovviamente, quando DevTools è stato creato inizialmente, non esisteva nessuno dei vantaggi sopra elencati. Per arrivare a questo punto sono stati necessari anni di lavoro nei gruppi di standard, nelle implementazioni di runtime e negli sviluppatori che utilizzano i moduli JavaScript per fornire feedback. Tuttavia, quando sono diventati disponibili i moduli JavaScript, abbiamo dovuto scegliere: continuare a gestire il nostro formato o investire nella migrazione al nuovo.
Il costo del nuovo splendente
Anche se i moduli JavaScript volevano utilizzare numerosi vantaggi, siamo rimasti nel mondo non standard di module.json
.
Raccogliere i vantaggi dei moduli JavaScript ci ha costretto a investire in modo significativo nell'eliminazione dei problemi tecnici, eseguendo una migrazione che potrebbe potenzialmente interrompere le funzionalità e introdurre bug di regressione.
A questo punto, la questione non è "Vogliamo utilizzare i moduli JavaScript?", ma la domanda "Quanto costa usare i moduli JavaScript?". In questo caso, abbiamo dovuto valutare il rischio di causare problemi agli utenti con le regressioni, il costo degli ingegneri che hanno impiegato (molto) tempo per la migrazione e lo stato peggiore temporaneo in cui avremmo lavorato.
L'ultimo punto si è rivelato molto importante. Anche se in teoria potremmo arrivare ai moduli JavaScript, durante una migrazione finiremmo con codice che dovrebbe prendere in considerazione sia i moduli module.json
sia quelli JavaScript.
Non solo è stato tecnicamente difficile da realizzare, ma significava anche che tutti gli ingegneri che lavorano su DevTools dovevano sapere come lavorare in questo ambiente.
Dovrà chiedersi continuamente "Per questa parte del codebase, sono module.json
o i moduli JavaScript e come faccio ad apportare modifiche?".
Anteprima: il costo nascosto di aiutare i nostri colleghi a eseguire una migrazione è stato maggiore del previsto.
Dopo l'analisi dei costi, abbiamo concluso che valeva comunque la pena eseguire la migrazione ai moduli JavaScript. Pertanto, i nostri obiettivi principali erano i seguenti:
- Assicurati di sfruttare al meglio i vantaggi dell'utilizzo dei moduli JavaScript.
- Assicurati che l'integrazione con il sistema esistente basato su
module.json
sia sicura e non abbia un impatto negativo sugli utenti (bug di regressione, frustrazione degli utenti). - Aiuta tutti i manutentori di DevTools a eseguire la migrazione, principalmente con controlli e bilanci integrati per evitare errori accidentali.
Fogli di lavoro, trasformazioni e debito tecnico
Sebbene l'obiettivo fosse chiaro, le limitazioni imposte dal formato module.json
si sono rivelate difficili da aggirare.
Ci sono volute diverse iterazioni, prototipi e modifiche all'architettura prima di sviluppare una soluzione soddisfacente.
Abbiamo scritto un documento di progettazione con la strategia di migrazione che abbiamo scelto.
Il documento di progettazione riportava anche la nostra stima del tempo iniziale: 2-4 settimane.
Spoiler alert: la parte più intensa della migrazione ha richiesto 4 mesi e l'intera operazione è durata 7 mesi.
Il piano iniziale, tuttavia, ha superato la prova del tempo: insegneremo al runtime di DevTools a caricare tutti i file elencati nell'array scripts
nel file module.json
utilizzando il vecchio metodo, mentre tutti i file elencati nell'array modules
con l'importazione dinamica dei moduli JavaScript.
Qualsiasi file che si trovi nell'array modules
può utilizzare le importazioni/esportazioni di ES.
Inoltre, la migrazione deve essere eseguita in due fasi (alla fine abbiamo suddiviso l'ultima in due fasi secondarie, vedi sotto): le fasi export
e import
.
Lo stato del modulo in ogni fase veniva monitorato in un foglio di lavoro di grandi dimensioni:
Uno snippet del foglio di avanzamento è disponibile pubblicamente qui.
export
-phase
La prima fase consiste nell'aggiungere istruzioni export
per tutti i simboli che dovevano essere condivisi tra moduli/file.
La trasformazione verrebbe automatizzata eseguendo uno script per cartella.
Dato che nel mondo di module.json
esiste il seguente simbolo:
Module.File1.exported = function() {
console.log('exported');
Module.File1.localFunctionInFile();
};
Module.File1.localFunctionInFile = function() {
console.log('Local');
};
dove Module
è il nome del modulo e File1
il nome del file. Nel nostro sourcetree, si tratta di front_end/module/file1.js
.
Il risultato sarà il seguente:
export function exported() {
console.log('exported');
Module.File1.localFunctionInFile();
}
export function localFunctionInFile() {
console.log('Local');
}
/** Legacy export object */
Module.File1 = {
exported,
localFunctionInFile,
};
Inizialmente, il nostro piano prevedeva di riscrivere le importazioni degli stessi file anche durante questa fase.
Ad esempio, nell'esempio precedente riscrivi Module.File1.localFunctionInFile
in localFunctionInFile
.
Tuttavia, ci siamo resi conto che sarebbe stato più facile automatizzare e applicare in modo più sicuro se avessimo separato queste due trasformazioni.
Pertanto, l'operazione "esegui la migrazione di tutti i simboli nello stesso file" diventerà la seconda sottofase della fase import
.
Poiché l'aggiunta della parola chiave export
in un file trasforma il file da uno "script" in un "modulo", gran parte dell'infrastruttura di DevTools ha dovuto essere aggiornata di conseguenza.
Sono inclusi il runtime (con importazione dinamica), ma anche strumenti come ESLint
per l'esecuzione in modalità modulo.
Una delle scoperte che abbiamo fatto durante la risoluzione di questi problemi è che i nostri test erano in esecuzione in modalità "di scarsa qualità".
Poiché i moduli JavaScript presuppongono che i file vengano eseguiti in modalità "use strict"
, ciò influirebbe anche sui nostri test.
Si è scoperto che una quantità non banale di test si basava su questo effetto scivoloso, tra cui un test che utilizzava un'istruzione tipo with
-controimmagine Basso.
Alla fine, l'aggiornamento della prima cartella per includere le istruzioni export
ha richiesto circa una settimana e diversi tentativi con reland.
import
-phase
Dopo che tutti i simboli sono stati esportati utilizzando istruzioni export
e sono rimasti nell'ambito globale (legacy), abbiamo dovuto aggiornare tutti i riferimenti ai simboli tra file per utilizzare le importazioni ES.
L'obiettivo finale è rimuovere tutti gli "oggetti di esportazione precedenti", ripulendo l'ambito globale.
La trasformazione verrebbe automatizzata eseguendo uno script per cartella.
Ad esempio, per i seguenti simboli esistenti nel mondo module.json
:
Module.File1.exported();
AnotherModule.AnotherFile.alsoExported();
SameModule.AnotherFile.moduleScoped();
Verranno trasformati in:
import * as Module from '../module/Module.js';
import * as AnotherModule from '../another_module/AnotherModule.js';
import {moduleScoped} from './AnotherFile.js';
Module.File1.exported();
AnotherModule.AnotherFile.alsoExported();
moduleScoped();
Tuttavia, questo approccio comportava alcune avvertenze:
- Non tutti i simboli sono stati denominati
Module.File.symbolName
. Alcuni simboli sono stati denominati esclusivamenteModule.File
o ancheModule.CompletelyDifferentName
. Questa incoerenza ha comportato la necessità di creare una mappatura interna dal vecchio oggetto globale al nuovo oggetto importato. - A volte si verificano conflitti tra i nomi a livello di modulo.
In particolare, abbiamo utilizzato un pattern per dichiarare determinati tipi di
Events
, in cui ogni simbolo era denominato semplicementeEvents
. Ciò significa che se ascoltavi più tipi di eventi dichiarati in file diversi, si verificava un conflitto di nomi nell'istruzioneimport
per questiEvents
. - A quanto pare, esistevano dipendenze circolari tra i file.
Questo non era un problema in un contesto di ambito globale, poiché l'utilizzo del simbolo avveniva dopo il caricamento di tutto il codice.
Tuttavia, se hai bisogno di un
import
, la dipendenza circolare verrà esplicitata. Questo non è un problema immediato, a meno che non siano presenti chiamate di funzioni con effetti collaterali nel codice a livello globale, come accadeva anche in DevTools. Tutto sommato, sono stati necessari alcuni interventi e il refactoring per rendere sicura la trasformazione.
Un nuovo mondo con i moduli JavaScript
A febbraio 2020, 6 mesi dopo l'inizio a settembre 2019, sono stati eseguiti gli ultimi svuotamenti della cache nella cartella ui/
.
Questo ha segnato la fine non ufficiale della migrazione.
Dopo aver lasciato che le cose si sistemassero, abbiamo contrassegnato ufficialmente la migrazione come completata il 5 marzo 2020. 🎉
Ora tutti i moduli di DevTools utilizzano i moduli JavaScript per condividere il codice.
Inseriamo ancora alcuni simboli nell'ambito globale (nei file module-legacy.js
) per i nostri test precedenti o per l'integrazione con altre parti dell'architettura di DevTools.
Con il passare del tempo le modifiche verranno rimosse, ma non li consideriamo un blocco per lo sviluppo futuro.
Abbiamo anche una guida allo stile per l'utilizzo dei moduli JavaScript.
Statistiche
Le stime conservative per il numero di elenchi di modifiche (abbreviazione di elenchi di modifiche, il termine utilizzato in Gerrit per rappresentare una modifica, simile a una richiesta pull di GitHub) coinvolti in questa migrazione sono di circa 250 elenchi di modifiche, eseguiti in gran parte da due ingegneri. Non abbiamo statistiche definitive sulla dimensione delle modifiche apportate, ma una stima prudente delle righe modificate (calcolata come somma della differenza assoluta tra inserimenti ed eliminazioni per ogni CL) è di circa 30.000 (~20% di tutto il codice frontend di DevTools).
Il primo file che utilizza export
è stato rilasciato in Chrome 79, reso disponibile nella versione stabile a dicembre 2019.
L'ultima modifica per eseguire la migrazione a import
è stata rilasciata in Chrome 83, la versione stabile è stata rilasciata a maggio 2020.
Siamo a conoscenza di una regressione che è stata inviata a Chrome stabile e che è stata introdotta nell'ambito di questa migrazione.
Il completamento automatico degli snippet nel menu dei comandi si è interrotto a causa di un'esportazione default
estranea.
Abbiamo riscontrato diverse altre regressioni, ma le nostre suite di test automatici e gli utenti di Chrome Canary le hanno segnalate e le abbiamo corrette prima che potessero raggiungere gli utenti di Chrome versione stabile.
Puoi vedere il percorso completo (non tutti i CL sono associati a questo bug, ma la maggior parte lo è) registrato su crbug.com/1006759.
Che cosa abbiamo imparato
- Le decisioni prese in passato possono avere un impatto duraturo sul progetto. Anche se i moduli JavaScript (e altri formati di moduli) erano disponibili da un po' di tempo, DevTools non era in grado di giustificare la migrazione. Decidere quando eseguire la migrazione e quando no è difficile e si basa su supposizioni.
- Le nostre stime iniziali erano in settimane anziché in mesi. Ciò è dovuto in gran parte al fatto che abbiamo riscontrato più problemi inaspettati del previsto nella nostra analisi dei costi iniziale. Anche se il piano di migrazione era solido, il problema tecnico era (più spesso di quanto avremmo voluto) il blocco.
- La migrazione dei moduli JavaScript includeva una grande quantità di pulizie del debito tecnico (apparentemente non correlate). La migrazione a un moderno formato di moduli standardizzato ci ha permesso di riallineare le nostre best practice di programmazione con lo sviluppo web odierno. Ad esempio, siamo riusciti a sostituire il nostro bundler Python personalizzato con una configurazione di aggregazione minima.
- Nonostante il grande impatto sul nostro codebase (~20% del codice è cambiato), sono state riportate pochissime regressioni. Abbiamo riscontrato numerosi problemi durante la migrazione dei primi due file, ma dopo un po' di tempo abbiamo ottenuto un flusso di lavoro solido e parzialmente automatizzato. Ciò significa che l'impatto negativo sui nostri utenti stabili per questa migrazione è stato minimo.
- Insegnare le complessità di una determinata migrazione ad altri manutentori è difficile e a volte impossibile. Le migrazioni di queste dimensioni sono difficili da seguire e richiedono una conoscenza approfondita del dominio. Il trasferimento di queste conoscenze di dominio ad altri che lavorano nella stessa base di codice non è auspicabile per il lavoro che stanno svolgendo. Sapere cosa condividere e quali dettagli non condividere è un'arte, ma necessaria. È quindi fondamentale ridurre il numero di migrazioni di grandi dimensioni o, almeno, non eseguirle contemporaneamente.
Scaricare i canali in 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.
- Inviaci il tuo feedback e le richieste di funzionalità all'indirizzo crbug.com.
- Segnala un problema DevTools utilizzando Altre opzioni > Guida > Segnala un problema DevTools in DevTools.
- Invia un tweet all'account @ChromeDevTools.
- Lascia commenti sulle novità nei video di YouTube di DevTools o sui video di YouTube con i suggerimenti per gli strumenti per sviluppatori.