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


315

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


1
您是否有可能不接受已接受的,错误的答案?
戴夫牛顿

9
尝试process.env.PWD...请参阅下面的答案。
亚历山大·米尔斯

Answers:


624

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

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

优点缺点

这在大多数情况下都可以正常工作,但是如果您使用pm2之类的启动器运行应用程序或运行mocha测试,则此方法将失败。

全局X

节点有一个名为的全局命名空间对象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');

优点缺点

可以始终如一地工作,但是您必须依赖全局变量,这意味着您无法轻松地重用组件/等。

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

应用程序根路径

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

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

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

优点缺点

在大多数情况下无需配置即可工作。还提供了一些不错的附加便利方法(请参阅项目页面)。最大的缺点是,如果出现以下情况,它将不起作用:

  • 您正在使用启动器,例如pm2
  • AND,该模块未安装在您应用的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

一个很好的方法是使用npm

"scripts": {
    "start": "NODE_PATH=. node app.js"
}

现在,您可以使用来启动您的应用程序了npm start,您会很高兴。我将其与我的force-node-path结合模块,这可以防止意外加载未NODE_PATH设置的应用程序。有关对执行环境变量的更多控制,请参见checkenv

一个陷阱: NODE_PATH 必须在节点应用程序外部设置。你不能做这样的事情process.env.NODE_PATH = path.resolve(__dirname)因为模块加载程序会在应用运行之前缓存要搜索的目录列表。

[16年4月6日增补]试图解决此问题的另一个非常有前途的模块是波浪形的


1
@Kevin在这种情况下,mocha是应用程序的入口。这只是为什么很难找到“项目根目录”的一个例子-它在很大程度上取决于情况和“项目根目录”的含义。
inxilpro

1
@凯文我完全理解。我的观点就是这样“项目根”的概念是容易的比一个懂计算机。如果您想要一个简单的方法,则需要对其进行配置。使用require.main.filename将工作大部分的时间,但不是所有的时间。
inxilpro 2014年

2
切向有关:这是组织节点的项目,这样你就不必对这个问题担心了一个令人难以置信的聪明的办法:allanhortle.com/2015/02/04/...
inxilpro

1
我不知道pm2是否发生变化或Node.js是否发生变化,但require.main.filename似乎可以与pm2一起工作。不知道摩卡咖啡。
贾斯汀·沃肯汀

8
path.parse(process.mainModule.filename).dir
Cory Robinson

53

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

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

如果需要可预测性和可靠性,则可能需要使您的应用程序要求设置某个环境变量。您的应用程序会查找MY_APP_HOME(或其他)内容,如果存在,并且该应用程序存在于该目录中,则一切正常。如果未定义或目录不包含您的应用程序,则它应退出并出现错误提示用户创建变量。可以将其设置为安装过程的一部分。

您可以使用诸如此类读取节点中的环境变量process.env.MY_ENV_VARIABLE


2
如果谨慎使用,这可能效果很好。但是在做bin/server.jsvs 时会给出不同的结果cd bin && server.js。(假设这些js文件被标记为可执行文件)
Myrne Stol 2013年

1
process.cwd()即使在运行摩卡测试时,使用对我来说也是一种魅力。谢谢!
Diogo Eichert

49

1-在项目根目录中创建一个文件,将其称为settings.js

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

module.exports = {
    POST_MAX_SIZE : 40 , //MB
    UPLOAD_MAX_FILE_SIZE: 40, //MB
    PROJECT_DIR : __dirname
};

3-在node_modules内部创建一个名为“设置”的新模块,并在模块index.js内部编写以下代码:

module.exports = require("../../settings");

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

var settings = require("settings");
settings.PROJECT_DIR; 

这样,您将拥有与此文件相关的所有项目目录;)


33
-1:要加载设置文件,您需要一个路径,然后获取该文件的参考路径?没有解决任何问题...
goliatone

2
感谢您抽出宝贵的时间来查看和编辑。它仍然感觉很脆弱,但这可能只是因为没有更好的方法来实现这一点
goliatone

8
用户需要牢记的是,这种方法node_modules通常不包含在版本控制中。因此,如果您与团队合作或需要克隆存储库,则必须提出另一种解决方案以使该设置文件保持同步。
Travesty3,2016年

@ Travesty3设置模块实际上是一个空模块,正在导出项目根目录中的文件内容:P
Fareed Alnamrouti

@goliatone使用他的解决方案,您可以从任何地方获取文件而无需知道其路径,您所需要知道的就是“设置”。没有它,您将必须明确知道要退出多少个文件夹,直到到达项目目录。之所以可行,是因为节点会自动搜索node_modules并始终知道其位置。

26

获得全局根的最简单方法(假设您使用NPM运行node.js应用程序“ npm start”等

var appRoot = process.env.PWD;

如果您想对以上内容进行交叉验证

假设您要交叉检查process.env.PWDnode.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.');
    }

