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

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


当前回答

只需使用path.resolve()方法即可。

import { resolve } from 'path';

app.use('/public/uploads', express.static(resolve('public', 'uploads')))

其他回答

您可以使用来自新的Error()的堆栈。这个错误不需要被抛出,也不会停止程序的执行。堆栈的第一行始终是错误及其消息,第二行是调用错误的文件。

由于这是一个方法(可能在util.js文件中),getDirname()调用的实际位置实际上是错误堆栈的第三行。

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;
};

在大多数情况下,使用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 path from 'path';
const __dirname = path.join(path.dirname(decodeURI(new URL(import.meta.url).pathname))).replace(/^\\([A-Z]:\\)/, "$1");

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

我创建了这个模块es-dirname,它将返回当前脚本dirname。

import dirname from 'es-dirname'

console.log(dirname())

它既适用于CommonJs脚本,也适用于Windows和Linux上的ES模块。

打开一个问题,如果有一个错误,因为脚本一直在我的项目中工作,但在其他一些情况下可能会失败。因此,不要在生产环境中使用它。这是一个临时的解决方案,我相信Node.js团队在不久的将来会发布一个健壮的方法来实现它。

不管你是否同意使用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,也许有改进。