Back to course

Modules (import/export) & Dynamic Imports

Content Reader1 words • 0:00 • Browser TTS

image.png

Modules (import/export) & Dynamic Imports – Organizing Code Architecture

Imagine you're an architect designing a modern smart city 🏙️. In the old days, cities were chaotic:

  • Everything in one giant space 🏗️ - All buildings, roads, utilities crammed together
  • No organization 📍 - Residential, commercial, and industrial areas mixed randomly
  • Resource conflicts ⚡ - Power grids, water systems, and communication networks interfering with each other
  • Impossible to maintain 🔧 - Any change required rebuilding entire sections
  • No privacy or security 🔓 - No boundaries between different areas and functions

Then you discovered modern urban planning principles with:

  1. Zoned districts 🏘️ - Each area has a specific purpose and responsibility
  2. Clean interfaces 🛤️ - Well-defined roads and connections between districts
  3. Resource management 💧 - Each zone manages its own utilities and exports what others need
  4. Load balancing 🚛 - Resources are imported only when and where needed
  5. Security boundaries 🔒 - Each district can protect its internal operations
  6. Scalable growth 📈 - New districts can be added without disrupting existing ones

JavaScript modules work exactly like this modern city planning system. They replaced the chaotic "everything-in-global-scope" approach with a sophisticated system for organizing code into discrete, reusable, and maintainable units.

Understanding modules is crucial for building scalable applications, managing dependencies, optimizing performance, and creating maintainable codebases that can grow and evolve over time.

The Evolution from Chaos to Order 📈

The Dark Ages: Global Scope Chaos 😤

Before modules, JavaScript applications were a nightmare of global pollution:

<!-- The old way: Multiple script tags -->
<script src="utils.js"></script>
<script src="config.js"></script>
<script src="api.js"></script>
<script src="ui.js"></script>
<script src="app.js"></script>
// utils.js - Everything goes into global scope
var API_URL = "https://api.example.com";
var currentUser = null;

function formatDate(date) {
  return date.toLocaleDateString();
}

function validateEmail(email) {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}

// config.js - Accidentally overwrites API_URL from utils.js!
var API_URL = "https://different-api.com"; // Conflict!
var theme = "dark";

// api.js - Depends on global variables
function fetchUser(id) {
  // Hopes that API_URL exists and hasn't been overwritten
  return fetch(API_URL + "/users/" + id);
}

// ui.js - More global pollution
var currentTheme = "light"; // Conflicts with theme variable!

function updateTheme(newTheme) {
  // Accidentally creates global variable
  themeColor = newTheme === "dark" ? "#333" : "#fff";
}

// app.js - Fragile dependencies on global state
function initApp() {
  // Hopes all global variables are available and correct
  if (typeof formatDate === "undefined") {
    console.error("Utils not loaded!");
    return;
  }
  
  // Vulnerable to any script that modifies globals
  fetchUser(123);
}

Problems with the global approach:

  • Naming conflicts: Variables accidentally overwrite each other
  • Dependency hell: No clear way to manage what depends on what
  • Load order matters: Scripts must be loaded in exact sequence
  • No encapsulation: All code is exposed and vulnerable
  • Hard to test: Everything is interconnected
  • Performance issues: All code loads upfront, even if unused

Modern ES6 Modules: Clean Architecture ✨

// utils.js - Clean exports with encapsulation
const API_CONFIG = {
  baseUrl: "https://api.example.com",
  timeout: 5000
};

export function formatDate(date) {
  return date.toLocaleDateString();
}

export function validateEmail(email) {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}

export { API_CONFIG };

// Or use default export
export default class DateFormatter {
  static format(date, options = {}) {
    return new Intl.DateTimeFormat('en-US', options).format(date);
  }
}
// api.js - Clear dependencies through imports
import { API_CONFIG } from './utils.js';

export class UserAPI {
  static async fetchUser(id) {
    const response = await fetch(`${API_CONFIG.baseUrl}/users/${id}`);
    return response.json();
  }
  
  static async updateUser(id, data) {
    const response = await fetch(`${API_CONFIG.baseUrl}/users/${id}`, {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(data)
    });
    return response.json();
  }
}
// app.js - Clean imports with clear dependencies
import { formatDate, validateEmail } from './utils.js';
import DateFormatter from './utils.js'; // Default import
import { UserAPI } from './api.js';

