NodeJS 使用 ES6 Modules 时获取 __dirname 和 __filename

背景

在使用 NodeJS 编写 ES Modules 代码时,是不可以和写 CommonJS 一样直接使用的 __dirname__filename 变量的。

解决

最好的做法是使用 url.fileURLToPath:

1
2
3
4
5
import { dirname } from 'path';
import { fileURLToPath } from 'url';

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

有的地方会提到 new URL('', import.meta.url).pathname 这种方式,这是并不推荐的,在官方文档对于 url.fileURLToPath 的说明有一些例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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)

另外要注意的一点是在 Vite 2 的 vite.config.js 中是不需要这么做的,在这里可以看到 Vite 会替换 vite.config.ts 文件中的 import.meta.url, __filename__dirname 为对应路径,所以我们可以直接使用。

同时,如果你的 NodeJS 程序只是用来读取文件,是没有太大必要使用 __dirname 的。多数的方法 path 参数是支持 <URL> 的,这意味着通过 new URL + import.meta.url 即可,例如:

Methodpath 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>

还有很多方法都支持 <URL> 参数,所以使用 new URL('<path or file>', import.meta.url) 就可以解决问题,不再需要通过 __dirname__filename

References