Surviving The Shift: How to Transition from CommonJS To ESM

What Would A Beginner Do? : Add"type": "module" in package.json.

When you get tired of seeing ERR_REQUIRE_ESM errors, you usually search StackOverflow and find a quick “fix”: add "type": "module" to your package.json.

You do it, feel a temporary sense of relief, change your require statements to import, and run the app. Then, a brand-new error appears:

ReferenceError: __dirname is not defined

Welcome to the next level on your road to ESM transition. In CommonJS, Node automatically wrapped your code in a helper function(function wrapper) that gave you global variables like __dirname (the path of the current directory) and __filename.

In ESM,

Get Shreyash’s stories in your inbox

Join Medium for free to get updates from this writer.

If your backend code relies on __dirname to find static assets or load files from your drive, your code is now broken. To fix it, you have to manually reconstruct them at the top of your files:

import { fileURLToPath } from 'url';
import { dirname } from 'path';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

Having to paste four lines of boilerplate at the top of a file just to find out what folder you are in, feels like a step backward, but it is the cost of “ESM” we have to pay to move forward.

How to Handle the Transition.

If you are tired of the module wars, you have three realistic ways to manage this transition without bugging your entire code.

  1. If you have a massive, older Express API and you cannot afford the time to rewrite hundreds of files to ESM, but you absolutely must use a newer, ESM-only library, use a dynamic import.

Dynamic import() works inside CommonJS because it returns a Promise, keeping the asynchronous nature of ESM intact:

// Keep using require for everything else
const express = require('express');

async function handleRequest(req, res) {
// Dynamically load the ESM-only library inside an async block
const { default: esmLibrary } = await import('esm-only-package');

const result = esmLibrary.doSomething(req.body);
res.json({ result });
}

It isn’t the prettiest syntax, but it keeps your legacy app running while letting you pull in modern packages.

2. If you are starting a brand-new Node.js project today, Save yourself the future headache and go full ESM from day one.

  • First things first, Add "type": "module" to your package.json
  • Use import/export exclusively.
  • Accept that you’ll need import.meta.url if you need to resolve local file paths.

By starting with ESM, you will never run into compatibility issues when installing modern npm packages.

3. If you want modern import syntax but absolutely hate dealing with Node’s configuration, let a build tool do the heavy lifting.

  • Write clean import/export statements, and let the TypeScript compiler (tsc) compile it down to perfectly compatible CommonJS behind the scenes.
  • Modern runtimes like support both require and import in the same file seamlessly, completely bypassing Node’s strict module division.

Similar Posts

Leave a Reply