export class App {
  async initialize() {
    try {
      const user = await UserAPI.fetchUser(123);
      console.log(`User joined: ${formatDate(user.joinDate)}`);
      console.log(`Email valid: ${validateEmail(user.email)}`);
    } catch (error) {
      console.error('Failed to initialize app:', error);
    }
  }
}

Benefits of the module approach:

  • Encapsulation: Each module has its own scope
  • Explicit dependencies: Clear imports/exports
  • Reusability: Modules can be used across projects
  • Testability: Each module can be tested in isolation
  • Performance: Code splitting and lazy loading
  • Maintainability: Changes are localized to modules

ES6 Module Fundamentals 📝

Export Syntax and Patterns 💡

Named Exports - Multiple exports per module

// math.js - Multiple named exports
export const PI = 3.14159;
export const E = 2.71828;

export function add(a, b) {
  return a + b;
}

export function multiply(a, b) {
  return a * b;
}

export class Calculator {
  static divide(a, b) {
    if (b === 0) throw new Error("Division by zero");
    return a / b;
  }
}

// Alternative: Export at the end
const GOLDEN_RATIO = 1.618;
function subtract(a, b) {
  return a - b;
}

export { GOLDEN_RATIO, subtract };

// Renaming exports
const internalFunction = () => "secret";
export { internalFunction as publicFunction };

Default Exports - One primary export per module

// logger.js - Default export
export default class Logger {
  constructor(name) {
    this.name = name;
  }
  
  log(message) {
    console.log(`[${this.name}] ${new Date().toISOString()}: ${message}`);
  }
  
  error(message) {
    console.error(`[${this.name}] ERROR: ${message}`);
  }
}

