Ottimizzazione all'avvio di JavaScript

Addy Osmani
Addy Osmani

Man mano che creiamo siti che dipendono maggiormente da JavaScript, a volte paghiamo per ciò che inviamo in modi non sempre facilmente visibili. In questo articolo illustreremo perché vuoi che il tuo sito si carichi e sia interattivo rapidamente sui dispositivi mobili con una piccola disciplina. La distribuzione di meno JavaScript può comportare meno tempo di trasmissione di rete, meno tempo speso nella decompressione del codice e meno tempo nell'analisi e nella compilazione di questo JavaScript.

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

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

Questo può rappresentare un problema, poiché il tipo di connessione di rete effettiva di cui dispone l'utente potrebbe non essere 3G, 4G o Wi-Fi. Stai usando la rete Wi-Fi del bar, ma sei connesso a un hotspot cellulare con velocità 2G.

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

  • Inviare solo il codice necessario a un utente.
  • Minimizzazione
  • Compressione
    • Come minimo, utilizza gzip per comprimere le risorse basate su testo.
    • Valuta l'utilizzo di Brotli ~q11. Brotli ha un rendimento migliore di gzip rispetto al rapporto di compressione. Ciò ha aiutato CertSimple a risparmiare il 17% sulle dimensioni dei byte JS compressi e a LinkedIn un risparmio del 4% sui tempi di caricamento.
  • Rimozione del codice inutilizzato.
  • Memorizzazione nella cache del codice per ridurre al minimo le corse della rete.
    • Utilizza la memorizzazione nella cache HTTP per garantire che le risposte dei browser vengano memorizzate nella cache in modo efficace. Determina la durata ottimale per gli script (max-age) e i token di convalida della fornitura (ETag) per evitare di trasferire byte invariati.
    • La memorizzazione nella cache dei service worker può rendere resiliente la rete dell'app 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 risorse che non sono state modificate. Se utilizzi Webpack, consulta l'articolo sull'hashing dei nomi file.

Analizza/compila

Una volta scaricato, uno dei costi più pesanti di JavaScript è il tempo impiegato da un motore JS per analizzare/compilare questo codice. In Chrome DevTools, le funzioni di analisi e compilazione fanno parte del tempo giallo "Scripting" nel riquadro Prestazioni.

ALT_TEXT_HERE

Le schede Dal basso verso l'alto e Struttura chiamate mostrano i tempi esatti di analisi/compilazione:

ALT_TEXT_HERE
Riquadro Prestazioni di Chrome DevTools > Bottom-up. Con le statistiche delle chiamate di runtime di V8 abilitate, possiamo vedere il tempo speso in fasi come Analizza e compila

Ma perché è importante?

ALT_TEXT_HERE

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

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

Rispetto a JavaScript, sono previsti numerosi costi per l'elaborazione di immagini di dimensioni equivalenti (devono comunque essere decodificate). Tuttavia, in media nell'hardware mobile, è più probabile che JS influisca negativamente sull'interattività di una pagina.

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

Quando parliamo di lentezza di analisi e compilazione, il contesto è importante; in questo caso parliamo di cellulari di media. In media, gli utenti possono avere telefoni con CPU e GPU lente, che non hanno cache L2/L3 e che potrebbero anche avere memoria limitata.

Le funzionalità di rete e del dispositivo non sempre corrispondono. Un utente con una connessione in fibra eccezionale non dispone necessariamente della migliore CPU per analizzare e valutare il codice JavaScript inviato al dispositivo. Questo vale anche per l'inversione... una connessione di rete terribile, ma una CPU ultraveloce. - Kristofer Baxter, LinkedIn

Di seguito puoi vedere il costo per l'analisi di circa 1 MB di codice JavaScript decompresso (semplice) su hardware di fascia bassa e alta. C'è una differenza di tempo da 2 a 5 volte per analizzare/compilare il codice tra i telefoni più veloci sul mercato e quelli più comuni.

ALT_TEXT_HERE
Questo grafico evidenzia i tempi di analisi per un bundle di JavaScript da 1 MB (~250 kB compresso con gzip) su computer e dispositivi mobili di classi diverse. Quando si analizza il costo dell'analisi, si tratta di cifre decompresse da considerare, ad esempio circa 250 kB di codice JavaScript compresso con gzip si decomprime a circa 1 MB di codice.

E un sito reale come CNN.com?

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

ALT_TEXT_HERE
Soprattutto vediamo i tempi di analisi confrontando le prestazioni del chip A11 Bionic di Apple con quelle dello Snapdragon 617 in un hardware Android più medio.

Ciò evidenzia l'importanza di eseguire test su hardware medio (come il Moto G4) anziché solo su uno smartphone che potrebbe avere in tasca. Il contesto, comunque, è importante: ottimizza le campagne 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. Questo può fornire opportunità di comprendere i reali vincoli di CPU/GPU con cui operano.

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

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

