15. Modules

Module Systems

CommonJS (Node.js)

// math.js
function add(a, b) {
    return a + b;
}

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

module.exports = { add, multiply };

// app.js
const math = require('./math');
console.log(math.add(5, 3)); // 8

AMD (Asynchronous Module Definition)

// math.js
define([], function() {
    function add(a, b) {
        return a + b;
    }

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

    return { add, multiply };
});

// app.js
require(['math'], function(math) {
    console.log(math.add(5, 3));
});

ES6 Modules

Exporting

// math.js
export function add(a, b) {
    return a + b;
}

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

export const PI = 3.14159;

export default function subtract(a, b) {
    return a - b;
}

// Named exports
export { add, multiply, PI };

Importing

// app.js
// Named imports
import { add, multiply, PI } from './math.js';
console.log(add(5, 3));     // 8
console.log(multiply(4, 2)); // 8
console.log(PI);            // 3.14159

// Default import
import subtract from './math.js';
console.log(subtract(10, 3)); // 7

// Import all
import * as math from './math.js';
console.log(math.add(5, 3));     // 8
console.log(math.default(10, 3)); // 7 (default export)

// Dynamic import (ES11+)
import('./math.js').then(module => {
    console.log(module.add(5, 3));
});

Module Features

Strict Mode

// Modules are automatically in strict mode
// This means:
// - Variables must be declared
// - Functions can't be called before declaration
// - 'this' is undefined in global scope
// - etc.

Top-Level Await (ES13+)

// module.js
const response = await fetch('/api/data');
export const data = await response.json();

// app.js
import { data } from './module.js';
console.log(data); // Data is already loaded

Import Attributes (ES2025) and JSON Modules

// JSON Modules are standardized; use import attributes with `with`
import config from './config.json' with { type: 'json' };
console.log(config.name);

// Dynamic import with attributes
const mod = await import('./data.json', { with: { type: 'json' } });

Module Patterns

Revealing Module Pattern

// calculator.js
const calculator = (function() {
    function add(a, b) {
        return a + b;
    }

    function subtract(a, b) {
        return a - b;
    }

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

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

    // Reveal only the public API
    return {
        add,
        subtract,
        multiply,
        divide
    };
})();

export default calculator;

Factory Pattern

// logger.js
export function createLogger(prefix = '') {
    return {
        info(message) {
            console.log(`${prefix}[INFO] ${message}`);
        },
        warn(message) {
            console.warn(`${prefix}[WARN] ${message}`);
        },
        error(message) {
            console.error(`${prefix}[ERROR] ${message}`);
        }
    };
}

// app.js
import { createLogger } from './logger.js';

const logger = createLogger('[APP]');
logger.info('Application started');

Circular Dependencies

Avoiding Circular Dependencies

// Bad: Circular dependency
// moduleA.js
import { funcB } from './moduleB.js';
export function funcA() {
    return funcB() + ' from A';
}

// moduleB.js
import { funcA } from './moduleA.js';
export function funcB() {
    return funcA() + ' from B';
}

// Good: Restructure to avoid circular dependency
// moduleA.js
export function funcA() {
    return 'Hello from A';
}

// moduleB.js
import { funcA } from './moduleA.js';
export function funcB() {
    return funcA() + ' and B';
}

Module Bundlers

Webpack Configuration

// webpack.config.js
const path = require('path');

module.exports = {
    entry: './src/index.js',
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, 'dist'),
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-env']
                    }
                }
            }
        ]
    },
    mode: 'development'
};

Rollup Configuration

// rollup.config.js
export default {
    input: 'src/main.js',
    output: {
        file: 'dist/bundle.js',
        format: 'iife',
        name: 'MyApp'
    }
};

Tree Shaking

// utils.js
export function usedFunction() {
    return "I'm used";
}

export function unusedFunction() {
    return "I'm not used";
}

// app.js
import { usedFunction } from './utils.js';
// unusedFunction is not imported, so it can be removed by tree shaking

console.log(usedFunction());

Dynamic Imports

Code Splitting

// app.js
const button = document.getElementById('load-feature');

button.addEventListener('click', async () => {
    try {
        // Dynamically import the feature module
        const { initFeature } = await import('./feature.js');
        initFeature();
    } catch (error) {
        console.error('Failed to load feature:', error);
    }
});

Conditional Loading

// app.js
async function loadModuleBasedOnCondition(condition) {
    if (condition) {
        const { moduleA } = await import('./moduleA.js');
        return moduleA;
    } else {
        const { moduleB } = await import('./moduleB.js');
        return moduleB;
    }
}

Module Resolution

Relative Imports

// From /src/components/Button.js
import utils from '../utils/helpers.js';     // ../utils/helpers.js
import styles from './Button.css';           // ./Button.css

Absolute Imports

// With path mapping in bundler config
import utils from 'utils/helpers';           // resolves to /src/utils/helpers.js
import Button from 'components/Button';      // resolves to /src/components/Button.js

Next Steps

Modules help organize and maintain large codebases. Next, let's explore advanced topics in ECMAScript.