Nei post precedenti abbiamo potuto apprezzare diverse interessanti caratteristiche offerte da webpack.
Oggi ci soffermeremo su un aspetto molto interessante, seppur non frequentemente utilizzato: il caricamento asincrono dei moduli Javascript ECMASCcript 6, come definito nelle Programmatic Loader API. Nel finale vedremo come integrare gli esempi in webpack ed alcuni test unitari.
Prima di tuffarci nel merito della questione, un breve accenno teorico sui moduli ECMASCript.
Una overview più completa sulle novità introdotte in ES6, potete trovarla in questo post di Nicola Malizia.
 

Moduli ECMAScript 6

I Moduli Standard ECMASCript 6 sono completamente statici, e  questa caratteristica ci impone di definire a compile-time cosa si intende importare o esportare, affinché la nostra applicazione reagisca nella desiderata maniera a run-time. Le Specifiche ES6 sui moduli sono composte principalmente di due parti:

  • Declarative Sintax: import ed export dei moduli;
  • Programmatic Loader API: queste ci consentono di configurare la modalità di caricamento dei moduli javascript, compreso il loro utilizzo all interno di costrutti condizionali o event handler;

Anche se oggi è molto comune vederlo, questo è un semplice esempio di module import:

import * as fibonacciModule from './src/fib.js'; // solo in caso di default export si omette il semicolon!!!!

le cui principali proprietà sono due:

  • lo statement deve essere necessariamente posizionato sulla sommità del files, come prima istruzione, al fine di evitare il suo inserimento all interno di statement condizionali (come if , switch) o gestori di eventi (event handler);
  • il module specifier  './src/fib.js' , è immutabile. Questa proprietà non ci consente di ottenerlo tramite la valutazione di un’ espressione calcolata a run-time (compute value), come una chiamata a funzione ad esempio;

Vediamo ora come queste due caratteristiche possono subire delle variazioni in corrispondenza del caricamento dinamico.
 

Import Dinamico (lazy loading)

La  proposal ECMASCript ‘import()’ di Domenic Cola , attualmente in stage 3, abilita il caricamento  dinamico dei moduli javascript. L operatore preposto  per questa operazione, come si intuisce facilmente è: import(), le cui caratteristiche sono le seguenti:

  • riceve come argomento una stringa, il module specifier, ma in questo caso può essere il risultato di una espressione ( computed value ), forzata a stringa (cast);
  • il risultato della chiamata a funzione è una Promise, ed una volta caricato il modulo, la promessa passa nello stato di fullfilled;

Molte funzionalità, in molti casi non sono necessarie in fase iniziale, e l import dinamico può risultare molto utile a caricarle solo quando strettamente necessario. Vediamo ora come applicare l import dinamico, attraverso dei casi d uso.
 

Uses Cases:  import dinamico via event handler

Al contrario dei named import ed export, l operatore import() può essere inserito all interno di event handler o costrutti condizionali come detto in precedenza. Vediamo un esempio relativo alla prima casistica:

button.addEventListener('click', event => {
    import('./fib')
        .then(fib => {
            console.log(fib.fibonacciIterative(8))
        })
        .catch(error => {
            /* Gestione Errore */
        })
});

Come potete vedere lo statement import() è stato inserito all interno della callback dell’  event listener applicato ad un elemento del DOM, nel caso specifico un button.
Alla pressione di questo avviene l import del modulo javascript, ed una volta entrati nello scope del then  sarà possibile invocarne tutte le funzionalità, come in una standard promise;
 

Uses Cases: import dinamico via construtti condizionali

Durante lo sviluppo di una Applicazione Web, più o meno complessa, può essere necessario caricare dei moduli di codice, al verificarsi di una condizione logica in un costrutto condizionale. Ad esempio il caricamento di un polyfill, può avvenire in circostanze e condizioni ben definite. Vediamo un esempio:

button.addEventListener('click', event => {
    import('./fib')
        .then(fib => {
            console.log(fib.fibonacciIterative(8))
        })
        .catch(error => {
            /* Gestione Errore */
        })
});

dove valgono le stesse ipotesi dell’esempio precedente.
 

Uses Cases: module specifier calcolato a run -time

Vi sono casistiche, come l internazionalizzazione, spesso indicata con la sigla i18n,  in cui il risulta di grande utilità calcolare il module specifier a runtime (compute value). Questo è un esempio:

import (`messages_${getLocale()}.js`)
    .then((i18n) => {
        console.log(i18n)
    }).catch((err) => {
        console.log("Caricamento Chunk fallito!!!!!")
    })
}

dove getLocale() è una funzione che restituisce come risultato il codice ISO della nazione corrispondente, il quale è formato da due sequenze alfanumeriche, la prima identifica  la lingua, l altra per la nazione. L Italia è rappresentata dal codice ISO it-it, per cui applicato all esempio precedente, il valore del module specifier una volta calcolato, sarà messages_it-it.
Questo meccanismo è alla base di molti framework lato server, come Spring ad esempio, dove i file di localizzazione sono posti tutti all interno di una folder specifica con il nome che segue la Naming Convention: messages_codice-ISO.
 

Uses Cases: accedere agli export via destructuring

Il destructuring ci viene in aiuto, quando dobbiamo accedere ad i moduli risolti nel corpo del then.