ES Modules Logo ESModules.com

Advanced Topics

Explore advanced ES Modules features including dynamic imports, top-level await, live bindings, and handling circular dependencies.

Dynamic Imports

Use the import() function to lazy-load modules on demand for code splitting and improved performance.

// Load module dynamically
const module = await import('./heavy-module.js');
module.doSomething();

// Conditional loading
if (userIsAdmin) {
  const adminFeature = await import('./admin-panel.js');
  adminFeature.init();
}

// Dynamic path (use with caution)
const lang = 'en';
const translations = await import(`./i18n/${lang}.js`);

Benefits:

  • Reduce initial bundle size
  • Load features on-demand
  • Improve application startup time
  • Better caching (split chunks)

Top-Level await

Use await outside of an async function at the top level of a module.

// Fetch config before app loads
const config = await fetch('/api/config').then(r => r.json());

// Load database connection
import { connectDB } from './database.js';
const db = await connectDB();

export { config, db };

⚠️ Important:

Top-level await blocks module loading. Modules that import this module will wait until the await completes.

Live Bindings

Imports are live, read-only views into exported variables. Changes in the exporting module are reflected in importing modules.

// counter.js
export let count = 0;
export function increment() { count++; }

// main.js
import { count, increment } from './counter.js';
console.log(count); // 0
increment();
console.log(count); // 1 (live binding!)

// count = 10; // ❌ Error: Assignment to constant variable
// Imports are read-only!

Circular Dependencies

ES Modules' live bindings can handle some circular dependency cases gracefully, unlike CommonJS.

// a.js
import { b } from './b.js';
export const a = 'a';
console.log('a.js:', b); // Works!

// b.js
import { a } from './a.js';
export const b = 'b';
console.log('b.js:', a); // May be undefined during init

// main.js
import './a.js'; // Resolves circular dependency

✅ Best Practice:

Avoid circular dependencies when possible. Refactor shared code into a third module.

Import Meta

import.meta provides metadata about the current module.

// Get module URL
console.log(import.meta.url);
// file:///path/to/module.js or https://example.com/module.js

// Load assets relative to module
const imagePath = new URL('./image.png', import.meta.url);

// In Vite/Webpack - environment variables
console.log(import.meta.env.VITE_API_KEY);

Live Advanced Examples

See advanced ES Module features in action - dynamic imports and top-level await executing live. Click "Edit in CodePen" to experiment!

Live Examples

What You're Seeing

  • Real dynamic imports loading modules on-demand
  • Top-level await making async API calls without wrappers
  • Live console output showing execution flow
  • Want to modify the code? Click "Edit in CodePen" above