相信大家都知道如何在Node 中載入一個模組:
1 2 3 |
|
沒錯,require
就是載入cjs 模組的API,但V8 本身是沒有cjs 模組系統的,所以node 是怎麼透過require
找到模組並且載入的呢? 【相關教學推薦:nodejs影片教學】
我們今天將對 Node.js 原始碼進行探索,深入理解 cjs 模組的載入過程。 我們閱讀的node 程式碼版本為v17.x:
內建模組為了知道
require 的工作邏輯,我們需要先了解內建模組是如何被載入到node 中的(諸如'fs','path','child_process',其中也包括一些無法被使用者引用的內部模組),準備好程式碼之後,我們首先要從node 啟動開始閱讀。
node 的main 函數在[src/node_main.cc](https://github.com/nodejs/node/blob/881174e016d6c27b20c70111e6eae2296b6c6293/src/node_main#Lccc/node_main#Lccc/node_main#Lcc/node_main#Lcc/node_main#Lcc/node_main#Lcc/node_main#Lcc/node_main#Lccc/node_main#Lccc/node_main#Lccc/node_main#Lcc/node_main#Lccc/node_main#Lcc/node_main#Lcc #[node::Start](https://github.com/nodejs/node/blob/881174e016d6c27b20c70111e6eae2296b6c6293/src/node.cc#L1134)##no# 來啟動一個#rrde 實例:這裡建立一個#rrde 實例來啟動一個#rde 實例來啟動一個#了事件循環,並且創建了一個
NodeMainInstance 的實例
main_instance 並且呼叫了它的
Run
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
|
Run 方法中呼叫
[CreateMainEnvironment](https://github.com/nodejs/node/blob/881174e016d6c27b20c70111e6eae2296b6c6293/src/node_main_instance. ##建立Environment 物件env 並呼叫其[RunBootstrapping](https://github.com/nodejs/node/blob/881174e016d6c27b20c70111e6eae296/nob. L398)
方法:<div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:php;toolbar:false">int NodeMainInstance::Run(const EnvSerializeInfo* env_info) {
Locker locker(isolate_);
Isolate::Scope isolate_scope(isolate_);
HandleScope handle_scope(isolate_);
int exit_code = 0;
DeleteFnPtr<Environment, FreeEnvironment> env =
CreateMainEnvironment(&exit_code, env_info);
CHECK_NOT_NULL(env);
Context::Scope context_scope(env->context());
Run(&exit_code, env.get());
return exit_code;
}</pre><div class="contentsignin">登入後複製</div></div><div class="contentsignin">登入後複製</div></div>
這裡的
[internal/bootstrap/loaders.js](https://github.com/nodejs/node/blob/881174e016d6c27b20c70111e6eae2296b6c6293/lib/internal/bootstrap/loaders.模組的[nativeModulerequire](https://github.com/nodejs/node/blob/881174e016d6c27b20c70111e6eae2296b6c6293/lib/internal/bootstrap/loaders#jsL3332),用於載入的內建函數,332)用於載入的函數,332)。
[internalBinding](https://github.com/nodejs/node/blob/881174e016d6c27b20c70111e6eae2296b6c6293/lib/internal/bootstrap/loaders.js#L##C 模組,用於載入的內建#C 模組,載入中的內建#. NativeModule](https://github.com/nodejs/node/blob/881174e016d6c27b20c70111e6eae2296b6c6293/lib/internal/bootstrap/loaders.js#L191) 則是內建的小型模組來模組。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
|
要注意的是,這個
require 函數只會被用於內建模組的加載,用戶模組的載入並不會用到它。 (這也是為什麼我們透過列印require('module')._cache 可以看到所有使用者模組,卻看不到
fs 等內建模組的原因,因為兩者的載入和快取維護方式並不一樣)。
使用者模組
[NodeMainInstance::Run](https://github .com/nodejs/node/blob/881174e016d6c27b20c70111e6eae2296b6c6293/src/node_main_instance.cc#L127) 函式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
我們已經透過一個去#cronment
對象,這個Environment
實例已經有了一個模組系統NativeModule用來維護內建模組。 然後程式碼會運行到Run 函數的另一個重載版本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
在這裡呼叫[LoadEnvironment](https://github.com /nodejs/node/blob/881174e016d6c27b20c70111e6eae2296b6c6293/src/api/environment.cc#L403)
:
:<div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:php;toolbar:false">MaybeLocal<Value> StartExecution(Environment* env, StartExecutionCallback cb) {
// 已省略其他运行方式,我们只看 `node index.js` 这种情况,不影响我们理解模块系统
if (!first_argv.empty() && first_argv != "-") {
return StartExecution(env, "internal/main/run_main_module");
}
}</pre><div class="contentsignin">登入後複製</div></div>
<p>在 <code>StartExecution(env, "internal/main/run_main_module")
这个调用中,我们会包装一个 function,并传入刚刚从 loaders 中导出的 require
函数,并运行 [lib/internal/main/run_main_module.js](https://github.com/nodejs/node/blob/881174e016d6c27b20c70111e6eae2296b6c6293/lib/internal/main/run_main_module.js)
内的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
所谓的包装 function 并传入 require
,伪代码如下:
1 2 3 |
|
所以这里是通过内置模块的 require
函数加载了 [lib/internal/modules/cjs/loader.js](https://github.com/nodejs/node/blob/881174e016d6c27b20c70111e6eae2296b6c6293/lib/internal/modules/cjs/loader.js#L172)
导出的 Module 对象上的 runMain
方法,不过我们在 loader.js
中并没有发现 runMain
函数,其实这个函数是在 [lib/internal/bootstrap/pre_execution.js](https://github.com/nodejs/node/blob/881174e016d6c27b20c70111e6eae2296b6c6293/lib/internal/bootstrap/pre_execution.js#L428)
中被定义到 Module
对象上的:
1 2 3 4 5 6 7 8 9 |
|
在 [lib/internal/modules/run_main.js](https://github.com/nodejs/node/blob/881174e016d6c27b20c70111e6eae2296b6c6293/lib/internal/modules/run_main.js#L74)
中找到 executeUserEntryPoint
方法:
1 2 3 4 5 6 7 8 9 10 |
|
参数 main
即为我们传入的入口文件 index.js
。可以看到,index.js
作为一个 cjs 模块应该被 Module._load
加载,那么 _load
干了些什么呢?这个函数是 cjs 模块加载过程中最重要的一个函数,值得仔细阅读:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
|
module
对象上的 [load](https://github.com/nodejs/node/blob/881174e016d6c27b20c70111e6eae2296b6c6293/lib/internal/modules/cjs/loader.js#L963)
函数用于执行一个模块的加载:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
实际的加载动作是在 Module._extensions[extension](this, filename);
中进行的,根据扩展名的不同,会有不同的加载策略:
fs.readFileSync
读取文件内容,将文件内容包在 wrapper 中,需要注意的是,这里的 require
是 Module.prototype.require
而非内置模块的 require
方法。1 2 3 4 |
|
而 Module.prototype.require
函数也是调用了静态方法 Module._load
实现模块加载的:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
看到这里,cjs 模块的加载过程已经基本清晰了:
初始化 node,加载 NativeModule,用于加载所有的内置的 js 和 c++ 模块
运行内置模块 run_main
在 run_main
中引入用户模块系统 module
通过 module
的 _load
方法加载入口文件,在加载时通过传入 module.require
和 module.exports
等让入口文件可以正常 require
其他依赖模块并递归让整个依赖树被完整加载。
在清楚了 cjs 模块加载的完整流程之后,我们还可以顺着这条链路阅读其他代码,比如 global
变量的初始化,esModule 的管理方式等,更深入地理解 node 内的各种实现。
更多node相关知识,请访问:nodejs 教程!
以上是探索 Node.js 原始碼,詳解cjs 模組的載入過程的詳細內容。更多資訊請關注PHP中文網其他相關文章!