从正在运行的node.js应用程序确定项目根目录

MrE*_*vil 284 node.js

有没有比process.cwd()确定正在运行的node.js进程的根目录更好的方法?类似的东西Rails.root,但对于Node.js. 我正在寻找尽可能可预测和可靠的东西.

inx*_*pro 559

有几种方法可以解决这个问题,每种方法各有利弊:

require.main.filename

来自http://nodejs.org/api/modules.html:

当文件直接从Node运行时,require.main设置为它module.这意味着您可以通过测试确定文件是否已直接运行require.main === module

因为module提供了一个filename属性(通常相当于__filename),所以可以通过检查获得当前应用程序的入口点require.main.filename.

因此,如果您想要应用程序的基本目录,您可以执行以下操作:

var path = require('path');
var appDir = path.dirname(require.main.filename);
Run Code Online (Sandbox Code Playgroud)

优点缺点

这在大多数情况下都会很好用,但如果您使用像pm2这样的启动器运行应用程序或运行mocha测试,则此方法将失败.

global.X

Node有一个名为的全局名称空间对象global- 您附加到此对象的任何内容都将在您的应用程序中随处可用.因此,在您的index.js(或者app.js您的主应用程序文件被命名)中,您可以只定义一个全局变量:

// index.js
var path = require('path');
global.appRoot = path.resolve(__dirname);

// lib/moduleA/component1.js
require(appRoot + '/lib/moduleB/component2.js');
Run Code Online (Sandbox Code Playgroud)

优点缺点

一致地工作,但你必须依赖一个全局变量,这意味着你不能轻易地重用组件/等.

process.cwd()

这将返回当前工作目录.不可靠可言,因为它完全依赖于哪个目录的过程中推出:

$ cd /home/demo/
$ mkdir subdir
$ echo "console.log(process.cwd());" > subdir/demo.js
$ node subdir/demo.js
/home/demo
$ cd subdir
$ node demo.js
/home/demo/subdir
Run Code Online (Sandbox Code Playgroud)

应用根路径

为了解决这个问题,我创建了一个名为app-root-path的节点模块.用法很简单:

var appRoot = require('app-root-path');
var myModule = require(appRoot + '/lib/my-module.js');
Run Code Online (Sandbox Code Playgroud)

应用程序根路径模块使用多种不同的技术来确定该应用程序的根路径,考虑到全球范围内安装的模块(例如,如果应用程序在运行/var/www/,但模块安装在~/.nvm/v0.x.x/lib/node/).它不会100%的工作时间,但它可以在大多数常见的情况下工作.

优点缺点

大多数情况下无需配置即可工作 还提供了一些不错的额外便利方法(参见项目页面).最大的问题是,如果:

  • 你正在使用像pm2这样的启动器
  • 并且,模块未安装在应用程序的node_modules目录中(例如,如果您全局安装它)

您可以通过设置APP_ROOT_PATH环境变量或通过调用.setPath()模块来解决这个问题,但在这种情况下,您最好使用该global方法.

NODE_PATH环境变量

如果您正在寻找确定当前应用程序根路径的方法,则上述解决方案之一可能最适合您.另一方面,如果您正在尝试可靠地解决加载应用模块的问题,我强烈建议您查看NODE_PATH环境变量.

Node的模块系统在各种位置查找模块. 其中一个位置是process.env.NODE_PATH点数.如果设置此环境变量,则可以require使用标准模块加载器的模块,而无需进行任何其他更改.

例如,如果设置NODE_PATH/var/www/lib,则以下内容可以正常工作:

require('module2/component.js');
// ^ looks for /var/www/lib/module2/component.js
Run Code Online (Sandbox Code Playgroud)

一个很好的方法是使用npm:

"scripts": {
    "start": "NODE_PATH=. node app.js"
}
Run Code Online (Sandbox Code Playgroud)

现在你可以启动你的应用程序npm start而且你是金色的.我将它与我的enforce-node-path模块结合起来,这可以防止在没有NODE_PATH设置的情况下意外加载应用程序.有关强制执行环境变量的更多控制,请参阅checkenv.

