簡體   English   中英

從正在運行的 node.js 應用程序確定項目根目錄

[英]Determine project root from a running node.js application

除了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測試,則此方法將失敗。

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.xx/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)的操作,因為模塊加載器會在您的應用程序運行之前緩存它將搜索的目錄列表。

[2016 年 4 月 6 日添加]嘗試解決此問題的另一個非常有前途的模塊是wavy

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

這樣,您將擁有與該文件相關的所有項目目錄;)

獲取全局根的最簡單方法(假設您使用 NPM 運行您的 node.js 應用程序“npm start”等

var appRoot = process.env.PWD;

如果您想交叉驗證上述內容

假設你想用你的 node.js 應用程序的設置來交叉檢查process.env.PWD 如果你想要一些運行時測試來檢查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')非常適合這個目的

簡單的:

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

就像將這一行添加到根目錄中的模塊一樣簡單,通常是app.jsapp.ts

global.__basedir = __dirname;

然后你的所有模塊都可以訪問 _basedir。

注意:對於 typescript 實現,請按照上述步驟操作,然后您將能夠使用global.__basedir使用根目錄路徑

我發現這對我來說始終如一,即使從子文件夾調用應用程序也是如此,因為它可以與某些測試框架一起使用,例如 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);

所有這些“根目錄”大多需要將一些虛擬路徑解析為真正的堆路徑,所以你應該看看path.resolve嗎?

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

也許您可以嘗試從__filename向上遍歷,直到找到package.json ,並確定這是您當前文件所屬的主目錄。

實際上,我發現最強大的解決方案可能也是微不足道的:您只需將以下文件放在項目的根目錄中:root-path.js,其中包含以下代碼:

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

前言

這是一個非常古老的問題,但它似乎在 2020 年和 2012 年一樣觸動了神經。我檢查了所有其他答案,但找不到提到的以下技術(它有其自身的局限性,但其他的是也不適用於所有情況):

Git + 子進程

如果您使用 Git 作為您的版本控制系統,那么確定項目根目錄的問題可以簡化為(我會考慮項目的正確根目錄 - 畢竟,您希望您的 VCS 具有盡可能完整的可見性范圍) :

檢索存儲庫根路徑

由於您必須運行 CLI 命令來執行此操作,因此我們需要生成一個子進程。 此外,由於項目根極不可能在運行時更改,我們可以在啟動時使用child_process模塊的同步版本。

我發現spawnSync()最適合這項工作。 至於要運行的實際命令,只需要git worktree (帶有--porcelain選項以便於解析)即可檢索根目錄的絕對路徑。

在答案末尾的示例中,我選擇返回一個路徑數組,因為可能有多個工作樹(盡管它們可能有公共路徑)只是為了確定。 請注意,當我們使用 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目錄 需要npm && CLI 啟動
process.env.PWD 指向當前目錄 相對於(是)啟動目錄
process.cwd() env.PWD相同 運行時的process.chdir(path)
require.main.filename 根 if === module require d 模塊上失敗

從上面的比較表中,以下方法是最普遍的:

  • 如果滿足require.main === module ,則require.main.filename作為獲取根目錄的簡單方法
  • 最近提出node_modules tree walk 使用了另一個假設:

如果模塊的目錄里面有node_modules dir,很有可能是root

對於主應用程序,它將獲取應用程序根目錄,而對於一個模塊——它的項目根目錄。

后備 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] :
            [];
    };

執行

我建議使用 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 並不少見,也不是不安全的)。 可以通過上述回退進行調解。

我發現使用 express 時有用的一種技術是在設置任何其他路由之前將以下內容添加到 app.js

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

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

無需使用全局變量,您將根目錄的路徑作為請求對象的屬性。

如果您的 app.js 位於項目的根目錄中,則此方法有效,默認情況下,它位於項目的根目錄中。

process.env上有一個INIT_CWD屬性。 這是我目前在我的項目中使用的。

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

祝你好運...

將其添加到您的主應用程序文件(例如 app.js)的開頭:

global.__basedir = __dirname;

這將設置一個全局變量,該變量將始終等同於您的應用程序的基本目錄。 像任何其他變量一樣使用它:

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

簡單的...

我知道這個已經太晚了。 但是我們可以通過兩種方法獲取根 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

process.mainModule自 v 14.0.0 起已棄用 參考答案時,請使用require.main ,其余的仍然成立。

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

獲取主模塊中的所有路徑並過濾掉帶有“node_modules”的路徑,然后獲取剩余路徑列表中的第一個。 意外的行為不會拋出錯誤,只是一個undefined

對我來說效果很好,即使在調用 ie $ mocha時也是如此。

如果您想從正在運行的 node.js 應用程序中確定項目根目錄,您也可以這樣做。

process.mainModule.path

它對我有用

process.env.PWD

在主文件頂部添加:

mainDir = __dirname;

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

console.log('mainDir ' + mainDir);
  • mainDir是全局定義的,如果您只在當前文件中需要它 - 請改用__dirname
  • main 文件通常位於項目的根文件夾中,命名為main.jsindex.jsgulpfile.js

這將逐步降低目錄樹,直到它包含一個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 ,因為這意味着它包含在嵌套包安裝中。

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

我用這個。

對於我名為mymodule的模塊

var BASE_DIR = __dirname.replace(/^(.*\/mymodule)(.*)$/, '$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. 可選步驟。 路徑配置可以在項目根目錄的.paths文件中定義。

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

您可以簡單地在 express app 變量中添加根目錄路徑,然后從應用程序中獲取此路徑。 為此添加app.set('rootDirectory', __dirname); 在您的 index.js 或 app.js 文件中。 並使用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 || '';
    }
}; 

找到電子應用程序的根路徑可能會很棘手。 因為在生產、開發、打包等不同條件下,主進程和渲染器的根路徑是不同的。

我編寫了一個 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' });

這將做:

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

我已經仔細閱讀了這里建議的所有解決方案,但沒有一個真正有用,因此這里是一個解決方案。

假設您需要在項目的根目錄下存在一個config.js文件,該文件位於/routes/api/users/profile.js ,而您不想將其導入為../../../config.js

  1. 如下所述,在項目的根目錄中創建目錄結構:
    • /模塊
      • index.js
      • package.json
  2. 模塊/ index.js
     module.exports.config = require('../config.js') 
  3. 模塊/package.js
     { "name": "modules", "main": "index.js", "version": "1.0.0", "dependencies": {} } 
  4. 現在運行
     npm install ./Modules 
  5. /routes/api/users/profile.js
     const { config } = require('modules') 

這樣,代碼編輯器的自動完成功能也將起作用。 不再有全局變量污染,相對較長的進口量,對環境變量的依賴性,最好的部分是,它將與pm2和nodemon一起使用。

path.dirname(process.mainModule.filename);

在現代版本的 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`

`

嘗試path._makeLong('some_filename_on_root.js');

例子:

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

這將從您的節點應用程序的根目錄返回完整路徑(與 package.json 的位置相同)

只需使用:

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

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM