Skip to content

nodejs 对 npm 支持非常良好

当使用 nodejs 导入模块时,如果模块路径不是以 ./ 或 ../ 开头,则 node 会认为导入的模块来自于 node_modules 目录,例如:

javascript
var _ = require("lodash");

它首先会从当前目录的以下位置寻找文件

shell
node_modules/lodash.js
node_modules/lodash/入口文件

若当前目录没有这样的文件,则会回溯到上级目录按照同样的方式查找

如果到顶级目录都无法找到文件,则抛出错误

上面提到的入口文件按照以下规则确定

  1. 查看导入包的package.json文件,读取main字段作为入口文件
  2. 若不包含main字段,则使用index.js作为入口文件

入口文件的规则同样适用于自己工程中的模块
在 node 中,还可以手动指定路径来导入相应的文件,这种情况比较少见

require查找机制

在 CommonJS 规范中,require 函数用于引入模块或包。require 会根据模块的路径或包名进行查找,最终找到对应的文件并加载模块。require 的查找机制可以分为以下几个步骤:

1. 核心模块优先

Node.js 内置了许多核心模块(如 fspath 等)。如果 require 的参数是核心模块的名称,Node.js 会直接返回该模块,无需进一步查找。

javascript
const fs = require('fs'); // 加载核心模块

如果 require('fs'),Node.js 会首先在核心模块中查找,找到后立即返回。

2. 路径查找

如果 require 参数是文件路径(相对路径或绝对路径),Node.js 会按以下顺序进行查找:

  • 绝对路径:如果传入的是绝对路径(如 /home/user/project/module.js),Node.js 会直接尝试加载该文件。
  • 相对路径:如果传入的是相对路径(如 ./module.js),Node.js 会根据当前文件所在路径,尝试解析成绝对路径,并加载对应文件。
javascript
const moduleA = require('./moduleA'); // 加载相对路径模块
const moduleB = require('/home/user/project/moduleB'); // 加载绝对路径模块

如果路径不包含文件后缀名,Node.js 会按以下顺序尝试文件扩展名:

  1. 尝试加载文件
    • ./moduleA.js
    • ./moduleA.json
    • ./moduleA.node(Node.js 的二进制模块)
  2. 尝试加载目录: 如果路径指向一个目录(如 ./myModule),Node.js 会在该目录下查找 package.json 文件中的 main 字段,如果找不到,会查找 index.js
    • ./myModule/package.jsonmain 字段
    • ./myModule/index.js
    • ./myModule/index.json
    • ./myModule/index.node

3. **node_modules**** 目录查找**

如果传入的 require 参数不是路径,而是包名(如 require('lodash')),Node.js 会尝试在当前模块所在目录及其上级目录中的 node_modules 文件夹中查找该包。

查找顺序如下:

  1. **当前目录的 ****node_modules**
    • Node.js 会先在当前模块的目录下的 node_modules 文件夹中查找包,比如 ./node_modules/lodash
  2. 向上查找: 如果当前目录的 node_modules 文件夹中没有找到,Node.js 会逐级向上查找:
    • ../node_modules/lodash
    • ../../node_modules/lodash
    • 一直到系统根目录(如 /C:\)的 node_modules
  3. 查找包结构: 一旦找到包所在目录,Node.js 会查找该包目录下的 package.json 文件,并加载 main 字段指定的入口文件。如果没有 main 字段,则会加载 index.js
javascript
const lodash = require('lodash'); // 从 node_modules 加载 lodash

4. 缓存机制

require 使用缓存机制,第一次加载模块后会缓存模块的输出。因此,后续对同一个模块的 require 调用不会重新加载文件,而是直接从缓存中返回结果。缓存提高了模块加载的性能。

缓存存储在 require.cache 对象中:

javascript
const moduleA = require('./moduleA'); // 首次加载,模块被缓存
const moduleAAgain = require('./moduleA'); // 从缓存中加载,效率更高

你可以通过删除 require.cache 中的某个模块来强制重新加载它。

5. 自定义模块解析

可以通过 NODE_PATH 环境变量设置额外的查找路径,require 也会从这些路径中查找模块。

此外,Node.js 支持 require.extensions 和自定义 loader 来修改模块加载行为(不推荐在现代项目中使用)。

总结

require 的查找机制包括以下几个关键步骤:

  1. 首先检查是否是核心模块。
  2. 如果是路径(相对或绝对),直接解析路径并尝试加载文件或目录。
  3. 对于包名,逐级向上查找 node_modules 目录。
  4. 加载时会优先查找 package.jsonmain 字段,若找不到则尝试加载 index.js 文件。
  5. 使用缓存机制避免重复加载同一模块。