node require模块加载napi开发的node模块分析(node v15.x源码)

本文深入探讨了Node.js加载NAPI模块的流程,从`require`的`resolve`、`cache`、`extensions`和`main`步骤开始,详细解释了模块加载的各个环节,包括`resolve`查询模块路径、`cache`缓存机制、`extensions`的废弃功能,以及`main`模块的识别。文章通过源码分析,展示了`require`如何加载不同类型模块,重点讲解了`.node`模块加载涉及的`process.dlopen`和`dlopen`函数,以及NAPI模块注册和执行过程,帮助读者理解Node.js内部的模块加载机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


node初学者,工作中涉及了napi模块开发,对于其可直接require的方式引入起了兴趣,因此查阅源码和其他大佬的经验等等,总结记录,目标是尽可能详细,这样之后忘掉了再来看就能少花时间去想了∩( ・ω・)∩

下面是napi插件部分的示例代码

在这里插入图片描述

从加载napi的第一步require结构开始查看

node require定义如下:

interface NodeRequire extends NodeJS.Require { }

declare var require: NodeRequire;

它来自nodejs.require,其结构如下

在这里插入图片描述

其中id为我们传给require的内容

resolve

resolve 是 require中的一个函数,可以用来查询某个模块的绝对路径

require.resolve(’./myModule.js’);

如果该路径不存在会报cannot find module “./myModule.js”的异常

resolve查询模块过程并不会去加载该模块

在resolve中还有个paths,作用是 返回一个数组,其中包含解析 request 过程中被查询的路径。 如果 request 字符串指向核心模块(例如 http 或 fs),则返回 null。

在这里插入图片描述
在这里插入图片描述

cache

cache 看名字即可以知道他是个缓存,其中缓存了require的所有的模块(亲测内置模块不会显示),大致结构如下

在这里插入图片描述

其中可以看出各module缓存的key为各自的绝对路径,因此可看出模块加载是按照模块存在的路径来判断的,就算是一模一样的模块,只要路径不同就会引入

extensions

extensions是一个已废弃的模块

新增于: v0.3.0

废弃于: v0.10.6

可用于获取模块支持的扩展名,或自定义文件的处理函数,格式如下

在这里插入图片描述

结果:

在这里插入图片描述

main

当文件直接从 Node.js 运行时,require.main 被设置为其模块。 这意味着可以通过测试 require.main === 模块来直接确定文件是否已运行。

对于 require.main.js 文件,如果通过 node require.main.js 运行则为 true, 但如果通过 require('./require.main') 运行则为 false

因为module提供了一个filename属性(通常相当于__filename),可以通过检查require.main.filename来获取当前应用的入口点。

require.main结构如下:

在这里插入图片描述

require加载模块流程

node加载模块使用的require加载模块,使用的是module.prototype.load方法

在给定的文件路径加载一个模块。 返回该模块的exports 属性。

在这里插入图片描述
其中主要为Module._load

1.在module.load中加载模块先通过parent.path和request拼出的relResolveCacheIdentifier,去relativeResolveCache的缓存中查找是否有记录的文件,此处的relativeResolveCache存储的是有父模块的module的缓存

//同一目录中(延迟加载)模块的快速路径。 需要间接缓存以允许缓存失效而不更改旧的缓存键名称。

如果有则Module._cache根据文件的绝对路径去寻找,找到既返回它的exports

在这里插入图片描述

这里需注意的updateChildren,会将parent的children更新,可能会导致一些问题,如这位

一行 delete require.cache 引发的内存泄漏血案 - 知乎 (zhihu.com)

relativeResolveCache缓存未命中,则继续进行下一步,

通过Module._resolveFilename获取完整路径

2.以node:开头的优先(原生模块)

在这里插入图片描述

3.其次是module._cache缓存中的

在这里插入图片描述

4.然后是原生模块

在这里插入图片描述

5.如果还是没有,则创建一个module

function Module(id = '', parent) {
   

 this.id = id;

 this.path = path.dirname(id);

 this.exports = {
   };

 moduleParentCache.set(this, parent);

 updateChildren(parent, this, false);

 this.filename = null;

 this.loaded = false;

 this.children = [];

}

根据是不是main来确定module.id和process的主模块,

然后存入module._cache,之后根据是否有父模块,来决定是否存入relativeResolveCache

在这里插入图片描述

之后进行load,根据是否报错和prototype等判断处理一番之后,返回exports,结束

在这里插入图片描述

到此根据流程判断,我们要寻找的.node模块加载,应该是在此处的module.load中进行了处理,我们找到此方法
在这里插入图片描述
可以看到在其中先是获取了文件名,路径,扩展名,然后Module._extensions根据扩展名传入this指针和filename执行相应的处理函数,此处的extensions既为require.extensions中的三类[js,json,node]

处理函数执行完,将loaded状态置为true,这之后的esmloader,似乎是esm和commonjs的一些处理

不同类型模块加载

下面看下不同类型模块加载,首先是js,判断扩展名等一系列条件,全部通过后,以utf8的方式读取文件内容,

在这里插入图片描述

通过module._compile来返回[exports, require, module, filename, dirname]等,截取的部分代码如下

json格式则反序列化,直接导出

在这里插入图片描述

然后是node模块,主要部分是process.dlopen,

在这里插入图片描述

DlOpen

我们接下来查看一下dlopen的实现

在这里插入图片描述

除去方法前边的一些异常判断,主要部分从node::Utf8Value filename(env->isolate(), args[1]);开始,这里取出了传入的filename,然后调用tryloadAddon,传入了filename,默认flags,和一个以dlib为参数的匿名函数,

我们先看一下tryloadAddon实现

inline void Environment::TryLoadAddon(
    const 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值