Alternative for __dirname in Node when using ECMAScript modules
Tags: javascript, ecmascript, node
Published: 2021-02-04 04:14:19

As I searched the [Stack Overflow](https://stackoverflow.com/q/46745014/1518921) 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`][1]): 

``` javascript
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)
```

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

- [**No __filename or __dirname**][2]

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

``` javascript
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:

``` javascript
import { readdirSync } from 'fs';

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

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

- [`fs.readFile(path[, options], callback)`][3] `path <string> | <Buffer> | <URL> | <integer>`
- [`fs.readFileSync(path[, options])`][4] `path <string> | <Buffer> | <URL> | <integer>`
- [`fsPromises.readdir(path[, options])`][5] `path <string> | <Buffer> | <URL>`
- [`fs.readdir(path[, options], callback)`][6] `path <string> | <Buffer> | <URL>`
- [`fs.readdirSync(path[, options])`][7] `path <string> | <Buffer> | <URL> | <integer>`

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)`][8]  (Node 12.2.0+) to load scripts at different levels from the level of the current script, example:

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

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

fooBar();
```

`foo-bar.js` contents:

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


  [1]: https://nodejs.org/api/url.html
  [2]: https://nodejs.org/dist/latest/docs/api/esm.html#esm_no_filename_or_dirname
  [3]: https://nodejs.org/api/fs.html#fs_fs_readfile_path_options_callback
  [4]: https://nodejs.org/api/fs.html#fs_fs_readfilesync_path_options
  [5]: https://nodejs.org/api/fs.html#fs_fspromises_readdir_path_options
  [6]: https://nodejs.org/api/fs.html#fs_fs_readdir_path_options_callback
  [7]: https://nodejs.org/api/fs.html#fs_fs_readdirsync_path_options
  [8]: https://nodejs.org/api/module.html#module_module_createrequire_filename