Reputation: 4853
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?
Upvotes: 397
Views: 216932
Reputation: 47614
Starting with Node.js 20.11 / 21.2, you can use import.meta.dirname
:
const __dirname = import.meta.dirname;
For Node.js 10.12 and higher there's an alternative that doesn't require creating multiple files and handles special characters in filenames across platforms:
import { dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = dirname(fileURLToPath(import.meta.url));
The built-in modules 'path'
and 'url'
can be optionally prefixed with the node
scheme as 'node:path'
and 'node:url'
since Node.js 14.14.
Upvotes: 639
Reputation: 4825
The most standardized way in 2021
Note: it doesn't work in Windows.
import { URL } from 'node: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;
Upvotes: 128
Reputation: 672
As of Node v21.2.0 and 20.11.0
import.meta.dirname
can replace __dirname
and
import.meta.filename
can replace __filename
const __dirname = import.meta.dirname;
const __filename = import.meta.filename;
Upvotes: 26
Reputation: 1391
Without additional imports:
const __dirname = new URL(import.meta.url + '/..').pathname
This adds a /
to the end.
Upvotes: 2
Reputation: 3856
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.
Upvotes: 3
Reputation: 18545
This works:
import path from 'node:path';
import url from 'node:url';
const DIR_NAME = path.dirname(url.fileURLToPath(import.meta.url));
Upvotes: 5
Reputation: 3903
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) } }) })
Upvotes: 0
Reputation: 11
i made 1 file const.js constaining :
Object.assign(global, {
__dirname: __dirname,
__filename: __filename,
});
then
import './const.js';
console.log(__dirname);
Upvotes: 1
Reputation: 3596
You can use the common-es package exactly for that:
npm i common-es
// # 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
Upvotes: 1
Reputation: 56936
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.
Upvotes: 2
Reputation: 373
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.
Upvotes: 2
Reputation: 507
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())
Upvotes: 0
Reputation: 9
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;
};
Upvotes: 0
Reputation: 6900
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)
Upvotes: 8
Reputation: 47
Just use path.resolve() method.
import { resolve } from 'path';
app.use('/public/uploads', express.static(resolve('public', 'uploads')))
Upvotes: 3
Reputation: 71384
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);
Upvotes: 65
Reputation: 740
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;
Upvotes: 9
Reputation: 1653
another option
import {createRequire} from 'module'; // need node v12.2.0
const require = createRequire(import.meta.url);
const __dirname = require.resolve.paths('.')[0];
Upvotes: 0
Reputation: 9714
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');
Upvotes: 43
Reputation: 754
process.cwd()
From documentation:
The
process.cwd()
method returns the current working directory of the Node.js process.
Upvotes: -6
Reputation: 71
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)
Upvotes: 6
Reputation: 3471
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 ); })();
Upvotes: 16
Reputation: 3603
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
Upvotes: 2
Reputation: 74
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("/"));
Upvotes: 2
Reputation: 13270
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.
Upvotes: 11
Reputation: 203231
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;
Upvotes: 19