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

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


当前回答

不管你是否同意使用global,我发现这是记忆和重构现有代码的最简单方法。

在代码执行的早期放置:

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

global.___filename = (path) => {
  return fileURLToPath(path);
};

global.___dirname = (path) => {
  return dirname(global.___filename(path));
};

然后在任何需要dirname或filename的文件中:

___filename(import.meta.url)
___dirname(import.meta.url)

当然,如果有宏,我就不需要传递import。meta了。Url,也许有改进。

其他回答

正如Geoff指出的那样,下面的代码返回的不是模块的路径,而是工作目录。

import path from 'path';
const __dirname = path.resolve();

使用——experimental-modules

2021年最标准化的方式

import { URL } from '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;

忘记使用join从当前文件创建路径,只使用URL

const pathToAdjacentFooFile = new URL('./foo.txt', import.meta.url).pathname;
const pathToUpperBarFile = new URL('../bar.json', import.meta.url).pathname;

在大多数情况下,使用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');

另一个选择

import {createRequire} from 'module'; // need node v12.2.0

const require = createRequire(import.meta.url);
const __dirname = require.resolve.paths('.')[0];

不管你是否同意使用global,我发现这是记忆和重构现有代码的最简单方法。

在代码执行的早期放置:

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

global.___filename = (path) => {
  return fileURLToPath(path);
};

global.___dirname = (path) => {
  return dirname(global.___filename(path));
};

然后在任何需要dirname或filename的文件中:

___filename(import.meta.url)
___dirname(import.meta.url)

当然,如果有宏,我就不需要传递import。meta了。Url,也许有改进。