Nodejs的require流程
nodejs的require流程
.
一、require简单流程
nodejs的require流程大致如下:

原图:https://images.cnblogs.com/cnblogs_com/blogs/668717/galleries/2013323/o_240518085412_require流程探索.png
// 以下是我个人阅读nodejs18.17.1源码总结,是后面补的,和上面流程图会稍微对不上,流程图是读的阮一峰的博客画的。注:我阅读过18.17.1前后的require源码,包括最新的(截止2024年9月),require虽然都在变,但流程基本没变 Module.prototype.require = function (id: string) { Module._load(id, this, false) } Module._load = function (request: string, parent: Object, isMain: boolean) { /** * 【一】解析请求路径 * 1、如果是内置模块,则直接返回内置模块名称,例如fs、path等 * 2、其他的必须返回绝对路径(且该路径能查到文件存在) * 注:如果路径不存在,则会报错 */ const filename = Module._resolveFilename(request, parent, isMain); //返回绝对路径 // 【二】查询缓存 const cachedModule = Module._cache[filename]; if (cachedModule !== undefined) { if(!cachedModule.loaded){ // 解决循环依赖,然后返回最终require的结果 return getExportsForCircularRequire(cachedModule); }else{ return cachedModule.exports; } } // 【三】验证导入模块是否是第三方维护模块(或者说验证是否是内置模块) const mod = loadBuiltinModule(filename, request); if (mod?.canBeRequiredByUsers && BuiltinModule.canBeRequiredWithoutScheme(filename)) { return mod.exports; } // 【四】当前模块存储到缓存 const module = cachedModule || new Module(filename, parent); Module._cache[filename] = module; // 【五】调用module.load let threw = true; try{ module.load(filename); threw = false; }finally{ if (threw) { // 清除缓存,因为load失败了 delete Module._cache[filename]; // 其他一些错误处理 ... } } return module.exports; } Module.prototype.load = function(filename:string){ // 归整化路径列表 this.paths = Module._nodeModulePaths(path.dirname(filename)); // 通过路径计算文件后缀 const extension = findLongestRegisteredExtension(filename); // 判断是否是mjs(因为默认是cjs,如果文件后缀不对,就报错) if (StringPrototypeEndsWith(filename, '.mjs') && !Module._extensions['.mjs']) throw new ERR_REQUIRE_ESM(filename, true); // 调用对应后缀的处理函数 【该处理函数是重点函数】 Module._extensions[extension](this, filename); // 标志该文件被加载成功 this.loaded = true; } Module._resolveFilename = function(request:string, parent:Object, isMain:boolean, options:?any) { // 判断是否是内置模块名称 if(如果该路径是内置模块){ return request; } let paths; if (typeof options === 'object' && options !== null) { /** * 配置处理(主要是options.paths的处理) * 用来决定request的读取范围,例如是node_module,还是其他自定义路径 */ paths = ... } // 查询与解析路径到绝对路径 const filename = Module._findPath(request, paths, isMain); if (filename) return filename; throw new Error(`Cannot find module '${request}'`); } Module._extensions[extension]=function(module, filename){ 参考上面的流程图,nodejs原始仅支持.js、.node、.json但是我看最新版源代码里似乎新增了一些 对.ts、.mjs的支持,但并不是直接写在Module._extensions[extension] 如果后缀是.js是,还需要特别关注 【module._compile(content:string, filename:string)函数,由于这个函数内涉及到对js代码的编译与处理 调用了nodejs的C++层面的东西,不方便整理出来,详细网络上有关于_compile的详解】 }
.
二、hook require的简单模板
function makeRequireFunction(_module_) { const Module = _module_.constructor; const Module_resolveFilename = Module._resolveFilename; /** * require的路径解析 * @param args * @returns */ Module._resolveFilename = function (...args) { let requestPath = args[0]; //请求路径 let parent = args[1]; //父模块 return Module_resolveFilename(...args); //调用原生路径解析 }; /** * require入口 * @param requirePath * @returns */ const myRequire = function(requirePath) { let _exports = _module_.require(requirePath); //调用原生require return _exports; }; /** * 自定义后缀解析 * 一般情况下在这里手动读取文件,自定义解析逻辑,然后导出exports对象 * @param _module_ 当前模块 * @param _filepath_ 当前文件绝对路径 */ Module._extensions['.bc'] = function (_module_, _filepath_) { // let myRequire = makeRequireFunction(_module_); // let _exports = myRequire("xxx") //调用自定义require // 解析完毕后的导出 _module_.exports={ }; }; myRequire.main = process.mainModule; myRequire.extensions = Module._extensions; myRequire.cache = Module._cache; return myRequire; }
require() 源码解读 - 阮一峰的网络日志 (ruanyifeng.com) 备用链接(https://files.cnblogs.com/files/blogs/668717/require源码解读-阮一峰的网络日志.pdf?t=1716020158&download=true)
(´-﹏-`;)毁灭吧,我累了
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具