Addenda: Package manager tips


Node.js require() 函数的语义被设计为足够通用以支持合理的目录结构。 诸如 dpkgrpmnpm 之类的包管理器程序有望发现无需修改即可从 Node.js 模块构建本机包。

下面给出了一个可行的建议目录结构:

假设想让位于 /usr/lib/node/<some-package>/<some-version> 的文件夹保存特定版本包的内容。

包可以相互依赖。 为了安装包 foo,可能需要安装包 bar 的特定版本。 bar 包本身可能存在依赖关系,在某些情况下,这些甚至可能发生冲突或形成循环依赖关系。

因为 Node.js 查找它加载的任何模块的 realpath(即,它解析符号链接)然后node_modules 文件夹中查找它们的依赖项,所以这种情况可以通过以下架构解决:

  • /usr/lib/node/foo/1.2.3/: foo 包的内容,版本 1.2.3。
  • /usr/lib/node/bar/4.3.2/: foo 依赖的 bar 包的内容。
  • /usr/lib/node/foo/1.2.3/node_modules/bar: /usr/lib/node/bar/4.3.2/ 的符号链接。
  • /usr/lib/node/bar/4.3.2/node_modules/*: bar 依赖的包的符号链接。

因此,即使遇到循环,或者如果存在依赖冲突,每个模块都将能够获得它可以使用的依赖版本。

foo 包中的代码执行 require('bar') 时,它将获得符号链接到 /usr/lib/node/foo/1.2.3/node_modules/bar 的版本。 然后,当 bar 包中的代码调用 require('quux') 时,它将获得符号链接到 /usr/lib/node/bar/4.3.2/node_modules/quux 的版本。

此外,为了使模块查找过程更加优化,与其将包直接放在 /usr/lib/node 中,还可以将它们放在 /usr/lib/node_modules/<name>/<version> 中。 这样 Node.js 就不会费心寻找 /usr/node_modules/node_modules 中缺失的依赖项了。

为了使模块可用于 Node.js 交互式解释器,将 /usr/lib/node_modules 文件夹添加到 $NODE_PATH 环境变量可能会很有用。 由于使用 node_modules 文件夹的模块查找都是相对的,并且基于调用 require() 的文件的真实路径,因此包本身可以位于任何位置。

The semantics of the Node.js require() function were designed to be general enough to support reasonable directory structures. Package manager programs such as dpkg, rpm, and npm will hopefully find it possible to build native packages from Node.js modules without modification.

Below we give a suggested directory structure that could work:

Let's say that we wanted to have the folder at /usr/lib/node/<some-package>/<some-version> hold the contents of a specific version of a package.

Packages can depend on one another. In order to install package foo, it may be necessary to install a specific version of package bar. The bar package may itself have dependencies, and in some cases, these may even collide or form cyclic dependencies.

Because Node.js looks up the realpath of any modules it loads (that is, it resolves symlinks) and then looks for their dependencies in node_modules folders, this situation can be resolved with the following architecture:

  • /usr/lib/node/foo/1.2.3/: Contents of the foo package, version 1.2.3.
  • /usr/lib/node/bar/4.3.2/: Contents of the bar package that foo depends on.
  • /usr/lib/node/foo/1.2.3/node_modules/bar: Symbolic link to /usr/lib/node/bar/4.3.2/.
  • /usr/lib/node/bar/4.3.2/node_modules/*: Symbolic links to the packages that bar depends on.

Thus, even if a cycle is encountered, or if there are dependency conflicts, every module will be able to get a version of its dependency that it can use.

When the code in the foo package does require('bar'), it will get the version that is symlinked into /usr/lib/node/foo/1.2.3/node_modules/bar. Then, when the code in the bar package calls require('quux'), it'll get the version that is symlinked into /usr/lib/node/bar/4.3.2/node_modules/quux.

Furthermore, to make the module lookup process even more optimal, rather than putting packages directly in /usr/lib/node, we could put them in /usr/lib/node_modules/<name>/<version>. Then Node.js will not bother looking for missing dependencies in /usr/node_modules or /node_modules.

In order to make modules available to the Node.js REPL, it might be useful to also add the /usr/lib/node_modules folder to the $NODE_PATH environment variable. Since the module lookups using node_modules folders are all relative, and based on the real path of the files making the calls to require(), the packages themselves can be anywhere.