JavaScript Module System

JavaScript Module System

Learning Objectives

  • Understand ES Modules (ESM) import/export syntax
  • Identify differences between CommonJS and ESM
  • Utilize dynamic import and code splitting
  • Understand the role of module bundlers

1. The Need for Modules

1.1 What Are Modules?

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    Module Benefits                               β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                                 β”‚
β”‚  Before (global scope pollution):                               β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚ <script src="lib1.js"></script>  <!-- var helper = ... --> β”‚  β”‚
β”‚  β”‚ <script src="lib2.js"></script>  <!-- var helper = ... --> β”‚  β”‚
β”‚  β”‚ <script src="app.js"></script>   <!-- helper? Which one? -->β”‚  β”‚
β”‚  β”‚                                                            β”‚  β”‚
β”‚  β”‚ Problems: name conflicts, dependency order, global pollutionβ”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                                                                 β”‚
β”‚  After (module system):                                         β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚ // lib1.js                                                 β”‚  β”‚
β”‚  β”‚ export const helper = () => { ... };                       β”‚  β”‚
β”‚  β”‚                                                            β”‚  β”‚
β”‚  β”‚ // app.js                                                  β”‚  β”‚
β”‚  β”‚ import { helper } from './lib1.js';                        β”‚  β”‚
β”‚  β”‚                                                            β”‚  β”‚
β”‚  β”‚ Benefits: clear dependencies, encapsulation, reusability   β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                                                                 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

1.2 Types of Module Systems

// 1. CommonJS (Node.js default)
const fs = require('fs');
module.exports = { myFunction };

// 2. ES Modules (ECMAScript standard, browser + Node.js)
import fs from 'fs';
export const myFunction = () => {};

// 3. AMD (RequireJS, legacy)
define(['dependency'], function(dep) {
    return { myFunction };
});

// 4. UMD (Universal, for compatibility)
(function(root, factory) {
    if (typeof define === 'function' && define.amd) {
        define(['dep'], factory);
    } else if (typeof module === 'object') {
        module.exports = factory(require('dep'));
    } else {
        root.myModule = factory(root.dep);
    }
}(this, function(dep) { ... }));

2. ES Modules (ESM) Basics

2.1 Export Syntax

// ==============================
// math.js - Named Exports
// ==============================

// Individual exports
export const PI = 3.14159;

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

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

export class Calculator {
    add(a, b) { return a + b; }
    subtract(a, b) { return a - b; }
}

// Batch exports
const multiply = (a, b) => a * b;
const divide = (a, b) => a / b;

export { multiply, divide };

// Renamed exports
const internalName = () => 'internal';
export { internalName as publicName };


// ==============================
// utils.js - Default Export
// ==============================

// Default export (one per file)
export default function formatDate(date) {
    return date.toISOString().split('T')[0];
}

// Or
function formatDate(date) {
    return date.toISOString().split('T')[0];
}
export default formatDate;

// Or (anonymous function)
export default function(date) {
    return date.toISOString().split('T')[0];
}


// ==============================
// Mixed Usage (recommended)
// ==============================

// api.js
export const API_URL = 'https://api.example.com';
export const fetchData = async (endpoint) => { /* ... */ };

// Also with default export
export default class ApiClient {
    constructor(baseUrl) {
        this.baseUrl = baseUrl;
    }
}

2.2 Import Syntax

// ==============================
// Named Imports
// ==============================

// Individual imports
import { add, subtract } from './math.js';
console.log(add(1, 2));  // 3

// Renamed imports
import { add as sum } from './math.js';
console.log(sum(1, 2));  // 3

// Namespace import (entire module)
import * as math from './math.js';
console.log(math.add(1, 2));  // 3
console.log(math.PI);         // 3.14159


// ==============================
// Default Imports
// ==============================

// Default import (name is flexible)
import formatDate from './utils.js';
import myFormatter from './utils.js';  // Same thing, different name

