Ottimizza il caricamento delle risorse

Nel modulo precedente, è stata esaminata la teoria alla base del percorso di rendering critico e il modo in cui le risorse di blocco del rendering e di blocco dell'analisi possono ritardare il rendering iniziale di una pagina. Ora che hai compreso alcuni aspetti teorici, puoi imparare alcune tecniche per ottimizzare il percorso di rendering critico.

Quando una pagina viene caricata, nel codice HTML vengono citate molte risorse che forniscono alla pagina il suo aspetto e il suo layout tramite CSS, nonché la sua interattività tramite JavaScript. In questo modulo vengono trattati una serie di concetti importanti relativi a queste risorse e al modo in cui influiscono sul tempo di caricamento di una pagina.

Blocco del rendering

Come discusso nel modulo precedente, CSS è una risorsa che blocca il rendering, in quanto impedisce al browser di eseguire il rendering di qualsiasi contenuto finché non viene creato il CSS Object Model (CSSOM). Il browser blocca il rendering per evitare un Flash of Unstyled Content (FOUC), che è indesiderabile dal punto di vista dell'esperienza utente.

Nel video precedente, si verifica un breve FOUC in cui puoi vedere la pagina senza alcuno stile. Successivamente, tutti gli stili vengono applicati una volta terminato il caricamento del CSS della pagina dalla rete e la versione senza stile della pagina viene immediatamente sostituita con la versione con stile.

In generale, un FOUC è qualcosa che non si vede normalmente, ma il concetto è importante da capire per sapere perché il browser blocca il rendering della pagina finché il CSS non viene scaricato e applicato alla pagina. Il blocco del rendering non è necessariamente indesiderabile, ma devi ridurre al minimo la sua durata mantenendo ottimizzato il CSS.

Blocco del parser

Una risorsa che blocca l'analisi interrompe l'analizzatore HTML, ad esempio un elemento <script> senza attributi async o defer. Quando il parser rileva un elemento <script>, il browser deve valutare ed eseguire lo script prima di procedere con l'analisi del resto dell'HTML. Ciò avviene intenzionalmente, in quanto gli script potrebbero modificare o accedere al DOM durante la sua costruzione.

<!-- This is a parser-blocking script: -->
<script src="/script.js"></script>

Quando si utilizzano file JavaScript esterni (senza async o defer), il parser viene bloccato dal momento in cui il file viene rilevato fino a quando non viene scaricato, analizzato ed eseguito. Quando si utilizza JavaScript incorporato, il parser viene bloccato in modo simile finché lo script incorporato non viene analizzato ed eseguito.

Lo scanner di precaricamento

Lo scanner di precaricamento è un'ottimizzazione del browser sotto forma di parser HTML secondario che analizza la risposta HTML non elaborata per trovare e recuperare in modo speculativo le risorse prima che il parser HTML principale le rilevi. Ad esempio, lo scanner di precaricamento consentirebbe al browser di iniziare a scaricare una risorsa specificata in un elemento <img>, anche quando il parser HTML è bloccato durante il recupero e l'elaborazione di risorse come CSS e JavaScript.

Per sfruttare lo scanner di precaricamento, le risorse critiche devono essere incluse nel markup HTML inviato dal server. I seguenti pattern di caricamento delle risorse non sono rilevabili dallo scanner di precaricamento:

  • Immagini caricate da CSS utilizzando la proprietà background-image. Questi riferimenti alle immagini sono in CSS e non possono essere rilevati dallo scanner di precaricamento.
  • Script caricati dinamicamente sotto forma di markup dell'elemento <script> inserito nel DOM utilizzando JavaScript o moduli caricati utilizzando import() dinamico.
  • HTML sottoposto a rendering sul client utilizzando JavaScript. Questo markup è contenuto all'interno di stringhe nelle risorse JavaScript e non è rilevabile dallo scanner di precaricamento.
  • Dichiarazioni CSS @import.

