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:
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
.
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:
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.
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:
If you run this file on a server using Node.js, you will get the following result:
If you load module.js in a web browser, you will see:
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:
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
.
You can also manipulate the URL to get the directory name and recreate __dirname
.
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:
You can now recreate this in an ES module using import.meta.dirname
.
But you can use a URL
object like this instead:
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
.