ALT_TEXT_HERE

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

La rimozione di codice JavaScript non critico dalle pagine può ridurre i tempi di trasmissione, le analisi e la compilazione che richiedono molte CPU e il potenziale sovraccarico di memoria. Ciò consente anche di rendere più interattive le pagine.

Tempo di esecuzione

Non si tratta solo di analisi e compilazione che possono avere un costo. L'esecuzione JavaScript (il codice in esecuzione dopo essere analizzato/compilato) è una delle operazioni che deve avvenire sul thread principale. Tempi di esecuzione lunghi possono anche indicare quanto tempo un utente può interagire con il tuo sito.

ALT_TEXT_HERE

Se lo script viene eseguito per più di 50 ms, il tempo all'interazione è ritardato dell'intero tempo necessario per scaricare, compilare ed eseguire il codice JS - Alex Russell

Per risolvere questo problema, JavaScript trae vantaggio dall'essere in piccoli blocchi per evitare di bloccare il thread principale. Scopri se puoi ridurre la quantità di lavoro eseguita durante l'esecuzione.

Altri costi

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

  • Memoria. Le pagine possono sembrare jank o messe in pausa spesso a causa della GC (garbage collection). Quando un browser recupera memoria, l'esecuzione JS viene messa in pausa in modo che un browser che raccoglie spesso elementi indesiderati possa mettere in pausa l'esecuzione con maggiore frequenza. Evita perdite di memoria e pause frequenti GC per evitare il jank delle pagine.
  • Durante il runtime, JavaScript a lunga esecuzione può bloccare il thread principale, causando la mancata risposta da parte delle pagine. Suddividi 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'interazione con Next Paint (INP).

Pattern per la riduzione dei costi di pubblicazione di JavaScript

Quando cerchi di mantenere lenti di analisi/compilazione e di trasmissione della rete per JavaScript, esistono pattern che possono aiutarti come la suddivisione in blocchi basati su route o PRPL.

PRPL

PRPL (push, rendering, pre-cache, caricamento lento) è un pattern che ottimizza l'interattività attraverso una suddivisione del codice e una memorizzazione nella cache aggressive:

ALT_TEXT_HERE

Vediamo l'impatto che può avere.

Analizziamo il tempo di caricamento di siti mobile e app web progressive utilizzando le Statistiche sulle chiamate di runtime di V8. Come possiamo vedere, il tempo di analisi (indicato in arancione) è una parte significativa di quelli che dedicano il proprio tempo a molti di questi siti:

ALT_TEXT_HERE

Wego, un sito che utilizza PRPL, riesce a ridurre i tempi di analisi dei percorsi, diventando interattivo molto rapidamente. Molti degli altri siti in alto hanno adottato la suddivisione del codice e i budget delle prestazioni per provare a ridurre i costi JS.

Bootstrapping progressivo

Molti siti ottimizzano la visibilità dei contenuti riducendo l'interattività. Per ottenere una prima verniciatura in presenza di bundle JavaScript di grandi dimensioni, gli sviluppatori a volte impiegano il rendering lato server e poi ne eseguono l'upgrade per collegare gestori di eventi quando finalmente JavaScript viene recuperato.

Fai attenzione, questa ha i suoi costi. 1) in genere invii una risposta HTML più grande che spinge la nostra interattività, 2) lasci l'utente in una valle inquietante dove metà dell'esperienza non può effettivamente essere interattiva fino al termine dell'elaborazione di JavaScript.

Il bootstrap progressivo potrebbe essere un approccio migliore. Invia una pagina poco funzionale (composta solo dai codici HTML/JS/CSS necessari per la route corrente). Man mano che arrivano più risorse, l'app può eseguire il caricamento lento e sbloccare altre funzionalità.

ALT_TEXT_HERE
Progressive Bootstrapping di Paul Lewis

Caricare il codice in modo proporzionale a ciò che viene visualizzato è il Santo Graal. PRPL e bootstrapping progressivo sono modelli 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 legati alla CPU. Mantenere questi livelli bassi è importante.

I team sono riusciti ad adottare budget rigidi per le prestazioni, in modo da mantenere ridotti i tempi di trasmissione e analisi/compilazione di JavaScript. Vedi "Can You Afford It?:" di Alex Russell budget per le prestazioni web reali" per indicazioni sui budget per i dispositivi mobili.

ALT_TEXT_HERE
È utile considerare quanto margine di miglioramento nell'architettura di JS possiamo lasciare per la logica dell'app.

Se stai creando un sito per dispositivi mobili, fai del tuo meglio per sviluppare hardware rappresentativo, mantenere bassi i tempi di analisi/compilazione di JavaScript e adottare un budget delle prestazioni per garantire che il tuo team sia in grado di tenere d'occhio i costi JavaScript.

Scopri di più