JavaScript Dynamic import()
JavaScript dynamic import() erklärt: Module bei Bedarf laden, Code Splitting, Lazy Loading, await- und .then()-Syntax sowie Fehlerbehandlung.
Dynamische Imports in JavaScript sind ein Feature, das in ECMAScript 2020 (ES2020) eingeführt wurde und es erlaubt, ein Modul zur Laufzeit, bei Bedarf zu laden – anstatt alles auf einmal beim ersten Parsen des Skripts. Im Gegensatz zur statischen import-Anweisung ist die import()-Form ein funktionsähnlicher Ausdruck, der ein Promise zurückgibt. Damit können Sie entscheiden, wann und ob ein Modul geladen werden soll – abhängig von Bedingungen, Benutzeraktionen oder dem Routing. Dieser Leitfaden behandelt die Syntax, die häufigsten Anwendungsfälle (Code Splitting, Lazy Loading, bedingtes Laden), die Fehlerbehandlung sowie ein vollständiges, ausführbares Beispiel.
Dieses Kapitel setzt voraus, dass Sie mit ES-Modulen und async/await vertraut sind.
Statischer import vs. dynamisches import()
Eine statische import-Anweisung muss auf der obersten Ebene eines Moduls stehen und wird vollständig aufgelöst, bevor irgendein Code des Moduls ausgeführt wird. Das macht sie vorhersehbar und werkzeugfreundlich, bedeutet aber auch, dass jedes statisch importierte Modul von Anfang an geladen wird – selbst Code, den der Benutzer vielleicht nie erreicht.
import() unterscheidet sich in drei wichtigen Punkten:
- Es ist ein Ausdruck, keine Anweisung, und kann daher überall stehen – in einem
if, einer Funktion oder einem Event-Handler. - Es akzeptiert einen dynamischen Bezeichner: Der Modulpfad kann eine Variable oder ein berechneter string sein, nicht nur ein String-Literal.
- Es gibt ein Promise zurück, das zum Namespace-Objekt des Moduls auflöst (ein object, dessen Eigenschaften die benannten Exporte des Moduls sind, plus
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());Da import() ein Promise zurückgibt, wird das Ergebnis mit await (innerhalb einer async-Funktion) oder mit .then()/.catch() behandelt:
// 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));Exporte aus dem Modulobjekt lesen
Der aufgelöste Wert ist das Modul-Namespace-Objekt. Benannte Exporte sind Eigenschaften; der Standard-Export befindet sich unter dem Schlüssel default. Destructuring macht den Zugriff übersichtlicher:
// 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"await import(...) funktioniert nur innerhalb einer async-Funktion oder auf der obersten Ebene eines ES-Moduls (Top-Level await). In einem gewöhnlichen <script> oder einer nicht-asynchronen Funktion verwenden Sie stattdessen die .then()-Form.
Häufige Anwendungsfälle
Dynamische Imports glänzen, wenn ein Teil Ihrer Anwendung nur bedingt genutzt wird oder nicht sofort benötigt wird. Im Folgenden sind die gängigsten Muster aufgeführt.
Code Splitting
Der häufigste Anwendungsfall für dynamische Imports ist Code Splitting: das Aufteilen Ihres Bundles in kleinere Chunks, die nur bei Bedarf geladen werden – typischerweise, wenn eine Route aufgerufen oder ein Feature genutzt wird. Im folgenden Beispiel wird ein umfangreiches Skript erst nach einem Klick des Benutzers geladen, anstatt den initialen Seitenaufruf zu verlangsamen.
button.addEventListener('click', function () {
import('./heavyScript.js').then(mod => {
mod.runHeavyTask();
});
});Da der Click-Handler synchron ist, wird hier die .then()-Form anstelle von await verwendet. Der Browser (oder Bundler) fordert heavyScript.js nur beim ersten Klick an; nachfolgende Klicks nutzen das gecachte Modul.
Messen Sie, bevor Sie splitten. Zu viele kleine dynamische Chunks können die Performance verschlechtern – jeder ist ein separater Netzwerk-Roundtrip. Reservieren Sie dynamische Imports für Code, der wirklich groß oder selten verwendet wird.
Lazy Loading von Komponenten
Frameworks wie React, Angular und Vue nutzen dynamische Imports intern, um Komponenten per Lazy Loading zu laden – eine Komponente wird erst beim ersten Rendern abgerufen.
// 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 kapselt den dynamischen Import, und React.Suspense zeigt den fallback an, bis der Chunk angekommen ist. Der Benutzer sieht Loading... nur für den kurzen Moment, in dem die Komponente geladen wird.
Erweiterte Nutzung dynamischer Imports
Bedingtes Laden
Da import() ein Ausdruck ist, können Sie ihn mit jeder Bedingung absichern – einem Feature-Flag, einer Benutzereinstellung, der Umgebung oder sogar dem Browser-Locale.
if (user.prefersAdvancedMode) {
const advanced = await import('./advancedEditor.js');
advanced.init();
}Benutzer, die den erweiterten Modus nie aktivieren, laden advancedEditor.js nie herunter. Sie können das Ganze mit einem dynamischen Bezeichner weiterführen – zum Beispiel ein anderes Modul je nach Locale laden:
const locale = navigator.language.startsWith('fr') ? 'fr' : 'en';
const messages = await import(`./locales/${locale}.js`);
console.log(messages.default.greeting);Bundler wie Webpack und Vite müssen wissen, welche Dateien möglicherweise geladen werden. Ein vollständig beliebiger Bezeichner (z. B. ein aus Benutzereingaben zusammengesetzter Pfad) kann nicht gebündelt werden. Halten Sie den variablen Teil des Pfads in einem bekannten Verzeichnis mit bekannter Erweiterung, wie im Locale-Beispiel oben.
Build-Tools und Node.js-Unterstützung
Wenn Sie import('./module.js') schreiben, geben Bundler wie Webpack, Rollup und Vite automatisch einen separaten Chunk aus und laden ihn bei Bedarf – in der Regel ist keine zusätzliche Konfiguration nötig. Im Browser wird natives import() von allen modernen Browsern unterstützt.
import() funktioniert auch in Node.js (v12+), einschließlich CommonJS-Dateien, was der Standardweg ist, um ein ES-Modul aus CommonJS-Code zu laden:
// 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();Modul-Metadaten mit import.meta
Innerhalb eines Moduls können Sie import.meta für kontextuelle Informationen lesen. Das am weitesten verbreitete Feld ist import.meta.url, das die URL des aktuellen Moduls enthält – nützlich zum Auflösen benachbarter Ressourcen:
// 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' } });Ein vollständiges Beispiel: Dynamisches Wetter-Widget
Das Wetter-Widget lädt das Modul zum Abrufen von Wetterdaten dynamisch nur dann, wenn der Benutzer es anfordert. Dies ist ein ideales Szenario für dynamische Imports, da das Laden von potenziell schwerem API-Interaktionscode bis zur tatsächlichen Verwendung verzögert wird.
Das Beispiel verwendet drei Dateien: eine HTML-Seite, ein Einstiegsskript und das verzögert geladene Modul.
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.';
}
});Dieser Code löst bei einer Benutzerinteraktion einen dynamischen Import aus:
- Event-Listener: Hängt einen Click-Handler an den Button an.
- Dynamischer Import: Verwendet
await import(), um das Modul erst beim Klick zu laden – das hält das initiale Bundle klein. - Fehlerbehandlung: Das
try...catchumschließt sowohl denimport()als auch den Daten-Aufruf, sodass ein fehlgeschlagener Download oder eine abgelehnte Anfrage die Fallback-Meldung anzeigt.
Dieser Ansatz macht Webseiten effizient und reaktionsschnell, indem Ressourcen nur bei Bedarf geladen und Benutzerinteraktionen sofortiges Feedback gegeben wird.
weatherModule.js:
export async function loadWeather() {
// Simulated API call
return new Promise(resolve => {
setTimeout(() => {
resolve({ weather: 'Sunny, 76°F' }); // Simulating weather data
}, 1000);
});
}Die Funktion simuliert das Abrufen von Daten aus einer entfernten Quelle, ohne eine echte API zu benötigen: Sie löst nach einer Sekunde Verzögerung auf, sodass Sie das verzögerte Laden in Aktion sehen können.
Erläuterung des Beispiels
- HTML-Aufbau: Stellt einen Button und einen Container für die Ausgabe bereit.
- Dynamischer Import in Aktion: Ein Klick auf den Button veranlasst
index.js,weatherModule.jsbei Bedarf zu laden. - Wetter-Modul: Simuliert eine API-Verzögerung und zeigt, wie dynamische Imports schwere oder bedingte Logik bis zur tatsächlichen Verwendung aufschieben.
Häufige Fallstricke
awaitaußerhalb eines Moduls oder einer async-Funktion. Top-Levelawait import()funktioniert nur in ES-Modulen; in einfachen Skripten oder nicht-asynchronen Callbacks verwenden Sie.then().defaultvergessen. Auf den Standard-Export eines Moduls wird über die Eigenschaftdefaultdes aufgelösten Objekts zugegriffen, nicht über das Objekt selbst.- Vollständig dynamische Pfade. Bundler können keinen Pfad aufteilen, den sie nicht analysieren können. Halten Sie den literalen Teil des Bezeichners (Verzeichnis und Erweiterung) statisch.
- Zu viele Splits. Jeder dynamische Chunk ist eine separate Anfrage. Teilen Sie großen oder selten verwendeten Code auf, nicht jeden kleinen Hilfscode.
Fazit
Dynamisches import() ermöglicht es, Module bei Bedarf zu laden und gibt ein Promise zurück, das zum Namespace-Objekt des Moduls auflöst. Es ermöglicht Code Splitting, Lazy-Loading von Komponenten, bedingtes Laden und locale-bewusstes Importieren – und verbessert die Startperformance, wenn es gezielt eingesetzt wird. Kombinieren Sie es mit async/await und solider Fehlerbehandlung, und verlassen Sie sich auf Ihren Bundler, um jeden import() in einen optimierten Chunk umzuwandeln.
Für ein tieferes Verständnis lesen Sie ES-Module: export und import, die Einführung in Module und Promises.