一个问题: NODE_PATH 必须在节点应用之外设置.你不能做一些事情,比如process.env.NODE_PATH = path.resolve(__dirname)因为模块加载器缓存它将在你的应用运行之前搜索的目录列表.

[16/6/16添加]试图解决这个问题的另一个非常有前途的模块是波浪状的.

  • `path.parse(process.mainModule.filename).dir` (8认同)
  • 切向相关:这是组织Node项目的一种非常聪明的方式,因此您不必担心这个问题:http://allanhortle.com/2015/02/04/store-your-client-code -in-node_modules.html (2认同)

izb*_*izb 50

__dirname不是全球性的; 它是当前模块的本地模块,因此每个文件都有自己的本地不同值.

如果您想要正在运行的进程的根目录,您可能确实想要使用process.cwd().

如果您想要可预测性和可靠性,那么您可能需要将应用程序的要求设置为某个环境变量.你的应用程序寻找MY_APP_HOME(或者其他),如果它在那里,应用程序存在于该目录中,那么一切都很好.如果未定义或目录不包含您的应用程序,则应退出并显示错误提示用户创建变量.它可以设置为安装过程的一部分.

您可以使用类似的内容读取节点中的环境变量process.env.MY_ENV_VARIABLE.

  • 如果谨慎使用,这可能会很好.但是当执行`bin/server.js` vs`cd bin && server.js`时会产生不同的结果.(假设这些js文件被标记为可执行) (2认同)
  • 使用“process.cwd()”对我来说就像一个魅力,即使在运行摩卡测试时也是如此。谢谢你! (2认同)

Far*_*uti 44

1-在项目根目录中创建一个文件,调用它settings.js

2-在此文件中添加此代码

module.exports = {
    POST_MAX_SIZE : 40 , //MB
    UPLOAD_MAX_FILE_SIZE: 40, //MB
    PROJECT_DIR : __dirname
};
Run Code Online (Sandbox Code Playgroud)

3-在node_modules里面创建一个新的模块名称"settings"并在模块index.js里面写下这段代码:

module.exports = require("../../settings");
Run Code Online (Sandbox Code Playgroud)

4-以及您希望项目目录使用的任何时间

var settings = require("settings");
settings.PROJECT_DIR; 
Run Code Online (Sandbox Code Playgroud)

通过这种方式,您将拥有与此文件相关的所有项目目录;)

  • -1:要加载设置文件,您需要一个路径,然后获取该文件的参考路径?没解决任何问题...... (32认同)
  • 用户希望用这种方法记住的一点是`node_modules`经常被排除在版本控制之外.因此,如果您与团队合作或者需要克隆您的存储库,您将不得不提出另一种解决方案来保持该设置文件同步. (6认同)
  • 赞成花时间审查和编辑.它仍然感觉很脆弱,但这可能只是因为没有更好的方法来实现这一目标 (2认同)

Van*_*Jr. 34

简单的:

