Sep 24, 2025

JavaScript CommonJS vs ES Modules: Import, Export, Migration, and Developer Best Practices

Tags

JavaScript modules solve the headaches of tangled codebases by letting developers split code into reusable, maintainable chunks. Today, two main systems dominate: CommonJS (CJS) and ES Modules (ESM). Let’s demystify the differences, explore practical use cases, and see how to migrate code the right way.


common-js-vs-es-module


What Are JavaScript Modules?


Modules are files that encapsulate functionality, isolating variables and exports so code can be split up and reused, improving organization and maintainability


CommonJS


  • Used in Node.js, relies on require() to import modules and module.exports to export.
  • Loads modules synchronously (blocking).


Example:



ES Modules (ESM/ES6 Modules)


  • Introduced in ES6, uses import and export syntax.
  • Loads modules asynchronously, natively supported in browsers and recent Node versions.


Example:



Migrating from CommonJS to ES Modules


  • Rename files to .mjs or set "type": "module" in package.json.
  • Change require('./lib') to import { foo } from './lib.js'.
  • Change module.exports = ... to export default ... or export { ... }.
  • Refactor dynamic behaviors: ES Modules imports must be at the top-level and are static.


Example migration:



Why CommonJS Isn't Tree Shakable (but ESM Is)


  • CommonJS: Dynamic; imports/exports are resolved at runtime, so bundlers can't safely remove unused code.
  • ES Modules: Static; imports/exports are available at build time, allowing bundlers (like webpack) to remove unused ("dead") code for smaller builds.


Circular Dependency: CommonJS vs ESM


  • CommonJS: Modules are loaded immediately. If two modules depend on each other, one may get an incomplete export.
  • ES Modules: Modules are initialized but not executed until import is resolved (“live bindings”). This makes circular references safer and more predictable.


How import.meta Works in ES Module


  • Provides metadata about the current module (like import.meta.url for path info).
  • Helps replace CommonJS globals like __dirname/__filename.


Example:



'require' vs 'import' in Node.js & Browser


Feature CommonJS (require) ES Modules (import)
Node.js Support Fully supported Supported since v12+
Browser Support Not natively Native (modern browsers)
Syntax require/module.exports import/export
Loading Synchronous Asynchronous
Use Case Server-side Client & server
Tree Shaking No Yes
Dynamic Import require() import()


Developer POV: Best Practices


  • New projects: Use ES Modules for browser and server code.
  • Migrating: Identify dynamic imports, refactor to static top-level imports before switching.
  • Tools: Take advantage of tree shaking by using ESM; avoid mixing CommonJS and ESM in one module.
  • Circular dependencies: Prefer ESM for safer live bindings.
  • Modern Node.js: Most libraries and frameworks now use ESM—use "type": "module" in new projects.


Conclusion 


Mastering modules is about writing clean, maintainable code and building faster, robust applications. If you want migration tips or deep-dive examples, just write down in comments below!


EmoticonEmoticon