如何使Node.js中的'require'变为绝对路径而非相对路径?

276
我希望我的文件总是由项目的根目录而不是当前模块来引用。例如,如果您查看Express.js' app.js第6行,您会发现
express = require('../../')

这真的很糟糕,以我之见。假设我想将所有示例仅向根目录靠近一个级别。那就不可能了,因为我必须更新超过30个示例,并且每个示例中多次更新。改成这样:
express = require('../')

我的解决方案是针对根目录的特殊情况:如果一个字符串以 $ 开头,那么它相对于项目的根文件夹。

我该怎么办?

更新 2

现在我正在使用RequireJS,它允许您以一种方式编写代码,既适用于客户端又适用于服务器。RequireJS 还允许您创建自定义路径。

更新 3

现在我转向使用 WebpackGulp.js,并使用 enhanced-require 来处理服务器端的模块。请参阅此处的原理: http://hackhat.com/p/110/module-loader-webpack-vs-requirejs-vs-browserify/


如果您决定使用显式根路径常量/变量,此答案适用于此。该解决方案使用一个微小的Github模块来确定根路径。 - steampowered
2
https://gist.github.com/branneman/8048520 - zloctb
链接已经失效,它重定向到一个通用页面。 - Peter Mortensen
40个回答

176

使用:

var myModule = require.main.require('./path/to/module');

只要您的主JavaScript文件位于项目的根目录下,它会像从主JavaScript文件中所需的那样需要该文件,因此它可以很好地工作......这是我所欣赏的。


