Alternative to __filename and __dirname in Node.js with Ecmascript Modules

Asked

Viewed 218 times

6

From more recent versions of Node.js, you can use the Ecmascript Modules standard (instead of the ancient Commonjs) for importing modules via extension .mjs or field type defined as module in the package.json.

However, between the differences, I realized that I can no longer use constants (from Node.js with Commonjs) __filename and __dirname to have access to the absolute path of the current file and current directory, respectively.

The question is: what are the substitutes for __filename and __dirname in Node.js with Ecmascript Modules?

2 answers

5

As the source on Soen suggests several uses for working with the import.meta.url Alternative for __dirname in Node when using the --experimental-modules flag, however what has not been said is that the purpose of the fileURLToPath is in addition to "resolve" Urls with file://, as the documentation itself demonstrates (url.fileURLToPath):

fileURLToPath('file:///C:/path/');    // Saída:   C:\path\ (Windows)
fileURLToPath('file://nas/foo.txt');  // Saída:   \\nas\foo.txt (Windows)
fileURLToPath('file:///你好.txt');    // Saída:   /你好.txt (POSIX)
fileURLToPath('file:///hello world'); // Saída:   /hello world (POSIX)

That is, it adjusts to the operating system also, this is useful where it is really strict the need of the tabs for the specific operating system (Windows or POSIX) and to treat Urls that are encoded with "percentage".

If the need to get the path of the current script is not strict to the "folder separator" and the import.meta.url does not carry the encoded URL, so you do not need the fileURLToPath, we can simply work with the object URL (see also https://developer.mozilla.org/en-US/docs/Web/API/URL), including the Node documentation itself suggests that:

Usually we need __dirname to get another file on the same level as the script or module that is running, so just use this:

console.log(new URL('./foo.txt', import.meta.url));

Ready, the new URL already treats the path, without needing to concatenate, without needing to do anything much more, of course this will return an object URL, but the native functions are ready for this, for example if you want to read a file (I used the toString just to see output, the ideal is to work with stream)

Note that most (if not all) common methods to work with "stream" have the parameter that accepts <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>

Examples:

import { readFileSync } from 'fs';

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

console.log(output.toString());

Whether to list the contents of a folder:

import { readdirSync } from 'fs';

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

Now of course I agree that if the intention is to do a "log of its own" or things of the kind is even worth some things done "at hand" with creating your own __dirname, but for the normal use of what Node.js already offers, within ESMODULES it is totally possible not to depend even on __filename nor __dirname, because the native resources with new URL already solve.


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) (only Node.js v12.2.0+) combined with import.meta.url to load scripts at different levels of the current script level, as this already helps avoid the need for __dirname, an example using the 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();

Content of foo-bar.js:

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

What would be similar to doing without "Ecmascript Modules":

const fooBar = require('./foo-bar');
  • @Luizfelipe the alternative is "you don’t need it", the idea is that if there is a path that works you don’t have to patch or do tricks to simulate __dirname and __filename, the new URL + import.meta.url already solve everything natively no strings needed ;)

  • @Luizfelipe what perfectly new URL + import.meta.url with native Apis resolve ;), as described in the answer.

4


Equivalent to __filename

There is a import.meta.url, which returns the URL (do not confuse URL with path, or path in English) of the current file under the protocol file:. An example:

import.meta.url;
//=> file:///Path/to/file.js

But note that this is not, in fact, a way (path), but a URL string (which uses the protocol file:). To convert it to a valid path, you can use fileURLToPath, available in the module url. An example:

import { fileURLToPath } from 'url';

fileURLToPath(import.meta.url);
//=> /Path/to/file.js

Equivalent to __dirname

And, to get the directory of the current file, you can use the function dirname, present in the module path. An example:

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

dirname(fileURLToPath(import.meta.url));
//=> /Path/to

Remember that some Apis, such as the another answer points, can support URL instances directly to the convenience of the programmer. In such cases, it may actually be preferable to use this approach, since it avoids the use of the function url::fileURLToPath explicitly.

It is important to note, however, that the fileURLToPath will continue to be used by Node.js, which will end up using the same function internally to convert the URL instance passed into a path (path), as originally expected.

Reference

  • It is diverse to solutions with import.meta.url depending on the source Alternative for __dirname in Node when using the --experimental-modules flag, when I first saw these answers I found a little exaggeration of almost all, if we know that will always return file:// up to a slice or substr, or anything nearby would avoid importing an entire module just to solve the __filename and __dirname. Of course I understand that they are native Apis and so people use.

  • That’s why I formulated the answer, to explain and even put what it implies to use fileURLToPath, since the origin of the problem begins there.

  • What is said in the already formulated answer, it serves to solve "more things" and the problem of adding a / when it will treat something from Windows as it understands the C: or D: or ect as something else, nor did I comment on this because I already thought it was more than enough to say that fileURLToPath is made for other purposes, using it as in the source responses https://stackoverflow.com/a/50052194/1518921 may even work, it does not mean that it is the main objective or medium, Since it can solve reading files ;)

Browser other questions tagged

You are not signed in. Login or sign up in order to post.