为了使用ES6模块,我在运行Node应用程序时使用了——experimental-modules标志。

然而,当我使用这个标志时,元变量__dirname不可用。是否有另一种方法来获得与此模式兼容的存储在__dirname中的相同字符串?


当前回答

节点10.12 +…

假设你正在从一个模块中工作,这个解决方案应该可以工作,并且还提供了__filename支持

import path from 'node:path';
import { fileURLToPath } from 'node:url';

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

好的事情是,你也只需要两行代码就可以支持CommonJS模块的require()。为此,你可以补充:

import { createRequireFromPath } from 'module';
const require = createRequireFromPath(__filename); 

其他回答

我使用这个选项,因为路径以file://开始,只需删除该部分。

const __filename = import.meta.url.slice(7);
const __dirname = import.meta.url.slice(7, import.meta.url.lastIndexOf("/"));

在大多数情况下,使用Node.js的本机(带有ES模块),而不是外部资源,在大多数情况下使用__filename和__dirname是完全不必要的。大多数(如果不是全部)本地读取(流)方法都支持新的URL + import.meta。Url,正如官方文档本身所暗示的那样:

没有__filename或__dirname 不加载JSON模块 没有require.resolve

正如你在方法的描述中看到的,path参数显示了支持的格式,其中包括<URL>,示例:

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>

因此,有了新的URL('<path or file>', import.meta.url),它就解决了问题,而且你不需要处理字符串和创建变量,以便以后连接。

例子:

看看如何在不需要__filename或任何变通方法的情况下读取与脚本相同级别的文件:

import { readFileSync } from 'fs';

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

console.log(output.toString());

列出脚本目录下的所有文件:

import { readdirSync } from 'fs';

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

注意:在示例中,我使用同步函数只是为了便于复制和执行。

如果意图是制作一个依赖于第三方的“自己的日志”(或类似的东西),那么手动做一些事情是值得的,但在语言和Node.js中这是不必要的,使用ESMODULES完全可以不依赖__filename和__dirname,因为带有新URL的本地资源已经解决了这个问题。


注意,如果你对在战略时刻使用require之类的东西感兴趣,并且需要主脚本的绝对路径,你可以使用module.createRequire(filename)(仅适用于Node.js v12.2.0 +)结合import.meta.url来加载当前脚本级别以外的级别的脚本,因为这已经有助于避免对__dirname的需求,一个使用import.meta.url和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();

来源:foo-bar.js:

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

这类似于不使用“ECMAScript模块”:

const fooBar = require('./foo-bar');

节点10.12 +…

假设你正在从一个模块中工作,这个解决方案应该可以工作,并且还提供了__filename支持

import path from 'node:path';
import { fileURLToPath } from 'node:url';

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

好的事情是,你也只需要两行代码就可以支持CommonJS模块的require()。为此,你可以补充:

import { createRequireFromPath } from 'module';
const require = createRequireFromPath(__filename); 

我使用:

import path from 'path';

const __dirname = path.resolve(path.dirname(decodeURI(new URL(import.meta.url).pathname)));

decodeURI很重要:在我的测试系统的路径中使用空格和其他东西。

Path.resolve()处理相对url。

编辑:

修复支持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 ); })();
import path from 'path';
const __dirname = path.join(path.dirname(decodeURI(new URL(import.meta.url).pathname))).replace(/^\\([A-Z]:\\)/, "$1");

这段代码也适用于Windows。(替换在其他平台上是安全的,因为路径。join只在Windows上返回反斜杠分隔符)