Take the MDN Browser Compatibility Survey and help us understand your issues, and what we and browser vendors can do to help make your life easier.

Ottimizzazione dell'avvio JavaScript

Anche se creiamo siti che si basano molto su JavaScript, a volte paghiamo per ciò che produciamo in modi non sempre facilmente prevedibili. In questo articolo spiegheremo perché un po' di disciplina potrebbe aiutarti se desideri che il tuo sito venga caricato e sia interattivo rapidamente sui dispositivi mobili. Offrire meno JavaScript può significare meno tempo di trasmissione di rete, meno codice da decomprimere e meno tempo per l'analisi e la compilazione di questo JavaScript.

Rete

Quando la maggior parte degli sviluppatori considera il costo di JavaScript, pensa in termini di costi di download e di esecuzione. L'invio di diversi byte di JavaScript richiede più tempo se la connessione dell'utente è lenta.

When a browser requests a
resource, that resource needs to be fetched and then decompressed. In the case
of resources like JavaScript, they must be parsed and compiled prior to
execution.

Questo può essere un problema, anche nei paesi del primo mondo, se l'effettivo tipo di connessione di rete che l'utente usa non è 3G, 4G o Wi-Fi. Puoi trovarti in un bar con Wi-Fi, ma essere collegato a un hotspot cellulare con velocità 2G.

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

  • Invio solo del codice necessario all'utente.
    • Usa il code-splitting per separare in JavaScript ciò che è critico da ciò che non lo è. I module bundler come webpack supportano il code-splitting.
    • Usa il caricamento lazily per il codice non-critico.
  • Minimizzazione
  • Compressione
    • Come minimo, usa gzip per comprimere le risorse basate sul testo.
    • Valuta l'utilizzo di Brotli ~q11. Brotli ottimizza il rapporto di compressione rispetto a gzip. Ha aiutato CertSimple a risparmiare il 17% sulle dimensioni dei byte JS compressi e LinkedIn a risparmiare il 4% sui tempi di caricamento.
  • Rimozione del codice inutilizzato.
  • Cache del codice per ridurre i tempi di rete
    • Usa l'HTTP caching per garantire che i browser memorizzino le risposte in modo efficace. Determina la durata ottimale degli script (max-age) e fornisci token di convalida (ETag) per evitare il trasferimento di byte invariati.
    • La memorizzazione nella cache dei Service Worker può rendere resiliente la rete dell'app e darti accesso a funzionalità come la cache del codice di V8.
    • Usa la memorizzazione nella cache a lungo termine per evitare di dover recuperare nuovamente le risorse che non sono state modificate. Se utilizzi Webpack, consulta hashing del nome del file.

Analisi/Compilazione

Una volta scaricato, uno dei costi maggiori di JavaScript è il tempo impiegato dal motore JS di analisi/compilazione di questo codice. In Chrome DevTools, queste due operazioni rientrano nel tempo giallo di "Scripting" del pannello Performance.

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

Pannello Chrome DevTools Performance > Bottom-Up. Con le Runtime Call Stats di V8 abilitate, possiamo vedere il tempo trascorso per fasi come l'Analisi e la Compilazione.

Ma perché questo è importante?

Trascorrere parecchio tempo ad analizzare/compilare il codice può ritardare parecchio il momento in cui l'utente viene a interagire con il tuo sito. Più JavaScript invii, più tempo ci vorrà per analizzare e compilare prima che il tuo sito sia interattivo.

Byte-per-byte, ** JavaScript è più costoso da elaborare per il browser rispetto ad una immagine di dimensioni equivalenti o un Web Font ** - Tom Dale

Rispetto a JavaScript, ci sono numerosi costi coinvolti nell'elaborazione di immagini di dimensioni equivalenti (devono ancora essere decodificate!) ma su un hardware mobile medio, JS ha maggiori probabilità di avere un impatto negativo sull'interattività di una pagina.

I byte di JavaScript e di immagine hanno costi molto diversi. Le immagini di solito non bloccano il thread principale né impediscono l'interazione con le interfacce mentre vengono decodificate e rasterizzate. Invece, JS può ritardare l'interattività a causa di analisi, compilazione e costi di esecuzione.

Quando ci riferiamo ad 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 potrebbero avere anche vincoli di memoria.

Le funzionalità di rete e le funzionalità del dispositivo non sempre corrispondono. Un utente con una straordinaria connessione in fibra ottica non ha necessariamente la migliore CPU per analizzare e valutare il codice JavaScript inviato al proprio dispositivo. Questo è vero anche in caso inverso... una terribile connessione di rete ma con una CPU molto veloce. Kristofer Baxter, LinkedIn

Qui sotto possiamo vedere il costo di analisi di ~1MB di JavaScript decompresso (semplice) su hardware di fascia bassa e di fascia alta. C'è una differenza di 2-5 volte nei tempi di analisi/compilazione del codice tra i telefoni più veloci sul mercato e quelli medi.

Questo grafico evidenzia i tempi di analisi per un bundle di 1 MB di JavaScript (circa 250 KB in formato gzip) su dispositivi desktop e mobili di classi diverse. Quando si considera il costo di analisi, sono le cifre decompresse da considerare, ad esempio 250KB gzip di JS si decomprime in ~1 MB di codice.

Che dire di un sito reale, come CNN.com?

Sull'iPhone 8 di fascia alta ci vogliono solo ~4 secondi per analizzare/compilare JS della CNN rispetto ai ~13 secondi per un telefono medio (Moto G4). Ciò può avere un impatto significativo sulla velocità con cui un utente interagisce con questo sito.

Sopra vediamo i tempi di analisi paragonando le prestazioni del chip Apple A11 Bionic allo Snapdragon 617 in un hardware Android medio.