require('path').resolve('./')
Run Code Online (Sandbox Code Playgroud)

  • [“如果在处理所有给定的路径段后,尚未生成绝对路径,则使用当前工作目录。”](https://nodejs.org/api/path.html#path_path_resolve_paths)。这只是执行“process.cwd()”的一种迂回方式。 (5认同)
  • 从根文件夹外部运行节点应用程序时,这不起作用! (4认同)

Ale*_*lls 20

获取全局根的最简单方法(假设你使用NPM运行你的node.js app'npm start'等)

var appRoot = process.env.PWD;
Run Code Online (Sandbox Code Playgroud)

如果你想交叉验证上面的内容

假设您要process.env.PWD与node.js应用程序的设置进行交叉检查.如果你想要一些运行时测试来检查它的有效性process.env.PWD,你可以用这个代码进行交叉检查(我编写的代码似乎运行良好).您可以使用package.json文件中的npm_package_name来交叉检查appRoot中最后一个文件夹的名称,例如:

    var path = require('path');

    var globalRoot = __dirname; //(you may have to do some substring processing if the first script you run is not in the project root, since __dirname refers to the directory that the file is in for which __dirname is called in.)

    //compare the last directory in the globalRoot path to the name of the project in your package.json file
    var folders = globalRoot.split(path.sep);
    var packageName = folders[folders.length-1];
    var pwd = process.env.PWD;
    var npmPackageName = process.env.npm_package_name;
    if(packageName !== npmPackageName){
        throw new Error('Failed check for runtime string equality between globalRoot-bottommost directory and npm_package_name.');
    }
    if(globalRoot !== pwd){
        throw new Error('Failed check for runtime string equality between globalRoot and process.env.PWD.');
    }
Run Code Online (Sandbox Code Playgroud)

你也可以使用这个NPM模块:require('app-root-path')它非常适用于此目的

  • `process.cwd()` (5认同)
  • 这在(大多数)unix系统上运行良好.一旦你想让你的npm模块/应用程序在Windows上工作,`PWD`是未定义的,这就失败了. (4认同)

Ole*_*ter 19

前言

\n

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

\n

Git + 子进程

\n

如果您使用 Git 作为版本控制系统,则确定项目根的问题可以简化为(我会考虑项目的正确根 - 毕竟,您希望您的 VCS 具有尽可能完整的可见性范围) :

\n
\n

检索存储库根路径

\n
\n

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

\n

我发现spawnSync()最适合这份工作。至于实际运行的命令git worktree(带有--porcelain易于解析的选项)是检索根的绝对路径所需的全部内容。

\n

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

\n

方法比较和回退

\n

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

\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n
解决方案优势主要问题
__filename指向模块文件相对于模块
__dirname指向模块目录与...一样__filename
node_modules树上漫步几乎保证根如果嵌套则复杂的树行走
path.resolve(".")如果 CWD 是 root,则为 root与...一样process.cwd()
process.argv\\[1\\]与...一样__filename与...一样__filename
process.env.INIT_CWD指向npm run目录需要npm&& CLI 启动
process.env.PWD指向当前目录相对于(是)启动目录
process.cwd()与...一样env.PWDprocess.chdir(path)在运行时
require.main.filename根如果=== modulerequired 模块失败
\n
\n

从上面的比较表来看,以下方法是最通用的:

\n
    \n
  • require.main.filenamerequire.main === module作为获得root if 的简单方法
  • \n
  • node_modules最近提出的树行走使用了另一个假设:
  • \n
\n
\n

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

\n
\n

对于主应用程序,它将获取应用程序根目录,对于模块 \xe2\x80\x94 ,它将获取其项目根目录。

\n

后备 1. 树行走

\n

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

\n
/**\n * @summary gets root by walking up node_modules\n * @param {import("fs")} fs\n * @param {import("path")} pt\n */\nconst getRootFromNodeModules = (fs, pt) =>\n\n    /**\n     * @param {string} [startPath]\n     * @returns {string[]}\n     */\n    (startPath = __dirname) => {\n\n        //avoid loop if reached root path\n        if (startPath === pt.parse(startPath).root) {\n            return [startPath];\n        }\n\n        const isRoot = fs.existsSync(pt.join(startPath, "node_modules"));\n\n        if (isRoot) {\n            return [startPath];\n        }\n\n        return getRootFromNodeModules(fs, pt)(pt.dirname(startPath));\n    };\n
Run Code Online (Sandbox Code Playgroud)\n

回退2.主模块

\n

第二个实现很简单:

\n
/**\n * @summary gets app entry point if run directly\n * @param {import("path")} pt\n */\nconst getAppEntryPoint = (pt) =>\n\n    /**\n     * @returns {string[]}\n     */\n    () => {\n\n        const { main } = require;\n\n        const { filename } = main;\n\n        return main === module ?\n            [pt.parse(filename).dir] :\n            [];\n    };\n
Run Code Online (Sandbox Code Playgroud)\n

执行

\n

我建议使用树步行者作为首选后备,因为它更通用:

\n
const { spawnSync } = require("child_process");\nconst pt = require(\'path\');\nconst fs = require("fs");\n\n/**\n * @summary returns worktree root path(s)\n * @param {function : string[] } [fallback]\n * @returns {string[]}\n */\nconst getProjectRoot = (fallback) => {\n\n    const { error, stdout } = spawnSync(\n        `git worktree list --porcelain`,\n        {\n            encoding: "utf8",\n            shell: true\n        }\n    );\n\n    if (!stdout) {\n        console.warn(`Could not use GIT to find root:\\n\\n${error}`);\n        return fallback ? fallback() : [];\n    }\n\n    return stdout\n        .split("\\n")\n        .map(line => {\n            const [key, value] = line.split(/\\s+/) || [];\n            return key === "worktree" ? value : "";\n        })\n        .filter(Boolean);\n};\n
Run Code Online (Sandbox Code Playgroud)\n

缺点

\n

最明显的一个是安装并初始化 Git,这可能是不可取的/难以置信的(附注:在生产服务器上安装Git并不罕见,也不是不安全)。可以通过如上所述的后备来调解。

\n


did*_*xga 12

就像将这一行添加到 root 中的模块一样简单,通常它是 app.js

global.__basedir = __dirname;
Run Code Online (Sandbox Code Playgroud)

然后 _basedir 将可以访问您的所有模块。


小智 10

我发现这对我来说一直都有效,即使从子文件夹调用应用程序,因为它可以与一些测试框架一样,如Mocha:

process.mainModule.paths[0].split('node_modules')[0].slice(0, -1);
Run Code Online (Sandbox Code Playgroud)

为什么会这样:

在运行时节点创建所有已加载文件的完整路径的注册表.模块首先加载,因此位于此注册表的顶部.通过选择注册表的第一个元素并在'node_modules'目录之前返回路径,我们可以确定应用程序的根目录.

它只是一行代码,但为了简单起见(我的缘故),我把它装入一个NPM模块:

https://www.npmjs.com/package/node-root.pddivine

请享用!

  • `process.mainModule` [自 v14.0.0 起已弃用](https://nodejs.org/api/process.html#process_process_mainmodule) - 使用 `require.main.paths[0].split('node_modules')[0 ].slice(0, -1);` 代替。 (2认同)

Aka*_*ash 9

上有一个INIT_CWD属性process.env。这就是我目前在我的项目中正在使用的内容。

const {INIT_CWD} = process.env; // process.env.INIT_CWD 
const paths = require(`${INIT_CWD}/config/paths`);
Run Code Online (Sandbox Code Playgroud)

祝你好运...

  • 对于一个包来说,它就像一个魅力一样,可以作为安装后步骤来操作它所调用的项目。然而,我还没有在另一层依赖项中测试它,其中项目使用使用我的包的依赖项。 (2认同)
  • @JamesDev,“INIT_CWD”解析为执行“npm-script”的“目录”。 (2认同)

Kon*_*aev 7

所有这些"根目录"主要需要解决一些虚拟路径到一个真正的桩路径,所以你应该看看path.resolve

var path= require('path');
var filePath = path.resolve('our/virtual/path.ext");
Run Code Online (Sandbox Code Playgroud)


kvz*_*kvz 7

也许您可以尝试向上遍历,__filename直到找到a package.json,并确定它是您当前文件所属的主目录.


Ben*_*ies 6

我发现在使用 express 时有用的一种技术是在设置任何其他路由之前将以下内容添加到 app.js

// set rootPath
app.use(function(req, res, next) {
  req.rootPath = __dirname;
  next();
});

app.use('/myroute', myRoute);
Run Code Online (Sandbox Code Playgroud)

无需使用全局变量,并且您将根目录的路径作为请求对象的属性。

如果您的 app.js 位于项目的根目录中,默认情况下,这会起作用。


Avi*_*uva 5

实际上,我发现最鲁棒的解决方案也许也是最简单的:您只需将以下文件放在项目的根目录中:root-path.js,其中包含以下代码:

import * as path from 'path'
const projectRootPath = path.resolve(__dirname)
export const rootPath = projectRootPath
Run Code Online (Sandbox Code Playgroud)