An illustration of a fire extinguisher positioned next two a row of matches that are all smoldering.

JavaScript Responsabile: parte prima

Stando ai numeri, JavaScript è un peso in termini di performance. Se questo trend persiste, a breve, la pagina media invierà almeno 400 KB di JS e questo è a malapena quello che viene trasferito. Come altre risorse basate su testo, JavaScript è quasi sempre inviato compresso, ma ciò potrebbe essere l’unica cosa consistente riguardo al suo invio.

L’articolo prosegue sotto

Sfortunatamente, sebbene ridurre il resource transfer time è una parte importante dell’intera questione della performance, la compressione non ha effetti sul tempo impiegato dai browser per processare uno script una volta che arriva nella sua interezza. Se un server invia 400 KB di JavaScript compresso, la quantità effettiva che i browser devono processare dopo la decompressione è più di un megabyte. Quando bene i device gestiscano questi carichi pesanti dipende, beh, dal device. È stato scritto molto su quanto siano abili i vari device nel processare molto JavaScript, ma la verità è che la quantità di tempo che occorre per processarne anche una quantità trascurabile varia enormemente tra device.

Prendete, per esempio, questo mio progetto buttato lì tanto per fare, che invia circa 23 KB di JavaScript non compresso. Su un MacBook Pro mid-2017, Chrome si pappa questo piccolo carico in circa 25 ms. Tuttavia, su un telefono Nokia 2 Android, questo tempo schizza a 190 ms. Non è una quantità di tempo insignificante, ma in ciascun caso, la pagina diventa interattiva in un tempo ragionevolmente veloce.

Adesso la grande domanda: come pensate che si comporterà quel piccolo Nokia 2 con la pagina media? Soffoca. Anche su una connessione veloce, usarlo per navigare nel web lo fa diventare un esercizio di pazienza dato che le pagine web piene zeppe di JavaScript lo rendono simile a un mattone per la maggior parte del tempo.

A performance timeline for a JavaScript-heavy website. Most of the timeline is JavaScript.
Figura 1. Una panoramica della performance timeline di un telefono Nokia 2 Android mentre naviga su una pagina in cui del JavaScript eccessivo monopolizza il thread principale.

Sebbene sia i device sia le reti su cui navigano siano in continuo miglioramento, ci stiamo mangiando quei guadagni, come suggeriscono i trend. Dobbiamo usare JavaScript responsabilmente. E si comincia dal comprendere quello che stiamo creando così come il modo in cui lo stiamo creando.<

La forma mentale di “siti” vs. “app”#section1

La terminologia a volte è strana: identifichiamo genericamente cose con termini inaccurati il cui significato è compreso da tutti. A volte sovraccarichiamo il termine “ape” per indicare anche “vespa”, nonostante le differenze tra api e vespe siano sostanziali, a tal punto da indurvi a gestire ciascuna delle due in maniera diversa. Per esempio, dovreste distruggere un nido di vespe, ma dal momento che le api sono insetti incredibilmente benefici e vulnerabili, potremmo optare per una ricollocazione del loro nido.

Potremmo essere altrettanto rapidi e approssimativi nello scambiare i termini “website” e “web app”. Le differenze tra i due sono meno chiare di quelle tra vespe e api, ma fonderli può portare a risultati dolorosi, derivanti dalle possibilità che ci concediamo quando qualcosa è semplicemente un “sito web” rispetto a quando è una “web app” completa. Se state facendo un sito web informativo per un’azienda, ci saranno meno probabilità che vi appoggerete a un potente framework per gestire i cambiamenti nel DOM o per implementare il routing client-side, almeno spero. Usare tools così poco adatti per questo compito non solo sarebbe dannoso per le persone che usano il sito ma probabilmente anche meno produttivo.

Quando realizziamo una web app, però, dobbiamo stare attenti. Stiamo installando dei package che danno inizio a centinaia, se non migliaia, di dipendenze, alcune delle quali non sappiamo nemmeno se siano sicure. Stiamo anche scrivendo delle configurazioni complicate per i module bundlers. In questo tipo di ambiente di sviluppo delirante ma tuttavia onnipresente, occorre conoscere e stare attenti per essere sicuri che quello che viene creato sia veloce e accessibile. Se dubitate di ciò, eseguite npm ls --prod nella root directory del vostro progetto e vedete se riconoscete tutto quello che c’è nell’elenco. Anche in caso affermativo, questo comando non include gli script di terze parti, e sono sicuro che il vostro sito ne ha almeno qualcuno.