Ciò evidenzia l'importanza del test su hardware medio (come il Moto G4) invece solo del telefono che potresti avere in tasca. Il contesto conta comunque: ottimizzare il dispositivo e le condizioni di rete degli utenti.

Google Analytics può fornire informazioni sulle classi di dispositivi mobili con cui gli utenti reali accedono al tuo sito. Questo può fornire l'opportunità di comprendere i reali vincoli CPU/GPU con cui stanno operando.

Stiamo inviando davvero troppo JavaScript? Probabilmente :)

Utilizzando l'archivio HTTP (primi ~500mila siti) per analizzare lo stato di JavaScript sui dispositivi mobili, possiamo vedere che il 50% dei siti richiede più di 14 secondi per essere interattivo. Questi siti passano fino a 4 secondi solo per analizzare e compilare JS.

Calcola il tempo necessario per recuperare ed elaborare JS e le altre risorse. Forse non ti sorprenderà sapere che gli utenti devono aspettare un bel po' prima di poter interagire con le pagine che desiderano usare. Possiamo sicuramente migliorare la situazione.

La rimozione di JavaScript non critico dalle tue pagine può ridurre i tempi di trasmissione, l'analisi e la compilazione intensiva della CPU e il potenziale sovraccarico della memoria. Questo aiuta anche a rendere interattive le tue pagine più velocemente.

Tempo di esecuzione

Non sono solo le fasi di analisi e compilazione ad avere un costo. L'esecuzione di JavaScript (il codice in esecuzione una volta analizzato/compilato) è una delle operazioni che devono verificarsi sul thread principale. Lunghi tempi di esecuzione possono anche condizionare quanto tempo un utente necessita per interagire con il tuo sito.

Se lo script viene eseguito per più di 50 ms, il tempo per divenire interattivo viene ritardato dall'intero periodo di tempo necessario per scaricare, compilare ed eseguire il JS - Alex Russell

Per risolvere questo problema, JavaScript beneficia di essere in piccoli blocchi per evitare di bloccare il thread principale. Cerca di scoprire 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 apparire come jank o essere messe in pausa frequentemente a causa di GC (garbage collection). Quando un browser recupera la memoria, l'esecuzione di JS viene messa in pausa, quindi un browser che raccoglie spesso garbage può sospendere l'esecuzione più frequentemente di quanto vorremmo. Evita perdite di memoria e frequenti pause gc per mantenere libere le pagine.
  • Durante il runtime, JavaScript di lunga esecuzione può bloccare il thread principale che non fa rispondere le pagine. Ridurre il lavoro in blocchi più piccoli (utilizzando requestAnimationFrame() o requestIdleCallback() per la pianificazione) può ridurre al minimo i problemi di reattività.

Modelli per ridurre i costi di JavaScript

Se cerchi di mantenere analisi/compilazione e tempi di trasmissione di rete per JavaScript lenti, ci sono schemi che possono essere d'aiuto come il chunking basato su route o PRPL.

PRPL

PRPL (Push, Render, Pre-cache, Lazy-load) è un pattern che ottimizza l'interattività attraverso il code-splitting e la memorizzazione nella cache aggressivi:

Vediamo l'impatto che può avere.

Analizziamo il tempo di caricamento dei popolari siti per dispositivi mobili e delle Progressive Web App utilizzando Runtime Call Stats di V8. Come possiamo vedere, il tempo di analisi (mostrato in arancione) è una parte significativa di dove molti di questi siti trascorrono il loro tempo:

Wego, un sito che utilizza PRPL, riesce a mantenere un tempo di analisi basso per i loro percorsi, diventando interattivo molto rapidamente. Molti degli altri siti hanno adottato il code-splitting e performance budget per provare a ridurre i costi del JS.

Progressive Bootstrapping

Molti siti ottimizzano la visibilità dei contenuti a scapito dell'interattività. Per ottenere un primo paint veloce quando hanno bundle JavaScript di grandi dimensioni, gli sviluppatori a volte impiegano il rendering sul lato server; quindi lo "aggiornano" per collegare i gestori di eventi quando il codice JavaScript viene finalmente scaricato.

Attenzione: questo ha i suoi costi. 1) Puoi inviare una risposta HTML più ampia che potrebbe influire sull'interattività, 2) Puoi lasciare l'utente in una situazione nella quale la metà dell'esperienza non può essere interattiva fino a quando JavaScript non finisce l'elaborazione.

Il Progressive Bootstrapping può essere un approccio migliore. Invia una pagina minimamente funzionale (composta solo dai HTML/JS/CSS necessari per il percorso corrente). Con l'arrivo di altre risorse, l'app può caricare e sbloccare più funzioni.

Progressive Bootstrapping di Paul Lewis

Caricare il codice proporzionalmente a ciò che è visibile è il risultato ideale. PRPL e Progressive Bootstrapping sono schemi che possono aiutare a raggiungere questo obiettivo.

Conclusioni

La dimensione della trasmissione è critica per reti di fascia bassa. Il tempo di analisi è significativo per i dispositivi con CPU. Mantenere questi indici bassi è significativo.

Molti team sono riusciti ad adottare rigorosi budget di prestazioni per mantenere bassi i tempi di trasmissione e analisi/compilazione di JavaScript. Consulta "Can You Afford It?: Real Web World Budgets" di Alex Russell come guida sui budget per dispositivi mobili.

È utile considerare quanto le decisioni architettoniche sulla capacità aggiuntiva di JS influiscano su quella della logica dell'app.

Se stai creando un sito per dispositivi mobili, fai del tuo meglio per sviluppare utilizzando hardware rappresentativo, mantieni tempi bassi di analisi//compilazione per JavaScript e adotta un Performance Budget per garantire al tuo team di tenere d'occhio i costi di JavaScript.

Per saperne di più

Translated by