Frontend Development 14 min read

Evolution of Frontend Module Systems and Major Module Specifications

This article traces the history of frontend modularization from early global‑function scripts through IIFE, CommonJS, ES6 modules, AMD and CMD, explaining why modularization is needed, how each approach works, their drawbacks, and the key features of each specification.

ByteFE
ByteFE
ByteFE
Evolution of Frontend Module Systems and Major Module Specifications

Preface

In ancient times web developers often felt desperate searching a huge JavaScript file for a tiny function or a variable, but modern frontend development relies on bundlers such as webpack, rollup, and Vite, all built around the crucial concept of frontend modularization.

What Is Modularization

Frontend modularization means breaking a large application into small, independent modules, each with its own functionality and interface that other modules can use, improving maintainability, reusability, and code clarity.

Why Frontend Modularization Is Needed

Traditional monolithic scripts suffer from poor maintainability, low reusability, weak testability, and limited extensibility.

Maintainability is bad: large codebases become hard to maintain.

Reusability is bad: duplicated code leads to redundancy.

Testability is bad: unit testing is difficult.

Extensibility is bad: scaling the application is hard.

Evolution of Frontend Modularization

Global Function Pattern

Encapsulate different functionalities into separate functions.
function fetchData() {
    ...
}
function handleData() {
    ...
}

Drawback: functions are attached to the global window object, polluting the namespace and causing naming conflicts.

Global Namespace Pattern

Wrap related functions inside an object to avoid global pollution.

var myModule = {
  fetchData() {
    ...
  },
  handleData() {
    ...
  }
};

Drawback: external code can still modify the module’s internal data.

IIFE Pattern (Immediately‑Invoked Function Expression)

function (global) {
    var data = 1;
    function fetchData() {
        ...
    }
    function handleData() {
        ...
    }
    window.myModule = {fetchData, handleData};
}(window);

Drawback: while data becomes private, the pattern cannot resolve inter‑module dependencies.

IIFE Enhanced with Custom Dependencies

Pass dependencies as arguments to solve module‑to‑module references.

function (global, otherModule) {
    var data = 1;
    function fetchData() {
        ...
    }
    function handleData() {
        ...
    }
    window.myModule = {fetchData, handleData, otherApi: otherModule.api};
}(window, window.other_module);

Remaining drawbacks:

Code becomes hard to read when many dependencies are passed.

It does not support large‑scale modular development.

No dedicated syntax support; the code looks primitive.

Even after these evolutions, problems persist: multiple <script> tags cause many HTTP requests, dependency order is ambiguous, and there is no clear interface or standard, which led to the emergence of formal module specifications.

Module Specifications

CommonJS

1. Overview

CommonJS is a JavaScript module specification originally created for server‑side (Node.js) modularity and defines how modules are structured, loaded, exported, and imported.

2. Basic Structure

Each file is a separate module with its own scope; variables, functions, and classes are private to the file unless exported.

3. Loading Method

Modules are loaded synchronously: when a module is required, its code runs immediately and the exported value is returned. Subsequent requires return the cached result.

4. Export and Import

Exports are done via module.exports or exports . module.exports is the real export object; exports is just a reference.

// Export a variable
module.exports.name = 'Tom';

// Export a function
exports.sayHello = function() {
  console.log('Hello!');
};

Other modules import with require :

var moduleA = require('./moduleA');
console.log(moduleA.name);
moduleA.sayHello();

5. Features

Implemented by the JS runtime.

Exports are copies of values.

Supports dynamic loading with caching, solving circular dependencies.

Synchronous loading guarantees execution order.

ES6 Modules

1. Overview

Before ES6, JavaScript lacked native modular support, leading to third‑party solutions with issues like naming conflicts. ES6 introduced the static ESModule syntax to address these problems.

2. Loading Method

In browsers, modules are loaded with <script type="module" src="./module.js"></script> ; in Node.js, the import keyword is used.

// In Node.js
import { name } from './module';

3. Export and Import

Various forms are supported:

Named export / import:

// module.js
export const name = '张三';
export function sayHello() { console.log('Hello'); }

// app.js
import { name, sayHello } from './module';

Default export / import:

// module.js
export default 'Hello World';

// app.js
import message from './module';

Mixed named and default export:

// module.js
export const name = '张三';
export function sayHello() { console.log('Hello'); }
export default 'Hello World';

// app.js
import message, { name, sayHello } from './module';

4. Features

Static analysis; cannot be placed inside block scopes.

Exports are live references; changes affect the original module.

Supports multiple named exports, default export, and mixed imports.

Modules are pre‑loaded and executed before dependent code runs.

AMD (Asynchronous Module Definition)

1. Overview

AMD, proposed by James Burke of RequireJS, emphasizes asynchronous loading and early execution.

2. Basic Syntax

define(id?, dependencies?, factory);

Typical definition:

define('module1', ['module2', 'module3'], function(module2, module3) {
  // module1 code
  return {};
});

Modules are loaded with require :

require(['module1', 'module2'], function(module1, module2) {
  // callback after all dependencies are loaded
});

CMD (Common Module Definition)

1. Overview

CMD, used mainly in the browser, combines features of CommonJS and AMD with on‑demand loading and delayed execution.

2. Basic Syntax

// Define a module without dependencies
define(function(require, exports, module) {
  exports.xxx = value;
  module.exports = value;
});

// Define a module with dependencies
define(function(require, exports, module) {
  var module2 = require('./module2'); // synchronous
  require.async('./module3', function(m3) {
    // async loading
  });
  exports.xxx = value;
});

// Use a module
define(function(require) {
  var m1 = require('./module1');
  var m4 = require('./module4');
  m1.show();
  m4.show();
});

CMD is designed for browser environments, loading modules asynchronously only when they are needed, and merges the strengths of CommonJS and AMD.

JavaScriptModularizationCommonJSAMDES6CMD
ByteFE
Written by

ByteFE

Cutting‑edge tech, article sharing, and practical insights from the ByteDance frontend team.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.