Ottimizzazione all'avvio di JavaScript

Quando creiamo siti che si basano maggiormente su JavaScript, a volte paghiamo per ciò che inviamo in modi che non sempre riusciamo a vedere facilmente. In questo articolo spiegheremo perché un po' di disciplina può essere utile se vuoi che il tuo sito venga caricato e sia interattivo rapidamente sui dispositivi mobili. Se carichi meno JavaScript, la trasmissione in rete richiede meno tempo, la decompressione del codice richiede meno tempo e anche l'analisi e la compilazione di questo JavaScript richiedono meno tempo.

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 codice JavaScript tramite la rete richiede più tempo quanto più lenta è la connessione dell'utente.

Quando un browser richiede una risorsa, questa deve essere recuperata e poi decompresa. 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 di un utente potrebbe non essere effettivamente 3G, 4G o Wi-Fi. Puoi essere connesso alla rete Wi-Fi di un bar, ma a un hotspot di telefonia cellulare con velocità 2G.

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

  • Inviare solo il codice di cui un utente ha bisogno.
  • Minimizzazione
  • Compressione
    • Come minimo, utilizza gzip per comprimere le risorse basate su testo.
    • Valuta la possibilità di utilizzare Brotli ~q11. Brotli supera gzip in termini di rapporto di compressione. Ha aiutato CertSimple a risparmiare 17% sulle dimensioni dei byte JS compressi e LinkedIn a risparmiare 4% sui tempi di caricamento.
  • Rimozione del codice inutilizzato.
  • Memorizza in cache il codice per ridurre al minimo le richieste alla rete.
    • Utilizza la memorizzazione nella cache HTTP per assicurarti che i browser memorizzino nella cache le risposte in modo efficace. Determina la durata ottimale per gli 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 rete della tua app resiliente 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 la sezione sull'hashing del nome del 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 Strumenti per sviluppatori di Chrome, l'analisi e la compilazione fanno parte del tempo "Scripting" giallo nel riquadro Rendimento.

ALT_TEXT_HERE

Le schede Bottom-Up e Call Tree mostrano i tempi esatti di analisi/compilazione:

ALT_TEXT_HERE
Pannello "Rendimento" di Chrome DevTools > Dal basso verso l'alto. Con le Statistiche chiamate in tempo di esecuzione di V8 abilitate, possiamo vedere il tempo trascorso in fasi come Analisi e Compilazione

Ma perché è importante?

ALT_TEXT_HERE

Se il codice impiega molto tempo per essere analizzato/compilato, l'utente può interagire con il tuo sito molto più tardi. Maggiore è il codice JavaScript inviato, più tempo occorre per analizzarlo e compilarlo prima che il sito sia interattivo.

Byte per byte, l'elaborazione di JavaScript è più onerosa per il browser rispetto all'immagine o al carattere web di dimensioni equivalenti. Tom Dale

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

ALT_TEXT_HERE
I byte di JavaScript e di 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 parliamo di analisi e compilazione lente, il contesto è importante: stiamo parlando di telefoni cellulari medi. Gli utenti medi possono avere telefoni con CPU e GPU lente, senza cache L2/L3 e che potrebbero persino avere limitazioni di memoria.

Le funzionalità della rete e quelle del dispositivo non corrispondono sempre. Un utente con una connessione in fibra eccezionale non ha necessariamente la CPU migliore per analizzare e valutare il codice JavaScript inviato al suo dispositivo. Lo stesso vale anche al contrario: una connessione di rete terribile, ma una CPU velocissima. - Kristofer Baxter, LinkedIn

Di seguito è riportato il costo dell'analisi di circa 1 MB di codice JavaScript decompilato (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 di JavaScript di 1 MB (~250 KB compresso con gzip) su computer e dispositivi mobili di classi diverse. Quando si esamina il costo dell'analisi, è necessario considerare le cifre decompresse, ad esempio circa 250 KB di codice JS compresso con gzip si decomprimono in circa 1 MB di codice.

E per un sito reale, come CNN.com?

Sull'iPhone 8 di fascia alta sono necessari solo circa 4 secondi per analizzare/compilare il codice JS di CNN, rispetto ai circa 13 secondi di uno smartphone medio (Moto G4). Ciò può influire notevolmente sulla rapidità con cui un utente può interagire completamente con questo sito.

ALT_TEXT_HERE
Sopra vediamo i tempi di analisi che confrontano le prestazioni del chip A11 Bionic di Apple con lo Snapdragon 617 in un hardware Android più medio.

