7 marzo 2021

Dynamic imports

Le istruzioni di export ed import che abbiamo visto nei capitolo precedente sono detti “statici”. La sintassi è molto semplice e rigorosa.

Come prima cosa, non possiamo generare dinamicamente parametri di import.

Il percorso al modulo deve essere una stringa, non può essere una chiamata a funzione. Questo non funzionerebbe:

import ... from getModuleName(); // Errore, sono ammesse solamente string

Secondo, non possiamo importare a run-time in base a determinate condizioni:

if(...) {
  import ...; // Errore, non è possibile farlo!
}

{
  import ...; // Errore, non possiamo scrivere gli import all'interno di nessun blocco
}

Questo accade perché import/export mirano a fornire uno scheletro per la struttura del codice. Questa è una buona cosa, poiché la struttura del codice può essere analizzata, i moduli possono essere raccolti and impacchettati in un singolo file (grazie ad alcuni strumenti) e gli export inutilizzati possono essere rimossi (“tree-shaken”). Questo è possibile solamente perché la struttura degli imports/exports è semplice e preimpostata.

Ma come possiamo importare un modulo dinamicamente, a seconda delle necessità?

L’espressione import()

L’espressione import(module) carica il modulo e ritorna una promise, che si risolve in un oggetto che contiene tutti gli export del modulo. Può essere quindi invocata in un qualsiasi punto del codice.

Possiamo utilizzarla dinamicamente ovunque, ad esempio:

let modulePath = prompt("Which module to load?");

import(modulePath)
  .then(obj => <module object>)
  .catch(err => <loading error, e.g. if no such module>)

oppure, potremmo utilizzare let module = await import(modulePath) se ci troviamo all’interno di una funzione asincrona.

Ad esempio, se abbiamo il seguente modulo say.js:

// 📁 say.js
export function hi() {
  alert(`Hello`);
}

export function bye() {
  alert(`Bye`);
}

…Allora il dyamic import può essere scritto così:

let {hi, bye} = await import('./say.js');

hi();
bye();

Oppure, se say.js ha un default export:

// 📁 say.js
export default function() {
  alert("Module loaded (export default)!");
}

…Quindi, per potervi accedere, possiamo utilizzare la proprietà default dell’oggetto:

let obj = await import('./say.js');
let say = obj.default;
// o, in una riga: let {default: say} = await import('./say.js');

say();

Qui vediamo l’esempio completo:

Risultato
say.js
index.html
export function hi() {
  alert(`Hello`);
}

export function bye() {
  alert(`Bye`);
}

export default function() {
  alert("Module loaded (export default)!");
}
<!doctype html>
<script>
  async function load() {
    let say = await import('./say.js');
    say.hi(); // Hello!
    say.bye(); // Bye!
    say.default(); // Modulo caricato (export default)!
  }
</script>
<button onclick="load()">Click me</button>
Da notare:

I dynamic import funzionano negli script regolari, non richiedono script type="module".

Da notare:

Anche se import() sembra una chiamata a funzione, in realtà è una speciale sintassi che utilizza le parentesi (in modo simile a super()).

Quindi non possiamo copiare import in una variabile o utilizzare call/apply. Non è una funzione.

Mappa del tutorial

Commenti

leggi questo prima di lasciare un commento…
  • Per qualsiasi suggerimento - per favore, apri una issue su GitHub o una pull request, piuttosto di lasciare un commento.
  • Se non riesci a comprendere quanto scitto nell'articolo – ti preghiamo di fornire una spiegazione chiara.
  • Per inserire delle righe di codice utilizza il tag <code>, per molte righe – includile nel tag <pre>, per più di 10 righe – utilizza una sandbox (plnkr, jsbin, codepen…)