console.log(formatDate(new Date()));


// ==============================
// Mixed Imports
// ==============================

// Default + Named together
import ApiClient, { API_URL, fetchData } from './api.js';

// Or
import ApiClient from './api.js';
import { API_URL, fetchData } from './api.js';


// ==============================
// Side Effect Imports
// ==============================

// Execute code only (polyfills, styles, etc.)
import './polyfill.js';
import './styles.css';  // When using bundlers


// ==============================
// Re-exports
// ==============================

// index.js (barrel file)
export { add, subtract } from './math.js';
export { default as formatDate } from './utils.js';
export * from './helpers.js';  // All named exports

2.3 Using in HTML

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>ES Modules</title>
</head>
<body>
    <!-- type="module" required -->
    <script type="module">
        import { add } from './math.js';
        console.log(add(1, 2));
    </script>

    <!-- External module file -->
    <script type="module" src="./app.js"></script>

    <!-- Non-module fallback (legacy browsers) -->
    <script nomodule src="./app-legacy.js"></script>
</body>
</html>

3. Dynamic Import

3.1 Basic Syntax

// Static import (top of file, parse time)
import { add } from './math.js';

// Dynamic import (runtime, returns Promise)
async function loadMath() {
    const math = await import('./math.js');
    console.log(math.add(1, 2));  // 3
}

// Or using then
import('./math.js').then(math => {
    console.log(math.add(1, 2));
});

// Accessing default export
const module = await import('./utils.js');
const formatDate = module.default;

3.2 Conditional Loading

// Module loading based on user permissions
async function loadAdminPanel() {
    if (user.isAdmin) {
        const { AdminPanel } = await import('./admin.js');
        return new AdminPanel();
    }
    return null;
}

// Loading after feature detection
async function loadPolyfill() {
    if (!window.IntersectionObserver) {
        await import('intersection-observer');
    }
}

// Route-based loading
const routes = {
    '/': () => import('./pages/Home.js'),
    '/about': () => import('./pages/About.js'),
    '/contact': () => import('./pages/Contact.js'),
};

async function loadPage(path) {
    const loader = routes[path];
    if (loader) {
        const module = await loader();
        return module.default;
    }
}

3.3 Code Splitting

// Lazy loading in React
import React, { lazy, Suspense } from 'react';

const HeavyComponent = lazy(() => import('./HeavyComponent'));

function App() {
    return (
        <Suspense fallback={<div>Loading...</div>}>
            <HeavyComponent />
        </Suspense>
    );
}

// Async components in Vue
const AsyncComponent = () => ({
    component: import('./AsyncComponent.vue'),
    loading: LoadingComponent,
    error: ErrorComponent,
    delay: 200,
    timeout: 3000
});

// Pure JavaScript
class Router {
    async loadRoute(path) {
        const pageModule = await import(`./pages/${path}.js`);
        const Page = pageModule.default;
        this.render(new Page());
    }
}

4. CommonJS vs ES Modules

4.1 Key Differences

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                CommonJS vs ES Modules Comparison                  β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                                  β”‚
β”‚  Feature       CommonJS              ES Modules                  β”‚
β”‚  ────────────────────────────────────────────────────────────── β”‚
β”‚  Syntax        require/exports        import/export               β”‚
β”‚  Load Time     runtime (sync)         parse time (static analysis)β”‚
β”‚  Default Env   Node.js               browser + Node.js           β”‚
β”‚  Dynamic Load  require() anywhere     import() separate syntax    β”‚
β”‚  Tree Shaking  difficult              possible                    β”‚
β”‚  Circular Refs partial support        better support              β”‚
β”‚  File Ext      .js (default)          .mjs or type="module"       β”‚
β”‚  this value    module.exports         undefined                   β”‚
β”‚                                                                  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

4.2 Code Comparison

// ==============================
// CommonJS
// ==============================

// utils.cjs
const helper = () => 'helper';
const PI = 3.14159;

