The ESM Mess: JavaScript's Module System Is Still Broken and Here's Why

The require() vs import collision is still causing real damage
Last week I added a dependency to a Node.js service we're building. Nothing fancy, a utility library I've used before. The project was CommonJS because it started years ago and nobody had time to migrate. The package I pulled in had just dropped its CJS build.
Build broke. require() of ES Module — that error you've seen a hundred times and still have to re-Google every time. I spent two hours chasing it down, patching around it with a dynamic import() inside an async wrapper, and quietly seething.
I've been writing JavaScript since before npm existed. I've seen a lot of things labeled "the future" that never fully arrived. ESM is the current reigning champion.
The Timeline Is Embarrassing
ES Modules were finalized in the spec in 2015. Node.js started experimental support in 2017. "Stable" support landed in Node 12 in 2019. It's now 2026. Nine years since the spec. Seven years since Node.js began support.
Current adoption? Depending on who's measuring: somewhere between 9% and 27% of npm packages have shipped ESM-only or dual-mode builds. The rest are still CommonJS.
Let that sit for a second. After nearly a decade of "ESM is the future" messaging, blog posts, conference talks, framework migrations, tool updates, the overwhelming majority of the JavaScript ecosystem is still on CommonJS.
This isn't a community being stubborn. It's a community that ran into real friction and stopped.
What the Friction Actually Looks Like
If you haven't hit it recently, here's how it goes in practice.
Scenario 1: Your project is CommonJS, you add an ESM-only package
You add the package. You require() it as you always have. Node throws:
Error [ERR_REQUIRE_ESM]: require() of ES Module /node_modules/some-package/index.js not supported.
The "fix" is to change your call site to use await import(). Which means it has to be inside an async function. Which means you restructure call chains. Which means you potentially break other things. If you do this in enough places, you've effectively started an unplanned, untested partial migration.
Scenario 2: Your project is ESM, you need a CommonJS package
Usually this one's fine — ESM can import CJS without too much trouble. But not always. Some CJS packages use patterns that trip up ESM's named imports. You end up writing:
Related Articles

Your Support Team Is Claude Code on a Timer
An L2 support engineer that's just Claude Code on a /loop, with all its state living in Slack reactions. No app, no database, no deploy — and it only pings you when it's real.

What an AI Audit Actually Finds
Most teams asking for AI don't have a model problem. They have a "which problem is even worth it" problem. Here's what a real AI audit surfaces — and why "don't build this" is often the most valuable line in the report.

Inside a Production Voice Agent: How the Stack Actually Ships
Production voice-AI has converged on a pattern: graph-based conversations, separated decision and response prompts, synthetic-call regression testing, and per-component latency budgets. Why the stack looks the way it does — and what most teams are still missing.
Building something like this?
I help teams ship AI in production — audits, consulting, custom agents, and eval systems. Start with an AI Audit (from $5k) for an honest read on what to build.



