ESM and `__dirname`

What in the actual heck

As a Node/TypeScript engineer constantly trying to prune build tools and layers of complexity away, I find ECMAscript modules quite the difficulty hurdle. A lot of the little rules and differences between ESM and CJS have deep technical reasons behind them, many of which I’m sure are difficult to explain.

One of those is the whole “where’s my __dirname and __filename?!” kerfuffle. And it’s probably instructive.

You can’t simply use __dirname in ESM to get a filesystem directory. The standard was written for browsers, not file systems. Sure, there’s a convention that overlaps file system semantics onto URLs. But there’s no guarantee that https://website.com/my/path overlays onto /var/www/my/path.

So, there wasn’t a replacement for __dirname in the standard when Node was implementing ECMAscript modules. My guess is the Node maintainers simply chose to not fill gaps between Common JS and ESM with their own, bespoke APIs. (That’s how Common JS came to be in the first place, something I’m sure they want to avoid in the future.) And look at it from the ESM standpoint: what in the world does __filename and __dirname mean from a web context where scripts, images, and anything can be loaded from any arbitrary path?

Thus, for the longest time, the Node-ESM folks had to deal with workarounds for __directory like the following:

// Somewhere at the top of your CommonJS files you're converting to ESM
import { fileURLToPath } from 'url';

const __dirname = fileURLToPath(new URL('.', import.meta.url));
const __filename = fileURLToPath(import.meta.url);

But that’s a far cry from a one-liner… and if you can’t easily or quickly upgrade Node, you’re still in for hard times.

And for the longest time, that’s where we were. Using three lines (including an import) to do what we previously could do with the nice, implicitly-declared __dirname and __filename variables.

But as of Node 20.11 (and I believe back ported to several other past versions), we have finally have the requisite one liner to make ESM conversions easier:

const __filename = import.meta.filename;
const __dirname = import.meta.dirname;

Huzzah! You might even be able to do a simple find-replace.

Personally, I’ve learned here to always ease the pathway for upgrades when considering breaking changes. For most switching to ESM, there are no discernable benefits–just a lot of work so that you can upgrade certain packages.

When even your successful upgrade path has few obvious benefits, breaking changes oughta be crystal clear and extremely low friction. I think that now with proper one-liner replacements for __dirname and __filename, I might be able to begin the long process of switching packages in our large monorepo to ESM.

In hindsight, it would have been nice to have had import.meta.dirname and filename before packages began to switch to ESM only.