2026 Is the Year of Full ES Modules Adoption in JavaScript
Hello HaWkers, after years of transition, 2026 marks the year when ES Modules (ESM) finally achieves full adoption in the JavaScript ecosystem. Node.js now allows importing ESM from CommonJS, major libraries are dropping CJS support, and the community is converging on a single module system.
If you're still using CommonJS in your projects, this is the time to migrate. Let's understand why this change is so important and how to make the transition safely.
What Changed in 2026
The Definitive Turn
Several factors converged to make 2026 the year of ESM.
Important milestones:
- Node.js 24 LTS: Complete ESM support as default
- Node.js 22+: Allows
import()of ESM within CJS files - npm trends: 70%+ of new libraries are ESM-only
- Major libraries: chalk, execa, got, ora dropped CJS
Ecosystem comparison:
| Year | ESM-only Libraries | Dual Libraries | CJS-only |
|---|---|---|---|
| 2020 | 5% | 15% | 80% |
| 2022 | 15% | 30% | 55% |
| 2024 | 35% | 40% | 25% |
| 2026 | 60% | 30% | 10% |
Quote from a Node.js core maintainer:
"We can finally say that ES Modules is the default path for JavaScript. Interoperability with CommonJS is mature enough for the transition to be smooth."
Why ES Modules Is Better
Technical Advantages
ES Modules offers concrete benefits over CommonJS.
1. Static import and tree-shaking:
// CommonJS - imports everything
const _ = require('lodash');
const result = _.map([1, 2, 3], x => x * 2);
// ESM - imports only what's needed
import { map } from 'lodash-es';
const result = map([1, 2, 3], x => x * 2);
// Result: 70% smaller bundle with ESM + tree-shaking2. Top-level await:
// ESM allows top-level await
const config = await fetch('/config.json').then(r => r.json());
const db = await connectDatabase(config.dbUrl);
export { db, config };
// CommonJS would require async wrapper or callbacks3. More predictable module resolution:
// ESM - always needs extension
import { helper } from './utils.js';
import data from './data.json' assert { type: 'json' };
// URLs and dynamic imports
const module = await import(`./plugins/${pluginName}.js`);4. Browser compatibility:
<!-- ESM works natively in browser -->
<script type="module">
import { render } from './app.js';
render(document.getElementById('root'));
</script>
How to Migrate from CommonJS to ESM
Step-by-Step Guide
Migrating an existing project requires specific steps.
Step 1: Update package.json
{
"name": "my-project",
"version": "1.0.0",
"type": "module",
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.cjs"
}
},
"engines": {
"node": ">=18.0.0"
}
}Step 2: Rename files (if necessary)
# If not using "type": "module", rename to .mjs
mv src/index.js src/index.mjs
# Or keep .js with "type": "module" in package.jsonStep 3: Update imports/exports
// Before (CommonJS)
const express = require('express');
const { readFile } = require('fs').promises;
const myModule = require('./myModule');
module.exports = { app, server };
module.exports.helper = helper;
// After (ESM)
import express from 'express';
import { readFile } from 'fs/promises';
import myModule from './myModule.js'; // Note the extension
export { app, server };
export { helper };Step 4: Handle __dirname and __filename
// CommonJS had global __dirname and __filename
// ESM needs to recreate them
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Usage
const configPath = join(__dirname, 'config.json');
Special Migration Cases
Dealing With CommonJS Dependencies
Not all libraries have migrated to ESM.
Importing CJS in ESM:
// Default import works for most cases
import lodash from 'lodash'; // CJS
// For named exports from CJS, may need:
import pkg from 'some-cjs-package';
const { namedExport } = pkg;
// Or use createRequire for special cases
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
const legacyLib = require('legacy-cjs-lib');Importing ESM in CJS (Node.js 22+):
// Now possible in Node.js 22+
// file.cjs or file without "type": "module"
async function main() {
// Dynamic import works
const { something } = await import('esm-only-package');
// Or at top of file with new feature
}
// Node.js 22+ also supports synchronous require() of ESM
// in some casesDual package (support both):
{
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
},
"./utils": {
"import": "./dist/utils.mjs",
"require": "./dist/utils.cjs"
}
}
}
Migration Tools
Automating the Process
Several tools help with migration.
1. cjs-to-esm
# Converts CJS files to ESM
npx cjs-to-esm src/**/*.js
# Options
npx cjs-to-esm --ext=.mjs src/**/*.js2. ESLint for validation
// .eslintrc.js
export default {
parserOptions: {
sourceType: 'module',
ecmaVersion: 2026,
},
rules: {
// Ensures correct ESM usage
'import/extensions': ['error', 'always'],
'import/no-commonjs': 'error',
},
};3. TypeScript with ESM
// tsconfig.json
{
"compilerOptions": {
"module": "NodeNext",
"moduleResolution": "NodeNext",
"target": "ES2022",
"outDir": "./dist",
"declaration": true
}
}4. Vite and modern bundlers
// vite.config.js - ESM by default
import { defineConfig } from 'vite';
export default defineConfig({
build: {
lib: {
entry: './src/index.js',
formats: ['es', 'cjs'],
fileName: (format) => `index.${format === 'es' ? 'mjs' : 'cjs'}`
}
}
});
Common Problems and Solutions
Troubleshooting
Some frequent problems in migration.
Error: "require is not defined"
// Problem: using require() in ESM file
// Solution: use import or createRequire
// Wrong
const fs = require('fs'); // Error in ESM
// Correct
import fs from 'fs';
// or
import { createRequire } from 'module';
const require = createRequire(import.meta.url);Error: "Cannot use import statement outside a module"
// Problem: file not being treated as ESM
// Solutions:
// 1. Add "type": "module" in package.json
// 2. Rename file to .mjs
// 3. Use --experimental-vm-modules flag (tests)Error: "ERR_MODULE_NOT_FOUND"
// Problem: missing extension in import
// ESM requires explicit extensions
// Wrong
import { helper } from './utils';
// Correct
import { helper } from './utils.js';Problem with JSON imports:
// ESM requires assertion for JSON
import data from './data.json' assert { type: 'json' };
// Or use createRequire
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
const data = require('./data.json');
Ecosystem Impact
Changes in Popular Libraries
Many libraries have already migrated or are migrating.
Libraries that dropped CommonJS:
| Library | ESM-only Version | CJS Alternative |
|---|---|---|
| chalk | 5.0+ | chalk@4 |
| execa | 6.0+ | execa@5 |
| got | 12.0+ | got@11 |
| ora | 6.0+ | ora@5 |
| globby | 13.0+ | globby@12 |
| node-fetch | 3.0+ | node-fetch@2 |
Library strategies:
- ESM-only: Forces users to migrate (chalk, got)
- Dual package: Supports both (axios, lodash)
- CJS with ESM wrapper: Maintains CJS, exports ESM (express)
Recommendation:
For new projects, use ESM from the start. For existing projects, plan gradual migration, starting with less critical modules.
Performance Benefits
Why ESM Is Faster
The shift to ESM brings performance gains.
Effective tree-shaking:
// Bundle size comparison (real example)
// Project using lodash
// CommonJS: 72KB (entire lodash)
// ESM with tree-shaking: 4KB (only used functions)Parallel loading in browser:
// ESM allows parallel loading of dependencies
// Browser resolves import graph and loads in parallel
// Result: 30-50% faster load time
// for applications with many modulesMore efficient cache:
// Each ESM module can be cached independently
// Changes in one module don't invalidate others
// Benefit in CI/CD and incremental buildsConclusion
2026 marks the consolidation of ES Modules as the JavaScript standard. The transition may seem laborious, but the benefits in terms of performance, compatibility, and developer experience justify the effort.
Key points:
- ES Modules is now the de facto JavaScript standard
- Node.js 22+ allows complete interoperability
- Migrating requires attention to extensions and syntax
- Tools automate much of the process
- Benefits include tree-shaking and better performance
Recommendations:
- Start new projects with ESM
- Plan migration of existing projects
- Use automatic conversion tools
- Test extensively after migration
- Maintain dual package if necessary for compatibility
For more on modern JavaScript, read: ES2026: New Features That Will Transform How We Write JavaScript.

