Ottimizzazione dell'avvio di JavaScript

Man mano che creiamo siti che si affidano maggiormente a JavaScript, a volte paghiamo per ciò che inviamo in modi che non sempre possiamo vedere facilmente. In questo articolo, spiegheremo perché un po' di disciplina può aiutarti se vuoi che il tuo sito si carichi e sia interattivo rapidamente sui dispositivi mobili. La distribuzione di meno JavaScript può significare meno tempo per la trasmissione di rete, meno tempo per la decompressione del codice e meno tempo per l'analisi e la compilazione di questo JavaScript.

Rete

Quando la maggior parte degli sviluppatori pensa al costo di JavaScript, lo fa in termini di costo di download ed esecuzione. L'invio di più byte di JavaScript tramite la rete richiede più tempo, più lenta è la connessione di un utente.

Quando un browser richiede una risorsa, questa deve essere recuperata e poi decompressa. Nel caso di risorse come JavaScript, queste devono essere analizzate e compilate prima dell'esecuzione.

Questo può essere un problema, in quanto il tipo di connessione di rete effettivo di un utente potrebbe non essere effettivamente 3G, 4G o Wi-Fi. Puoi essere connesso al Wi-Fi di un bar, ma anche a un hotspot di rete dati con velocità 2G.

Puoi ridurre il costo di trasferimento di rete di JavaScript tramite:

  • Inviare solo il codice necessario all'utente.
  • Minimizzazione
  • Compressione
    • Come minimo, utilizza gzip per comprimere le risorse basate su testo.
    • Valuta l'utilizzo di Brotli ~q11. Brotli supera gzip nel rapporto di compressione. Ha aiutato CertSimple a risparmiare il 17% sulle dimensioni dei byte JS compressi e LinkedIn a risparmiare il 4% sui tempi di caricamento.
  • Rimuovi il codice inutilizzato.
  • Memorizzazione del codice nella cache per ridurre al minimo i passaggi di rete.
    • Utilizza la memorizzazione nella cache HTTP per assicurarti che i browser memorizzino nella cache le risposte in modo efficace. Determina la durata ottimale degli script (max-age) e fornisci token di convalida (ETag) per evitare di trasferire byte invariati.
    • La memorizzazione nella cache dei service worker può rendere la tua app resiliente alla rete e darti accesso immediato a funzionalità come la cache del codice di V8.
    • Utilizza la memorizzazione nella cache a lungo termine per evitare di dover recuperare nuovamente le risorse che non sono cambiate. Se utilizzi Webpack, consulta l'articolo sull'hashing dei nomi file.

Analizza/compila

Una volta scaricato, uno dei costi più elevati di JavaScript è il tempo necessario a un motore JS per analizzare/compilare questo codice. In Chrome DevTools, l'analisi e la compilazione fanno parte del tempo di "Scripting" giallo nel riquadro Prestazioni.

ALT_TEXT_HERE

Le schede Bottom-Up e Albero delle chiamate mostrano i tempi esatti di analisi/compilazione:

ALT_TEXT_HERE
Pannello Prestazioni di Chrome DevTools > Bottom-Up. Con le statistiche sulle chiamate di runtime di V8 attivate, possiamo vedere il tempo impiegato in fasi come Analisi e Compilazione

Ma perché è importante?

ALT_TEXT_HERE

Un tempo di analisi/compilazione del codice lungo può ritardare notevolmente il momento in cui un utente può interagire con il tuo sito. Più JavaScript invii, più tempo sarà necessario per analizzarlo e compilarlo prima che il tuo sito diventi interattivo.

Byte per byte, JavaScript è più costoso da elaborare per il browser rispetto a un'immagine o un web font di dimensioni equivalenti - Tom Dale

Rispetto a JavaScript, l'elaborazione di immagini di dimensioni equivalenti comporta numerosi costi (devono comunque essere decodificate), ma sull'hardware mobile medio, JS ha maggiori probabilità di influire negativamente sull'interattività di una pagina.

ALT_TEXT_HERE
I byte di JavaScript e delle immagini hanno costi molto diversi. In genere, le immagini non bloccano il thread principale o impediscono alle interfacce di diventare interattive durante la decodifica e la rasterizzazione. Tuttavia, JS può ritardare l'interattività a causa dei costi di analisi, compilazione ed esecuzione.