// Or export default directly
export default function createUniqueId() {
  return `id-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}

// Mixed exports (default + named)
// theme.js
const defaultTheme = {
  colors: { primary: '#007bff', secondary: '#6c757d' },
  spacing: { sm: '8px', md: '16px', lg: '24px' }
};

export const darkTheme = {
  colors: { primary: '#0d6efd', secondary: '#495057' },
  spacing: { sm: '8px', md: '16px', lg: '24px' }
};

export function createCustomTheme(overrides) {
  return { ...defaultTheme, ...overrides };
}

export default defaultTheme;

Import Syntax and Patterns 📥

Named Imports

// Import specific named exports
import { add, multiply, Calculator } from './math.js';

console.log(add(5, 3)); // 8
console.log(multiply(4, 2)); // 8
const result = Calculator.divide(10, 2); // 5

// Import with renaming
import { add as sum, multiply as product } from './math.js';

console.log(sum(1, 2)); // 3
console.log(product(3, 4)); // 12

// Import all named exports
import * as MathUtils from './math.js';

console.log(MathUtils.PI); // 3.14159
console.log(MathUtils.add(1, 2)); // 3
const calc = new MathUtils.Calculator();

Default Imports

// Import default export (name is up to you)
import Logger from './logger.js';
import createId from './utils.js';

const logger = new Logger('App');
logger.log('Application started');

const uniqueId = createId();
console.log(uniqueId);

// Mixed imports (default + named)
import defaultTheme, { darkTheme, createCustomTheme } from './theme.js';

console.log(defaultTheme.colors.primary); // '#007bff'
console.log(darkTheme.colors.primary);    // '#0d6efd'

const customTheme = createCustomTheme({
  colors: { primary: '#ff6b35' }
});

Re-exports and Module Aggregation

// index.js - Barrel export pattern
export { formatDate, validateEmail } from './utils.js';
export { UserAPI } from './api.js';
export { default as Logger } from './logger.js';
export { default as defaultTheme, darkTheme } from './theme.js';

// Create clean public API
// components/index.js
export { Button } from './Button.js';
export { Input } from './Input.js';
export { Modal } from './Modal.js';
export { default as Card } from './Card.js';

// Usage becomes cleaner
import { Button, Input, Modal, Card } from './components/index.js';
// Instead of multiple imports

Advanced Module Patterns 🚀

Conditional Exports

// config.js - Environment-based exports
const isDevelopment = process.env.NODE_ENV === 'development';

const baseConfig = {
  api: {
    timeout: 5000,
    retries: 3
  }
};

const developmentConfig = {
  ...baseConfig,
  api: {
    ...baseConfig.api,
    baseUrl: 'http://localhost:3000',
    debug: true
  },
  logging: {
    level: 'debug',
    enableConsole: true
  }
};

const productionConfig = {
  ...baseConfig,
  api: {
    ...baseConfig.api,
    baseUrl: 'https://api.production.com',
    debug: false
  },
  logging: {
    level: 'error',
    enableConsole: false
  }
};

export default isDevelopment ? developmentConfig : productionConfig;

// Export configuration utilities
export function getApiUrl() {
  return isDevelopment ? 'http://localhost:3000' : 'https://api.production.com';
}

export function isDebugMode() {
  return isDevelopment;
}

Factory Pattern with Modules

// database.js - Database factory
class DatabaseConnection {
  constructor(config) {
    this.config = config;
    this.connected = false;
  }
  
  async connect() {
    // Simulate connection
    console.log(`Connecting to ${this.config.host}:${this.config.port}`);
    this.connected = true;
  }
  
  async query(sql, params = []) {
    if (!this.connected) throw new Error('Not connected to database');
    console.log(`Executing: ${sql}`, params);
    return { rows: [], affectedRows: 0 };
  }
}

// Factory function
export function createConnection(type, config) {
  const connections = {
    mysql: () => new DatabaseConnection({ ...config, driver: 'mysql' }),
    postgres: () => new DatabaseConnection({ ...config, driver: 'postgres' }),
    sqlite: () => new DatabaseConnection({ ...config, driver: 'sqlite' })
  };
  
  const factory = connections[type];
  if (!factory) {
    throw new Error(`Unknown database type: ${type}`);
  }
  
  return factory();
}

// Usage
import { createConnection } from './database.js';

const db = createConnection('postgres', {
  host: 'localhost',
  port: 5432,
  database: 'myapp'
});

Plugin System with Modules

// plugin-system.js
class PluginManager {
  constructor() {
    this.plugins = new Map();
    this.hooks = new Map();
  }
  
  register(name, plugin) {
    if (typeof plugin.install !== 'function') {
      throw new Error('Plugin must have install method');
    }
    
    this.plugins.set(name, plugin);
    plugin.install(this);
  }
  
  addHook(name, callback) {
    if (!this.hooks.has(name)) {
      this.hooks.set(name, []);
    }
    this.hooks.get(name).push(callback);
  }
  
  async runHook(name, data) {
    const callbacks = this.hooks.get(name) || [];
    let result = data;
    
    for (let callback of callbacks) {
      result = await callback(result);
    }
    
    return result;
  }
}

export default new PluginManager();

// analytics-plugin.js
export default {
  name: 'analytics',
  
  install(pluginManager) {
    pluginManager.addHook('user-action', this.trackUserAction);
    pluginManager.addHook('page-view', this.trackPageView);
  },
  
  trackUserAction(data) {
    console.log('Analytics: User action', data);
    return data;
  },
  
  trackPageView(data) {
    console.log('Analytics: Page view', data);
    return data;
  }
};

// app.js
import pluginManager from './plugin-system.js';
import analyticsPlugin from './analytics-plugin.js';

pluginManager.register('analytics', analyticsPlugin);

// Usage
await pluginManager.runHook('user-action', { action: 'click', target: 'button' });

Dynamic Imports – Runtime Module Loading 🔄

Understanding Dynamic Imports 💡

What are dynamic imports? They allow you to import modules at runtime using the import() expression, which returns a Promise that resolves to the module.

Mental Model: Think of dynamic imports like on-demand delivery services - instead of ordering everything upfront, you call for what you need exactly when you need it.

Basic Dynamic Import Syntax 📝

// Static imports (load at compile time)
import { utils } from './utils.js'; // Always loaded

// Dynamic imports (load at runtime)
async function loadUtils() {
  try {
    const { utils } = await import('./utils.js');
    return utils;
  } catch (error) {
    console.error('Failed to load utils:', error);
  }
}

// Or using .then()
import('./utils.js')
  .then(module => {
    console.log('Utils loaded:', module);
  })
  .catch(error => {
    console.error('Failed to load utils:', error);
  });

Code Splitting and Lazy Loading 🎯

Application 1: Route-Based Code Splitting

// router.js - Load route components on demand
class Router {
  constructor() {
    this.routes = new Map();
    this.currentComponent = null;
  }
  
  addRoute(path, importFunction) {
    this.routes.set(path, importFunction);
  }
  
  async navigate(path) {
    const importFunction = this.routes.get(path);
    if (!importFunction) {
      console.error(`Route not found: ${path}`);
      return;
    }
    
    try {
      // Show loading indicator
      this.showLoading();
      
      // Dynamically import the component
      const module = await importFunction();
      const Component = module.default || module;
      
      // Clean up previous component
      if (this.currentComponent && this.currentComponent.cleanup) {
        this.currentComponent.cleanup();
      }
      
      // Initialize new component
      this.currentComponent = new Component();
      await this.currentComponent.render();
      
      this.hideLoading();
    } catch (error) {
      console.error(`Failed to load route ${path}:`, error);
      this.showError('Failed to load page');
    }
  }
  
  showLoading() {
    document.body.innerHTML = '<div>Loading...</div>';
  }
  
  hideLoading() {
    // Loading is replaced by component render
  }
  
  showError(message) {
    document.body.innerHTML = `<div>Error: ${message}</div>`;
  }
}

// Setup routes with dynamic imports
const router = new Router();

router.addRoute('/', () => import('./pages/Home.js'));
router.addRoute('/about', () => import('./pages/About.js'));
router.addRoute('/contact', () => import('./pages/Contact.js'));
router.addRoute('/dashboard', () => import('./pages/Dashboard.js'));

// Usage
router.navigate('/dashboard'); // Only loads dashboard code when needed

Application 2: Feature-Based Loading

// feature-loader.js - Load features on demand
class FeatureLoader {
  constructor() {
    this.loadedFeatures = new Map();
    this.loadingPromises = new Map();
  }
  
  async loadFeature(featureName) {
    // Return already loaded feature
    if (this.loadedFeatures.has(featureName)) {
      return this.loadedFeatures.get(featureName);
    }
    
    // Return existing loading promise
    if (this.loadingPromises.has(featureName)) {
      return this.loadingPromises.get(featureName);
    }
    
    // Start loading
    const loadingPromise = this.importFeature(featureName);
    this.loadingPromises.set(featureName, loadingPromise);
    
    try {
      const feature = await loadingPromise;
      this.loadedFeatures.set(featureName, feature);
      this.loadingPromises.delete(featureName);
      return feature;
    } catch (error) {
      this.loadingPromises.delete(featureName);
      throw error;
    }
  }
  
  async importFeature(featureName) {
    const featureMap = {
      'advanced-charts': () => import('./features/advanced-charts.js'),
      'pdf-export': () => import('./features/pdf-export.js'),
      'real-time-chat': () => import('./features/real-time-chat.js'),
      'video-player': () => import('./features/video-player.js'),
      'image-editor': () => import('./features/image-editor.js')
    };
    
    const importFunction = featureMap[featureName];
    if (!importFunction) {
      throw new Error(`Unknown feature: ${featureName}`);
    }
    
    const module = await importFunction();
    return module.default || module;
  }
  
  async enableFeature(featureName, config = {}) {
    try {
      const Feature = await this.loadFeature(featureName);
      const instance = new Feature(config);
      
      if (instance.initialize) {
        await instance.initialize();
      }
      
      return instance;
    } catch (error) {
      console.error(`Failed to enable feature ${featureName}:`, error);
      throw error;
    }
  }
  
  isFeatureLoaded(featureName) {
    return this.loadedFeatures.has(featureName);
  }
  
  getLoadedFeatures() {
    return [...this.loadedFeatures.keys()];
  }
}

// Usage example
const featureLoader = new FeatureLoader();

// Button click handler
async function handleAdvancedChartRequest() {
  try {
    const chartFeature = await featureLoader.enableFeature('advanced-charts', {
      theme: 'dark',
      animations: true
    });
    
    chartFeature.createChart('sales-data', {
      type: 'line',
      data: [1, 2, 3, 4, 5]
    });
  } catch (error) {
    console.error('Failed to load charts:', error);
  }
}

Application 3: Conditional Loading Based on User Preferences

// preference-loader.js - Load modules based on user settings
class PreferenceBasedLoader {
  constructor(userPreferences) {
    this.preferences = userPreferences;
    this.loadedModules = new Map();
  }
  
  async loadForUser() {
    const loadingTasks = [];
    
    // Load theme based on preference
    if (this.preferences.theme === 'dark') {
      loadingTasks.push(this.loadDarkTheme());
    }
    
    // Load accessibility features if needed
    if (this.preferences.accessibility.screenReader) {
      loadingTasks.push(this.loadScreenReaderSupport());
    }
    
    if (this.preferences.accessibility.highContrast) {
      loadingTasks.push(this.loadHighContrastMode());
    }
    
    // Load language pack
    if (this.preferences.language !== 'en') {
      loadingTasks.push(this.loadLanguagePack(this.preferences.language));
    }
    
    // Load advanced features for premium users
    if (this.preferences.subscription === 'premium') {
      loadingTasks.push(this.loadPremiumFeatures());
    }
    
    try {
      await Promise.all(loadingTasks);
      console.log('User preferences loaded successfully');
    } catch (error) {
      console.error('Some preferences failed to load:', error);
    }
  }
  
  async loadDarkTheme() {
    const module = await import('./themes/dark-theme.js');
    this.loadedModules.set('dark-theme', module.default);
    module.default.apply();
  }
  
  async loadScreenReaderSupport() {
    const module = await import('./accessibility/screen-reader.js');
    this.loadedModules.set('screen-reader', module.default);
    module.default.initialize();
  }
  
  async loadHighContrastMode() {
    const module = await import('./accessibility/high-contrast.js');
    this.loadedModules.set('high-contrast', module.default);
    module.default.enable();
  }
  
  async loadLanguagePack(language) {
    const module = await import(`./i18n/${language}.js`);
    this.loadedModules.set(`lang-${language}`, module.default);
    // Apply translations
    window.i18n = module.default;
  }
  
  async loadPremiumFeatures() {
    const [analytics, advancedReports, collaboration] = await Promise.all([
      import('./premium/analytics.js'),
      import('./premium/advanced-reports.js'),
      import('./premium/collaboration.js')
    ]);
    
    this.loadedModules.set('analytics', analytics.default);
    this.loadedModules.set('advanced-reports', advancedReports.default);
    this.loadedModules.set('collaboration', collaboration.default);
    
    // Initialize premium features
    analytics.default.initialize();
    advancedReports.default.setup();
    collaboration.default.connect();
  }
}

// Usage
const userPreferences = {
  theme: 'dark',
  language: 'es',
  accessibility: {
    screenReader: true,
    highContrast: false
  },
  subscription: 'premium'
};

const loader = new PreferenceBasedLoader(userPreferences);
await loader.loadForUser();

Advanced Dynamic Import Patterns 🎨

Pattern 1: Progressive Enhancement

// progressive-enhancement.js
class ProgressiveEnhancer {
  constructor() {
    this.enhancements = new Map();
  }
  
  async enhanceBasedOnCapabilities() {
    // Check device capabilities and load appropriate enhancements
    
    // Enhanced animations for high-performance devices
    if (this.isHighPerformanceDevice()) {
      await this.loadEnhancement('smooth-animations');
    }
    
    // Offline support for PWA-capable browsers
    if ('serviceWorker' in navigator) {
      await this.loadEnhancement('offline-support');
    }
    
    // WebRTC for modern browsers
    if (this.supportsWebRTC()) {
      await this.loadEnhancement('video-chat');
    }
    
    // WebGL for supported browsers
    if (this.supportsWebGL()) {
      await this.loadEnhancement('3d-visualizations');
    }
    
    // Touch gestures for touch devices
    if ('ontouchstart' in window) {
      await this.loadEnhancement('touch-gestures');
    }
  }
  
  async loadEnhancement(name) {
    try {
      const enhancementMap = {
        'smooth-animations': () => import('./enhancements/smooth-animations.js'),
        'offline-support': () => import('./enhancements/offline-support.js'),
        'video-chat': () => import('./enhancements/video-chat.js'),
        '3d-visualizations': () => import('./enhancements/3d-visualizations.js'),
        'touch-gestures': () => import('./enhancements/touch-gestures.js')
      };
      
      const module = await enhancementMap[name]();
      this.enhancements.set(name, module.default);
      
      if (module.default.initialize) {
        await module.default.initialize();
      }
      
      console.log(`Enhancement loaded: ${name}`);
    } catch (error) {
      console.warn(`Failed to load enhancement ${name}:`, error);
    }
  }
  
  isHighPerformanceDevice() {
    // Simple heuristics for device performance
    return navigator.hardwareConcurrency >= 4 && 
           navigator.deviceMemory >= 4;
  }
  
  supportsWebRTC() {
    return !!(navigator.getUserMedia || 
              navigator.webkitGetUserMedia || 
              navigator.mozGetUserMedia ||
              (navigator.mediaDevices && navigator.mediaDevices.getUserMedia));
  }
  
  supportsWebGL() {
    const canvas = document.createElement('canvas');
    return !!(canvas.getContext('webgl') || canvas.getContext('experimental-webgl'));
  }
}

// Initialize progressive enhancement
const enhancer = new ProgressiveEnhancer();
enhancer.enhanceBasedOnCapabilities();

Pattern 2: Error Recovery and Fallbacks

// robust-loader.js
class RobustModuleLoader {
  constructor() {
    this.cache = new Map();
    this.fallbacks = new Map();
    this.retryAttempts = new Map();
  }
  
  async loadWithFallback(primary, fallback, options = {}) {
    const { maxRetries = 3, retryDelay = 1000 } = options;
    
    try {
      return await this.loadModule(primary, maxRetries, retryDelay);
    } catch (primaryError) {
      console.warn(`Primary module ${primary} failed, trying fallback:`, primaryError);
      
      if (fallback) {
        try {
          return await this.loadModule(fallback, maxRetries, retryDelay);
        } catch (fallbackError) {
          console.error(`Fallback ${fallback} also failed:`, fallbackError);
          throw new Error(`Both primary (${primary}) and fallback (${fallback}) modules failed to load`);
        }
      } else {
        throw primaryError;
      }
    }
  }
  
  async loadModule(modulePath, maxRetries, retryDelay) {
    // Check cache first
    if (this.cache.has(modulePath)) {
      return this.cache.get(modulePath);
    }
    
    let attempt = 0;
    const maxAttempts = maxRetries + 1;
    
    while (attempt < maxAttempts) {
      try {
        const module = await import(modulePath);
        this.cache.set(modulePath, module);
        this.retryAttempts.delete(modulePath);
        return module;
      } catch (error) {
        attempt++;
        this.retryAttempts.set(modulePath, attempt);
        
        if (attempt >= maxAttempts) {
          throw error;
        }
        
        console.warn(`Attempt ${attempt} failed for ${modulePath}, retrying in ${retryDelay}ms`);
        await this.delay(retryDelay);
      }
    }
  }
  
  async loadCriticalModule(modulePath, alternatives = []) {
    const allPaths = [modulePath, ...alternatives];
    
    for (let i = 0; i < allPaths.length; i++) {
      try {
        return await this.loadModule(allPaths[i], 2, 500);
      } catch (error) {
        console.warn(`Critical module attempt ${i + 1} failed:`, error);
        
        if (i === allPaths.length - 1) {
          // Last attempt failed
          throw new Error(`All critical module alternatives failed: ${allPaths.join(', ')}`);
        }
      }
    }
  }
  
  delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
  
  getLoadingStats() {
    return {
      cachedModules: this.cache.size,
      retryAttempts: Object.fromEntries(this.retryAttempts)
    };
  }
}

// Usage
const loader = new RobustModuleLoader();

// Load with fallback
try {
  const module = await loader.loadWithFallback(
    './modules/advanced-feature.js',
    './modules/basic-feature.js',
    { maxRetries: 2, retryDelay: 1500 }
  );
} catch (error) {
  console.error('Both modules failed to load:', error);
}

// Load critical module with multiple alternatives
try {
  const criticalModule = await loader.loadCriticalModule(
    './core/essential-feature.js',
    [
      './core/essential-feature-v2.js',
      './core/essential-feature-legacy.js',
      './fallbacks/minimal-feature.js'
    ]
  );
} catch (error) {
  console.error('Critical module loading failed:', error);
}

Performance Optimization with Modules 📊

Bundle Splitting Strategies 🎯

// webpack-like bundle splitting simulation
class BundleManager {
  constructor() {
    this.loadedChunks = new Set();
    this.chunkManifest = {
      vendor: ['./vendor/react.js', './vendor/lodash.js'],
      common: ['./utils/common.js', './utils/helpers.js'],
      home: ['./pages/Home.js', './components/HomeSpecific.js'],
      dashboard: ['./pages/Dashboard.js', './components/Charts.js'],
      settings: ['./pages/Settings.js', './components/SettingsForm.js']
    };
  }
  
  async loadChunk(chunkName) {
    if (this.loadedChunks.has(chunkName)) {
      console.log(`Chunk ${chunkName} already loaded`);
      return;
    }
    
    const modules = this.chunkManifest[chunkName];
    if (!modules) {
      throw new Error(`Unknown chunk: ${chunkName}`);
    }
    
    console.log(`Loading chunk: ${chunkName}`);
    const startTime = performance.now();
    
    try {
      // Load all modules in the chunk
      const loadPromises = modules.map(modulePath => 
        import(modulePath).catch(error => {
          console.error(`Failed to load module ${modulePath}:`, error);
          throw error;
        })
      );
      
      await Promise.all(loadPromises);
      
      this.loadedChunks.add(chunkName);
      const loadTime = performance.now() - startTime;
      console.log(`Chunk ${chunkName} loaded in ${loadTime.toFixed(2)}ms`);
      
    } catch (error) {
      console.error(`Failed to load chunk ${chunkName}:`, error);
      throw error;
    }
  }
  
  async preloadChunk(chunkName) {
    // Preload with low priority (use requestIdleCallback if available)
    if (window.requestIdleCallback) {
      window.requestIdleCallback(() => {
        this.loadChunk(chunkName).catch(error => {
          console.warn(`Preload failed for chunk ${chunkName}:`, error);
        });
      });
    } else {
      // Fallback for browsers without requestIdleCallback
      setTimeout(() => {
        this.loadChunk(chunkName).catch(error => {
          console.warn(`Preload failed for chunk ${chunkName}:`, error);
        });
      }, 0);
    }
  }
  
  async loadRoute(routeName) {
    // Load vendor chunk first (shared dependencies)
    await this.loadChunk('vendor');
    
    // Load common utilities
    await this.loadChunk('common');
    
    // Load route-specific chunk
    await this.loadChunk(routeName);
  }
  
  getLoadingStats() {
    return {
      loadedChunks: [...this.loadedChunks],
      totalChunks: Object.keys(this.chunkManifest).length,
      loadedPercentage: (this.loadedChunks.size / Object.keys(this.chunkManifest).length * 100).toFixed(1) + '%'
    };
  }
}

// Usage
const bundleManager = new BundleManager();

// Load route with dependencies
await bundleManager.loadRoute('dashboard');

// Preload likely next routes
bundleManager.preloadChunk('settings');

console.log(bundleManager.getLoadingStats());

Module Caching and Invalidation 🔄

// module-cache.js
class ModuleCacheManager {
  constructor() {
    this.cache = new Map();
    this.versions = new Map();
    this.loadTimes = new Map();
  }
  
  async loadModuleWithCache(modulePath, options = {}) {
    const { 
      forceReload = false, 
      version = null,
      maxAge = Infinity 
    } = options;
    
    const cacheKey = this.getCacheKey(modulePath, version);
    
    // Check if cached version is still valid
    if (!forceReload && this.isCacheValid(cacheKey, maxAge)) {
      console.log(`Using cached module: ${modulePath}`);
      return this.cache.get(cacheKey);
    }
    
    // Load fresh module
    console.log(`Loading fresh module: ${modulePath}`);
    const startTime = performance.now();
    
    try {
      // Add cache-busting parameter if version specified
      const moduleUrl = version ? `${modulePath}?v=${version}` : modulePath;
      const module = await import(moduleUrl);
      
      const loadTime = performance.now() - startTime;
      
      // Cache the module
      this.cache.set(cacheKey, module);
      this.versions.set(modulePath, version);
      this.loadTimes.set(cacheKey, Date.now());
      
      console.log(`Module ${modulePath} loaded in ${loadTime.toFixed(2)}ms`);
      return module;
      
    } catch (error) {
      console.error(`Failed to load module ${modulePath}:`, error);
      throw error;
    }
  }
  
  getCacheKey(modulePath, version) {
    return version ? `${modulePath}@${version}` : modulePath;
  }
  
  isCacheValid(cacheKey, maxAge) {
    if (!this.cache.has(cacheKey)) return false;
    
    const loadTime = this.loadTimes.get(cacheKey);
    const age = Date.now() - loadTime;
    
    return age <= maxAge;
  }
  
  invalidateModule(modulePath) {
    // Remove all versions of this module from cache
    const keysToDelete = [];
    for (let key of this.cache.keys()) {
      if (key.startsWith(modulePath)) {
        keysToDelete.push(key);
      }
    }
    
    keysToDelete.forEach(key => {
      this.cache.delete(key);
      this.loadTimes.delete(key);
    });
    
    this.versions.delete(modulePath);
    console.log(`Invalidated cache for: ${modulePath}`);
  }
  
  clearCache() {
    this.cache.clear();
    this.versions.clear();
    this.loadTimes.clear();
    console.log('Module cache cleared');
  }
  
  getCacheStats() {
    return {
      cachedModules: this.cache.size,
      totalSize: this.estimateCacheSize(),
      oldestEntry: this.getOldestEntry(),
      newestEntry: this.getNewestEntry()
    };
  }
  
  estimateCacheSize() {
    // Rough estimation of cache size
    return this.cache.size * 50; // Assume 50KB per module (rough estimate)
  }
  
  getOldestEntry() {
    let oldest = Infinity;
    for (let time of this.loadTimes.values()) {
      if (time < oldest) oldest = time;
    }
    return oldest === Infinity ? null : new Date(oldest);
  }
  
  getNewestEntry() {
    let newest = 0;
    for (let time of this.loadTimes.values()) {
      if (time > newest) newest = time;
    }
    return newest === 0 ? null : new Date(newest);
  }
}

// Usage
const cacheManager = new ModuleCacheManager();

// Load with caching
const module1 = await cacheManager.loadModuleWithCache('./utils.js', {
  version: '1.2.0',
  maxAge: 5 * 60 * 1000 // 5 minutes
});

// This will use cached version
const module2 = await cacheManager.loadModuleWithCache('./utils.js', {
  version: '1.2.0'
});

// Force reload
const module3 = await cacheManager.loadModuleWithCache('./utils.js', {
  version: '1.3.0',
  forceReload: true
});

console.log(cacheManager.getCacheStats());

Summary

Core Concepts

  • ES6 Modules: Native module system with import/export syntax
  • Static imports: Compile-time imports with dependency analysis
  • Dynamic imports: Runtime imports returning Promises
  • Code splitting: Loading code only when needed

Key Benefits

  • Encapsulation: Each module has its own scope
  • Dependency management: Clear import/export relationships
  • Performance: Load only needed code (lazy loading)
  • Maintainability: Modular, testable, reusable code
  • Tree shaking: Remove unused code in build process

Import/Export Patterns

  • Named exports: Multiple exports per module
  • Default exports: One primary export per module
  • Re-exports: Aggregate modules for clean APIs
  • Conditional exports: Environment-based module loading

Dynamic Import Use Cases

  • Route-based splitting: Load pages on navigation
  • Feature flags: Load features based on user permissions
  • Progressive enhancement: Load based on device capabilities
  • Error recovery: Fallback modules for failed loads

Performance Strategies

  • Bundle splitting: Separate vendor, common, and feature code
  • Preloading: Load likely-needed modules in background
  • Caching: Avoid reloading modules unnecessarily
  • Lazy loading: Load on user interaction or visibility

Best Practices

  • Clear naming: Use descriptive import/export names
  • Barrel exports: Create clean public APIs with index files
  • Error handling: Graceful fallbacks for dynamic imports
  • Performance monitoring: Track loading times and cache efficiency

Common Pitfalls to Avoid

  • Circular dependencies: Modules importing each other
  • Over-splitting: Too many small modules hurting performance
  • Missing error handling: Dynamic imports can fail
  • Cache invalidation: Stale modules in development

My Personal Insight

The module system completely transformed how I structure JavaScript applications. The shift from global chaos to organized, encapsulated modules was like moving from a messy shared workspace to a well-organized office building.

Dynamic imports were the real game-changer - the ability to load code on-demand opened up architectural patterns I never considered before. Code splitting, lazy loading, and progressive enhancement became natural parts of my development process.

The key insight is that modules aren't just about organization - they're about performance, maintainability, and creating scalable architectures that can grow with your application's needs.

Module 7 Complete! 🎉

Congratulations! You've mastered Module 7: Modern JavaScript (ES6+) covering:

  • ✅ Destructuring & Spread/Rest Operators
  • ✅ Template Literals & Tagged Templates
  • ✅ Symbols, Sets, Maps & WeakMaps
  • ✅ Iterators & Generators
  • ✅ Modules (import/export) & Dynamic Imports

Next Up: Module 8 - Browser APIs & Web Platform 🌐

Now we'll dive into Browser APIs & Web Platform to master DOM manipulation, event handling, network requests, storage, and Progressive Web App capabilities.

Remember: Modules aren't just syntax features - they're the foundation of modern JavaScript architecture that enables scalable, maintainable, and performant applications! 🚀✨

Photo of Rahul Aher

Written by Rahul Aher

I'm Rahul, Sr. Software Engineer (SDE II) and passionate content creator. Sharing my expertise in software development to assist learners.

More about me