Uno studio per migliorare la giornata dello sviluppatore Frontend

Edoardo Cavazza
Chialab Open Source
6 min readDec 20, 2017
Un tipico esemplare di sviluppatore che installa le dipendenze NPM.

Introduzione

La specifica ECMAScript 2015 (ES6) per JavaScript ha migliorato, e di molto, lo sviluppo di applicazioni web, consentendo una migliore organizzazione del codice e dei file sorgenti (scaffolding) e l’adozione di strumenti disponibili in altri ambienti di sviluppo (static code analysis, sistemi di dipendenze, autocompletion…).

Tutto questo ha un prezzo: per lanciare un’applicazione web nel browser, dobbiamo installare centinaia di moduli node e ricompilare a ogni modifica il codice JavaScript necessario per l’esecuzione. Secondi che diventano ore durante la settimana, file system esplosi, uso di RAM e CPU ai massimi livelli e, nei computer “meno potenti”, ventole che turbinano come motori aeronautici.

Il desiderio

Come sviluppatore vorrei creare una classica To-Do List app, basata su un sistema a componenti e usando JSX, e vedere il risultato nel browser, senza dover usare un bundler, un transpiler e un watcher.

😱😱😱

Supponendo che lo sviluppatore in questione voglia utilizzare Preact, solo scaricandone il boilerplate e le relative dipendenze, si ritroverebbe con circa 1.500 moduli node, 26.000 file e 200MB di disco occupato. La maggior parte di queste dipendenze servono per far funzionare un web server di sviluppo basato su Webpack.

Ma il nostro sviluppatore non vuole usare niente di tutto questo, per cui non gli resta che affidarsi unicamente alle feature del browser.

Come lanciare un’applicazione ES6/7/X senza un bundler

A partire da Chrome 62, Edge 16, Safari 11 e Firefox 54 è possibile importare un modulo ES6 nel browser tramite:

Quando il browser incontra questa istruzione inzia a risolvere ricorsivamente i vari import /export presenti nel file sorgente specificato.

Per quanto di suo questa nuova feature possa essere fantastica, da sola non basta a risolvere le esigenze del nostro programmatore. Infatti, il browser è in grado di risolvere solo dipendenze relative, e non ha nessuna informazione riguardo all’esistenza di eventuali dipendenze presenti nella cartella node_modules. Inoltre, l’utilizzo di JSX causerebbe un SyntaxError, non rientrando nelle specifiche del linguaggio.

Service Workers

È qui che ci vengono in soccorso i Service Workers. Questi speciali Worker, una volta registrati, possono intercettare le chiamate di rete e modificarne il response.

In questo modo possiamo intercettare l’import di un file JavaScript, parsarne il contenuto e intervenire dove è necessario.

A questo punto i passaggi potrebbero essere:

  • intercettare le chiamate ai file JavaScript
  • scaricare il contenuto dei file JavaScript
  • individuare gli import non relativi e mapparli nella cartella node_modules
  • individuare la sintassi JSX e convertirla in istruzioni accettate dal browser
  • restituire il file modificato al thread principale.

Infine, il browser dovrebbe essere in grado di interpretare correttamente il file e di risolvere le dipendenze.

Approfondimento sui Service Workers su MDN: Using Service Workers

Lo sviluppo di Unchained

Unchained è un proof of concept ospitato su GitHub che mette in pratica l’idea sopra riportata. Così come i vari bundler e transpiler che usiamo, è pensato come libreria che divide il processo in fasi distinte ed è pluggabile.

Fornisce inoltre un set di helper per l’utilizzo dei Service Workers e un polyfill per il dynamic import() ed è composto da un core (con un’interfaccia simile a quella di Rollup) che si occupa di risolvere l’import di un file attraverso i seguenti passaggi:

  • Trasformazione: per operazioni di modifica del codice e transpiling di sintassi non standard
  • Risoluzione: esegue una ricerca degli statement di import nel file, ne estrapola il path e, se necessario, lo modifica
  • Finalizzazione: restituisce il codice generato.

Tutte le modifiche al sorgente vengono effettuate tramite Babel Standalone, una versione di Babel che funziona nel browser e che consente ai plugin di lavorare sul Abstract Syntax Tree (AST) del codice e generare una sourcemap per il file finale.