module.exports = { helper, PI };
// Or
exports.helper = helper;
exports.PI = PI;

// app.cjs
const { helper, PI } = require('./utils.cjs');
const utils = require('./utils.cjs');
console.log(utils.PI);


// ==============================
// ES Modules
// ==============================

// utils.mjs
export const helper = () => 'helper';
export const PI = 3.14159;

// app.mjs
import { helper, PI } from './utils.mjs';
import * as utils from './utils.mjs';
console.log(utils.PI);

4.3 Using ESM in Node.js

// package.json
{
    "name": "my-package",
    "type": "module",  // Use ESM for entire project
    "exports": {
        ".": {
            "import": "./dist/index.mjs",
            "require": "./dist/index.cjs"
        }
    }
}
// Using .mjs extension (regardless of type)
// utils.mjs
export const hello = () => 'Hello';

// app.mjs
import { hello } from './utils.mjs';

// Importing ESM from CommonJS
// (Node.js 14+, without top-level await)
async function main() {
    const { hello } = await import('./utils.mjs');
    console.log(hello());
}
main();

5. Module Patterns

5.1 Barrel Files

// components/index.js (barrel file)
export { default as Button } from './Button.js';
export { default as Input } from './Input.js';
export { default as Modal } from './Modal.js';
export { Card, CardHeader, CardBody } from './Card.js';

// Consumer side
import { Button, Input, Modal, Card } from './components';
// Instead of
// import Button from './components/Button.js';
// import Input from './components/Input.js';
// ...

5.2 Factory Pattern

// logger.js
export function createLogger(prefix) {
    return {
        log: (msg) => console.log(`[${prefix}] ${msg}`),
        error: (msg) => console.error(`[${prefix}] ERROR: ${msg}`),
        warn: (msg) => console.warn(`[${prefix}] WARNING: ${msg}`),
    };
}

// Usage
import { createLogger } from './logger.js';
const logger = createLogger('App');
logger.log('Started');  // [App] Started

5.3 Singleton Pattern

// config.js (module itself is a singleton)
let instance = null;

class Config {
    constructor() {
        if (instance) {
            return instance;
        }
        this.settings = {};
        instance = this;
    }

    set(key, value) {
        this.settings[key] = value;
    }

    get(key) {
        return this.settings[key];
    }
}

export default new Config();

// Usage (always same instance)
import config from './config.js';
config.set('api_url', 'https://api.example.com');

5.4 Plugin Pattern

// core.js
class App {
    constructor() {
        this.plugins = [];
    }

    use(plugin) {
        plugin.install(this);
        this.plugins.push(plugin);
        return this;  // Chaining
    }
}

export default new App();

// plugins/logger.js
export default {
    install(app) {
        app.log = (msg) => console.log(`[App] ${msg}`);
    }
};

// plugins/analytics.js
export default {
    install(app) {
        app.track = (event) => console.log(`Track: ${event}`);
    }
};

// main.js
import app from './core.js';
import logger from './plugins/logger.js';
import analytics from './plugins/analytics.js';

app.use(logger).use(analytics);
app.log('Hello');     // [App] Hello
app.track('pageview'); // Track: pageview

6. Bundlers and Modules

6.1 Role of Bundlers

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    Bundler                                       β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                                 β”‚
β”‚  Input:                       Output:                           β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”                 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚ app.js  β”‚                 β”‚                             β”‚   β”‚
β”‚  β”‚ └─ a.js β”‚   ──────────▢  β”‚ bundle.js (single file)     β”‚   β”‚
β”‚  β”‚ └─ b.js β”‚    Bundler      β”‚                             β”‚   β”‚
β”‚  β”‚ └─ c.js β”‚                 β”‚ + chunk1.js (code splitting)β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                 β”‚ + chunk2.js                 β”‚   β”‚
β”‚                              β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚                                                                 β”‚
β”‚  Key Features:                                                  β”‚
β”‚  - Dependency analysis and resolution                           β”‚
β”‚  - Code transformation (Babel, TypeScript)                      β”‚
β”‚  - Code splitting                                               β”‚
β”‚  - Tree shaking (remove unused code)                            β”‚
β”‚  - Minification                                                 β”‚
β”‚  - Asset handling (CSS, images)                                 β”‚
β”‚                                                                 β”‚
β”‚  Popular Bundlers: Vite, webpack, esbuild, Rollup, Parcel      β”‚
β”‚                                                                 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

