97

I use the flag --experimental-modules when running my node application in order to use ES6 modules.

However when I use this flag the metavariable __dirname is not available. Is there an alternative way to get the same string that is stored in __dirname that is compatible with this mode?

Amygdaloideum
  • 1,775
  • 1
  • 7
  • 15
  • Here's an work around to get [`__dirname`](https://esdiscuss.org/topic/nodejss-filename-dirname-in-es6-modules) working in ES6, have a look – kgangadhar Oct 14 '17 at 14:17

11 Answers11

161

As of Node.js 10.12 there's an alternative that doesn't require creating multiple files and handles special characters in filenames across platforms:

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

const __dirname = dirname(fileURLToPath(import.meta.url));
GOTO 0
  • 28,453
  • 18
  • 97
  • 127
  • 1
    Cool, but how to setting up WebStrom IDE to understand this syntax? – ViES Jun 07 '18 at 03:01
  • @ViES I don't know, but I found [this issue](https://youtrack.jetbrains.com/issue/WEB-32919) which is already fixed, so that should be possible with the latest version I think. – GOTO 0 Jun 07 '18 at 06:20
  • 3
    What are the advantages of this solution over [`path.dirname(process.argv[1])`](https://stackoverflow.com/a/53826979/1269037)? – Dan Dascalescu Dec 18 '18 at 05:42
  • 12
    @DanDascalescu `__dirname` is not the process directory path, it is the path of the current module's directory. – GOTO 0 Dec 18 '18 at 06:06
  • I was trying this solution in an electron main process but was unable to overwrite `__dirname` (guessing its readonly) so ended up using `const dirname = ...`. – Lokua Dec 23 '18 at 20:11
  • 1
    This didn't work for me under Linux. Anyone else experienced the same problem? – Markus Ende Mar 15 '19 at 07:26
  • 1
    On Windows (untested on other platforms) this solution results in a leading `\` character in the path, which breaks quite a bit of stuff. I can trim it to resolve the issue, but I'm not sure if that solution is cross-platform compatible. – Micah Zoltu Mar 23 '19 at 08:16
  • I also noticed Windows is adding a leading \ And it looks like `path.dirname(process.argv[1])` _is_ giving me the path of the current module's directory. – Mr. Nobody May 05 '19 at 00:26
  • Perhaps it doesn't require creating multiple files, but it does require duplicating these three lines of code in every module that wants to use `__dirname`. That's ugly. Is there really no better solution for `--experimental-modules`? – Szczepan Hołyszewski Nov 28 '20 at 16:58
  • If don't like needless dependencies, and don't care about legacy operating systems like Bob... `import.meta.url.split('/').slice(2,-1).join('/')`. This doesn't apply to processing untrusted content like zip files... don't even try – Ray Foss Mar 13 '21 at 21:47
21

There have been proposals about exposing these variables through import.meta, but for now, you need a hacky workaround that I found here:

// expose.js
module.exports = {__dirname};

// use.mjs
import expose from './expose.js';
const {__dirname} = expose;
robertklep
  • 174,329
  • 29
  • 336
  • 330
  • 1
    The curly brackets step is not required and besides this method is really not recommended because if `./expose.js` is in another directory it will give the value `__dirname` of that directory, not of the current script... see my answer – vdegenne Apr 17 '19 at 16:39
17

For Node 10.12 +...

Assuming you are working from a module, this solution should work, and also gives you __filename support as well

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

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

The nice thing is that you are also only two lines of code away from supporting require() for CommonJS modules. For that you would add:

import { createRequireFromPath } from 'module';
const require = createRequireFromPath(__filename); 
Mike Brant
  • 66,858
  • 9
  • 86
  • 97
11

I used:

import path from 'path';

const __dirname = path.resolve(path.dirname(decodeURI(new URL(import.meta.url).pathname)));

decodeURI was important: used spaces and other stuff within the path on my test system.

path.resolve() handles relative urls.

edit:

fix to support windows (/C:/... => C:/...):

import path from 'path';

const __dirname = (() => {let x = path.dirname(decodeURI(new URL(import.meta.url).pathname)); return path.resolve( (process.platform == "win32") ? x.substr(1) : x ); })();
BananaAcid
  • 2,608
  • 28
  • 34
8

The most standardized way in 2021

import { URL } from 'url'; // in Browser, the URL in native accessible on window

const __filename = new URL('', import.meta.url).pathname;
// Will contain trailing slash
const __dirname = new URL('.', import.meta.url).pathname;

And forget about join to create paths from the current file, just use the URL

const pathToAdjacentFooFile = new URL('./foo.txt', import.meta.url).pathname;
const pathToUpperBarFile = new URL('../bar.json', import.mera.url).pathname;
Rudolf Gröhling
  • 3,195
  • 2
  • 21
  • 35
  • 1
    Thanks for the answer, one question though, the spaces in the returned path are replaced with `%20`, I am sure if it will be problem – Alex G May 07 '21 at 07:56
  • 1
    @AlexG the result is obviously URL encoded, for nice output, it must be decoded with `decodeURIComponent`. – Rudolf Gröhling May 18 '21 at 11:25
6

I made this module es-dirname that will return the current script dirname.

import dirname from 'es-dirname'

console.log(dirname())

It works both in CommonJs scripts and in ES Modules both on Windows and Linux.

Open an issue there if have an error as the script has been working so far in my projects but it might fail in some other cases. For this reason do not use it in a production environment. And this is a temporary solution as I am sure the Node.js team will release a robust way to do it in a near future.

vdegenne
  • 8,291
  • 10
  • 65
  • 90
  • You intentionally error and examine the stack...that's clever. – Conley Owens Dec 13 '20 at 00:23
  • Talk about overengineering :) Note for TS users: if you are not ready to use `@ts-expect-error`, don't even bother (although it's indeed clever). Why not just `throw` an error? – Oleg Valter Feb 02 '21 at 19:44
6

In most cases, using what is native to Node.js (with ES Modules), not external resources, the use of __filename and __dirname for most cases can be totally unnecessary. Most (if not all) of the native methods for reading (streaming) supports the new URL + import.meta.url, exactly as the official documentation itself suggests:

As you can see in the description of the methods, the path parameter shows the supported formats, and in them include the <URL>, examples:

Method path param supports
fs.readFile(path[, options], callback) <string>, <Buffer>, <URL>, <integer>
fs.readFileSync(path[, options]) <string>, <Buffer>, <URL>, <integer>
fs.readdir(path[, options], callback) <string>, <Buffer>, <URL>
fs.readdirSync(path[, options]) <string>, <Buffer>, <URL>, <integer>
fsPromises.readdir(path[, options]) <string>, <Buffer>, <URL>
fsPromises.readFile(path[, options]) <string>, <Buffer>, <URL>, <FileHandle>

So with new URL('<path or file>', import.meta.url) it solves and you don't need to be treating strings and creating variables to be concatenated later.

Examples:

See how it is possible to read a file at the same level as the script without needing __filename or any workaround:

import { readFileSync } from 'fs';

const output = readFileSync(new URL('./foo.txt', import.meta.url));

console.log(output.toString());

List all files in the script directory:

import { readdirSync } from 'fs';

readdirSync(new URL('./', import.meta.url)).forEach((dirContent) => {
  console.log(dirContent);
});

Note: In the examples I used the synchronous functions just to make it easier to copy and execute.

If the intention is to make a "own log" (or something similar) that will depend on third parties, it is worth some things done manually, but within the language and Node.js this is not necessary, with ESMODULES it is totally possible not to depend on either __filename and neither __dirname, since native resources with new URL with already solve it.


Note that if you are interested in using something like require at strategic times and need the absolute path from the main script, you can use module.createRequire(filename) (Node.js v12.2.0 + only) combined with import.meta.url to load scripts at levels other than the current script level, as this already helps to avoid the need for __dirname, an example using import.meta.url with module.createRequire:

import { createRequire } from 'module';

const require = createRequire(import.meta.url);

// foo-bar.js is a CommonJS module.
const fooBar = require('./foo-bar');

fooBar();

Source from foo-bar.js:

module.exports = () => {
    console.log('hello world!');
};

Which is similar to using without "ECMAScript modules":

const fooBar = require('./foo-bar');
Guilherme Nascimento
  • 7,990
  • 6
  • 41
  • 107
  • If I'm digesting all this as it was intended, you are basically saying the same thing as the accepted answer and the node docs you linked: "`__filename` and `__dirname` use cases can be replicated via `import.meta.url`." – jacobq May 21 '21 at 17:41
  • @jacobq This is not the same thing as the accepted answer, on the contrary, it says that in fact filename and dirname should not be necessary for most situations, since all native NodeJS APIs recognize the `URL` class. I was going to point out some problems about the use of "fileURLToPath" in the specific way, but it seemed to me that the explanation here in the answer was enough to understand that we don't need a filename and dirname. Thanks for commenting. – Guilherme Nascimento May 21 '21 at 17:52
  • A common use case is to refer to a file by its path relative to the location of the script file, e.g. `fs.readFile(path.resolve(__dirname, ...))`. When using ESM `__dirname` can be replaced with `path.dirname(url.fileURLToPath(import.meta.url));`. How can you do this without using `__dirname` or `import.meta.url`? It seems quite necessary to have some way to "know where you [the script/module] are", regardless of whether expressing that as a path a URL, etc. You seem to say that `URL` is solving the problem but `URL` can only solve it when using something like `import.meta.url` to inform it. – jacobq May 21 '21 at 18:18
  • @jacobq I didn't say without "import.meta.url" at any point in the answer. In `fs.readFile()`, the path param supports `new URL`. All native NodeJS APIs support native. And I will repeat what I said in the answer: **the use of __filename and __dirname for most cases can be totally unnecessary** ... I didn't say that there won't be times when you won't use it, but for the vast majority you don't really need "dirname" or "filename", the "new URL + import.meta.url" solve it, this is from the documentation itself I didn't say it ... – Guilherme Nascimento May 21 '21 at 18:39
  • ... See again the link: https://nodejs.org/dist/latest/docs/api/esm.html#esm_no_filename_or_dirname, in link read where it says: "No __filename or __dirname", "No JSON Module Loading", "No Native Module Loading" and "No require.resolve" – Guilherme Nascimento May 21 '21 at 18:39
  • Yes, that's the exact part of the docs that I quoted in my first comment. I think we're misunderstanding each other. I thought that you were saying that we can dispense with the functionality provided by `__filename`, `__dirname`, `import.meta.url`, etc. but rather it seems that you are just advocating the use of URLs over file system path strings. Repeating your answer did not help me at all. Thanks for trying to help though. – jacobq May 24 '21 at 21:23
  • @jacobq I'm not defending "URL", I am stating what works natively very well without needing strings following exactly what is suggested in the documentation. using examples to illustrate how well it works. I do not understand why my answer would deserve a downvote, there is nothing opinionated here, these are arguments demonstrating exactly how to use the native APIs without the need for workarounds. – Guilherme Nascimento May 24 '21 at 21:47
4
import path from 'path';
const __dirname = path.join(path.dirname(decodeURI(new URL(import.meta.url).pathname))).replace(/^\\([A-Z]:\\)/, "$1");

This code also works on Windows. (the replacement is safe on other platforms, since path.join returns back-slash separators only on Windows)

Venryx
  • 8,962
  • 7
  • 50
  • 61
  • Tried it, and didn't work for me on Windows: `Error: ENOENT: no such file or directory, open 'C:\C:\Projects\...such and such...\SomeFile.ts'`. The [edited code](https://stackoverflow.com/a/51118243/2441655) by BananaAcid works, however. – Venryx Jul 09 '20 at 06:59
  • 1
    I found a short fix for your answer, to work on Windows (confirmed working): `[existing code].replace(/^\\([A-Z]:\\)/, "$1");` Added as edit suggestion. – Venryx Jul 09 '20 at 07:29
3

I use this option, since the path starts with file:// just remove that part.

const __filename = import.meta.url.slice(7);
const __dirname = import.meta.url.slice(7, import.meta.url.lastIndexOf("/"));
Navaru
  • 76
  • 2
  • 6
  • 2
    On Windows, this needs to be `slice(8)`, else it results in `/C:/...`, which is resolved to `C:/C:/...`. See the [edited code](https://stackoverflow.com/a/51118243/2441655) by BananaAcid for a cross-platform version. – Venryx Jul 09 '20 at 07:10
1

As Geoff pointed out the following code returns not the module's path but working directory.

import path from 'path';
const __dirname = path.resolve();

works with --experimental-modules

t-reksio
  • 1,961
  • 15
  • 10
  • 3
    Unfortunately, this just returns the current working directory, not the module's path. https://nodejs.org/docs/latest-v10.x/api/path.html#path_path_resolve_paths – Geoff Oct 03 '19 at 15:26
  • Very useful if the goal is finding your 'public' webdir. – Halsafar May 21 '21 at 03:29
-4
process.cwd()

From documentation:

The process.cwd() method returns the current working directory of the Node.js process.

Aqua
  • 409
  • 4
  • 7