111
votes

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?

11
Here's an work around to get __dirname working in ES6, have a lookkgangadhar

11 Answers

183
votes

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));
20
votes

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;
17
votes

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); 
12
votes

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 ); })();
11
votes

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;
8
votes

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');
6
votes

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.

4
votes
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)

3
votes

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("/"));
1
votes

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

-4
votes
process.cwd()

From documentation:

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