Nodejs的require流程

nodejs的require流程

.

一、require简单流程

nodejs的require流程大致如下:

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;
}

参考:
github-nodejs/node

require() 源码解读 - 阮一峰的网络日志 (ruanyifeng.com) 备用链接(https://files.cnblogs.com/files/blogs/668717/require源码解读-阮一峰的网络日志.pdf?t=1716020158&download=true)

通过字节码保护Node.js源码之原理篇 - 知乎 (zhihu.com)

posted @ 2024-05-20 06:49  麦块程序猿  阅读(85)  评论(0编辑  收藏  举报