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.
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.
- Utilizza la suddivisione del codice per suddividere il codice JavaScript in elementi fondamentali e non. I bundler dei moduli come webpack supportano la separazione del codice.
- Caricamento lento nel codice non critico.
- Minimizzazione
- Utilizza UglifyJS per minimizzare il codice ES5.
- Utilizza babel-minify o uglify-es per ridurre al minimo ES2015 e versioni successive.
- 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.
- Identifica le opportunità per il codice che può essere rimosso o caricato in modo lazy con la copertura del codice di DevTools.
- Utilizza babel-preset-env e browserlist per evitare di transpilare le funzionalità già presenti nei browser moderni. Gli sviluppatori esperti potrebbero scoprire che un'attenta analisi dei loro bundle webpack li aiuta a identificare le opportunità per eliminare le dipendenze non necessarie.
- Per lo stripping del codice, consulta tree-shaking, le ottimizzazioni avanzate di Closure Compiler e i plug-in di trimming delle librerie come lodash-babel-plugin o ContextReplacementPlugin di webpack per librerie come Moment.js.
- 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.
Le schede Bottom-Up e Call Tree mostrano i tempi esatti di analisi/compilazione:

Ma perché è importante?
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.

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.

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.

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.

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.
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.
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()
orequestIdleCallback()
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:
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:
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à.

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.

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ù
- Chrome Dev Summit 2017 - Best Practices per il caricamento moderno
- Rendimento iniziale di JavaScript
- Risolvere la crisi del rendimento web - Nolan Lawson
- Puoi permettertelo? Budget per il rendimento reale di Alex Russell
- Valutare framework e librerie web - Kristofer Baxter
- Risultati dell'esperimento di Cloudflare con Brotli per la compressione (tieni presente che Brotli dinamico a una qualità superiore può ritardare il rendering iniziale della pagina, quindi valuta attentamente. Ti consigliamo di comprimere staticamente.
- Performance Futures – Sam Saccone