Back to blog

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-shaking

2. 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 callbacks

3. 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.json

Step 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 cases

Dual 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/**/*.js

2. 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:

  1. ESM-only: Forces users to migrate (chalk, got)
  2. Dual package: Supports both (axios, lodash)
  3. 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 modules

More efficient cache:

// Each ESM module can be cached independently
// Changes in one module don't invalidate others

// Benefit in CI/CD and incremental builds

Conclusion

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:

  1. ES Modules is now the de facto JavaScript standard
  2. Node.js 22+ allows complete interoperability
  3. Migrating requires attention to extensions and syntax
  4. Tools automate much of the process
  5. 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.

Let's go! 🦅

Comments (0)

This article has no comments yet 😢. Be the first! 🚀🦅

Add comments