Node require 学习笔记
date
Feb 27, 2023
slug
node-require
status
Published
tags
Nodejs
summary
分析 require 模块加载机制
type
Post
require 的使用场景
- 加载模块类型:
- 内置模块:
require('fs') - node_modules 依赖:
require('lodash') - 开发模块:
require('./utils')
- 支持加载的文件类型:
- .js
- .node(调用
process.dlopen(module, path.toNamespacedPath(filename))处理) - .json
- .mjs
- 其他类型(均被当作 .js 处理)
除了
.node 文件,其余文件均使用 fs.readFileSync 读取文件内容然后构造可执行的 compiledWrapper 函数处理。require 执行流程分析
流程图示

require module 对象属性记录
id: 源码文件路径,如:/Users/i7eo/Documents/Workspace/GitHub/eopol/cli-helper-test/src/require-orignal.js根Moduleid为 '.'
path: 源码文件所在的文件夹,通过path.dirname(id)生成,例如:/Users/i7eo/Documents/Workspace/GitHub/eopol/cli-helper-test/src根据这个向上递归生成一级一级的目录(paths)用来寻找node_modules最上层为/node_modules
exports: 模块输出的内容,默认为{}
parent: 父模块信息
filename: 源码文件路径
loaded: 是否已经加载完毕
children: 子模块对象集合
paths: 模块查询范围
流程详细描述
require('internal/modules/cjs/loader').Module.runMain(process.argv[1]);加载主流程,runMain中判断当前是 cjs 还是 esm
- require 加载路径必为 string。
validateString(id, 'id');
Module._load(process.argv[1], null, true)加载当前文件(主模块,第二个参数 parent 为 null,第三个参数 ismain 为 true)
Module._resolveFilename根据 paths、ext(.js/.json/…)、packageManager 拼出绝对路径
Module._cache[filename]查看是否有缓存,如果有即刻返回
- 通过
loadBuiltinModule带着上面拼出的绝对路径去内置库找(BuiltinModule.map存储所有内置模块,已 _ 开头)如果有即刻返回
- 如果不是内置模块,通过 new Module 创建实例,并且把创建的实例更新至 parent 下的 children 中
- 将创建的实例进行缓存,key 为文件绝对路径
- 调用
module.load加载具体文件
- 通过
findLongestRegisteredExtension找到当前文件的解析器(Module._extensions['.js']/Module._extensions['.node']/Module._extensions['.json']),并调用读取内容(fs.readFileSync)以及生成可以执行函数(module._compile其中调用compileFunction将读取的内容以及 exports, require, module, filename, dirname 传入,生成一个可执行函数compiledWrapper)
- 在
_compile方法底部调用compiledWrapper并将结果返回
以上为执行
node ./src/require-orignal.js 文件内容前主流程的过程。上述过程会先于文件内容执行,文件内容中的 require 断点直接进入下面 12- 因为主流程已走完,require 方法已经当作参数传入当前文件所以直接调用
require('xxx')会直接进入Module.prototype.require,该方法加载传入的指定路径文件(ismain 为 false),其中利用requireDepth加减来控制是否加载完毕,文件中的依赖也是通过调用Module.prototype.require加载。调用Module._load重复上述 3-11 的过程
杂项记录
require连续加载同一个模块时,是如何进行缓存的?- 两个缓存
- 父
module的path+\x00文件名对应的绝对路径+文件名.后缀 - 绝对路径+文件名.后缀,对应的
Module对象
path.dirname(filename)获取文件路径
path.basename(filename)获取文件名(带后缀)
- 每需要一次
requirerequireDepth加一,执行完require进来的模块后,requireDepth减一
Module._resolveFilename()调用Module._findPath()之后,在返回的文件名不存在,没有模块,报错
Module._nodeModulePaths(path.dirname(filename))产生的node_modules数组,最终保存在了Module对象上
- internal/modules/cjs/loader.js findLongestRegisteredExtension(filename) 获取 扩展名,默认返回 js
- path.sep 路径分隔符 Mac: '/' window: '\'
- '#!' 名字是 shebang
- 加载内置模块和其他模块的
compileFunction方法来源模块是不一样的
思考
- commonjs 加载主模块流程?
流程详细描述 1-11
- commonjs 如何加载内置模块?
流程详细描述 1-6
- commonjs 如何加载 node_moduls 模块?同12-3-11