不错的想法(: 然后你可以定义一些其他方法来在require.main模块中重新映射应用程序。我认为你可以这样做:require.main.req('client/someMod')。不错的想法,但这比我的当前requirejs更冗长。而且我认为这并不值得,因为我也不喜欢browserify,因为它的变化不是即时的,会错过一些变化(因为我的代码应该在浏览器和node.js中都运行)。 - Totty.js
4
如果您觉得它太冗长,只需使用 .bind() : var rootReq = require.bind( require.main ) ; rootReq( './path/to/module' ) ; 翻译:如果您认为代码过于冗长,可以使用 .bind() 方法简化:var rootReq = require.bind( require.main ) ; rootReq( './path/to/module' ) ; - cronvel
是的,这对于仍想在客户端使用Browserify的人可能很有用。对我来说不再需要了,但无论如何感谢您的回答(: - Totty.js
8
如果“main”位于您的项目根目录下 :) - Alexander Mills
19
如果代码已经进行了像Mocha测试这样的单元测试覆盖,那么这个解决方案将不起作用。 - alx lark
显示剩余2条评论

133

Browserify Handbook中有一个非常有趣的部分:

避免使用 ../../../../../../..

并不是所有应用程序都适合放在公共npm上,而在许多情况下,设置私有npm或git repo的开销仍然很大。以下是一些避免使用 ../../../../../../../ 相对路径的方法。

node_modules

有些人反对将特定于应用程序的模块放入 node_modules 中,因为不太清楚如何检查您的内部模块而又不检查来自npm的第三方模块。

答案非常简单!如果您有一个忽略 node_modules.gitignore 文件:

node_modules
您可以为每个内部应用程序模块添加一个带有!的异常:
node_modules/*
!node_modules/foo
!node_modules/bar
请注意,如果父目录已被忽略,则无法取消忽略子目录。因此,您需要使用“ node_modules / *”技巧忽略 node_modules 中的每个目录内部,而不是忽略它本身。然后,您就可以添加例外情况。
现在,在应用程序的任何地方,您将能够使用 require('foo')或 require('bar'),而不必担心非常庞大和脆弱的相对路径。
如果您有很多模块,并希望将它们与npm安装的第三方模块更分开,请将它们全部放在 node_modules / app 之类的目录下:
node_modules/app/foo
node_modules/app/bar
现在你可以在应用程序的任何地方使用 require('app/foo') 或者 require('app/bar')
在你的 .gitignore 文件中,只需要添加一个例外,即 node_modules/app
node_modules/*
!node_modules/app

如果您的应用程序在package.json中配置了transforms,那么您需要在node_modules/foonode_modules/app/foo组件目录中创建一个单独的package.json文件,并添加自己的transform字段。这是因为转换不适用于模块之间的边界。这将使您的模块更加 robust,以应对应用程序中的配置更改,并且更容易地在应用程序外独立地重用这些包。

符号链接

如果您正在开发一个可以做符号链接并且不需要支持Windows的应用程序,则另一个有用的技巧是将lib/app/文件夹建立符号链接到node_modules中。从项目根目录执行:

ln -s ../lib node_modules/app

现在你可以从项目的任何位置使用 require('app/foo.js') 来引入 lib/foo.js 文件。

自定义路径

你可能会在一些地方看到使用 $NODE_PATH 环境变量或者 opts.paths 来添加目录,让 node 和 browserify 查找模块。

与大多数其他平台不同,使用 shell 风格的路径数组和 $NODE_PATH 在 node 中并不如有效利用 node_modules 目录优秀。

这是因为你的应用更紧密地耦合于运行时环境配置, 所以有更多的移动部件,只有当你的环境正确设置时,你的应用程序才能工作。

node 和 browserify 都支持但不鼓励使用 $NODE_PATH


27
将它放在 node_modules 文件夹中的唯一缺点是,这使得删除(rm -rf node_modules)该文件夹变得更加困难。 - Michael
14
不算太难:git clean -dx node_modules。 - Programming Guy
4
如果您忘记了 git clean 的语法,可以始终使用 rm -rf node_modules && git checkout node_modules 命令——请确保在 node_modules 子目录中有任何更改时使用 git stash - derenio
3
我喜欢使用node_modules这个想法,但不喜欢用它来存储源代码,因为源代码可能会很不稳定。把模块分离出来,并将其作为原始项目的依赖项发布,会更有意义吧?这提供了一个清晰的解决方案,可以避免node_modules目录的不稳定性,只需要依赖npm,而不是git、符号链接或$NODE_PATH解决方案。 - Kevin Koshiol
3
NODE_PATH 似乎是解决问题的方法。"只有在正确设置环境时,您的应用程序才能正常工作",这一点总是正确的!难道不是更容易在一个文件中设置好环境,而不是更改每个文件中的每个导入吗? - CpILL
显示剩余10条评论

94

我喜欢为共享代码创建一个新的node_modules文件夹,然后让Node.js和'require'发挥它们最擅长的作用。

例如:

- node_modules // => these are loaded from your *package.json* file
- app
  - node_modules // => add node-style modules
    - helper.js
  - models
    - user
    - car
- package.json
- .gitignore

例如,如果您在car/index.js中,您可以require('helper'),Node.js会找到它!

node_modules的工作原理

Node.js具有一种聪明的算法来解析模块,这是与竞争对手平台不同的。

如果您从/beep/boop/bar.jsrequire('./foo.js'),Node.js将在/beep/boop/foo.js中查找./foo.js。以./../开头的路径始终是相对于调用require()的文件而言的本地路径。

但是,如果您从/beep/boop/foo.js中'require'一个非相对名称,例如require('xyz'),Node.js按顺序搜索这些路径,在第一个匹配项停止,并在未找到任何内容时引发错误:

/beep/boop/node_modules/xyz
/beep/node_modules/xyz
/node_modules/xyz

每个存在的xyz目录,Node.js将首先查找xyz/package.json文件以查看是否存在"main"字段。 "main"字段定义了在使用require()函数请求该目录路径时应该加载哪个文件。

例如,如果/beep/node_modules/xyz是第一个匹配项,并且/beep/node_modules/xyz/package.json中有:

{
  "name": "xyz",
  "version": "1.2.3",
  "main": "lib/abc.js"
}

那么来自/beep/node_modules/xyz/lib/abc.js的导出将被require('xyz')返回。

如果没有package.json或没有"main"字段,则假定使用index.js

/beep/node_modules/xyz/index.js

3
加载模块的运作方式很好地解释了。 - goenning
3
这是一个非常优雅的解决方案,避免了以上答案中出现的所有问题。在我看来应该被视为“答案”。 - rodurico
运行npm install会删除内部的node模块... - SlurpGoose
@SlurpGoose是真的吗?你能提供来源吗? - Noname

38

大局观

虽然看起来“非常糟糕”,但给它一点时间。实际上,这是非常好的。显式的require()提供了完全透明和易于理解的方式,就像在项目生命周期中呼吸新鲜空气。

可以这样想:你正在阅读一个示例,尝试使用Node.js,并且你已经认为它“非常糟糕”。你开始对Node.js社区的领袖——那些编写和维护Node.js应用程序的工作时间比任何人都多的人——产生怀疑。作者犯了这样的初级错误的几率有多大?(我同意,在我的Ruby和Python背景下,一开始看起来像灾难。)

围绕Node.js存在很多炒作和反炒作。但是,当所有争议平息时,我们将承认显式模块和“本地优先”包是采用Node.js的主要推动力。

常见情况

当然,从当前目录、父目录、祖父目录、曾祖父目录等搜索node_modules。所以你已经安装的已经按照这个规则工作了。通常你可以在项目的任何位置使用require("express")

如果你发现自己从项目根目录加载常用文件(可能是因为它们是常用的实用函数),那么这是一个很大的提示,说明现在是时候创建一个包了。包非常简单:把你的文件移动到node_modules/并放置一个package.json文件。然后,在你的整个项目中,该命名空间中的所有内容都可以访问。包是将你的代码加入全局命名空间的正确方式。

其他解决方法

我个人不使用这些技术,但它们确实回答了你的问题,当然你比我更了解你自己的情况。

你可以将$NODE_PATH设置为你的项目根目录。这样,当你调用require()时,该目录将被搜索。

接下来,你可以妥协并从所有示例中要求一个常见的本地文件。该常见文件只需重新导出祖父目录中的真实文件即可。

examples/downloads/app.js(以及许多类似的文件)

var express = require('./express')

examples/downloads/express.js

module.exports = require('../../')

现在当你迁移这些文件时,最糟糕的情况是修复一个shim模块。


16
我同意Node.js的开发人员一定有选择相对路径require的原因。但是我仍然看不到它的优点,也没有从你的回答中得到启示。它仍然让我感觉“不好”。;) - Adam Schmideg
28
你在质疑Node.js社区的领导者。这些领导者决定使用回调函数而非Future/Promise。我大部分的Node.js咨询工作都涉及到诅咒这些“领导者”,并说服人们转向JVM。在使用了几个月Node.js后,这变得更加容易了 :) - David Sergey
12
@nirth,转到JVM?天啊,为什么? - Ivancho
42
你在对Node.js社区的领导人持怀疑态度,请避免使用这种让人感到泄气的语气。 - atlex2
21
他确实在对节点负责人的决定进行反思,这是行业进步的方式。如果节点团队没有对支撑基于线程的并发模型的领导者进行反思,我们就不可能拥有 Node。 - d512
显示剩余4条评论

23

如果你使用yarn而不是npm,你可以使用workspaces

假设我有一个名为services的文件夹,我希望更轻松地引用它:

.
├── app.js
├── node_modules
├── test
├── services
│   ├── foo
│   └── bar
└── package.json
要创建一个Yarn工作区,请在services文件夹内创建一个package.json文件:
{
  "name": "myservices",
  "version": "1.0.0"
}
在您的主要 package.json 文件中添加:

"private": true,
"workspaces": ["myservices"]

在项目的根目录下运行yarn install

然后,在您的代码中的任何位置,您可以执行:

const { myFunc } = require('myservices/foo')

而不是像这样:

const { myFunc } = require('../../../../../../services/foo')

9
也许有必要澄清一下,这只适用于 yarn,而不是 npm?我原以为它也适用于 npm,所以花了一些时间思考我做错了什么,直到我改用 yarn 才发现。这可能是一个愚蠢的假设,但也许我不是唯一一个这样想的人。 - ArneHugo
2
我稍微编辑了一下以澄清。对于造成的困惑,我很抱歉。 - cyberwombat
这不是仅仅取决于你的团队合作能力吗?(; 也许我很傻,但如果你这样做,而你的团队成员使用npm(经常发生,非常烦人,+1 for npm),这不会导致构建失败吗?如果"workspaces"是专有的话,这只是一个猜测。我会用webpack和你的eslint配置来解决这个问题... - schmerb
1
@schmerb 我想你确实需要同意使用 Yarn,但你需要做出这个决定 - 只是使用 npm 和 yarn 安装程序包会造成混乱。 - cyberwombat
1
我看到了这篇关于强制使用npm/yarn的文章:https://www.freecodecamp.org/news/how-to-force-use-yarn-or-npm/ - cyberwombat
2
npm 7也支持工作区。请参考npm 7 understands workspaces - xmedeko

20

请查看node-rfr

就是这么简单:

var rfr = require('rfr');
var myModule = rfr('projectSubDir/myModule');

我认为第二行应该是 var myModule = rfr('/projectSubDir/myModule'); - Sikorski
1
从文档中: var module2 = rfr('lib/module2'); // 可以省略前导斜杠。 - igelineau
4
我尝试过在Node上运行,它可以正常工作,但是会破坏VS Code的代码导航...我还没有找到解决方法,无法在VS中使用自动完成... - Alex Mantaut

15

在我的项目中,我使用process.cwd()。例如:

var Foo = require(process.cwd() + '/common/foo.js');

值得注意的是,这将导致require一个绝对路径,尽管我还没有遇到过这样的问题。


3
这个想法不好,因为CWD(当前工作目录)不一定是应用程序保存的目录。 - jiwopene
如果你只设置一次,它就非常好用:global.dir = process.cwd(); 然后你可以在任何地方引用它。 - vanwinter

13

我认为,最简单的方法是定义自己的函数作为GLOBAL对象的一部分。 在你的项目根目录下创建projRequire.js文件,并将以下内容添加到其中:

var projectDir = __dirname;

module.exports = GLOBAL.projRequire = function(module) {
  return require(projectDir + module);
}

在你的主文件中,在require任何项目特定模块之前:

// init projRequire
require('./projRequire');

之后以下内容适用于我:

// main file
projRequire('/lib/lol');

// index.js at projectDir/lib/lol/index.js
console.log('Ok');


@Totty,我想出了另一种解决方案,可以适用于你在评论中描述的情况。 简要描述将是 tl;dr ,所以最好用我的测试项目结构图来展示。


直到现在,这似乎是最好的方法。我在我的index.js文件中执行:GLOBAL.requires = require('r').r; 但是我在vows测试中遇到了问题,它们不运行index.js,因此我的测试失败,因为requireS未定义。无论如何,现在我可以在每个测试的顶部添加GLOBAL.requires = require('r').r;。有更好的想法吗?https://github.com/totty90/production01_server/commit/6a827ff5f73dd111746cce80fcc3aad7e2bcc6b8 - Totty.js
对于非测试文件:https://github.com/totty90/production01_server/blob/global-require/index.js#L1,在每个测试文件中:https://github.com/totty90/production01_server/blob/global-require/test/services.getWorkers.test.js - Totty.js
当我在"pathes-test/node_modules/other.js"中,并且需要引入"pathes-test/node_modules/some.js"时,问题就出现了。我应该使用require('./some')而不是require("prj/some")。这样一来,我的整个应用程序都将位于node_modules目录中。 - Totty.js
@Totty,从prj/other需要prj/some没有问题(刚刚测试了require('prj/some'))。你的所有应用程序通用模块都可以放在那里(例如数据库层)。无论你的lib在哪里都没有影响。试一试看是否适合。 - Aleksei Zabrodskii
是的,我已经更新了它:https://github.com/totty90/production01_server/tree/master/node_modules/production,效果很好。但是我能否在不使用node_modules的情况下将所有文件上移一级? - Totty.js

11

这个问题有一个很好的讨论,可以在 这里 找到。

我遇到了同样的架构问题:想要一种方法来给我的应用程序更多的组织和内部命名空间,而不会:

  • 将应用程序模块与外部依赖项混合或为应用程序特定的代码烦扰私有npm存储库
  • 使用相对requires,这使得重构和理解更加困难
  • 使用符号链接或更改node路径,这可能会隐藏源位置并且不适用于源控制

最后,我决定使用文件命名约定来组织我的代码,而不是目录。结构看起来像这样:

  • npm-shrinkwrap.json
  • package.json
  • node_modules
    • ...
  • src
    • app.js
    • app.config.js
    • app.models.bar.js
    • app.models.foo.js
    • app.web.js
    • app.web.routes.js
    • ...

然后在代码中:

var app_config = require('./app.config');
var app_models_foo = require('./app.models.foo');

或只是

var config = require('./app.config');
var foo = require('./app.models.foo');

并且外部依赖项像往常一样可以从node_modules中获取:

var express = require('express');

通过这种方式,所有应用程序代码都分层组织到模块中,并相对于应用程序根目录对所有其他代码可用。

主要的缺点当然是在文件浏览器中,你无法像实际组织成目录一样展开/折叠树形结构。但我喜欢它非常明确地说明了所有代码来自哪里,而且不使用任何“魔法”。


从您提供的代码片段中,解决方案#7,“包装器”非常简单和方便。 - Pier-Luc Gendreau
我看到另一个小的便利 - 将文件“移动”到不同的“文件夹”变成了重命名操作 - 这比移动文件更容易。此外,我倾向于注意到,在项目上工作半个小时后,几乎所有的应用程序树都被展开了。添加1个文件夹层级可以使大型代码库易于管理,而且不会引入太多已经可读的../x/x - Ski
1
你正在重新发明文件夹,使用点号代替斜杠,以克服Node.js中明显的缺陷。 - Simone Gianni
对于一个小项目,我认为这是一个优雅的解决方案。但如果你在团队中工作,很难明确何时最好使用这个约定。 - TastyWheat

11

假设您的项目根目录是当前工作目录,则应该可以正常工作:

// require built-in path module
path = require('path');

// require file relative to current working directory
config = require( path.resolve('.','config.js') );

config = require('./config.js'); 是有效的。 - cespon
8
@cespon 这只是相对于需要该文件的文件。 - protometa

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接