从正在运行的 node.js 应用程序确定项目根目录
Posted
技术标签:
【中文标题】从正在运行的 node.js 应用程序确定项目根目录【英文标题】:Determine project root from a running node.js application 【发布时间】:2012-05-03 04:39:38 【问题描述】:除了process.cwd()
,还有其他方法可以获取当前项目根目录的路径名吗? Node 是否实现了类似 ruby 的属性Rails.root
。我正在寻找稳定可靠的东西。
【问题讨论】:
您是否有可能不接受已接受的错误答案? 试试process.env.PWD
...看看我下面的答案。
尝试 path.dirname(require.main.filename)
【参考方案1】:
__dirname
不是全局的;它是当前模块的本地文件,因此每个文件都有自己的本地不同值。
如果你想要运行进程的根目录,你可能确实想使用process.cwd()
。
如果您想要可预测性和可靠性,那么您可能需要让您的应用程序要求设置某个环境变量。您的应用程序查找MY_APP_HOME
(或其他),如果它在那里,并且应用程序存在于该目录中,那么一切都很好。如果它未定义或目录不包含您的应用程序,那么它应该退出并提示用户创建变量的错误。它可以设置为安装过程的一部分。
您可以使用process.env.MY_ENV_VARIABLE
之类的方式读取节点中的环境变量。
【讨论】:
如果谨慎使用,这可能会很好。但是在执行bin/server.js
与cd bin && server.js
时会给出不同的结果。 (假设这些 js 文件被标记为可执行)
使用 process.cwd()
对我来说就像是一种魅力,即使在运行 mocha 测试时也是如此。谢谢!【参考方案2】:
1- 在项目根目录中创建一个文件,命名为 settings.js
2- 在这个文件中添加这个代码
module.exports =
POST_MAX_SIZE : 40 , //MB
UPLOAD_MAX_FILE_SIZE: 40, //MB
PROJECT_DIR : __dirname
;
3- 在 node_modules 中创建一个新的模块,命名为“settings”,并在模块 index.js 中编写以下代码:
module.exports = require("../../settings");
4- 任何时候你想要你的项目目录,只要使用
var settings = require("settings");
settings.PROJECT_DIR;
通过这种方式,您将拥有与该文件相关的所有项目目录;)
【讨论】:
-1:要加载设置文件,您需要一个路径,然后获取该文件的引用路径?没有解决任何问题... 赞成花时间审查和编辑。它仍然感觉很脆弱,但这可能只是因为没有更好的方法来实现这一点 使用这种方法用户需要记住的一点是,node_modules
通常被排除在版本控制之外。因此,如果您与团队合作或需要克隆您的存储库,您将不得不想出另一个解决方案来保持该设置文件同步。
@Travesty3 设置模块实际上是一个空模块,它正在导出项目根目录中文件的内容:P
@goliatone 使用他的解决方案,您可以在不知道其路径的情况下从任何地方获取文件,您所需要知道的只是“设置”。如果没有它,您必须明确知道要退出多少个文件夹,直到您到达项目目录。这是可行的,因为 node 会自动搜索 node_modules 并且总是知道它在哪里。【参考方案3】:
有很多方法可以解决这个问题,每种方法都有自己的优缺点:
require.main.filename
来自http://nodejs.org/api/modules.html:
当一个文件直接从 Node 运行时,
require.main
被设置为它的module
。也就是说你可以通过测试require.main === module
来判断一个文件是否直接运行过因为
module
提供了一个filename
属性(一般相当于__filename
),所以可以通过检查require.main.filename
获得当前应用的入口点。
因此,如果您想要应用程序的基本目录,您可以这样做:
const dirname = require('path');
const appDir = dirname(require.main.filename);
优点和缺点
这在大多数情况下都很好用,但如果您使用 pm2 之类的启动器运行应用程序或运行 mocha 测试,则此方法将失败。
module.paths
Node 将所有模块搜索路径发布到module.paths
。我们可以遍历这些并选择第一个解析的。
async function getAppPath()
const dirname = require('path');
const constants, promises: access = require('fs');
for (let path of module.paths)
try
await access(path, constants.F_OK);
return dirname(path);
catch (e)
// Just move on to next path
优点和缺点
这有时会起作用,但在包中使用时并不可靠,因为它可能返回安装包的目录而不是安装应用程序的目录。
使用全局变量
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');
优点和缺点
始终如一地工作,但您必须依赖全局变量,这意味着您无法轻松重用组件/等。
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 的节点模块。用法很简单:
const appRoot = require('app-root-path');
const myModule = require(`$ appRoot /lib/my-module.js`);
app-root-path 模块使用多种技术来确定应用程序的根路径,同时考虑到全局安装的模块(例如,如果您的应用程序在 /var/www/
中运行,但模块是安装在~/.nvm/v0.x.x/lib/node/
)。它不会在 100% 的时间里工作,但它会在最常见的情况下工作。
优点和缺点
在大多数情况下无需配置即可工作。还提供了一些不错的附加便利方法(参见项目页面)。最大的缺点是它在以下情况下不起作用:
您正在使用启动器,例如 pm2 并且,该模块未安装在您应用的node_modules
目录中(例如,如果您将其安装在全局范围内)
您可以通过设置APP_ROOT_PATH
环境变量或在模块上调用.setPath()
来解决此问题,但在这种情况下,您最好使用global
方法。
NODE_PATH 环境变量
如果您正在寻找一种方法来确定当前应用的根路径,那么上述解决方案之一可能最适合您。另一方面,如果您试图解决可靠地加载应用程序模块的问题,我强烈建议您查看 NODE_PATH
环境变量。
Node 的Modules system 在多个位置寻找模块。 One of these locations is wherever process.env.NODE_PATH
points。如果你设置了这个环境变量,那么你可以使用标准模块加载器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
启动您的应用程序,您就可以了。我将它与我的enforce-node-path 模块结合使用,它可以防止在未设置NODE_PATH
的情况下意外加载应用程序。有关强制执行环境变量的更多控制,请参阅checkenv。
一个问题: NODE_PATH
必须设置在节点应用的外部。你不能做像process.env.NODE_PATH = path.resolve(__dirname)
这样的事情,因为模块加载器会在你的应用程序运行之前缓存它会搜索的目录列表。
[2016 年 4 月 6 日添加] 另一个非常有希望尝试解决此问题的模块是 wavy。
【讨论】:
@Kevin 在这种情况下,mocha 是您的应用程序的入口点。这只是为什么找到“项目根目录”如此困难的一个例子——它在很大程度上取决于具体情况以及“项目根目录”的含义。 @Kevin 我完全理解。我的意思是“项目根”的概念对于人类 来说比计算机 更容易理解。如果你想要一个万无一失的方法,你需要配置它。使用require.main.filename
将在大多数 时间起作用,但不是所有 时间。
切线相关:这是一种非常聪明的方式来组织你的 Node 项目,这样你就不必太担心这个问题了:allanhortle.com/2015/02/04/…
我不知道 pm2 是否发生了变化或 Node.js 发生了变化,但 require.main.filename
似乎可以与 pm2 一起使用。不知道摩卡。
path.parse(process.mainModule.filename).dir
【参考方案4】:
所有这些“根目录”大多需要将一些虚拟路径解析为真正的堆路径,所以你应该看看path.resolve
吗?
var path= require('path');
var filePath = path.resolve('our/virtual/path.ext');
【讨论】:
【参考方案5】:在 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'));
【讨论】:
【参考方案6】:获取全局根目录的最简单方法(假设您使用 NPM 运行您的 node.js 应用程序“npm start”等)
var appRoot = process.env.PWD;
如果要交叉验证以上内容
假设您想交叉检查 process.env.PWD
与您的 node.js 应用程序的设置。如果你想要一些运行时测试来检查process.env.PWD
的有效性,你可以用这段代码交叉检查它(我写的似乎运行良好)。您可以将 appRoot 中最后一个文件夹的名称与 package.json 文件中的 npm_package_name 进行交叉检查,例如:
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')
,它非常适合这个目的
【讨论】:
这适用于(大多数)unix 系统。只要您希望您的 npm 模块/应用程序在 Windows 上运行,PWD
就未定义,这将失败。
process.cwd()
@MuhammadUmer 为什么process.cwd()
总是与项目根目录相同?
如果你在根文件中调用它,那么它会是【参考方案7】:
也许您可以尝试从__filename
向上遍历,直到找到package.json
,然后确定这是您当前文件所属的主目录。
【讨论】:
【参考方案8】:我发现在使用 express 时有用的一种技术是在设置任何其他路由之前将以下内容添加到 app.js
// set rootPath
app.use(function(req, res, next)
req.rootPath = __dirname;
next();
);
app.use('/myroute', myRoute);
无需使用全局变量,您将根目录的路径作为请求对象的属性。
如果您的 app.js 位于项目的根目录中(默认情况下),则此方法有效。
【讨论】:
【参考方案9】:在主文件顶部添加:
mainDir = __dirname;
然后在您需要的任何文件中使用它:
console.log('mainDir ' + mainDir);
mainDir
是全局定义的,如果您只在当前文件中需要它 - 请改用 __dirname
。
主文件通常在项目的根目录下,命名为main.js
、index.js
、gulpfile.js
。
【讨论】:
【参考方案10】:试试path._makeLong('some_filename_on_root.js');
示例:
cons path = require('path');
console.log(path._makeLong('some_filename_on_root.js');
这将从你的节点应用程序的根目录返回完整路径(与 package.json 相同的位置)
【讨论】:
【参考方案11】:我用这个。
对于我名为mymodule
的模块
var BASE_DIR = __dirname.replace(/^(.*\/mymodule)(.*)$/, '$1')
【讨论】:
【参考方案12】:实际上,我发现最强大的解决方案可能也是微不足道的: 您只需将以下文件放在项目的根目录中:root-path.js,其中包含以下代码:
import * as path from 'path'
const projectRootPath = path.resolve(__dirname)
export const rootPath = projectRootPath
【讨论】:
【参考方案13】:我发现这对我来说始终有效,即使从子文件夹调用应用程序也是如此,因为它可以使用某些测试框架,例如 Mocha:
process.mainModule.paths[0].split('node_modules')[0].slice(0, -1);
为什么有效:
在运行时节点创建所有加载文件的完整路径的注册表。模块首先加载,因此位于此注册表的顶部。通过选择注册表的第一个元素并返回“node_modules”目录之前的路径,我们能够确定应用程序的根目录。
这只是一行代码,但为了简单起见(我的缘故),我将它黑盒化到一个 NPM 模块中:
https://www.npmjs.com/package/node-root.pddivine
享受吧!
【讨论】:
process.mainModule
deprectaed since: v14.0.0 - 改用require.main.paths[0].split('node_modules')[0].slice(0, -1);
。
如果你有多个 node_modules 文件夹会发生什么,就像 Electron 应用程序一样【参考方案14】:
让它变得性感??。
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
三个简单的步骤解决丑陋的路径问题。
-
安装包:
npm install sexy-require --save
在主应用程序文件的顶部包含一次require('sexy-require')
。
require('sexy-require');
const routers = require('/routers');
const api = require('$api');
...
可选步骤。路径配置可以在项目根目录的.paths
文件中定义。
$db = /server/database
$api-v1 = /server/api/legacy
$api-v2 = /server/api/v2
【讨论】:
看起来不错,可惜名字这么可笑。 @JHH 好吧...我必须找到一个更好的名字【参考方案15】:
process.mainModule
是 deprecated,因为 v 14.0.0。参考答案时,请userequire.main
,其余的仍然成立。
process.mainModule.paths
.filter(p => !p.includes('node_modules'))
.shift()
获取主模块中的所有路径并过滤掉带有“node_modules”的路径,
然后获取剩余路径列表中的第一个。意外行为不会抛出错误,只是一个undefined
。
对我来说效果很好,即使打电话给 $ mocha
。
【讨论】:
【参考方案16】:您可以简单地在 express app 变量中添加根目录路径,然后从应用程序中获取此路径。为此,在您的 index.js 或 app.js 文件中添加 app.set('rootDirectory', __dirname);
。并使用req.app.get('rootDirectory')
获取代码中的根目录路径。
【讨论】:
【参考方案17】:老问题,我知道,但是没有提到要使用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 || '';
;
【讨论】:
【参考方案18】:将其添加到您的主应用文件(例如 app.js)的开头位置:
global.__basedir = __dirname;
这会设置一个始终等同于您应用的基本目录的全局变量。像任何其他变量一样使用它:
const yourModule = require(__basedir + '/path/to/module.js');
简单...
【讨论】:
【参考方案19】:只需使用:
path.resolve("./") ... output is your project root directory
【讨论】:
这很好用! path.resolve(".") 也有效 只给出当前目录,可能不是根目录。【参考方案20】:我知道这个已经太晚了。 但是我们可以通过两种方法获取根 URL
第一种方法
var path = require('path');
path.dirname(require.main.filename);
第二种方法
var path = require('path');
path.dirname(process.mainModule.filename);
参考链接:-https://gist.github.com/geekiam/e2e3e0325abd9023d3a3
【讨论】:
【参考方案21】:只要将这一行添加到你的模块根目录就可以了,通常是app.js
global.__basedir = __dirname;
然后你的所有模块都可以访问 _basedir。
【讨论】:
简单、轻松、有效【参考方案22】:查找电子应用程序的根路径可能会很棘手。因为在生产、开发、打包等不同条件下,主进程和渲染器的根路径是不同的。
我写了一个 npm 包electron-root-path 来捕获一个电子应用程序的根路径。
$ 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' );
【讨论】:
【参考方案23】:__dirname 将为您提供根目录,只要您位于根目录中的文件中。
// ProjectDirectory.js (this file is in the project's root directory because you are putting it there).
module.exports =
root()
return __dirname;
在其他文件中:
const ProjectDirectory = require('path/to/ProjectDirectory');
console.log(`Project root directory is $ProjectDirectory.root`);
【讨论】:
所以它没有给你根目录。 什么?它为您提供根目录。试试吧。您是否由于某种原因无法访问项目根目录中的 JS 文件?只要你是,创建一个 JS 对象并让它检查 __dirname。保证它会起作用。 @AndrewKoster __dirname 不会按设计为您提供根目录,因为它为您提供当前脚本的目录名称。此外,它可以从脚本更改为脚本。如果您拥有与 docroot 相同的内容,那就不再是巧合了,并且只讲述了一个事实 - 您将 app.js 存储在您的 docroot 中。 如果你故意把它放在那里,这个ProjectDirectory.js 位于根目录中并非巧合。这是我发布的工作解决方案的一个设计特征。【参考方案24】:这样就可以了:
path.join(...process.argv[1].split(/\/|\\/).slice(0, -1))
【讨论】:
【参考方案25】:
process.env
上有一个 INIT_CWD
属性。这是我目前在我的项目中使用的。
const INIT_CWD = process.env; // process.env.INIT_CWD
const paths = require(`$INIT_CWD/config/paths`);
祝你好运……
【讨论】:
就像一个包的魅力一样,它作为一个安装后步骤操纵它被调用的项目。然而,我还没有在另一层依赖项中对其进行测试,其中一个项目使用了一个使用我的包的依赖项。 @JamesDev,INIT_CWD
解析为执行 npm-script
的 directory
。【参考方案26】:
如果您想从正在运行的 node.js 应用程序中确定项目根目录,您也可以这样做。
process.mainModule.path
【讨论】:
[tsserver 6385] 'mainModule' is deprecated
【参考方案27】:
path.dirname(process.mainModule.filename);
【讨论】:
【参考方案28】:对我有用
process.env.PWD
【讨论】:
【参考方案29】:这将逐步下移目录树,直到它包含一个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
,因为这意味着它包含在嵌套包安装中。
【讨论】:
【参考方案30】:序言
这是一个非常古老的问题,但它在 2020 年似乎和 2012 年一样触动了人们的神经。 我检查了所有其他答案,但找不到提到的以下技术(它有其自身的局限性,但其他技术也不适用于所有情况):
Git + 子进程
如果您使用 Git 作为您的version control system,则可以将确定项目根目录的问题简化为(我会考虑项目的正确根目录 - 毕竟,您希望您的 VCS 具有最充分的可见性可能的范围):
检索存储库根路径
由于您必须运行 CLI 命令来执行此操作,因此我们需要生成一个子进程。此外,由于项目根极不可能在运行时更改,我们可以在启动时使用child_process
模块的同步版本。
我发现spawnSync()
最适合这份工作。至于实际运行的命令,只需git worktree
(带有--porcelain
选项以便于解析)即可检索根目录的绝对路径。
在答案末尾的示例中,我选择返回一个路径数组,因为可能有multiple worktrees(尽管它们可能有共同的路径)只是为了确定。请注意,当我们使用 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 require d modules |
从上面的对比表来看,以下几种方式最为通用:
如果遇到require.main === module
,require.main.filename
作为获取根的简单方法
node_modules
tree walk 提议 recently 使用了另一个假设:
如果模块的目录里面有
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] :
[];
;
实施
我建议使用 tree walker 作为首选后备,因为它更通用:
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 installed on production servers 并不少见,unsafe 也不是罕见的)。可以通过上述回退进行调解。
【讨论】:
以上是关于从正在运行的 node.js 应用程序确定项目根目录的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 nvm 设置默认 Node.js 版本? [复制]
在 Node.JS 中引用相对于应用程序根目录的文件的正确方法