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.
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
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
