除了process.cwd()
之外,是否有其他方法可以获取当前项目根目录的路径? Node是否实现了像Ruby中的属性Rails.root
一样的东西。我正在寻找一些常数和可靠的方法。
除了process.cwd()
之外,是否有其他方法可以获取当前项目根目录的路径? Node是否实现了像Ruby中的属性Rails.root
一样的东西。我正在寻找一些常数和可靠的方法。
有许多方法可以处理这个问题,每种方法都有其优缺点:
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 测试时,这个方法会失败。同时,在使用 Node.js ES 模块时,require.main
不可用,这也不起作用。
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%地工作,但在大多数常见情况下,它将起作用。
在大多数情况下无需配置即可正常工作。还提供了一些方便的附加方法(请参见项目页面)。最大的缺点是,如果:
node_modules
目录内(例如,如果您将其全局安装)您可以通过设置APP_ROOT_PATH
环境变量或调用模块上的.setPath()
来解决此问题,但在这种情况下,最好使用global
方法。
如果您正在寻找一种确定当前应用程序根路径的方法,则上述解决方案中的一种可能最适合您。另一方面,如果您要解决可靠地加载应用程序模块的问题,我强烈建议查看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
启动应用程序,并且一切都很顺利。我将此与我的enforce-node-path模块结合使用,该模块可防止在没有设置NODE_PATH
的情况下意外加载应用程序。要更精细地控制强制执行环境变量,请参见checkenv。
一个小问题: 必须在node应用程序之外设置NODE_PATH
。您不能像process.env.NODE_PATH = path.resolve(__dirname)
这样做,因为模块加载器在应用程序运行之前缓存它将搜索的目录列表。
[添加于4/6/16] 另一个非常有前途的模块,试图解决这个问题是wavy。
require.main.filename
似乎可以与pm2一起使用。不确定mocha是否也可以。 - Justin Warkentinpath.parse(process.mainModule.filename).dir
- Cory Robinsonpath.resolve(__dirname);
,它与 __dirname
有什么不同? - faressoft"start": "NODE_PATH=. node app.js"
results in the literal .
being returned inside the app for me. I had to use:NODE_PATH=$PWD
to get the actual path in process.env.NODE_PATH
- Jon Wyatt__dirname
不是全局变量,它只是本地模块的变量,所以每个文件都有自己不同的值。
如果您想要运行进程的根目录,那么您可能需要使用process.cwd()
。
如果您想要可预测性和可靠性,则可能需要将其作为应用程序的要求设置某个环境变量。您的应用程序查找MY_APP_HOME
(或其他)是否存在,如果存在,并且该目录包含您的应用程序,则一切正常。如果未定义或目录不包含您的应用程序,则应该退出并提示用户创建变量。它可以作为安装过程的一部分进行设置。
您可以使用类似process.env.MY_ENV_VARIABLE
的方法在node中读取环境变量。
bin/server.js
和 cd bin && server.js
时会得到不同的结果。(假设这些 js 文件已标记为可执行文件) - Myrne Stolprocess.cwd()
对我非常有效,即使在运行 mocha 测试时也是如此。谢谢! - Diogo Eichert1- 在项目根目录下创建一个名为settings.js的文件。
2- 在这个文件中添加以下代码。
module.exports = {
POST_MAX_SIZE : 40 , //MB
UPLOAD_MAX_FILE_SIZE: 40, //MB
PROJECT_DIR : __dirname
};
在node_modules中创建一个名为“settings”的新模块,并在模块index.js中编写以下代码:
module.exports = require("../../settings");
4- 任何时候您想要访问您的项目目录,只需使用
var settings = require("settings");
settings.PROJECT_DIR;
以这种方式,您将拥有相对于此文件的所有项目目录 ;)
node_modules
通常被排除在版本控制之外。因此,如果你与团队合作或者需要克隆你的代码库,你将需要另一种解决方案来使设置文件保持同步。 - Travesty3简单:
require('path').resolve('./')
process.cwd()
的一种迂回方式。 - dosentmatter获取全局根目录的最简单方法(假设您使用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')
,它非常适合这个目的。
PWD
将未定义并且会失败。 - Jeremy Wiebeprocess.cwd()
- Muhammad Umerprocess.cwd()
总是与项目根目录相同? - Alexander Mills只需在你的根模块中添加这行代码,通常是app.js
或app.ts
。
global.__basedir = __dirname;
那么,_basedir
将能够被所有你的模块访问。
注意: 对于 TypeScript 实现,请按照上述步骤操作,然后您将能够使用 global.__basedir
来获取根目录路径。
__basedir
的地方都加上ts-ignore
。这有点像向window
作用域添加额外的字段... - dcsan尝试从__dirname
向上遍历,直到找到一个package.json
文件,并确定该文件所在的目录是你当前文件所属的应用程序主根目录。
根据Node文档
package.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
}
package.json
,始终位于项目根目录。 - João Pimentel Ferreira.next
文件夹中,该文件夹恰好有一个package.json
文件。但是这个逻辑是正确的,所以我只需要找到一个唯一的文件来代表我们的应用程序。 - windmaomao我发现这对我来说始终有效,即使应用程序是从子文件夹调用的,就像某些测试框架(如Mocha)那样:
process.mainModule.paths[0].split('node_modules')[0].slice(0, -1);
为什么它可行:
运行时,Node会创建一个所有已加载文件完整路径的注册表。模块首先被加载,因此在这个注册表的顶部。通过选择注册表的第一个元素并返回“node_modules”目录之前的路径,我们能够确定应用程序的根目录。
这只是一行代码,但出于简单(以及我自己的方便),我将其封装成了一个NPM模块:
https://www.npmjs.com/package/node-root.pddivine
享受吧!
编辑:
process.mainModule
从v14.0.0开始已弃用
改用require.main
代替:
require.main.paths[0].split('node_modules')[0].slice(0, -1);
process.mainModule
自 v14.0.0 起已弃用 - 请使用 require.main.paths[0].split('node_modules')[0].slice(0, -1);
替代。 - RobCnode_modules/
目录始终位于项目的根目录。这就是为什么它能工作的原因。 - João Pimentel Ferreira前言
这是一个非常老的问题,但它似乎在2020年和2012年一样引起了关注。 我已经检查了所有其他答案,但没有找到下面提到的技术(它有自己的限制,但其他方法也不适用于每种情况):
Git + 子进程
如果您使用Git作为版本控制系统,那么确定项目根目录的问题可以简化为以下内容(我认为这是项目的适当根目录 - 毕竟,您希望您的VCS具有最广泛的可见性范围):
检索存储库根路径
由于必须运行CLI命令才能执行此操作,因此我们需要生成子进程。此外,由于项目根目录极不可能在运行时更改,因此我们可以在启动时使用child_process
模块的同步版本。
我发现spawnSync()
最适合此工作。至于要运行的实际命令,git worktree
(带有--porcelain
选项以便于解析)就足以检索根的绝对路径。
在答案结尾的示例中,我选择返回路径数组,因为可能会有多个工作树(尽管它们很可能具有共同的路径),以确保安全起见,由于我们使用CLI命令,shell
选项应设置为true
(安全性不应成为问题,因为没有不受信任的输入)。
方法比较和备用方案
理解可能出现VCS无法访问的情况,我在分析文档和其他答案后包括了几个备用方案。提出的解决方案归结为以下内容(不包括第三方模块和软件包):
解决方案 | 优点 | 主要问题 |
---|---|---|
__filename |
指向模块文件 | 相对于模块 |
__dirname |
指向模块目录 | 与__filename 相同 |
node_modules 树遍历 |
几乎保证根目录 | 如果嵌套,则需要复杂的树遍历 |
path.resolve(".") |
如果CWD是根目录,则为根目录 | 与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 ,则为根目录 |
在require 的模块上失败 |
从上面的比较表中,以下方法最通用:
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));
};
Fallback 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并不罕见,也不不安全)。可以通过上述描述的备选方案来解决。
所有这些“根目录”大多需要将一些虚拟路径解析为真实的文件路径,所以你可能应该查看 path.resolve
?
var path= require('path');
var filePath = path.resolve('our/virtual/path.ext');
process.env.PWD
…请看下面我的回答。 - Alexander Mills