Quello che tendiamo a dimenticare è che l’ambiente occupato da website e web app è unico ed è lo stesso. Entrambe sono soggetti alle stesse pressioni ambientali imposte dall’ampio spettro di reti e device. Questi vincoli non svaniscono improvvisamente quando decidiamo di chiamare “app” quello che creiamo, né i telefoni dei nostri utenti guadagnano nuovi poteri magici quando lo facciamo.

È nostra responsabilità valutare chi usa quello che realizziamo e accettare che le condizioni sotto le quali accedono a internet possano essere diverse da quello che abbiamo supposto. Dobbiamo conoscere il risultato che stiamo cercando di offrire e solo allora potremo creare qualcosa che mirabilmente miri a quello scopo, anche se non è esaltante da realizzare.

Ciò significa rivalutare la nostra dipendenza da JavaScript e come il suo uso, in particolare quando esclude HTML e CSS, può tentarci ad adottare pattern insostenibili che danneggiano performance e accessibilità.

Non permettete ai framework di forzarvi ad usare pattern insostenibili#section2

Sono stato testimone di alcune strane scoperte nelle codebase mentre lavoravo con dei team che dipendevano dai framework per poter essere altamente produttivi. Una caratteristica comune a molti di loro è la scarsa accessibilità e i pessimi pattern di performance che spesso ne derivano. Prendete, per esempio, il componente React qui sotto:

import React, { Component } from "react";
import { validateEmail } from "helpers/validation";

class SignupForm extends Component {
  constructor (props) {
    super(props);

    this.handleSubmit = this.handleSubmit.bind(this);
    this.updateEmail = this.updateEmail.bind(this);
    this.state.email = "";
  }

  updateEmail (event) {
    this.setState({
      email: event.target.value
    });
  }

  handleSubmit () {
    // If the email checks out, submit
    if (validateEmail(this.state.email)) {
      // ...
    }
  }

  render () {
    return (
      
); } }

Ci sono chiaramente dei problemi di accessibilità qui:

  1. Una form che non usa l’elemento <form> non è una form. In effetti, potreste nasconderlo specificando role="form" nel <div> padre, ma se state creando una form, e questa ne ha tutto l’aspetto, usate un elemento <form> con gli appropriati attributi action e method. L’attributo action è cruciale, dal momento che assicura che la form farà ancora qualcosa in assenza di JavaScript, posto, ovviamente, che il componente sia server-rendered.
  2. Uno <span> non è un sostituto per l’elemento <label>, che fornisce dei benefici di accessibilità che <span> non dà.
  3. Se abbiamo intenzione di fare qualcosa lato client prima di fare l’invio di una form, allora dovremmo spostare l’azione destinata all’handler onClick dell’elemento <button> all’handler onSubmit dell’elemento <form>.
  4. Tra l’altro, perché usare JavaScript per validare un indirizzo email quando HTML5 offre controlli di validazione della form in quasi tutti i browser, giù giù fino a IE10? Qui c’è l’opportunità di fare affidamento sul browser e usare un input type appropriato, così come l’attributo required, ma sappiate che farlo funzionare nel modo corretto con gli screen reader richiede un po’ di know-how.
  5. Sebbene non sia un problema di accessibilità, questo componente non fa affidamento su alcun metodo state o lifecycle, il che significa che può essere rifattorizzato in un componente functional stateless, che usa molto meno JavaScript rispetto a un componente React completo.

Tenendo presenti queste cose, possiamo rifattorizzare questo componente:

import React from "react";

const SignupForm = props => {
  const handleSubmit = event => {
    // Needed in case we're sending data to the server XHR-style
    // (but will still work if server-rendered with JS disabled).
    event.preventDefault();

    // Carry on...
  };
  
  return (
    <form method="POST" action="/signup" onSubmit={handleSubmit}>
      <label for="email" class="email-label">Enter your email:</label>
      <input type="email" id="email" required />
      <button>Sign Up</button>
    </form>
  );
};

