Why Modern JavaScript Needs Module Systems: From CJS to ESM Explained

This article explains the evolution of JavaScript module standards—from early CommonJS and AMD/UMD approaches to the native ES modules (ESM)—and shows how bundlers, browsers, and tooling work together to manage dependencies, improve code organization, and enable modern web development.

Alibaba Terminal Technology
Alibaba Terminal Technology
Alibaba Terminal Technology
Why Modern JavaScript Needs Module Systems: From CJS to ESM Explained

Module Development

Why Modularize?

Early web applications loaded JavaScript directly, but growing requirements made projects larger and harder to maintain. Managing many variables and functions in the global scope leads to hidden dependencies, ordering problems, and maintenance difficulty. Modularization provides a clear way to organize and isolate code.

The Role of Modules

Modules group related variables and functions, expose only the intended API, and enable import/export between modules. This makes code splitting similar to building with LEGO blocks and clarifies which parts can be removed safely. Common standards include CommonJS (CJS), AMD, CMD, UMD, and the native ES modules (ESM).

Existing Module Standards

CJS (CommonJS) – primarily used on the Node.js side:

const _ = require('lodash');
module.exports = function doSomething(n) {
    // implementation
};

AMD (Asynchronous Module Definition) – works in browsers:

define(['dep1', 'dep2'], function (dep1, dep2) {
    return function () {};
});

UMD (Universal Module Definition) – works both in Node and browsers:

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        define([], factory);
    } else if (typeof module === 'object' && module.exports) {
        module.exports = factory();
    } else {
        root.returnExports = factory();
    }
}(typeof self !== 'undefined' ? self : this, function () {
    // module body
    return {};
}));

What Is ESM?

ESM is the ECMAScript 6 standard module system, providing a native way for JavaScript to import and export code without a build step.

<script type="module">
  import { html, Component, render } from 'https://unpkg.com/htm/preact/standalone.module.js';
  class App extends Component {
    state = { count: 0 };
    add = () => { this.setState({ count: this.state.count + 1 }); };
    render() {
      return html`
        <div class="app">
          <div>count: ${this.state.count}</div>
          <button onClick=${this.add}>Add Todo</button>
        </div>`;
    }
  }
  render(html`<${App} page="All" />`, document.body);
</script>

Browser‑Side Implementation

Webpack Workflow (Traditional)

Local module parsing (via webpack or Babel) converts import to CJS.

All libraries are bundled into a single JavaScript file.

The bundle is served and downloaded by the browser.

The code is executed.

ESM Execution Flow

Start a server that hosts the original ES source files.

Load the entry file; the browser parses it as a module (recognizing type="module").

Traverse the dependency graph, fetching each imported module.

Map URLs to module records and cache them.

Parse each module in strict mode, handling .mjs extensions when needed.

Execute modules using a depth‑first post‑order traversal so that leaf modules run first, and each module runs only once.

Why ESM Became Popular

Stable ES syntax.

Widespread HTTP/2 adoption.

Modern browsers support native modules.

Consistent development and deployment pipelines.

Fast startup and new loading model.

Why It Is Not Yet Universal

Partial browser compatibility.

Legacy code and historical baggage.

Incomplete ecosystem support.

Practical Considerations

When adopting ESM in a project you need to:

Write code using native import / export syntax.

Handle third‑party libraries that are still CJS (e.g., using cjs-to-esm or bundlers).

Serve modules via CDN (e.g., unpkg , skypack ).

Provide fallbacks for browsers that do not understand type="module" (using nomodule scripts or SystemJS).

Support JSX either through a build step or via runtime libraries like HTM.

Existing Tooling

Snowpack – hosts node_modules, supports assets, JSX, TypeScript, HMR, etc.

Vite – the recommended successor to Snowpack, offering fast dev server and native ESM support.

Future of ESM

Since Firefox 60 (May 2018) all major browsers support ESM natively, and Node.js adds full ESM support in v10+. Ongoing proposals such as dynamic import, import.meta, and module location aim to bridge remaining gaps between browsers and Node.

In the near future, working with modules will become even smoother and more enjoyable.

References

ECMAScript modules in browsers – jakearchibald.com

JavaScript module landscape – zhihu.com

HTM – github.com/developit/htm

How I Build JavaScript Apps In 2021 – timdaub.github.io

ES modules: A cartoon deep‑dive – hacks.mozilla.org

Import maps – github.com/WICG/import-maps

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

frontendJavaScriptwebpackESMModulesbundling
Alibaba Terminal Technology
Written by

Alibaba Terminal Technology

Official public account of Alibaba Terminal

0 followers
Reader feedback

How this landed with the community

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.