require
是 Node.js 中 CommonJS 模块系统的核心函数,用于加载模块。了解 require
查找文件的优先级对于模块化开发和问题排查非常重要。
基本查找流程
当使用 require('module')
时,Node.js 会按照以下顺序查找模块:
- 检查是否是核心模块
- 检查是否是从
node_modules
加载 - 检查是否是文件模块
- 检查是否是目录模块
详细查找优先级
1. 核心模块(内置模块)
Node.js 有一组编译到二进制文件中的核心模块(如 fs
、path
、http
等)。如果 require
的参数是核心模块名,则直接返回该模块,不再继续查找。
javascript
const fs = require('fs'); // 直接加载核心模块
2. 以 /
、./
或 ../
开头的文件模块
如果模块标识符以 /
、./
或 ../
开头,Node.js 会将其视为文件模块 或目录模块。
文件模块查找顺序:
- 精确文件名:直接查找指定的文件
require('./module')
→ 查找module
- 尝试添加
.js
扩展名require('./module')
→ 查找module.js
- 尝试添加
.json
扩展名require('./module')
→ 查找module.json
- 尝试添加
.node
扩展名(编译的C++插件)require('./module')
→ 查找module.node
目录模块查找顺序(当路径指向目录时):
- 查找目录下的
package.json
文件中的main
字段指定的文件require('./some-directory')
→ 查找some-directory/package.json
中的main
字段
- 如果没有
package.json
或main
字段,则查找目录下的index.js
- 然后查找
index.json
- 最后查找
index.node
3. node_modules
目录查找
如果不是核心模块,也不是文件/目录模块,Node.js 会从当前目录开始,向上逐级查找 node_modules
目录,直到文件系统的根目录。
查找顺序示例(假设在 /home/user/projects/foo.js
中调用 require('bar')
):
/home/user/projects/node_modules/bar
/home/user/node_modules/bar
/home/node_modules/bar
/node_modules/bar
在每个 node_modules
目录中,查找顺序与文件模块相同:
bar
bar.js
bar.json
bar.node
bar/package.json
中的main
字段bar/index.js
bar/index.json
bar/index.node
4. 全局文件夹(NODE_PATH)
如果以上都找不到,Node.js 会检查 NODE_PATH
环境变量中指定的目录(现代 Node.js 项目中较少使用)。
缓存机制
Node.js 会对加载的模块进行缓存,后续的 require()
调用会返回缓存中的模块对象。缓存是基于解析后的文件名 的,所以 require('./module')
和 require('./module.js')
可能会指向同一个缓存模块。
实际示例
示例1:文件模块
arduino
project/
├── app.js
├── module.js
└── data.json
javascript
// app.js
const mod = require('./module'); // 查找 module.js
const data = require('./data'); // 查找 data.json
示例2:目录模块
java
project/
├── app.js
└── my-module/
├── index.js
└── package.json
javascript
// app.js
const mod = require('./my-module'); // 查找 my-module/package.json 或 my-module/index.js
示例3:node_modules 查找
go
project/
├── app.js
└── node_modules/
└── lodash/
├── package.json
└── index.js
javascript
// app.js
const _ = require('lodash'); // 查找 node_modules/lodash
特殊情况处理
1. 符号链接
当使用符号链接时,require
会解析符号链接的真实路径进行查找。
2. 大小写敏感
在 Linux/macOS 上文件名是大小写敏感的,Windows 上不敏感。建议统一使用小写文件名以避免跨平台问题。
3. 模块循环依赖
Node.js 能处理模块间的循环依赖,但需要注意模块可能处于未完全初始化的状态。
现代 Node.js 的扩展
从 Node.js v12 开始,支持 package.json
中的 "exports"
字段,提供了更精细的模块导出控制:
json
{
"name": "my-package",
"exports": {
".": "./lib/index.js",
"./feature": "./lib/feature.js"
}
}
这允许:
- 隐藏模块内部结构
- 定义子路径导出
- 提供不同环境的实现(如浏览器和Node.js)
总结
Node.js 的 require
查找优先级可以概括为:
- 核心模块 → 2. 文件/目录模块 → 3. node_modules 查找 → 4. NODE_PATH
理解这个查找顺序有助于:
- 正确组织项目结构
- 解决模块加载失败的问题
- 理解第三方模块的加载机制
- 优化模块查找性能
在实际开发中,建议:
- 使用明确的文件扩展名
- 合理组织
node_modules
- 利用
package.json
的main
和exports
字段 - 避免复杂的嵌套和循环依赖