281

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?

Quentin
  • 914,110
  • 126
  • 1,211
  • 1,335
Amygdaloideum
  • 3,683
  • 2
  • 13
  • 16
  • 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

24 Answers24

439

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
  • 42,323
  • 22
  • 125
  • 158
  • 2
    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
  • 5
    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
  • 30
    @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
  • 3
    This didn't work for me under Linux. Anyone else experienced the same problem? – Markus Ende Mar 15 '19 at 07:26
  • 3
    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
  • 2
    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
  • 3
    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
  • 2
    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
  • To understand what this answer does, check out [this explanation](https://shramko.dev/blog/dirname-error). – Chinmay Ghule Jan 24 '23 at 13:03
115

The most standardized way in 2021

Note: it doesn't work in Windows.

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.meta.url).pathname;
Patrick McElhaney
  • 57,901
  • 40
  • 134
  • 167
Rudolf Gröhling
  • 4,611
  • 4
  • 27
  • 37
  • 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
  • I switched to node 16, from now on my projects are ESM and this was the definitive answer I needed to get my project to work because it crashed with `__dirname` and `path.join` – Damian Rivas Jul 27 '21 at 11:03
  • 35
    Does not work in Windows - results in something like "/C:/..." - better use fileUrlToPath: https://nodejs.org/api/url.html#url_url_fileurltopath_url – JeffRSon Oct 07 '21 at 18:04
  • 7
    Don't even need to import `URL` as it's [available as a global](https://nodejs.org/api/url.html#class-url). – Tamlyn Nov 25 '21 at 20:44
  • Is there some fix/workaround for babel+jest, as using `import.meta.url` gives this error when running a test on an implementation that uses it: `SyntaxError: Cannot use 'import.meta' outside a module` – Juha Untinen Nov 26 '21 at 11:14
  • const __filename = new URL(import.meta.url).pathname; <- doesn't need an extra ''. – Louis Huang Apr 12 '22 at 15:46
  • @Tamlyn Yes, but you'd need to address it through the global variable... ie `globalThis.URL` – Ray Foss Sep 15 '22 at 01:04
  • @JeffRSon Can u give a full solution with using `fileUrlToPath`? currently I do `const filePath = new URL('../../foo.mts', import.meta.url).toString(); const fixedPath = fileURLToPath(filePath);` – eliezra236 Feb 01 '23 at 08:18
  • @eliezra236: what's the problem? Should work, although I would leave out '.toString()' – JeffRSon Feb 01 '23 at 21:53
  • @JeffRSon I would prefer 1 liner that doesn't involve creating 2 variables or nesting functions just for 1 file path – eliezra236 Feb 21 '23 at 13:08
  • @eliezra236 you can do `fileURLToPath(new URL('', import.meta.url))` – murtuzaali_surti May 08 '23 at 08:24
50

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 'node:path';
import { fileURLToPath } from 'node: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); 
Eliaz Bobadilla
  • 479
  • 4
  • 16
Mike Brant
  • 70,514
  • 10
  • 99
  • 103
  • 2
    There is no `createRequireFromPath` - it's `createRequire` and takes "import.meta.url" as parameter – JeffRSon Oct 07 '21 at 18:09
  • 1
    Unlike the answer above, this works on Windows when you need `__dirname`, which you might if you're adapting a bunch of code that already uses `path.join(__dirname, ...)`. – jdunning Apr 15 '22 at 02:22
  • Top half works first time for my use case when removing node: from the imports – Reece Daniels May 17 '22 at 15:22
34

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');
Protomen
  • 9,471
  • 9
  • 57
  • 124
  • 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
  • 2
    @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. – Protomen 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
  • 1
    @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 ... – Protomen May 21 '21 at 18:39
  • 1
    ... 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" – Protomen 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, I'm just saying that it's possible to dispense and that forcing a `__dirname` and a `__filename` for nodejs "I/O" resources is not necessary to use strings, it will only be necessary if you to maybe execute an external command (eg. shell command) or maybe you want to make a text log that pinpoints the current location of the script that executed something, in that case "string" would be required. – Protomen Jun 16 '21 at 20:52
  • 2
    This is a very grey area. The reason that URL's are used, and supported by node, is to adhere as closely as possible to the standardizing ESM specification. That being said, you can not do away with possix style filepaths, or windows style filepaths in node. Especially when working with CLI's, or printing file trees (those are use cases I needed them for), I am sure there are more. Node has to be able to work with both. That's the only way to have a serverside JS RTE with ESM support. To close: File-pathnames are indispensable in node, and there is no disadvantage to working with them. – JΛYDΞV Jun 16 '22 at 06:15
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
  • 198,204
  • 35
  • 394
  • 381
  • 7
    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
  • This is the only way that I found to make the `__dirname` work under both CJS & ESM mode. – Huan Aug 30 '21 at 10:45
  • Should this be `expose.cjs` rather than `expose.js`? And you can import in just one line using `import { __dirname } from './expose.cjs';`? – drmrbrewer Nov 17 '21 at 12:44
16

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
  • 3,221
  • 35
  • 38
11

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
  • 12,272
  • 14
  • 80
  • 106
9
import path from 'path';
import { fileURLToPath } from 'url';

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


// do not use the following code which is bad for CJK characters
const __filename = new URL('', import.meta.url).pathname;
percy507
  • 710
  • 9
  • 11
6
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
  • 15,624
  • 10
  • 70
  • 96
user2962433
  • 71
  • 1
  • 2
  • 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
  • 2
    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
6

Since other answers, while useful, don't cover both cross-platform cases (Windows POSIX) and/or path resolution other than the __dirname or __filename and it's kind of verbose to repeat this kind of code everywhere:

import { dirname, join } from 'path'
import { fileURLToPath } from 'url'

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

const somePath = join(__dirname, '../some-dir-or-some-file')

I just published a NPM package called esm-path to help with this kind of recurring task, hoping it can also be useful to others.

It's documented but here how to use it:

import { getAbsolutePath } from 'esm-path'

const currentDirectoryPath = getAbsolutePath(import.meta.url)
console.log(currentDirectoryPath)

const parentDirectoryPath = getAbsolutePath(import.meta.url, '..')
console.log(parentDirectoryPath)

// Adapt the relative path to your case
const packageJsonFilePath = getAbsolutePath(import.meta.url, '../package.json')
console.log(packageJsonFilePath)

// Adapt the relative path to your case
const packageJsonFilePath = getAbsolutePath(import.meta.url, '..' , 'package.json')
console.log(packageJsonFilePath)
Ivan Gabriele
  • 6,433
  • 5
  • 39
  • 60
3

Just use path.resolve() method.

import { resolve } from 'path';

app.use('/public/uploads', express.static(resolve('public', 'uploads')))
Romserg
  • 47
  • 3
  • Works in vite configs for resolver alias: resolve: { alias: [ { find: "@", replacement: path.resolve("src"), }, ], }, – juliushuck Mar 09 '22 at 22:08
  • 1
    Unfortunately, this is not an equivalent for `__dirname`. `__dirname` is always relative to the file you call `__dirname` from. `resolve()` gives you a URL relative to the directory which you run the file from (current working directory), which can be the same, but is very often different. – samanime Oct 22 '22 at 11:48
2

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
  • 74
  • 2
  • 6
  • 4
    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
2

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
  • 3,251
  • 18
  • 12
  • 5
    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
  • What is the difference between current working directory and __dirname? – Ollie Williams Aug 31 '21 at 15:45
  • 1
    better late than never: you can run a node script within a subfolder `node src/dir/script.js`, thus the `__dirname` will be `~/src/dir/script` but your working dir will be `~`. `~` is the "project root". This is simplified. But in practice working dir is where you are running your command and __dirname is actually the dir where you script is. Both can be the same sometimes btw. – Alex Rintt Dec 28 '22 at 18:52
2

create a file called root-dirname.js in your project root with this in it:

import { dirname } from 'path'

const dn = dirname(new URL(import.meta.url).hostname)
const __dirname = process.platform === 'win32' ? dn.substr(1) : dn // remove the leading slash on Windows
export const rootDirname = __dirname

Then just import rootDirname when you want the path to the project root folder.

Other than that, Rudolf Gröhling's answer is also correct.

arifcelik
  • 3
  • 2
danday74
  • 52,471
  • 49
  • 232
  • 283
2

Agree or disagree with the use of global, I found this to be the easiest way to remember and refactor existing code.

Put somewhere early in your code execution:

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

global.___filename = (path) => {
  return fileURLToPath(path);
};

global.___dirname = (path) => {
  return dirname(global.___filename(path));
};

And then in whichever file you need dirname or filename:

___filename(import.meta.url)
___dirname(import.meta.url)

Of course if we had macros, I wouldn't need to pass import.meta.url, perhaps there's an improvement.

Nom
  • 63
  • 5
2

This works:

import path from 'node:path';
import url from 'node:url';

const DIR_NAME = path.dirname(url.fileURLToPath(import.meta.url));
Ronnie Royston
  • 16,778
  • 6
  • 77
  • 91
2

This is how I solved it for quicker access.

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

/**
 * ES6 __filename polyfill
 *
 * @param {String} fileLocation - Use `import.meta.url`
 * @returns {String} - File path
 * @example
 * const __filename = _filename(import.meta.url)
 */
export const _filename = fileLocation => {
  return fileURLToPath(fileLocation)
}

/**
 * ES6 __dirname polyfill
 *
 * @param {String} fileLocation - Use `import.meta.url`
 * @returns {String} - Directory path
 * @example
 * const __dirname = _dirname(import.meta.url)
 */
export const _dirname = fileLocation => {
  return dirname(fileURLToPath(fileLocation))
}

Save this as es6-polyfills.js and then import _filename and _dirname where required. Remember to set __dirname and __filename as described in comments.

Vladimir Jovanović
  • 3,288
  • 1
  • 20
  • 27
1

You can use the common-es package exactly for that:

npm i common-es

Usage

// # myProjectFile.js or myProjectfile.ts

import { getGlobals } from 'common-es'
const { __dirname, __filename } = getGlobals(import.meta.url)
// now you can use __dirname or file name normally as you would do in commonjs

If you're not familiar with this weird syntax, read this reference on MDN Here

Normal
  • 1,616
  • 15
  • 39
0

another option

import {createRequire} from 'module'; // need node v12.2.0

const require = createRequire(import.meta.url);
const __dirname = require.resolve.paths('.')[0];
William Leung
  • 1,556
  • 17
  • 26
0

You can use the stack from a new Error(). The error doesn't need to be thrown, and won't stop program execution either. The first line of the stack will always be the error and its message, with the second line being the file the which the error was invoked from.

Since this is a method (which is probably in a util.js file), the real location of the getDirname() call is actually the third line of the error stack.

export const getDirname = () => {
    // get the stack
    const { stack } = new Error();
    // get the third line (the original invoker)
    const invokeFileLine = stack.split(`\n`)[2];
    // match the file URL from file://(.+)/ and get the first capturing group
    //     the (.+) is a greedy quantifier and will make the RegExp expand to the largest match
    const __dirname = invokeFileLine.match(/file:\/\/(.+)\//)[1];
    return __dirname;
};
  • 1
    Your answer could be improved by adding more information on what the code does and how it helps the OP. – Tyler2P Mar 06 '22 at 16:08
0

I have also published a package on NPM called cross-dirname (forked from es-dirname). The package is tested with Node.js (ESM and CJS), Deno and GJS.

Example:

import dirname from 'cross-dirname'

console.log(dirname())
JumpLink
  • 487
  • 6
  • 13
0

i made 1 file const.js constaining :

Object.assign(global, {
    __dirname: __dirname,
    __filename: __filename,
});

then

import './const.js';
console.log(__dirname);
0

Get catch-all issue has been resolved and worked for me:

Syntax: In my server.js

const express = require("express");

const path = require('path');

__dirname = path.resolve();

const app = express();

app.get('/*', function(req, res) { res.sendFile(path.join(__dirname, './public/index.html'), function(err) { if (err) { res.status(500).send(err) } }) })

ArunDhwaj IIITH
  • 3,833
  • 1
  • 24
  • 14
-5
process.cwd()

From documentation:

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

Aqua
  • 716
  • 8
  • 10