Blog post

__dirname is back in Node.js with ES modules

Blog Author Phil Nash

Phil Nash

Developer Advocate JS/TS

5 min read

  • JavaScript

ECMAScript modules (or ES modules) are the new standard format to package JavaScript code for reuse. There is a huge and ongoing shift in the Node.js world to move from CommonJS to ES modules, but there has been friction along the way.


One of those bits of friction was recently removed: getting access to the current module's directory is now easy again!

TL;DR

In an ES module, instead of using __dirname or __filename, you can now use:

import.meta.dirname  // The current module's directory name (__dirname)
import.meta.filename // The current module's file name (__filename)

If you're interested, there is more to this story, so read on.

Getting the current directory

With access to the directory path of the current module, you can traverse the file system relative to where your code is located and read or write files within your project or dynamically import code. The way to access this information has changed over the years, from CommonJS's implementation to the latest update to ES modules. Let's take a look at how it has evolved.

The old CommonJS way

Node.js initially used the CommonJS module system. CommonJS provided two variables that returned the current module's directory name and file name. Those variables were __dirname and __filename.

__dirname  // The current module's directory name
__filename // The current module's file name

The old ES module way

__dirname and __filename are unavailable in an ES module. Instead, you used to need the following code to reproduce them:

import * as url from 'url';

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

I could never remember this boilerplate code and always found myself reaching for Sam Thorogood's explanation of how to get __dirname back. There had to be an easier way.

The new ES module way

Finally, after much discussion, there is now a better way. Since Node.js version 20.11.0, Deno version 1.40.0 and Bun version 1.0.23, you can call on the dirname and filename properties of the import.meta object.

import.meta.dirname  // The current module's directory name
import.meta.filename // The current module's file name

How did we get here?

As I wrote at the beginning of the article, ES modules are a JavaScript standard. However, JavaScript started its life as a language that ran in web browsers. Node.js popularised running JavaScript on the server but had to use or invent a number of conventions. One early choice that the Node.js project made was to adopt the CommonJS module system and everything that came with it.


ES modules were designed with both browser and server environments in mind. Browsers typically don't have file system access, so providing access to a current directory or file name doesn't make sense. However, browsers deal in URLs, and a file path can be provided in URL format using the file:// scheme. So ES modules have a reference to the URL of the module. You've seen that already above, as import.meta.url. Let's take a look at what you can do with a URL in Node.js.

URLs everywhere

Consider an ES module called module.js with the following code:

console.log(import.meta.url);

If you run this file on a server using Node.js, you will get the following result:

$ node module.js
file:///path/to/module.js

If you load module.js in a web browser, you will see:

https://example.com/module.js

Both results are URLs but have different schemes based on the context.


To make things a little more confusing, import.meta.url is a string that describes a URL rather than actually being a URL object. You can turn it into a real URL object by passing the string to the URL constructor:

const fileUrl = new URL(import.meta.url);
console.log(url.protocol);

// Node.js: "file:"
// Browser: "https:"

And this is where the original replacement for __dirname and __filename in Node.js came from. With a URL object, you can use Node.js's URL module to turn the module's URL into a file path, recreating __filename.

import * as url from "url";

const fileUrl = new URL(import.meta.url);
const filePath = url.fileURLToPath(fileUrl);
console.log(filePath);

// /path/to/module.js

You can also manipulate the URL to get the directory name and recreate __dirname.

import * as url from "url";

const directoryUrl = new URL(".", import.meta.url);
const directoryPath = url.fileURLToPath(directoryUrl);
console.log(directoryPath);

// /path/to

You can use URLs instead of strings

You may think you need to work with path strings to perform common file actions within Node.js. It turns out that many Node.js APIs that work on string paths also work with URL objects.


The most common use of __dirname is traversing a directory to find a data file you want to load. For example, if your module.js file is in the same directory as a file called data.json and you want to load the data into your script, you would previously have used __dirname like this:

const { join } = require("node:path");
const { readFile } = require("node:fs/promises");

function readData() {
  const filePath = join(__dirname, "data.json");
  return readFile(filePath, { encoding: "utf8" });
} 

You can now recreate this in an ES module using import.meta.dirname.

import { join } from "node:path";
import { readFile } from "node:fs/promises";

function readData() {
  const filePath = join(import.meta.dirname, "data.json");
  return readFile(filePath, { encoding: "utf8" });
} 

But you can use a URL object like this instead:

import { readFile } from "node:fs/promises";

function readData() {
  const fileUrl = new URL("data.json", import.meta.url);
  return readFile(fileUrl, { encoding: "utf8" });
} 

Since ES modules bring consistency to JavaScript written for both client and server, using a URL object over a path string can do the same. If you want to read about more use cases for URLs instead of paths, check out the article on alternatives to __dirname.

Where can you find import.meta.dirname?

import.meta.dirname and import.meta.filename can be used in the latest versions of Node.js, Deno and Bun.


Bun had already implemented import.meta.dir and import.meta.path, which are equivalent. dirname and filename are now aliases of dir and path.


Since the properties only refer to the underlying file system, they are only available when the import.meta.url scheme is "file:". That is, they aren't available in a browser environment; trying to use import.meta.dirname in a browser will simply return undefined.

A blend of simplicity and interoperability

It's great that the Node.js community, Deno, and Bun have all decided to implement these properties. As codebases move and new projects are started using ES modules, reducing the friction to change is helpful to the entire ecosystem.


It's also important to note what you can achieve using import.meta.url in all JavaScript environments and consider whether using URL objects can make your code more consistent across both front and back-end code.


At the very least, we can now remove some boilerplate code in favour of import.meta.dirname.


Get new blogs delivered directly to your inbox!

Stay up-to-date with the latest Sonar content. Subscribe now to receive the latest blog articles. 

By submitting this form, you agree to the storing and processing of your personal data as described in the Privacy Policy and Cookie Policy. You can withdraw your consent by unsubscribing at any time.

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

SonarLint

Clean Code from the start in your IDE

Up your coding game and discover issues early. SonarLint takes linting to another level empowering you to find & fix issues in real time.

Install SonarLint
SonarQube

Clean Code for teams and enterprises

Empower development teams with a self-hosted code quality and security solution that deeply integrates into your enterprise environment; enabling you to deploy clean code consistently and reliably.

SonarCloud

Clean Code in your cloud workflow

Enable your team to deliver clean code consistently and efficiently with a code review tool that easily integrates into the cloud DevOps platforms and extend your CI/CD workflow.