Imagine you're an architect designing a modern smart city 🏙️. In the old days, cities were chaotic:
Then you discovered modern urban planning principles with:
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.
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:
// 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:
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;
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
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' });
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.
// 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);
});
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();
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);
}
// 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-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());
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.
Congratulations! You've mastered Module 7: Modern JavaScript (ES6+) covering:
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! 🚀✨
I'm Rahul, Sr. Software Engineer (SDE II) and passionate content creator. Sharing my expertise in software development to assist learners.
More about me