W3docs

JavaScript Dynamic import()

Impara il dynamic import() di JavaScript — carica moduli su richiesta per il code splitting e il lazy loading, con sintassi await e .then(), gestione degli errori ed esempi eseguibili.

Gli import dinamici in JavaScript sono una funzionalità introdotta in ECMAScript 2020 (ES2020) che ti permette di caricare un modulo a runtime, su richiesta, invece di caricare tutto in una volta quando lo script viene analizzato per la prima volta. A differenza dell'istruzione import statica, la forma import() è un'espressione simile a una funzione che restituisce una promise, quindi puoi decidere quando e se caricare un modulo in base a condizioni, azioni dell'utente o routing. Questa guida tratta la sintassi, i casi d'uso più comuni (code splitting, lazy loading, caricamento condizionale), la gestione degli errori e un esempio completo eseguibile.

Questo capitolo presuppone che tu abbia familiarità con i moduli ES e con async/await.

Import statico vs. dynamic import()

Un'istruzione import statica deve comparire al livello superiore di un modulo e viene completamente risolta prima che il codice del modulo venga eseguito. Questo la rende prevedibile e compatibile con gli strumenti di sviluppo, ma significa anche che ogni modulo importato staticamente viene recuperato in anticipo — anche il codice che l'utente potrebbe non raggiungere mai.

import() è diverso in tre modi importanti:

  • È un'espressione, non un'istruzione, quindi può comparire ovunque — all'interno di un if, una funzione o un gestore di eventi.
  • Accetta uno specificatore dinamico: il percorso del modulo può essere una variabile o una stringa calcolata, non solo un letterale stringa.
  • Restituisce una promise che si risolve nell'oggetto namespace del modulo (un object le cui proprietà sono le esportazioni con nome del modulo, più default).
// Static import — runs at parse time, must be top-level
import { formatDate } from './utils.js';

// Dynamic import — runs when this line executes, can be anywhere
const utils = await import('./utils.js');
utils.formatDate(new Date());

