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.
Rete
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.
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.
- Utilizza la suddivisione del codice per suddividere il codice JavaScript in ciò che è critico e in ciò che non lo è. I bundle di moduli come webpack supportano la suddivisione del codice.
- Caricamento lento del codice non critico.
- Minimizzazione
- Utilizza UglifyJS per minimizzare il codice ES5.
- Usa babel-minify o uglify-es per minimizzare ES2015+.
- 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.
- Identifica le opportunità di codice che può essere rimosso o caricato lentamente con la copertura del codice di DevTools.
- Utilizza babel-preset-env e browserlist per evitare il transpiling delle funzionalità già presenti nei browser moderni. Gli sviluppatori esperti potrebbero trovare utile un'analisi accurata dei loro bundle webpack per identificare le opportunità per tagliare le dipendenze non necessarie.
- Per l'eliminazione del codice, consulta tree-shaking, le ottimizzazioni avanzate di Closure Compiler e i plug-in per il taglio delle librerie come lodash-babel-plugin o ContextReplacementPlugin del webpack per librerie come Moment.js.
- 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.
Le schede Dal basso verso l'alto e Struttura chiamate mostrano i tempi esatti di analisi/compilazione:
Ma perché è importante?
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.
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.
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.
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.
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.
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.
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()
orequestIdleCallback()
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:
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:
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à.
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.
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ù
- Chrome Dev Summit 2017 - Best practice per il caricamento moderno
- Prestazioni all'avvio di JavaScript
- Solving the web performance crisis - Nolan Lawson
- Puoi permettertelo? Budget per le prestazioni reali - Alex Russell
- La valutazione di framework e librerie web - Kristofer Baxter
- Risultati di Cloudflare degli esperimenti con Brotli per la compressione (tieni presente che Brotli dinamico con una qualità superiore può ritardare il rendering iniziale della pagina, quindi valuta con attenzione. Probabilmente vorrai comprimere in modo statico.)
- Performance Futures - Sam Saccone