DEV Community

Cover image for Alternative for __dirname in Node when using ECMAScript modules
Guilherme Nascimento
Guilherme Nascimento

Posted on

Alternative for __dirname in Node when using ECMAScript modules

As I searched the Stack Overflow I came across several solutions that suggest using import.meta.url with fileURLToPath, however what was not mentioned is that the purpose of fileURLToPath is beyond "resolving" URLs with file://, as the documentation itself demonstrates (url.fileURLToPath):

fileURLToPath('file:///C:/path/');    // Output:   C:\path\ (Windows)
fileURLToPath('file://nas/foo.txt');  // Output:   \\nas\foo.txt (Windows)
fileURLToPath('file:///你好.txt');    // Output:   /你好.txt (POSIX)
fileURLToPath('file:///hello world'); // Output:   /hello world (POSIX)
Enter fullscreen mode Exit fullscreen mode

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, as the Node.js documentation itself suggests that we use:

For example, reading a file at the same level as the current script:

import { readFileSync } from 'fs';

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

console.log(output.toString());
Enter fullscreen mode Exit fullscreen mode

List all files in the script directory:

import { readdirSync } from 'fs';

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

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

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.

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

Note that if you are interested in using something like "require" in strategic moments, you can use module.createRequire(filename) (Node 12.2.0+) to load scripts at different levels from the level of the current script, example:

import { createRequire } from 'module';
const require = createRequire(import.meta.url);

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

fooBar();
Enter fullscreen mode Exit fullscreen mode

foo-bar.js contents:

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

Top comments (3)

Collapse
 
eshimischi profile image
eshimischi

Some additions:

import { fileURLToPath } from 'url';

const __filename = fileURLToPath(import.meta.url);

new URL('file:///C:/path/').pathname; // Incorrect: /C:/path/
fileURLToPath('file:///C:/path/'); // Correct: C:\path\ (Windows)

new URL('file://nas/foo.txt').pathname; // Incorrect: /foo.txt
fileURLToPath('file://nas/foo.txt'); // Correct: \nas\foo.txt (Windows)

new URL('file:///你好.txt').pathname; // Incorrect: /%E4%BD%A0%E5%A5%BD.txt
fileURLToPath('file:///你好.txt'); // Correct: /你好.txt (POSIX)

new URL('file:///hello world').pathname; // Incorrect: /hello%20world
fileURLToPath('file:///hello world'); // Correct: /hello world (POSIX)
@since — v10.12.0

@param url — The file URL string or URL object to convert to a path.

@return — The fully-resolved platform-specific Node.js file path.

Collapse
 
routinepoutine profile image
Matthew

I'm here because of Express, where I can't apply style sheets to pug through EcmaScript 2015 import, but only through require && __dirname, 'public'

Collapse
 
brcontainer profile image
Guilherme Nascimento

Thanks for commenting. I didn't get to test the "pug" with express, but I believe that "import" recognizes the different formats ("modules" and "export"), maybe it is an internal problem, I will test it as soon as possible. If it is something you need to configure comment explaining here.