Quando diciamo che l'analisi e la compilazione sono lente, il contesto è importante: stiamo parlando di smartphone medi. Gli utenti medi possono avere smartphone con CPU e GPU lente, senza cache L2/L3 e che potrebbero persino avere vincoli di memoria.

Le funzionalità di rete e quelle del dispositivo non sempre corrispondono. Un utente con una connessione Fiber straordinaria non ha necessariamente la CPU migliore per analizzare e valutare il codice JavaScript inviato al suo dispositivo. Questo vale anche al contrario: una connessione di rete pessima, ma una CPU velocissima. — Kristofer Baxter, LinkedIn

Di seguito possiamo vedere il costo dell'analisi di circa 1 MB di JavaScript decompresso (semplice) su hardware di fascia bassa e alta. Esiste una differenza di 2-5 volte nel tempo di analisi/compilazione del codice tra gli smartphone più veloci sul mercato e quelli medi.

ALT_TEXT_HERE
Questo grafico evidenzia i tempi di analisi per un bundle JavaScript da 1 MB (~250 KB con compressione gzip) su computer e dispositivi mobili di diverse classi. Quando si esamina il costo dell'analisi, è necessario considerare i dati decompressi, ad esempio un file JS compresso con gzip da ~250 kB viene decompresso in ~1 MB di codice.

E un sito reale, come CNN.com?

Sull'iPhone 8 di fascia alta, l'analisi/compilazione del codice JS di CNN richiede solo circa 4 secondi, rispetto ai circa 13 secondi di uno smartphone medio (Moto G4). Questo può influire notevolmente sulla rapidità con cui un utente può interagire completamente con il sito.

ALT_TEXT_HERE
Sopra vediamo i tempi di analisi che confrontano le prestazioni del chip A11 Bionic di Apple con quelle dello Snapdragon 617 nell'hardware Android più comune.

Questo evidenzia l'importanza di eseguire test su hardware di livello medio (come Moto G4) anziché solo sullo smartphone che potresti avere in tasca. Tuttavia, il contesto è importante: ottimizza per le condizioni del dispositivo e della rete dei tuoi utenti.

ALT_TEXT_HERE
Google Analytics può fornire informazioni sulle classi di dispositivi mobili con cui gli utenti reali accedono al tuo sito. In questo modo, puoi comprendere i vincoli reali della CPU/GPU con cui operano.

Stiamo davvero inviando troppo codice JavaScript? Ehm, forse :)

Utilizzando HTTP Archive (circa 500.000 siti principali) per analizzare lo stato di JavaScript sui dispositivi mobili, possiamo vedere che il 50% dei siti impiega più di 14 secondi per diventare interattivo. Questi siti impiegano fino a 4 secondi solo per analizzare e compilare JS.

ALT_TEXT_HERE

Se si tiene conto del tempo necessario per recuperare ed elaborare JS e altre risorse, non sorprende che gli utenti possano dover attendere un po' prima di avere la sensazione che le pagine siano pronte per l'uso. Possiamo sicuramente fare di meglio.

La rimozione di JavaScript non critico dalle pagine può ridurre i tempi di trasmissione, l'analisi e la compilazione che richiedono un uso intensivo della CPU e il potenziale overhead della memoria. In questo modo, le tue pagine diventano interattive più rapidamente.

Tempo di esecuzione