Questo evidenzia l'importanza di eseguire test su hardware medio (come il Moto G4) anziché solo sullo smartphone che potresti avere in tasca. Tuttavia, il contesto è importante: ottimizza in base alle condizioni del dispositivo e della rete degli 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, possono essere identificati i vincoli reali della CPU/GPU con cui operano.

Stiamo davvero inviando troppo JavaScript? Ehm, forse :)

Utilizzando HTTP Archive (circa 500.000 siti principali) per analizzare lo stato di JavaScript su 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 il codice JS.

ALT_TEXT_HERE

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

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

Tempo di esecuzione

Non solo l'analisi e la compilazione possono avere un costo. L'esecuzione di JavaScript (esecuzione del codice dopo l'analisi/la compilazione) è una delle operazioni che deve avvenire sul thread principale. Tempi di esecuzione lunghi possono anche ritardare il tempo necessario a un utente per interagire con il tuo sito.

ALT_TEXT_HERE

Se lo script viene eseguito per più di 50 ms, il tempo di risposta viene ritardato per l'intero tempo necessario per scaricare, compilare ed eseguire il codice JS. - Alex Russell

Per risolvere il problema, JavaScript è suddiviso in piccoli blocchi per evitare di bloccare il thread principale. Scopri se puoi ridurre il lavoro eseguito durante l'esecuzione.

Altri costi

JavaScript può influire sul rendimento della pagina in altri modi:

  • Memoria. Le pagine possono sembrare tremolanti o essere messe in pausa di frequente a causa della raccolta del garbage. Quando un browser recupera la memoria, l'esecuzione di JS viene messa in pausa, pertanto un browser che raccoglie spesso i dati inutili può mettere in pausa l'esecuzione più spesso di quanto vorremmo. Evita le perdite di memoria e le interruzioni frequenti del GC per mantenere le pagine senza scatti.
  • Durante l'esecuzione, il codice JavaScript a lungo termine può bloccare il thread principale causando la mancata risposta delle pagine. Suddividere il 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 il costo di caricamento di JavaScript

Quando cerchi di mantenere lenti i tempi di analisi/compilazione e di trasmissione di rete per JavaScript, esistono pattern che possono essere utili, come lo 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 memorizzazione nella cache aggressive:

ALT_TEXT_HERE

Vediamo 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 (in arancione) rappresenta una parte significativa del tempo trascorso da molti di questi siti:

ALT_TEXT_HERE

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

Bootstrapping progressivo

Molti siti ottimizzano la visibilità dei contenuti a discapito dell'interattività. Per ottenere un primo rendering rapido quando hai bundle JavaScript di grandi dimensioni, a volte gli sviluppatori utilizzano il rendering lato server, quindi lo "esegui l'upgrade" per collegare gli elaboratori di eventi quando il codice JavaScript viene finalmente recuperato.

Fai attenzione: questa opzione ha dei costi. 1) in genere invii una risposta HTML più grande che può aumentare la nostra interattività, 2) puoi lasciare l'utente in una valle inquietante in cui metà dell'esperienza non può essere effettivamente interattiva fino al termine dell'elaborazione di JavaScript.

Il bootstrap progressivo potrebbe essere un approccio migliore. Invia una pagina minimamente funzionale (composta solo da HTML/JS/CSS necessari per il percorso corrente). Man mano che arrivano più risorse, l'app può caricarle in modo lazy e sbloccare altre funzionalità.

ALT_TEXT_HERE
Bootstrapping progressivo di Paul Lewis

Caricare codice proporzionale a ciò che è visibile è il Santo Graal. PRPL e il bootstrap 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 vincoli di CPU. Mantenerli bassi è importante.

I team hanno ottenuto ottimi risultati adottando budget di rendimento rigorosi per mantenere bassi i tempi di trasmissione e analisi/compilazione di JavaScript. Guarda "Can You Afford It? Budget delle prestazioni web reali" per indicazioni sui budget per il mobile.

ALT_TEXT_HERE
È utile valutare quanto "spazio di manovra" JS le decisioni di architettura che prendiamo possono lasciarci per la logica dell'app.

Se stai creando un sito che ha come target i dispositivi mobili, fai del tuo meglio per svilupparlo su hardware rappresentativo, mantieni bassi i tempi di analisi/compilazione di JavaScript e adotta un budget di prestazioni per assicurarti che il tuo team possa tenere d'occhio i costi di JavaScript.

Scopri di più