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)
(´-﹏-`;)毁灭吧,我累了