I plugin esistenti (molto semplici per ora) sono:

  • common: trasforma gli statement di require e module.exports tipici del sistema CommonJS in statement ES6
  • babel: permette di accedere alla libreria Babel Standalone in modo da usare plugin per sintassi non standard (ad esempio, JSX, Flow o Typescript!)
  • text: converte un file di testo in un modulo ES6 che esporta una stringa di testo
  • json: converte un file JSON in un modulo ES6 che esporta un oggetto JavaScript
  • env: esegue l’inject di variabili d’ambiente (in questo caso, necessariamente passati in configurazione, in quanto il browser non ha reale accesso all’ambiente)
  • resolve: implementazione parziale che converte gli import di moduli node nel loro path relativo.

Inoltre Unchained usa il sistema di cache fornito dai Service Workers basandosi sulla header ETag: in questo modo al reload della pagina il sistema evita i passaggi di trasformazione e risoluzione per i file che non sono cambiati, rendendo quasi immediato l’inject delle modifiche.

Rendiamo felice lo sviluppatore

Alla fine, tornando al problema del nostro sviluppatore, possiamo ipotizzare una soluzione. Per farlo useremo il semplice esempio della home di preactjs.com.

Setup del progetto

Avremo bisogno di due dipendenze:

npm init -y
npm install preact unchained-js
# 2 moduli, 65 items, < 2MB

e dei seguenti file:

  • index.html: esegue la registrazione del Service Worker e importa i file dell’applicazione
  • sw.js: il Service Worker da importare, usa il core di Unchained per il transpiling dei sorgenti.

Ora, basta avviare un server per l’applicazione:

npm install -g http-server
http-server .

e… fatto!

In console, potete vedere che Unchained elenca i file che cerca di risolvere alla prima apertura, e ai successivi refresh li risolverà usando la cache.

Se si modifica un file, per esempio l’etichetta del bottone in todolist.component.js, solo il file cambiato verrà ricaricato.

È possibile scaricare qui i file di progetto già pronti

Conclusioni

Anche se il supporto dei browser è limitato (Chrome, Firefox con la flag dom.moduleScripts.enabled attiva), trovo che questo approccio semplifichi molto il processo di configurazione del progetto. In particolare:

  • riduce notevolmente la quantità delle dipendenze da installare
  • il transpiling dei file JavaScript non blocca il thread UI principale, perché tutto avviene nel contesto del Service Worker
  • dopo il primo avvio, l’aggiornamento dei file sorgenti è molto veloce
  • dynamic import() e code splitting automatici
  • mantiene il supporto alle sourcemap.

Ma ci sono anche delle conseguenze:

  • i tempi per il primo avvio non sono performanti (per la mancanza di accesso diretto al file system e perché viene generato un AST per ogni file)
  • non c’è treeshaking, una feature molto potente presente in Rollup e Webpack (ma più utile in fase in di distribuzione che in quella di sviluppo).

Caso reale

Le cose sono sempre facili quando si tratta di una To-Do List App, mentre lo sono molto meno nei casi reali. Ho provato a introdurre Unchained in un progetto complesso, con molte dipendenze tra cui jQuery, Moment, CKEditor e una libreria di componenti, con i seguenti risultati:

Risultati per MacBook Pro (Retina, 15-inch, Mid 2015) e Chrome 63.0.3239.84

Futuro

Nonostante Unchained implementi la proposta di questo studio, mi piacerebbe approfondirne alcune parti:

  • velocizzare il primo avvio usando unicamente trasformazioni dell’AST (al momento disabilitate per problemi nella creazione delle sourcemap)
  • verificare un eventuale integrazione con Turbo quando sarà disponibile, in modo da migliorare la risoluzione dei path non relativi
  • estendere la compatibilità ai browser che non supportano Service Workers o ES6 modules
  • la possibilità di eseguire le trasformazioni di Unchained anche in ambiente node, in modo da creare bundle delle applicazioni 100% compatibili con la preview in-browser
  • blob (per il caricamento di immagini e font) e postCSS plugins.

Ringraziamenti

Un grazie speciale a xho, che ha transpilato il mio linguaggio macchina in uno comprensibile agli umani.

--

--

Edoardo Cavazza
Chialab Open Source

Software Architect and Accessibility Consultant @ Chialab. Working on synergy between designers, developers and tools. More: WebComponents, Edtech, Typography