Modularity is a fundamental principle of modern development, allowing code to be organized into independent, reusable components. JavaScript’s module system evolution has led to several standards, each with its own specifics and use cases.
Main JavaScript Module Systems
1. CommonJS - Server Standard
Usage: Originally created for Node.js, now also used in browsers via bundlers.
Syntax:
// Export
module.exports = { functionA, variableB };
exports.functionC = functionC;
// Import
const module = require('./module');
const { functionA } = require('./module');
Support:
Node.js: all versions
Browsers: via Webpack, Browserify, Rollup
2. AMD (Asynchronous Module Definition)
Usage: Designed for browsers with asynchronous loading.
Syntax:
// Module definition
define(['dependency'], function(dependency) {
return {
exportedFunction: function() {
// use dependency
}
};
});
// Module loading
require(['module'], function(module) {
module.exportedFunction();
});
Support:
Browsers: via RequireJS
Node.js: via wrappers
3. UMD (Universal Module Definition)
Usage: Cross-platform solution compatible with CommonJS, AMD and global variables.
Syntax:
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define(['exports'], factory);
} else if (typeof exports === 'object' && typeof exports.nodeName !== 'string') {
// CommonJS
factory(exports);
} else {
// Global variable
factory((root.myModule = {}));
}
}(typeof self !== 'undefined' ? self : this, function(exports) {
// Module code
exports.example = function() {};
}));
4. ES6 Modules - Native Standard
Usage: Modern ECMAScript standard supported by all current environments.
Syntax:
// Export
export const name = 'value';
export function functionName() {};
export default function() {};
// Import
import { name, functionName } from './module';
import defaultExport from './module';
Browser Support
ES6 Modules in Browsers
Chrome: since version 61 (2017)
Firefox: since version 60 (2018)
Safari: since version 11 (2017)
Edge: since version 16 (2017)
Usage:
<!-- Module loading -->
<script type="module" src="app.js"></script>
<!-- Inline module -->
<script type="module">
import { functionName } from './module.js';
functionName();
</script>
Legacy Browser Support
For browsers without ES6 module support use:
Transpilation (Babel)
Module bundlers (Webpack, Rollup, Parcel)
Combination of type=“module” and nomodule:
<script type="module" src="app.es6.js"></script>
<script nomodule src="app.legacy.js"></script>
Node.js Support
Module Support History
Node.js 0.1.0-8.x: CommonJS only
Node.js 8.5.0: experimental ES6 module support (with --experimental-modules flag)
Node.js 12.0.0: improved ES6 module support
Node.js 13.2.0: stable ES6 module support without flags
Node.js 14.0.0+: full ES6 module support in LTS releases
Using ES6 Modules in Node.js
.mjs extension:
// module.mjs
export const value = 10;
// app.mjs
import { value } from './module.mjs';
“type” field in package.json:
{
"type": "module",
"main": "app.js"
}
Mixed projects:
.js files - CommonJS
.mjs files - ES6 modules
.cjs files - CommonJS (when “type”: “module”)
Practical Recommendations
Module System Selection
New projects: use ES6 modules
Libraries: support both CommonJS and ES6 (via dual export)
Legacy projects: stay with CommonJS until refactoring
Cross-Module System Compatibility
Importing CommonJS in ES6:
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
const commonJSModule = require('./common-js-module.cjs');
Importing ES6 in CommonJS (with dynamic import):
async function loadModule() {
const es6Module = await import('./es6-module.mjs');
}
Build Optimization
Use tree-shaking with ES6 modules
Minimize circular dependencies
Split code with dynamic imports
Migration from CommonJS to ES6 Modules
Rename files to .mjs or set “type”: “module”
Replace module.exports with export
Replace require() with import
Update relative import paths (add extensions)
Fix __dirname and __filename references
Migration Example
Before (CommonJS):
// math.js
const PI = 3.14;
function sum(a, b) { return a + b; }
module.exports = { PI, sum };
// app.js
const { PI, sum } = require('./math');
console.log(sum(5, 10));
After (ES6 modules):
// math.js
export const PI = 3.14;
export function sum(a, b) { return a + b; }
// app.js
import { PI, sum } from './math.js';
console.log(sum(5, 10));
Module Tools
Bundlers and Transpilers
Webpack: supports all module systems
Rollup: optimized for ES6 modules
Babel: transpiles ES6+ to compatible code
Parcel: zero configuration, supports all formats
Utilities
esm: package for using ES6 modules in Node.js
@babel/plugin-transform-modules-commonjs: transforms ES6 to CommonJS
Conclusion
JavaScript’s module ecosystem has evolved significantly, with ES6 modules becoming the universal solution for all platforms. For new projects, use ES6 modules with transpilation and bundling for backward compatibility.
For existing CommonJS projects, plan gradual migration to ES6 modules using modern Node.js features and bundlers. This improves performance, enables tree-shaking, and simplifies long-term maintenance.
Regardless of the chosen module system, remember the core principles of modularity: loose coupling, strong cohesion, and clear interfaces between system components.