您还可以使用此NPM模块:require('app-root-path')该模块非常适合此目的


5
在(大多数)Unix系统上,这非常有用。您希望npm模块/应用程序在Windows上运行时,它PWD是未定义的,因此会失败。
Jeremy Wiebe'3

1
process.cwd()
穆罕默德·乌默

@MuhammadUmer为什么process.cwd()总是与项目根目录相同?
亚历山大·米尔斯

如果您在根文件中调用它,那么它将是
Muhammad Umer

14

我发现即使是从子文件夹中调用应用程序时,这对我来说也是一致的,就像某些测试框架(例如Mocha)一样:

process.mainModule.paths[0].split('node_modules')[0].slice(0, -1);

工作原理:

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

这只是一行代码,但是为了简单起见(我为我着想),我将其黑盒装在NPM模块中:

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

请享用!


1
process.mainModule 从以下版本开始停用:v14.0.0-require.main.paths[0].split('node_modules')[0].slice(0, -1);改为使用。
RobC

10

所有这些“根目录”大部分都需要将一些虚拟路径解析为真实的堆路径,所以您应该看看path.resolve吗?

var path= require('path');
var filePath = path.resolve('our/virtual/path.ext');

9

只需将此行添加到root中的模块中,通常就是app.js

global.__basedir = __dirname;

然后,_basedir将可用于所有模块。


8

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


7

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

import * as path from 'path'
const projectRootPath = path.resolve(__dirname)
export const rootPath = projectRootPath

4

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

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

app.use('/myroute', myRoute);

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

如果您的app.js位于项目的根目录(默认情况下位于目录根目录)中,则此方法有效。


4

将此添加到主应用程序文件(例如app.js)开头的位置:

global.__basedir = __dirname;

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

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

简单...



3

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

const {INIT_CWD} = process.env; // process.env.INIT_CWD 
const paths = require(`${INIT_CWD}/config/paths`);

祝好运...


1
对于作为操作项目的软件包的附件而言,它的工作方式类似于安装后的步骤。但是,我尚未在另一层依赖项中对其进行测试,在该层中,一个项目使用的依赖项使用了我的包。
JamesDev

1
@JamesDev,INIT_CWD解析为directory从其中npm-script被exectued。
阿卡什(Akash)

2

如果您想从正在运行的node.js应用程序确定项目根目录,也可以轻松进行。

process.mainModule.path

1

在主文件的顶部添加:

mainDir = __dirname;

然后在需要的任何文件中使用它:

console.log('mainDir ' + mainDir);
  • mainDir是全局定义的,如果仅在当前文件中需要,则使用- __dirname代替。
  • 主文件通常是在项目的根文件夹并命名一样main.jsindex.jsgulpfile.js

1

我用这个

为我的模块命名 mymodule

var BASE_DIR = __dirname.replace(/^(.*\/mymodule)(.*)$/, '$1')


1

让它性感sexy。

const users = require('../../../database/users'); // 👎 what you have
// OR
const users = require('$db/users'); // 👍 no matter how deep you are
const products = require('/database/products'); // 👍 alias or pathing from root directory


解决丑路问题的三个简单步骤。

  1. 安装软件包: npm install sexy-require --save
  2. require('sexy-require')在主应用程序文件的顶部包含一次。

    require('sexy-require');
    const routers = require('/routers');
    const api = require('$api');
    ...
  3. 可选步骤。路径配置可以在.paths项目根目录下的文件中定义。

    $db = /server/database
    $api-v1 = /server/api/legacy
    $api-v2 = /server/api/v2

似乎很体面,可惜它有这么荒谬的名字。
JHH

@JHH好...我必须找到一个更好的名字
苏丹

1

这将逐步降低目录树,直到它包含一个node_modules目录,该目录通常指示您的项目根目录:

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

function getProjectRoot(currentDir = __dirname.split(path.sep)) {
  if (!currentDir.length) {
    throw Error('Could not find project root.')
  }
  const nodeModulesPath = currentDir.concat(['node_modules']).join(path.sep)
  if (fs.existsSync(nodeModulesPath) && !currentDir.includes('node_modules')) {
    return currentDir.join(path.sep)
  }
  return this.getProjectRoot(currentDir.slice(0, -1))
}

它还确保node_modules返回的路径中没有任何内容,因为这意味着它包含在嵌套软件包安装中。


1

process.mainModule弃用因为V 14.0.0。参考答案时,请使用 require.main,其余的仍然保留。

process.mainModule.paths
  .filter(p => !p.includes('node_modules'))
  .shift()

获取主模块中的所有路径,并使用“ node_modules”过滤掉所有路径,然后获取剩余路径列表中的第一个。意外行为不会引发错误,只是一个undefined

即使调用ie,对我也很好$ mocha


0

在app.js中创建一个函数

/*Function to get the app root folder*/