Non solo questo componente adesso è più accessibile, ma usa anche meno JavaScript. In un mondo che affoga in JavaScript, cancellarne delle righe dovrebbe essere terapeutico. Il browser ci dà così tanto gratuitamente e noi dovremmo cercare di trarne vantaggio il più spesso possibile.

Questo non vuol dire che i pattern non accessibili capitino solo quando si usano i framework, ma piuttosto che un’unica preferenza per JavaScript alla fine farà emergere i gap nella nostra comprensione di HTML e CSS. Questi gap nelle conoscenze spesso risulteranno in errori di cui magari non siamo nemmeno a conoscenza. I framework possono essere dei tool utili che fanno aumentare la produttività, ma è fondamentale un’educazione continua nelle tecnologie web principali per creare delle esperienze usabili, indipendentemente dai tool che si usano.

Fidatevi della web platform e andrete lontano velocemente#section3

Visto che stiamo già parlando dei framework, va detto che la web platform è di per sé un framework formidabile. Come ha mostrato la sezione precedente, è meglio quando possiamo basarci su pattern con un markup assodato e su feature del browser stabili. L’alternativa è reinventarle, con tutte le nefaste conseguenze che tale impresa comporta, o addirittura peggio: semplicemente presupporre che l’autore di ogni pacchetto JavaScript che installiamo abbia risolto il problema in maniera esaustiva e oculata.

APPLICAZIONI SINGLE PAGE#section4

Uno dei compromessi a cui gli sviluppatori scendono rapidamente è l’adozione del modello single page application (SPA), anche se non va bene per il progetto. Sì, effettivamente c’è un guadagno nella performance percepita con il routing client-side di una SPA, ma cosa perdiamo? La funzionalità di navigazione propria del browser, sebbene sincrona, fornisce un sacco di vantaggi. Per prima cosa, la cronologia è gestita secondo una specifica complessa. Gli utenti senza JavaScript, che sia per loro scelta o meno, non perderanno l’accesso completamente. Perché le SPA rimangano disponibili quando non lo è JavaScript, il rendering server-side diventa improvvisamente una cosa da considerare.

Two series of screenshots. On the left, we have a blank screen for several seconds until the app appears after 5.24s. On the right, the basic components appear at 4ms and the site is fully usable at 5.16s.
Figura 2.Un confronto con una app di esempio che viene caricata su una connessione lenta. La app a sinistra dipende interamente da JavaScript per fare il rendering della pagina. L’app sulla destra fa il render di una response sul server, ma poi usa client-side hydration per attaccare i componenti al markup esistente reso lato server.

Anche l’accessibilità è danneggiata se un client-side router non fa sapere alle persone che il contenuto della pagina è cambiato. La conseguenza è che chi dipende dalle tecnologie assistive deve capire da solo i cambiamenti che sono avvenuti sulla pagina, cosa che può rivelarsi piuttosto ardua.

Poi c’è la nostra vecchia nemesi: l’overhead. Alcuni router client-side sono molto piccoli, ma quando cominciate con React, un router compatibile e magari addirittura una libreria di state management, state accettando che c’è una certa quantità di codice che non potrete mai ottimizzare, in questo caso circa 135 KB. Considerate attentamente quello che state creando e se un router client side valga il compromesso che state inevitabilmente facendo. Tipicamente, stareste meglio senza.

Se siete preoccupati della performance percepita della navigazione, potreste appoggiarvi su rel=prefetch per caricare in maniera ipotetica dei documenti sulla stessa origine. Questo ha un incredibile effetto sul miglioramento della performance di caricamento delle pagine percepita, visto che il documento è subito disponibile in cache. Poiché i prefetch sono fatti con bassa priorità, sarà anche meno probabile che concorrano per la banda con risorse critiche.

Screenshot showing a list of assets loaded on a webpage. 'writing/' is labeled as prefetched on initial navigation. This asset is then loaded in 2ms when actually requested by the user.
Figura 3. L’HTML per l’URL writing/ di cui viene fatto prefetch nella pagina iniziale. Quando l’utente richiede l’URL writing/, il suo HTML viene caricato istantaneamente dalla cache del browser.

Lo svantaggio primario del link prefetching è che dovete essere coscienti che può essere potenzialmente inutile. Quicklink, un piccolo script di prefetching di Google, mitiga in qualche modo questo effetto controllando che il client attuale sia su una connessione lenta – o abbia abilitato la modalità di risparmio dati ed evita di default il prefetch dei link su cross-origins.