Poiché import() restituisce una promise, gestisci il risultato con await (all'interno di una funzione async) oppure con .then()/.catch():

// With await
const mod = await import('./utils.js');

// With .then() / .catch()
import('./utils.js')
  .then(mod => mod.formatDate(new Date()))
  .catch(err => console.error('Failed to load module:', err));

Leggere le esportazioni dall'oggetto modulo

Il valore risolto è l'oggetto namespace del modulo. Le esportazioni con nome sono proprietà; l'esportazione predefinita si trova sotto la chiave default. La destrutturazione rende tutto più pulito:

// math.js exports: export function add(a,b){...}, export default function greet(){...}
const { add, default: greet } = await import('./math.js');

console.log(add(2, 3)); // 5
console.log(greet());   // "hello"
Informazione

await import(...) funziona solo all'interno di una funzione async o al livello superiore di un modulo ES (top-level await). In un <script> ordinario o in una funzione non asincrona, usa invece la forma .then().

Casi d'uso comuni

Gli import dinamici eccellono quando una parte della tua applicazione viene usata in modo condizionale o non è immediatamente necessaria. Di seguito sono riportati i pattern più comuni.

Code splitting

Il caso d'uso più comune per gli import dinamici è il code splitting: suddividere il bundle in chunk più piccoli che vengono caricati solo quando necessario — tipicamente quando si visita una route o si utilizza una funzionalità. Di seguito, uno script pesante viene recuperato solo dopo che l'utente fa clic, invece di gonfiare il caricamento iniziale della pagina.

button.addEventListener('click', function () {
    import('./heavyScript.js').then(mod => {
        mod.runHeavyTask();
    });
});

Poiché il gestore del clic è sincrono, qui viene usata la forma .then() invece di await. Il browser (o il bundler) richiede heavyScript.js solo al primo clic; i clic successivi riutilizzano il modulo memorizzato nella cache.

Attenzione

Misura prima di dividere. Aggiungere troppi chunk dinamici di piccole dimensioni può danneggiare le prestazioni — ognuno è un round trip di rete separato. Riserva gli import dinamici per il codice che è genuinamente grande o raramente utilizzato.

Lazy loading dei componenti

Framework come React, Angular e Vue usano gli import dinamici internamente per il lazy loading dei componenti — un componente viene recuperato solo quando viene renderizzato per la prima volta.

// Lazy loading a component in React
const LazyComponent = React.lazy(() => import('./LazyComponent'));

function App() {
    return (
        <React.Suspense fallback={<div>Loading...</div>}>
            <LazyComponent />
        </React.Suspense>
    );
}

React.lazy avvolge l'import dinamico e React.Suspense mostra il fallback finché il chunk non arriva. L'utente vede Loading... solo per il breve momento in cui il componente viene recuperato.

Utilizzo avanzato degli import dinamici

Caricamento condizionale

Poiché import() è un'espressione, puoi proteggerla con qualsiasi condizione — un feature flag, un'impostazione utente, l'ambiente o persino la lingua del browser.

if (user.prefersAdvancedMode) {
    const advanced = await import('./advancedEditor.js');
    advanced.init();
}

Gli utenti che non abilitano mai la modalità avanzata non scaricano mai advancedEditor.js. Puoi estendere questo approccio con uno specificatore dinamico — caricare un modulo diverso per ogni lingua, ad esempio:

const locale = navigator.language.startsWith('fr') ? 'fr' : 'en';
const messages = await import(`./locales/${locale}.js`);
console.log(messages.default.greeting);
Attenzione

I bundler come Webpack e Vite devono sapere quali file potrebbero essere caricati. Uno specificatore completamente arbitrario (ad es. un percorso costruito dall'input dell'utente) non può essere incluso nel bundle. Mantieni la parte variabile del percorso in una directory e un'estensione noti, come nell'esempio delle lingue sopra.

Supporto nei build tool e in Node.js

Quando scrivi import('./module.js'), bundler come Webpack, Rollup e Vite emettono automaticamente un chunk separato e lo caricano su richiesta — di solito non è necessaria alcuna configurazione aggiuntiva. Nel browser, import() nativo è supportato in tutti i browser moderni.

import() funziona anche in Node.js (v12+), incluso all'interno di file CommonJS, che è il modo standard per caricare un modulo ES da codice CommonJS:

// Loading an ESM module from a CommonJS file
async function run() {
    const { default: chalk } = await import('chalk');
    console.log(chalk.green('Loaded an ESM package from CommonJS'));
}
run();

Metadati del modulo con import.meta

All'interno di un modulo puoi leggere import.meta per informazioni contestuali. Il campo più ampiamente supportato è import.meta.url, che contiene l'URL del modulo corrente — utile per risolvere risorse adiacenti:

// Resolve a JSON file relative to the current module
const dataUrl = new URL('./data.json', import.meta.url);
const data = await import(dataUrl, { with: { type: 'json' } });

Un esempio completo: widget meteo dinamico

Il widget meteo caricherà dinamicamente il modulo per il recupero dei dati meteorologici solo quando l'utente lo richiede. Questo è uno scenario ideale per gli import dinamici, poiché ritarda il caricamento di codice potenzialmente pesante per l'interazione con le API finché non è effettivamente necessario.

L'esempio usa tre file: una pagina HTML, uno script di ingresso e il modulo caricato in modo lazy.

index.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Dynamic Weather Widget</title>
</head>
<body>
    <h1>Weather Widget</h1>
    <button id="loadWeather">Load Weather</button>
    <div id="weatherOutput">Click the button to load the weather.</div>

    <script src="index.js"></script>
</body>
</html>

index.js:

document.getElementById('loadWeather').addEventListener('click', async () => {
    const output = document.getElementById('weatherOutput');
    try {
        const weatherModule = await import('./weatherModule.js');
        const data = await weatherModule.loadWeather();
        output.textContent = `Weather: ${data.weather}`;
    } catch (err) {
        output.textContent = 'Failed to load weather data.';
    }
});

Questo codice attiva un import dinamico all'interazione dell'utente:

  1. Gestore eventi: Associa un gestore del clic al pulsante.
  2. Import dinamico: Usa await import() per recuperare il modulo solo al clic, mantenendo il bundle iniziale piccolo.
  3. Gestione degli errori: Il blocco try...catch avvolge sia import() che la chiamata ai dati, quindi un download fallito o una richiesta rifiutata mostra il messaggio di fallback.

Questo approccio aiuta a rendere le pagine web efficienti e reattive caricando le risorse solo quando necessario e fornendo un feedback immediato alle interazioni dell'utente.

weatherModule.js:

export async function loadWeather() {
    // Simulated API call
    return new Promise(resolve => {
        setTimeout(() => {
            resolve({ weather: 'Sunny, 76°F' });  // Simulating weather data
        }, 1000);
    });
}

La funzione simula il recupero di dati da una sorgente remota senza bisogno di una vera API: si risolve dopo un secondo di ritardo in modo che tu possa vedere il caricamento differito in azione.

Spiegazione dell'esempio

  • Configurazione HTML: Fornisce un pulsante e un contenitore per l'output.
  • Import dinamico in azione: Facendo clic sul pulsante, index.js carica weatherModule.js su richiesta.
  • Modulo meteo: Simula un ritardo API, mostrando come gli import dinamici rimandano la logica pesante o condizionale finché non è effettivamente necessaria.

Errori comuni

  • await fuori da un modulo o da una funzione async. Il top-level await import() funziona solo nei moduli ES; negli script normali o nei callback non asincroni, usa .then().
  • Dimenticare .default. L'esportazione predefinita di un modulo si raggiunge tramite la proprietà default dell'object risolto, non l'object stesso.
  • Percorsi completamente dinamici. I bundler non possono dividere un percorso che non riescono ad analizzare. Mantieni la parte letterale dello specificatore (directory ed estensione) statica.
  • Over-splitting. Ogni chunk dinamico è una richiesta separata. Dividi il codice grande o raramente usato, non ogni piccolo helper.

Conclusione

Il dynamic import() ti permette di caricare moduli su richiesta, restituendo una promise che si risolve nell'oggetto namespace del modulo. Alimenta il code splitting, i componenti caricati in modo lazy, il caricamento condizionale e gli import con supporto multilingua — migliorando le prestazioni di avvio quando usato con criterio. Combinalo con async/await e una solida gestione degli errori, e affidati al tuo bundler per trasformare ogni import() in un chunk ottimizzato.

Per approfondire, consulta moduli ES: export e import, l'introduzione ai moduli e le promise.

Esercitazione

Pratica
Quali affermazioni riguardo al dynamic import() di JavaScript sono corrette?
Quali affermazioni riguardo al dynamic import() di JavaScript sono corrette?
Was this page helpful?