var appRootFolder = function(dir,level){
    var arr = dir.split('\\');
    arr.splice(arr.length - level,level);
    var rootFolder = arr.join('\\');
    return rootFolder;
}

// view engine setup
app.set('views', path.join(appRootFolder(__dirname,1),'views'));

0

您只需将根目录路径添加到express应用程序变量中,然后从应用程序获取此路径。为此,添加app.set('rootDirectory', __dirname);您的index.js或app.js文件。并req.app.get('rootDirectory')用于在代码中获取根目录路径。


0

我知道是老问题,但是没有提及要使用的问题 progress.argv。argv数组包含完整的路径名和文件名(带或不带.js扩展名),用作节点要执行的参数。因为它也可以包含标志,所以必须对其进行过滤。

这不是您可以直接使用的示例(因为使用了我自己的框架),但是我认为它为您提供了一些方法。我还使用一种缓存方法来避免调用此函数给系统带来过多压力,尤其是在未指定扩展名(并且需要检查文件是否存在)的情况下,例如:

node myfile

要么

node myfile.js

这就是我缓存它的原因,另请参见下面的代码。


function getRootFilePath()
{
        if( !isDefined( oData.SU_ROOT_FILE_PATH ) )
        {
            var sExt = false;

            each( process.argv, function( i, v )
            {
                 // Skip invalid and provided command line options
                if( !!v && isValidString( v ) && v[0] !== '-' )
                {
                    sExt = getFileExt( v );

                    if( ( sExt === 'js' ) || ( sExt === '' && fileExists( v+'.js' )) )
                    {

                        var a = uniformPath( v ).split("/"); 

                         // Chop off last string, filename
                        a[a.length-1]='';

                         // Cache it so we don't have to do it again.
                        oData.SU_ROOT_FILE_PATH=a.join("/"); 

                         // Found, skip loop
                        return true;
                    }
                }
            }, true ); // <-- true is: each in reverse order
        }

        return oData.SU_ROOT_FILE_PATH || '';
    }
}; 

0

找到电子应用程序的根路径可能会很棘手。因为在不同的条件(例如生产,开发和打包条件)下,主流程和渲染器的根路径不同。

我已经编写了一个npm软件包电子根路径来捕获电子应用程序的根路径。

$ npm install electron-root-path

or 

$ yarn add electron-root-path


// Import ES6 way
import { rootPath } from 'electron-root-path';

// Import ES2015 way
const rootPath = require('electron-root-path').rootPath;

// e.g:
// read a file in the root
const location = path.join(rootPath, 'package.json');
const pkgInfo = fs.readFileSync(location, { encoding: 'utf8' });



0

前言

这是一个非常老的问题,但它似乎在2020年和2012年一样令人不安。我检查了所有其他答案,但没有找到技术(请注意,这是有局限性的,但其他所有条件都没有)同样适用于所有情况)。

GIT +子进程

如果将GIT用作版本控制系统,则可以将确定项目根目录的问题减少到(我认为这是项目的正确根目录-毕竟,您希望VCS具有尽可能完整的可见性范围) :

检索式存储库根路径

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

我发现spawnSync()最适合这份工作。至于要运行的实际命令,git worktree(带有--porcelain易于解析选项),我们就需要获取绝对根路径。

在示例中,我选择返回一个路径数组,因为可能有一个以上的工作树(尽管它们可能具有共同的路径)只是为了确定。请注意,由于我们使用CLI命令,因此shell应将选项设置为true(安全性不成问题,因为没有不可信的输入)。

方法比较和后备

了解到VCS不可访问的情况,在分析了文档和其他答案之后,我提供了一些后备方法。综上所述,建议的解决方案归结为(不包括第三方模块和特定于软件包的):

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

从上面的比较表中,最通用的是两种方法:

  • require.main.filename如果require.main === module满足的话,这是一种容易扎根的方法
  • node_modules最近提出的树木漫步使用另一个假设:

如果模块的目录中包含node_modules目录,则可能是根目录

对于主应用程序,它将获取应用程序根目录;对于模块,将获取其应用程序根目录。

后退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 并不少见,也不安全)。如上所述,可以通过后备情况来进行调解。

笔记

  1. 进一步扩展方法1的一些想法:
    • 介绍配置作为功能参数
    • export 使其成为模块的功能
    • 检查是否已安装和/或初始化了GIT

参考文献

  1. git worktree 参考
  2. spawnSync 参考
  3. require.main 参考
  4. path.dirname() 参考


-1

尝试 path._makeLong('some_filename_on_root.js');

例:

cons path = require('path');
console.log(path._makeLong('some_filename_on_root.js');

这将从节点应用程序的根目录返回完整路径(package.json的相同位置)


-1

只需使用:

 path.resolve("./") ... output is your project root directory

这很棒!path.resolve( “”)的作品,以及
诺埃尔·申克

这样只会给出当前目录,而该目录可能不是根目录。
奥拉德

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.