Non sono solo l'analisi e la compilazione che possono avere un costo. L'esecuzione di JavaScript (l'esecuzione del codice una volta analizzato/compilato) è una delle operazioni che devono essere eseguite sul thread principale. Tempi di esecuzione lunghi possono anche ritardare il momento in cui un utente può interagire con il tuo sito.

ALT_TEXT_HERE

Se lo script viene eseguito per più di 50 ms, il tempo di interattività viene ritardato dell'intero tempo necessario per scaricare, compilare ed eseguire il JS. Alex Russell

Per risolvere questo problema, è utile suddividere JavaScript in piccoli blocchi per evitare di bloccare il thread principale. Verifica se puoi ridurre la quantità di lavoro svolto durante l'esecuzione.

Altri costi

JavaScript può influire sulle prestazioni della pagina in altri modi:

  • Memoria. Le pagine possono sembrare instabili o interrompersi di frequente a causa della garbage collection (GC). Quando un browser recupera memoria, l'esecuzione di JS viene messa in pausa, quindi un browser che esegue spesso la garbage collection può mettere in pausa l'esecuzione più spesso di quanto vorremmo. Evita le perdite di memoria e le frequenti pause di garbage collection per evitare i jank nelle pagine.
  • Durante il runtime, il codice JavaScript con tempi di esecuzione lunghi può bloccare il thread principale, causando pagine che non rispondono. La suddivisione del lavoro in parti più piccole (utilizzando requestAnimationFrame() o requestIdleCallback() per la pianificazione) può ridurre al minimo i problemi di reattività, il che può contribuire a migliorare l'Interaction to Next Paint (INP).

Pattern per ridurre i costi di pubblicazione di JavaScript

Quando cerchi di mantenere bassi i tempi di analisi/compilazione e di trasmissione di rete per JavaScript, esistono pattern che possono aiutarti, come il chunking basato su route o PRPL.

PRPL

PRPL (Push, Render, Pre-cache, Lazy-load) è un pattern che ottimizza l'interattività tramite la suddivisione del codice e la pre-memorizzazione nella cache aggressive:

ALT_TEXT_HERE

Visualizziamo l'impatto che può avere.

Analizziamo il tempo di caricamento dei siti mobile e delle app web progressive più popolari utilizzando le statistiche sulle chiamate di runtime di V8. Come possiamo vedere, il tempo di analisi (mostrato in arancione) rappresenta una parte significativa del tempo impiegato da molti di questi siti:

ALT_TEXT_HERE

Wego, un sito che utilizza PRPL, riesce a mantenere un tempo di analisi basso per i suoi percorsi, diventando interattivo molto rapidamente. Molti degli altri siti sopra citati hanno adottato la suddivisione del codice e i budget delle prestazioni per cercare di ridurre i costi di JS.

Bootstrapping progressivo

Molti siti ottimizzano la visibilità dei contenuti a scapito dell'interattività. Per ottenere un first paint rapido quando hai bundle JavaScript di grandi dimensioni, a volte gli sviluppatori utilizzano il rendering lato server, quindi eseguono l'upgrade per collegare i gestori di eventi quando il codice JavaScript viene finalmente recuperato.

Fai attenzione: questa operazione comporta dei costi. 1) In genere invii una risposta HTML più grande che può influire sull'interattività, 2) puoi lasciare l'utente in una situazione di incertezza in cui metà dell'esperienza non può essere effettivamente interattiva finché JavaScript non termina l'elaborazione.

Il bootstrap progressivo potrebbe essere un approccio migliore. Invia una pagina con funzionalità minime (composta solo dal codice HTML/JS/CSS necessario per la route corrente). Man mano che arrivano altre risorse, l'app può eseguire il caricamento lento e sbloccare altre funzionalità.

ALT_TEXT_HERE
Progressive Bootstrapping di Paul Lewis

Il caricamento del codice proporzionale a ciò che è visibile è il Santo Graal. PRPL e il bootstrapping progressivo sono pattern che possono aiutarti a raggiungere questo obiettivo.

Conclusioni

Le dimensioni di trasmissione sono fondamentali per le reti di fascia bassa. Il tempo di analisi è importante per i dispositivi con limitazioni della CPU. Mantenere bassi questi valori è importante.

I team hanno ottenuto risultati positivi adottando budget delle prestazioni rigorosi per mantenere bassi i tempi di trasmissione e analisi/compilazione di JavaScript. Consulta l'articolo "Can You Afford It?: Real-world Web Performance Budgets" per indicazioni sui budget per i dispositivi mobili.

ALT_TEXT_HERE
È utile considerare quanto "margine" di JS le decisioni architettoniche che prendiamo possono lasciarci per la logica dell'app.

Se stai creando un sito destinato ai dispositivi mobili, fai del tuo meglio per svilupparlo su un hardware rappresentativo, mantieni bassi i tempi di analisi/compilazione di JavaScript e adotta un budget delle prestazioni per assicurarti che il tuo team sia in grado di tenere sotto controllo i costi di JavaScript.

Scopri di più