6.2 Vite Example

// vite.config.js
import { defineConfig } from 'vite';

export default defineConfig({
    build: {
        rollupOptions: {
            output: {
                // Manual chunk configuration
                manualChunks: {
                    vendor: ['react', 'react-dom'],
                    utils: ['lodash', 'date-fns'],
                },
            },
        },
    },
});

// Dynamic imports automatically split chunks
const AdminPage = () => import('./pages/Admin.js');

6.3 Tree Shaking

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

export function unusedFunction() {  // Removed from bundle
    return 'unused';
}

// app.js
import { usedFunction } from './utils.js';
// unusedFunction is not imported

console.log(usedFunction());

// Bundle result (with tree shaking)
// unusedFunction code is not included

7. Real-World Project Structure

project/
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ index.js          # Entry point
β”‚   β”œβ”€β”€ app.js            # Main app logic
β”‚   β”‚
β”‚   β”œβ”€β”€ components/       # UI components
β”‚   β”‚   β”œβ”€β”€ index.js      # Barrel file
β”‚   β”‚   β”œβ”€β”€ Button.js
β”‚   β”‚   β”œβ”€β”€ Modal.js
β”‚   β”‚   └── Card/
β”‚   β”‚       β”œβ”€β”€ index.js
β”‚   β”‚       β”œβ”€β”€ Card.js
β”‚   β”‚       └── Card.css
β”‚   β”‚
β”‚   β”œβ”€β”€ utils/            # Utility functions
β”‚   β”‚   β”œβ”€β”€ index.js
β”‚   β”‚   β”œβ”€β”€ format.js
β”‚   β”‚   └── validation.js
β”‚   β”‚
β”‚   β”œβ”€β”€ services/         # API calls
β”‚   β”‚   β”œβ”€β”€ index.js
β”‚   β”‚   β”œβ”€β”€ api.js
β”‚   β”‚   └── auth.js
β”‚   β”‚
β”‚   β”œβ”€β”€ store/            # State management
β”‚   β”‚   β”œβ”€β”€ index.js
β”‚   β”‚   └── userStore.js
β”‚   β”‚
β”‚   └── constants/        # Constants
β”‚       └── index.js
β”‚
β”œβ”€β”€ public/
β”‚   └── index.html
β”‚
β”œβ”€β”€ package.json
└── vite.config.js

7.2 Import Path Aliases

// vite.config.js
import { defineConfig } from 'vite';
import path from 'path';

export default defineConfig({
    resolve: {
        alias: {
            '@': path.resolve(__dirname, './src'),
            '@components': path.resolve(__dirname, './src/components'),
            '@utils': path.resolve(__dirname, './src/utils'),
        },
    },
});

// Usage
import { Button } from '@components';
import { formatDate } from '@utils';
// Instead of
// import { Button } from '../../../components';

Summary

ESM Core Syntax

Feature Syntax
Named Export export const foo = ...
Default Export export default ...
Named Import import { foo } from '...'
Default Import import foo from '...'
Namespace Import import * as mod from '...'
Dynamic Import await import('...')
Re-export export { foo } from '...'

Best Practices

  1. Prefer Named Exports: Better IDE autocomplete, easier tree shaking
  2. Use Barrel Files: Clean import paths
  3. Avoid Circular Dependencies: Organize dependency direction
  4. Utilize Dynamic Imports: Lazy load large modules

Next Steps


References

to navigate between lessons