Questi pattern di caricamento delle risorse sono tutte risorse rilevate in un secondo momento e pertanto non traggono vantaggio dallo scanner di precaricamento. Evitali quando possibile. Se evitare questi pattern non è possibile, puoi utilizzare un suggerimento preload per evitare ritardi nel rilevamento delle risorse.

CSS

Il CSS determina la presentazione e il layout di una pagina. Come descritto in precedenza, il CSS è una risorsa che blocca il rendering, pertanto l'ottimizzazione del CSS potrebbe avere un impatto notevole sul tempo di caricamento complessivo della pagina.

Minimizzazione

La minimizzazione dei file CSS riduce le dimensioni di un file CSS, rendendone più rapido il download. Ciò si ottiene principalmente rimuovendo i contenuti da un file CSS di origine, ad esempio spazi e altri caratteri invisibili, e restituendo il risultato in un file appena ottimizzato:

/* Unminified CSS: */

/* Heading 1 */
h1 {
  font-size: 2em;
  color: #000000;
}

/* Heading 2 */
h2 {
  font-size: 1.5em;
  color: #000000;
}
/* Minified CSS: */
h1,h2{color:#000}h1{font-size:2em}h2{font-size:1.5em}

Nella sua forma più semplice, la minimizzazione del CSS è un'ottimizzazione efficace che potrebbe migliorare l'FCP del tuo sito web e, in alcuni casi, anche l'LCP. Strumenti come i bundler possono eseguire automaticamente questa ottimizzazione per te nelle build di produzione.

Rimuovi il CSS inutilizzato

Prima di eseguire il rendering di qualsiasi contenuto, il browser deve scaricare e analizzare tutti i fogli di stile. Il tempo necessario per completare l'analisi include anche gli stili non utilizzati nella pagina corrente. Se utilizzi un bundler che combina tutte le risorse CSS in un unico file, è probabile che i tuoi utenti scarichino più CSS del necessario per il rendering della pagina corrente.

Per scoprire i CSS inutilizzati per la pagina corrente, utilizza lo strumento Copertura in Chrome DevTools.

Uno screenshot dello strumento di copertura in Chrome DevTools. Nel riquadro inferiore è selezionato un file CSS che mostra una notevole quantità di CSS inutilizzati dal layout di pagina corrente.
Lo strumento di copertura in Chrome DevTools è utile per rilevare CSS (e JavaScript) non utilizzati dalla pagina corrente. Può essere utilizzato per dividere i file CSS in più risorse da caricare in pagine diverse, anziché spedire un bundle CSS molto più grande che può ritardare il rendering della pagina.

La rimozione del CSS inutilizzato ha un duplice effetto: oltre a ridurre il tempo di download, ottimizzi la costruzione dell'albero di rendering, poiché il browser deve elaborare meno regole CSS.

Evita le dichiarazioni CSS @import

Sebbene possa sembrare conveniente, devi evitare le dichiarazioni @import nel CSS:

/* Don't do this: */
@import url('style.css');

Analogamente al funzionamento dell'elemento <link> in HTML, la dichiarazione @import in CSS consente di importare una risorsa CSS esterna da un foglio di stile. La differenza principale tra questi due approcci è che l'elemento HTML <link> fa parte della risposta HTML e viene quindi rilevato molto prima di un file CSS scaricato da una dichiarazione @import.

Il motivo è che, affinché una dichiarazione @import venga rilevata, è necessario prima scaricare il file CSS che la contiene. Ciò genera quella che è nota come catena di richieste che, nel caso dei CSS, ritarda il tempo necessario per il rendering iniziale di una pagina. Un altro svantaggio è che i fogli di stile caricati utilizzando una dichiarazione @import non possono essere rilevati dallo scanner di precaricamento e diventano quindi risorse di blocco del rendering rilevate in ritardo.

<!-- Do this instead: -->
<link rel="stylesheet" href="style.css">

Nella maggior parte dei casi, puoi sostituire @import utilizzando un elemento <link rel="stylesheet">. Gli elementi <link> consentono di scaricare i fogli di stile contemporaneamente e riducono il tempo di caricamento complessivo, a differenza delle dichiarazioni @import, che scaricano i fogli di stile in sequenza.

CSS critico incorporato

Il tempo necessario per scaricare i file CSS può aumentare l'FCP di una pagina. L'incorporamento degli stili critici nel documento <head> elimina la richiesta di rete per una risorsa CSS e, se eseguito correttamente, può migliorare i tempi di caricamento iniziali quando la cache del browser di un utente non è preparata. Il CSS rimanente può essere caricato in modo asincrono o aggiunto alla fine dell'elemento <body>.

<head>
  <title>Page Title</title>
  <!-- ... -->
  <style>h1,h2{color:#000}h1{font-size:2em}h2{font-size:1.5em}</style>
</head>
<body>
  <!-- Other page markup... -->
  <link rel="stylesheet" href="non-critical.css">
</body>

Lo svantaggio è che l'incorporamento di una grande quantità di CSS aggiunge più byte alla risposta HTML iniziale. Poiché le risorse HTML spesso non possono essere memorizzate nella cache per molto tempo o per niente, ciò significa che il CSS incorporato non viene memorizzato nella cache per le pagine successive che potrebbero utilizzare lo stesso CSS in fogli di stile esterni. Testa e misura le prestazioni della tua pagina per assicurarti che i compromessi valgano la pena.

Demo CSS

JavaScript

JavaScript è alla base della maggior parte dell'interattività sul web, ma ha un costo. L'invio di troppo JavaScript può rallentare la risposta della pagina web durante il caricamento della pagina e può persino causare problemi di reattività che rallentano le interazioni, il che può essere frustrante per gli utenti.

JavaScript di blocco del rendering

Quando carichi elementi <script> senza gli attributi defer o async, il browser blocca l'analisi e il rendering finché lo script non viene scaricato, analizzato ed eseguito. Allo stesso modo, gli script incorporati bloccano il parser finché lo script non viene analizzato ed eseguito.

async contro defer

async e defer consentono il caricamento di script esterni senza bloccare il parser HTML, mentre gli script (inclusi quelli incorporati) con type="module" vengono posticipati automaticamente. Tuttavia, async e defer presentano alcune differenze importanti da comprendere.

Una rappresentazione di vari meccanismi di caricamento degli script, tutti che descrivono in dettaglio i ruoli di analisi, recupero ed esecuzione in base a vari attributi utilizzati, come async, defer, type=&#39;module&#39; e una combinazione di tutti e tre.
Fonte: https://html.spec.whatwg.org/multipage/scripting.html

Gli script caricati con async vengono analizzati ed eseguiti immediatamente dopo il download, mentre gli script caricati con defer vengono eseguiti al termine dell'analisi del documento HTML, ovvero contemporaneamente all'evento DOMContentLoaded del browser. Inoltre, gli script async potrebbero essere eseguiti in ordine sparso, mentre gli script defer vengono eseguiti nell'ordine in cui appaiono nel markup.

Rendering lato client

In generale, dovresti evitare di utilizzare JavaScript per il rendering di contenuti critici o dell'elemento LCP di una pagina. Questa tecnica è nota come rendering lato client e viene utilizzata in modo esteso nelle applicazioni a pagina singola (SPA).

Il markup sottoposto a rendering da JavaScript aggira lo scanner di precaricamento, in quanto le risorse contenute nel markup sottoposto a rendering lato client non sono rilevabili. Ciò potrebbe ritardare il download di risorse cruciali, come un'immagine LCP. Il browser inizia a scaricare l'immagine LCP solo dopo l'esecuzione dello script e l'aggiunta dell'elemento al DOM. A sua volta, lo script può essere eseguito solo dopo essere stato individuato, scaricato e analizzato. Questo è noto come catena di richieste critiche e deve essere evitato.

Inoltre, il rendering del markup utilizzando JavaScript ha maggiori probabilità di generare attività lunghe rispetto al markup scaricato dal server in risposta a una richiesta di navigazione. L'utilizzo esteso del rendering lato client dell'HTML può influire negativamente sulla latenza di interazione. Ciò è particolarmente vero nei casi in cui il DOM di una pagina è molto grande, il che attiva un lavoro di rendering significativo quando JavaScript modifica il DOM.

Minimizzazione

Come per CSS, la minimizzazione di JavaScript riduce le dimensioni del file di una risorsa di script. Ciò può portare a download più rapidi, consentendo al browser di passare più rapidamente all'analisi e alla compilazione di JavaScript.

Inoltre, la minimizzazione di JavaScript va oltre la minimizzazione di altri asset, come i CSS. Quando JavaScript viene compresso, non vengono rimossi solo spazi, tabulazioni e commenti, ma anche i simboli nel codice JavaScript di origine vengono abbreviati. Questo processo è talvolta noto come uglification. Per vedere la differenza, prendi il seguente codice sorgente JavaScript:

// Unuglified JavaScript source code:
export function injectScript () {
  const scriptElement = document.createElement('script');
  scriptElement.src = '/js/scripts.js';
  scriptElement.type = 'module';

  document.body.appendChild(scriptElement);
}

Quando il codice sorgente JavaScript precedente viene compresso, il risultato potrebbe avere un aspetto simile al seguente snippet di codice:

// Uglified JavaScript production code:
export function injectScript(){const t=document.createElement("script");t.src="/js/scripts.js",t.type="module",document.body.appendChild(t)}

Nello snippet precedente, puoi notare che la variabile leggibile scriptElement nell'origine è abbreviata in t. Se applicati a una vasta raccolta di script, i risparmi possono essere piuttosto significativi, senza influire sulle funzionalità fornite dal codice JavaScript di produzione di un sito web.

Se utilizzi un bundler per elaborare il codice sorgente del tuo sito web, l'offuscamento viene spesso eseguito automaticamente per le build di produzione. Anche gli uglifier, come Terser, ad esempio, sono altamente configurabili, il che ti consente di modificare l'aggressività dell'algoritmo di uglification per ottenere il massimo risparmio. Tuttavia, i valori predefiniti di qualsiasi strumento di offuscamento sono in genere sufficienti per trovare il giusto equilibrio tra le dimensioni dell'output e la conservazione delle funzionalità.

Demo JavaScript

Verifica le tue conoscenze

Qual è il modo migliore per caricare più file CSS nel browser?

La dichiarazione CSS @import.
Riprova.
Più elementi <link>.
Esatto!

Che cosa fa lo scanner di precaricamento del browser?

Si tratta di un parser HTML secondario che esamina il markup non elaborato per individuare le risorse prima che il parser DOM possa farlo, in modo da individuarle prima.
Esatto!
Rileva gli elementi <link rel="preload"> in una risorsa HTML.
Riprova.

Perché il browser blocca temporaneamente l'analisi dell'HTML per impostazione predefinita durante il download delle risorse JavaScript?

Per evitare un Flash of Unstyled Content (FOUC).
Riprova.
Poiché la valutazione di JavaScript è un'attività che richiede un utilizzo elevato della CPU, la sospensione dell'analisi HTML offre maggiore larghezza di banda alla CPU per completare il caricamento degli script.
Riprova.
Perché gli script possono modificare o accedere in altro modo al DOM.
Esatto!

Passaggio successivo: aiutare il browser con i suggerimenti per le risorse

Ora che hai capito come le risorse caricate nell'elemento <head> possono influire sul caricamento iniziale della pagina e su varie metriche, è il momento di andare avanti. Nel modulo successivo, vengono esplorate le indicazioni sulle risorse e come possono fornire suggerimenti preziosi al browser per iniziare a caricare le risorse e aprire connessioni a server cross-origin prima di quanto farebbe altrimenti senza.