nodejs 对 npm 支持非常良好
当使用 nodejs 导入模块时,如果模块路径不是以 ./ 或 ../ 开头,则 node 会认为导入的模块来自于 node_modules 目录,例如:
var _ = require("lodash");
它首先会从当前目录的以下位置寻找文件
node_modules/lodash.js
node_modules/lodash/入口文件
若当前目录没有这样的文件,则会回溯到上级目录按照同样的方式查找
如果到顶级目录都无法找到文件,则抛出错误
上面提到的入口文件按照以下规则确定
- 查看导入包的package.json文件,读取main字段作为入口文件
- 若不包含main字段,则使用index.js作为入口文件
入口文件的规则同样适用于自己工程中的模块
在 node 中,还可以手动指定路径来导入相应的文件,这种情况比较少见
require查找机制
在 CommonJS 规范中,require
函数用于引入模块或包。require
会根据模块的路径或包名进行查找,最终找到对应的文件并加载模块。require
的查找机制可以分为以下几个步骤:
1. 核心模块优先
Node.js 内置了许多核心模块(如 fs
、path
等)。如果 require
的参数是核心模块的名称,Node.js 会直接返回该模块,无需进一步查找。
const fs = require('fs'); // 加载核心模块
如果 require('fs')
,Node.js 会首先在核心模块中查找,找到后立即返回。
2. 路径查找
如果 require
参数是文件路径(相对路径或绝对路径),Node.js 会按以下顺序进行查找:
- 绝对路径:如果传入的是绝对路径(如
/home/user/project/module.js
),Node.js 会直接尝试加载该文件。 - 相对路径:如果传入的是相对路径(如
./module.js
),Node.js 会根据当前文件所在路径,尝试解析成绝对路径,并加载对应文件。
const moduleA = require('./moduleA'); // 加载相对路径模块
const moduleB = require('/home/user/project/moduleB'); // 加载绝对路径模块
如果路径不包含文件后缀名,Node.js 会按以下顺序尝试文件扩展名:
- 尝试加载文件:
./moduleA.js
./moduleA.json
./moduleA.node
(Node.js 的二进制模块)
- 尝试加载目录: 如果路径指向一个目录(如
./myModule
),Node.js 会在该目录下查找package.json
文件中的main
字段,如果找不到,会查找index.js
。./myModule/package.json
的main
字段./myModule/index.js
./myModule/index.json
./myModule/index.node
3. **node_modules**
** 目录查找**
如果传入的 require
参数不是路径,而是包名(如 require('lodash')
),Node.js 会尝试在当前模块所在目录及其上级目录中的 node_modules
文件夹中查找该包。
查找顺序如下:
- **当前目录的 **
**node_modules**
:- Node.js 会先在当前模块的目录下的
node_modules
文件夹中查找包,比如./node_modules/lodash
。
- Node.js 会先在当前模块的目录下的
- 向上查找: 如果当前目录的
node_modules
文件夹中没有找到,Node.js 会逐级向上查找:../node_modules/lodash
../../node_modules/lodash
- 一直到系统根目录(如
/
或C:\
)的node_modules
。
- 查找包结构: 一旦找到包所在目录,Node.js 会查找该包目录下的
package.json
文件,并加载main
字段指定的入口文件。如果没有main
字段,则会加载index.js
。
const lodash = require('lodash'); // 从 node_modules 加载 lodash
4. 缓存机制
require
使用缓存机制,第一次加载模块后会缓存模块的输出。因此,后续对同一个模块的 require
调用不会重新加载文件,而是直接从缓存中返回结果。缓存提高了模块加载的性能。
缓存存储在 require.cache
对象中:
const moduleA = require('./moduleA'); // 首次加载,模块被缓存
const moduleAAgain = require('./moduleA'); // 从缓存中加载,效率更高
你可以通过删除 require.cache
中的某个模块来强制重新加载它。
5. 自定义模块解析
可以通过 NODE_PATH
环境变量设置额外的查找路径,require
也会从这些路径中查找模块。
此外,Node.js 支持 require.extensions
和自定义 loader 来修改模块加载行为(不推荐在现代项目中使用)。
总结
require
的查找机制包括以下几个关键步骤:
- 首先检查是否是核心模块。
- 如果是路径(相对或绝对),直接解析路径并尝试加载文件或目录。
- 对于包名,逐级向上查找
node_modules
目录。 - 加载时会优先查找
package.json
的main
字段,若找不到则尝试加载index.js
文件。 - 使用缓存机制避免重复加载同一模块。