Anche i Service workers sono immensamente vantaggiosi per la performance percepita dai returning user, sia che usiamo il client side routing sia che non lo usiamo, purché sappiate come muovervi. Quando facciamo precache di routes con un service worker, otteniamo molti degli stessi benefici del link prefetch, ma con un grado di controllo molto maggiore rispetto alle request e response. Che pensiate al vostro sito come un’“app” o meno, aggiungergli un service worker è forse uno degli usi di JavaScript più responsabile che esista oggi.

JAVASCRIPT NON È LA SOLUZIONE AI VOSTRI PROBLEMI DI LAYOUT#section5

Se installiamo un package per risolvere un problema di layout, procediamo con cautela e chiediamoci “cosa sto cercando di ottenere?”. CSS è progettato per svolgere questo compito e non richiede astrazioni per essere usato in maniera efficace. La maggior parte dei problemi di layout che i package JavaScript cercano di risolvere come il box placement, l’allineamento e le dimensioni, la gestione del text overflow e addirittura interi sistemi di layout sono già risolvibili con CSS. I moderni layout engine come Flexbox e Grid sono supportati sufficientemente bene che non dovrebbe esserci bisogno di cominciare un progetto con un layout framework. CSS è il framework. Quando avremo le feature queries, applicare il progressive enhancement ai layout perché adottino i nuovi layout engine improvvisamente non sarà più così difficile.

/* Your mobile-first, non-CSS grid styles goes here */

/* The @supports rule below is ignored by browsers that don't
   support CSS grid, _or_ don't support @supports. */
@supports (display: grid) {
  /* Larger screen layout */
  @media (min-width: 40em) {
    /* Your progressively enhanced grid layout styles go here */
  }
}

Usare JavaScript per soluzioni di layout e problemi di presentazione non è una novità. Era qualcosa che facevamo quando ci raccontavamo bugie nel 2009 dicendo che ogni sito web sarebbe dovuto apparire in IE6 esattamente identico agli altri browser più potenti dell’epoca. Se stiamo ancora sviluppando siti web che appaiono identici in ogni browser nel 2019, dovremmo rivalutare i nostri obiettivi di sviluppo. Ci sarà sempre un browser che dovremo supportare che non può fare tutto quello che i moderni browser evergreen possono fare. La parità visuale totale su tutte le piattaforme non è solo uno scopo che ci siamo prefissi in vano, è il nemico principale del progressive enhancement.

Non sono qui per uccidere JavaScript#section6

Non fatevi trarre in inganno: non ho nulla contro JavaScript. Mi ha permesso di avere una carriera e, se devo essere onesto con me stesso, è stato una fonte di gioia per più di dieci anni. Come ogni relazione a lungo termine, più ci passo del tempo più imparo a conoscerlo. È un linguaggio maturo, ricco di feature che diventa sempre più potente ed elegante ogni anno che passa.

Tuttavia, ci sono delle volte in cui mi sembra che JavaScript e io siamo ai ferri corti. Sono critico riguardo a JavaScript. O forse più precisamente, sono critico del modo in cui abbiamo sviluppato la tendenza a vederlo come la prima risorsa per realizzare cose per il web. Mentre esamino un altro bundle simile a una matassa di luci natalizie ingarbugliate, diventa chiaro che il web è ebbro di JavaScript. Lo utilizziamo quasi per tutto, anche quando non si l’occasione giusta. A volte mi chiedo quanto sarà terribile il post-sbornia.

In una serie di articoli che seguiranno, vi darò dei consigli più pratici da seguire per arginare la marea invadente del JavaScript eccessivo e come possiamo discuterne in maniera tale che quello che creeremo per il web sarà usabile, o almeno lo sarà maggiormente, per tutti, ovunque. Alcuni dei consigli saranno preventivi, altri somiglieranno più a misure tipo l’alcol post-sbornia. In ciascun caso, i risultati saranno gli stessi, speriamo. Credo che dovremmo tutti amare il web e comportarci correttamente con esso, ma voglio che pensiamo a come renderlo più resiliente e inclusivo per tutti.


Nessun commento

Hai qualcosa da dire?

Abbiamo disattivato i commenti, ma puoi vedere quello che gli altri hanno detto prima che li disattivassimo.

Altro da ALA