除了process.cwd()之外,是否有其他方法来获取当前项目根目录的路径名。Node是否实现了ruby的属性Rails.root之类的东西。我要找的是稳定可靠的东西。


当前回答

你可以简单地在express app变量中添加根目录路径,并从app中获取这个路径。在index.js或app.js文件中。并使用req.app.get('rootDirectory')在代码中获取根目录路径。

其他回答

序言

这是一个非常古老的问题,但它似乎在2020年和2012年一样触动了人们的神经。 我检查了所有其他的答案,没有找到下面提到的技巧(它有自己的局限性,但其他的也不是适用于所有的情况):

Git +子进程

如果你使用Git作为你的版本控制系统,确定项目根的问题可以简化为(我认为项目的正确根-毕竟,你会希望你的VCS有尽可能充分的可见范围):

检索存储库根路径

由于您必须运行CLI命令来执行此操作,因此我们需要生成一个子进程。此外,由于项目根不太可能在运行时中期更改,我们可以在启动时使用child_process模块的同步版本。

我发现spawnSync()最适合这项工作。至于实际要运行的命令,检索根目录的绝对路径所需要的就是git工作树(带有——porcelain选项,以便于解析)。

在答案后面的示例中,我选择返回一个路径数组,因为可能存在多个工作树(尽管它们可能具有公共路径)。注意,当我们使用CLI命令时,shell选项应该设置为true(安全性不应该成为问题,因为没有不可信的输入)。

方法比较和后备

了解到VCS可能无法访问的情况,在分析了文档和其他答案后,我包含了一些备用方案。建议解决方案可归纳为(不含第三方模块和包):

Solution Advantage Main Problem
__filename points to module file relative to module
__dirname points to module dir same as __filename
node_modules tree walk nearly guaranteed root complex tree walking if nested
path.resolve(".") root if CWD is root same as process.cwd()
process.argv\[1\] same as __filename same as __filename
process.env.INIT_CWD points to npm run dir requires npm && CLI launch
process.env.PWD points to current dir relative to (is the) launch dir
process.cwd() same as env.PWD process.chdir(path) at runtime
require.main.filename root if === module fails on required modules

从上面的对比表来看,以下方法是最普遍的:

require.main.filename作为获取根文件的简单方法。Main ===模块满足 最近提出的Node_modules树遍历使用了另一个假设:

如果模块的目录中有node_modules dir,那么它很可能是根目录

对于主应用程序,它将获得应用程序根,而对于一个模块——它的项目根。

回退1。树走

我的实现使用了一种更宽松的方法,一旦找到目标目录就停止,因为对于给定的模块,它的根是项目根。我们可以链接调用或扩展它,使搜索深度可配置:

/**
 * @summary gets root by walking up node_modules
 * @param {import("fs")} fs
 * @param {import("path")} pt
 */
const getRootFromNodeModules = (fs, pt) =>

    /**
     * @param {string} [startPath]
     * @returns {string[]}
     */
    (startPath = __dirname) => {

        //avoid loop if reached root path
        if (startPath === pt.parse(startPath).root) {
            return [startPath];
        }

        const isRoot = fs.existsSync(pt.join(startPath, "node_modules"));

        if (isRoot) {
            return [startPath];
        }

        return getRootFromNodeModules(fs, pt)(pt.dirname(startPath));
    };

回退2。主模块

第二个实现很简单:

/**
 * @summary gets app entry point if run directly
 * @param {import("path")} pt
 */
const getAppEntryPoint = (pt) =>

    /**
     * @returns {string[]}
     */
    () => {

        const { main } = require;

        const { filename } = main;

        return main === module ?
            [pt.parse(filename).dir] :
            [];
    };

实现

我建议使用树行者作为首选的备用工具,因为它更多功能:

const { spawnSync } = require("child_process");
const pt = require('path');
const fs = require("fs");

/**
 * @summary returns worktree root path(s)
 * @param {function : string[] } [fallback]
 * @returns {string[]}
 */
const getProjectRoot = (fallback) => {

    const { error, stdout } = spawnSync(
        `git worktree list --porcelain`,
        {
            encoding: "utf8",
            shell: true
        }
    );

    if (!stdout) {
        console.warn(`Could not use GIT to find root:\n\n${error}`);
        return fallback ? fallback() : [];
    }

    return stdout
        .split("\n")
        .map(line => {
            const [key, value] = line.split(/\s+/) || [];
            return key === "worktree" ? value : "";
        })
        .filter(Boolean);
};

缺点

最明显的是安装和初始化Git,这可能是不可取的/不可信的(旁注:在生产服务器上安装Git并不少见,也不是不安全的)。如上所述,可以通过回退来调节。

__dirname不是全局变量;它是当前模块的局部值,因此每个文件都有自己的局部值,不同的值。

如果您想要运行进程的根目录,则可能需要使用process.cwd()。

如果希望获得可预测性和可靠性,那么可能需要将设置某个环境变量作为应用程序的要求。你的应用程序寻找MY_APP_HOME(或任何东西),如果它在那里,应用程序存在于该目录,那么一切都很好。如果它是未定义的,或者目录不包含您的应用程序,那么它应该退出,并提示用户创建变量。可以将其设置为安装过程的一部分。

您可以使用process.env.MY_ENV_VARIABLE来读取节点中的环境变量。

在你的主应用文件(例如app.js)开始的地方添加这个:

global.__basedir = __dirname;

这将设置一个全局变量,该变量将始终等同于应用程序的基本目录。像使用其他变量一样使用它:

const yourModule = require(__basedir + '/path/to/module.js');

简单的…

path.dirname(process.mainModule.filename);

尝试从__dirname向上遍历,直到找到一个包。Json,并确定这是你当前文件所属的应用主根目录。

根据Node文档

包。json文件通常位于Node.js项目的根目录。

const fs = require('fs')
const path = require('path')

function getAppRootDir () {
  let currentDir = __dirname
  while(!fs.existsSync(path.join(currentDir, 'package.json'))) {
    currentDir = path.join(currentDir, '..')
  }
  return currentDir
}