Ridurre il tempo di esecuzione JavaScript: spedire meno, poi rinviare, poi dividere

  • vuetelemetry
  • Guide
  • 8 min di lettura

Lighthouse fornisce il numero, ma la soluzione non è quasi mai nel vostro codice. Gran parte del tempo di esecuzione appartiene a dipendenze che girano prima che qualcosa sia visibile. Tre mosse, nell'ordine che funziona davvero, e l'unica misurazione da fare per prima.

Lighthouse vi dice che l'esecuzione JavaScript ha richiesto 3,4 secondi e vi propone un elenco di file. L'istinto è aprire il vostro codice e ottimizzare i cicli. È quasi sempre il posto sbagliato, e il motivo è aritmetico: in un'applicazione tipica il vostro codice è una piccola parte dei byte che il browser analizza, compila ed esegue.

Il tempo di esecuzione sono tre costi distinti che gli strumenti mostrano come uno solo. L'analisi trasforma il testo in un albero sintattico astratto, la compilazione lo trasforma in bytecode e la valutazione lo esegue davvero. Tutti e tre crescono con la quantità di JavaScript spedita, ed è per questo che la prima leva non è un codice più veloce ma meno codice.

Misurare prima, mai indovinare

Uno svincolo cittadino visto dall'alto: quattro corsie di auto, taxi e un autobus completamente fermi in coda a destra, mentre le due corsie sopraelevate a sinistra sono del tutto vuote.
Uno svincolo cittadino visto dall'alto: quattro corsie di auto, taxi e un autobus completamente fermi in coda a destra, mentre le due corsie sopraelevate a sinistra sono del tutto vuote.

Misurate prima di toccare qualsiasi cosa. Aprite il pannello Performance, registrate un caricamento di pagina con la CPU rallentata a 4x e leggete la vista Bottom-Up raggruppata per URL. Attribuisce i millisecondi ai file. Un'ipotesi su quale dipendenza sia costosa è sbagliata abbastanza spesso da far perdere una giornata.

Spedire meno, prima di tutto

Prima mossa: spedire meno. La scheda Coverage dei DevTools di Chrome mostra quale percentuale di ogni file non è mai stata eseguita. Valori sopra il 70 per cento di codice inutilizzato sono comuni, e di solito indicano una libreria importata per una sola funzione, un pacchetto di polyfill per browser che non supportate più, o un'intera libreria di date o di icone dove bastavano tre import.

  • Registrare con la CPU rallentata a 4x e leggere Bottom-Up per URL prima di cambiare qualsiasi cosa
  • Scheda Coverage: i file inutilizzati oltre il 70 per cento sono i primi candidati alla rimozione
  • Sostituire una libreria con una funzione nativa del browser quando esiste, Intl prima di una libreria di date
  • defer per gli script che toccano il DOM, tag di terze parti caricati dopo l'evento load
  • Dividere per route solo dopo aver rimosso ciò che non dovrebbe essere spedito affatto

La sostituzione è raramente spettacolare. Una libreria di formattazione delle date da 70 kB può spesso essere sostituita da `Intl.DateTimeFormat`, che non costa nulla perché è già nel browser. Quella sola sostituzione elimina in un colpo analisi, compilazione e valutazione, invece di renderle più veloci.

Rinviare ciò che il primo paint non richiede

Seconda mossa: rinviare ciò che non serve al primo paint. Analytics, widget di chat, banner del consenso e librerie di caroselli girano tutti prima che la pagina sia utilizzabile senza contribuirvi. `defer` su un tag script mantiene il download in parallelo e sposta l'esecuzione dopo l'analisi; `async` lo esegue appena arriva, il che è peggio per tutto ciò che tocca il DOM.

Per i tag di terze parti in particolare, caricarli dopo l'evento load, o alla prima interazione, sposta tutto il loro costo fuori dalla finestra che viene misurata e, soprattutto, fuori dalla finestra in cui l'utente aspetta.

Dividere il bundle, in quest'ordine

Terza mossa: dividere il bundle. La suddivisione del codice per route significa che un visitatore della home page non analizza il percorso di pagamento. In un'applicazione Vue se ne occupano `defineAsyncComponent` e l'`import()` dinamico, e i bundler moderni dividono automaticamente per route quando le route vengono importate in modo differito.

L'ordine di queste tre mosse conta. Dividere un bundle che contiene 200 kB di libreria inutilizzata rinvia solo il momento in cui la pagate. Rimuovere prima la libreria rende la suddivisione più piccola e il guadagno più grande.

L'ordine di queste tre mosse conta. Dividere un bundle che contiene 200 kB di libreria inutilizzata rinvia solo il momento in cui la pagate. Rimuovere prima la libreria rende la suddivisione più piccola e il guadagno più grande.

- vuetelemetry

Ciò che il numero non dice

Un avvertimento sul numero stesso. Total Blocking Time e Interaction to Next Paint sono ciò che vive il visitatore; il tempo di esecuzione JavaScript ne è una componente, non un sinonimo. Ridurre il tempo di esecuzione su una pagina già veloce non cambia nulla di percepibile, e una pagina può avere un cattivo punteggio di esecuzione e sembrare fluida perché il lavoro avviene dopo il paint.

I dati sul campo chiudono la discussione. Un test di laboratorio su un portatile veloce nasconde proprio la popolazione che soffre: telefoni Android di fascia media su una rete congestionata, dove un'analisi di 300 millisecondi diventa due secondi. Se i numeri sul campo sono buoni e quelli di laboratorio brutti, credete al campo.

Prima di ottimizzare gli script, conviene verificare che i byte siano compressi: gzip vs Brotli, e cosa abbiamo trovato misurando 336 enti pubblici.

FAQ

Cosa causa un tempo di esecuzione JavaScript elevato?

Il volume di JavaScript che il browser deve analizzare, compilare e valutare, dominato nella maggior parte dei progetti dalle dipendenze più che dal codice dell'applicazione. I task lunghi sul thread principale all'avvio, di solito l'inizializzazione del framework e i tag di terze parti, sono ciò che trasforma quel volume in un numero che l'utente percepisce.

Il tempo di esecuzione è la stessa cosa del Total Blocking Time?

No. Il tempo di esecuzione è quanto durano gli script; il Total Blocking Time misura per quanto tempo il thread principale è rimasto bloccato per più di 50 millisecondi di fila tra il First Contentful Paint e l'interattività. Il tempo di esecuzione ne è una componente, e il lavoro dopo il paint può aggiungere tempo di esecuzione senza aggiungere tempo di blocco.

defer o async riducono il tempo di esecuzione?

Nessuno dei due riduce il lavoro; entrambi spostano il momento in cui avviene. defer esegue lo script dopo l'analisi dell'HTML, nell'ordine del documento, il che non blocca il rendering ed è la scelta predefinita giusta per tutto ciò che tocca il DOM. async lo esegue appena scaricato, in un ordine imprevedibile.

Quanto JavaScript è troppo?

Non esiste una soglia universale, e qualsiasi numero presentato come tale è una regola empirica. Il test utile sono i dati sul campo: se Interaction to Next Paint e Largest Contentful Paint restano entro le soglie per utenti reali su dispositivi reali, la quantità che spedite è difendibile, qualunque cosa dica il numero di byte.

Devo abbandonare il mio framework per risolvere?

Raramente, e non come primo passo. Rimuovere le dipendenze inutilizzate, rinviare i tag di terze parti e dividere per route recupera di solito più tempo di una migrazione di framework, con una frazione del rischio. Considerate la migrazione solo se la misurazione indica l'inizializzazione del framework stessa dopo queste tre mosse.

Stack correlato