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

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

561732 次浏览

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

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

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

你可以用类似process.env.MY_ENV_VARIABLE的方法读取节点中的环境变量。

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;

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

解决这个问题有很多种方法,每种方法都有其优缺点:

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这样的启动器运行应用程序或运行摩卡测试,这个方法将会失败。这在使用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
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

为了解决这个问题,我创建了一个名为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的模块系统在不同的位置查找模块。其中一个位置是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_PATH 必须被设置为节点应用程序的。你不能做类似process.env.NODE_PATH = path.resolve(__dirname)这样的事情,因为模块加载器在应用程序运行之前缓存了它要搜索的目录列表。

另一个真正有希望解决这个问题的模块是波浪

所有这些“根dirs”大多需要解析一些虚拟路径到一个真实的堆路径,所以可能你应该看看path.resolve?

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

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

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

var appRoot = process.env.PWD;

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

假设你想交叉检查你的node.js应用程序的设置process.env.PWD。如果你想要一些运行时测试来检查process.env.PWD的有效性,你可以用这段代码来交叉检查它(我写的似乎工作得很好)。你可以用包中的npm_package_name交叉检查apot中最后一个文件夹的名称。Json文件,例如:

    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'),它非常适合这个目的

尝试从__dirname向上遍历,直到找到package.json,并确定这是当前文件所属的应用程序主根目录。

根据节点文档

包。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
}

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

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


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

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

如果你的app.js在你的项目的根目录中,这是有效的,默认情况下,它是。

在主文件的顶部添加:

mainDir = __dirname;

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

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

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

例子:

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

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

我用这个。

对于命名为mymodule的模块

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

实际上,我发现也许微不足道的解决方案也是最健壮的: 你只需将以下文件放在你的项目的根目录:root-path.js,它有以下代码:

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

我发现这对我来说是一致的,即使应用程序是从子文件夹中调用的,因为它可以与一些测试框架,如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自v14.0.0起为弃用

使用require.main代替:

require.main.paths[0].split('node_modules')[0].slice(0, -1);

让它变得性感💃🏻.

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. Optional step. Path configuration can be defined in .paths file on root directory of your project.

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

从v 14.0.0开始,process.mainModule就是弃用。参考答案时,请使用 require.main,其余仍然成立。

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

获取主模块中的所有路径,并过滤掉带有"node_modules"的路径, 然后获取剩余路径列表中的第一个。意外行为不会抛出错误,只会抛出undefined.

即使在调用ie $ mocha时也适用于我。

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

老问题,我知道,但是没有问题提到使用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 || '';
}
};

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

global.__basedir = __dirname;

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

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

简单的…

只使用:

 path.resolve("./") ... output is your project root directory
我知道这已经太迟了。 但是我们可以通过两个方法获取根URL

1号方法

var path = require('path');
path.dirname(require.main.filename);

2方法

var path = require('path');
path.dirname(process.mainModule.filename);

参考链接:—https://gist.github.com/geekiam/e2e3e0325abd9023d3a3

就像在根模块中添加这一行一样简单,通常它是app.jsapp.ts

global.__basedir = __dirname;

然后所有模块都可以访问_basedir。

对于typescript实现,遵循上面的步骤,然后你将能够使用根目录路径使用global.__basedir

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

我写了一个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' });

__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}`);

这样做:

path.join(...process.argv[1].split(/\/|\\/).slice(0, -1))

process.env上有一个INIT_CWD属性。这就是我目前在我的项目中所做的工作。

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

祝你好运…

如果你想从一个正在运行的node.js应用程序中确定项目根,你也可以简单地这样做。

process.mainModule.path
path.dirname(process.mainModule.filename);

这对我很有用

process.env.PWD

这将向下目录树,直到它包含一个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,因为这意味着它包含在嵌套包安装中。

序言

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

Git +子进程

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

检索存储库根路径

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

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

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

方法比较和后备

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

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

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

  • 如果require.main === module被满足,require.main.filename是获取根的简单方法
  • 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 安装在生产服务器上并不少见,不安全的也不常见)。如上所述,可以通过回退来调节。

简单:

require('path').resolve('./')

在npm的现代版本中,你可以在exports中添加一个条目,用作速记。注意,如果你想同时引用根目录本身和根目录内的文件,你将分别需要././*:

package.json:

{
"imports": {
"#root": "./",
"#root/*": "./*",
...
},
...
}

./index.js:

import {namedExport} from '#root/file.js'

./file.js:

export const namedExport = {
hi: "world",
};

然后:

$ node --experimental-specifier-resolution=node index.js

你可以用constants.js文件进一步扩展它,在那里你可以使用上面答案中的一个方法,或者输入一个绝对路径,如果你需要路径本身的话

你也可以使用 git rev-parse --show-toplevel 假设您正在处理git存储库

一个非常简单可靠的解决方案是cd ..递归搜索package.json,考虑到这个文件总是在项目根目录上。

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
}
const __root = `${__dirname.substring(0, __dirname.lastIndexOf('projectName'))}projectName`