Node源码解析:模块加载和Node.js启动

2023-06-14 0 976

两本书不黑晶版:

两本书漂亮版:215566435/Fz-node,讨厌的 ,吕方

深入细致下层:Node.js开启和组件读取

以后早已从js微观看了 nodejs 标识符,所以那时太少说,间接上看深入细致至 c++下层的源标识符.

从赫赫有名的main早已开始

//node_main.cc int main(int argc, char *argv[]) { return node::Start(argc, argv); }

去除许多网络平台推论代码,他们走进了最有名的c词汇表达式,那个表达式只不过而已为的是带出node::Start(argc, argv);,他们竭尽全力深入细致进上看

int Start(int argc, char** argv) { //… V8::Initialize(); const int exit_code = Start(uv_default_loop(), argc, argv, exec_argc, exec_argv); //…. V8::Dispose(); v8_platform.Dispose(); //…. return exit_code; }

oop()之后,又走进了一个start表达式。

//…. LoadEnvironment(&env); { //…. do { //事件循环在这里才早已开始 uv_run(env.event_loop(), UV_RUN_DEFAULT); more = uv_loop_alive(env.event_loop()); } while (more == true);

在深度遍历了几个Start之后,他们走进第一个重要的表达式。那个函数做的事情只不过就是读取他们node.js的乱七八糟组件以及跑他们的一早已开始的标识符了。什么意思呢?只不过就是node hello.js,第一遍跑他们的标识符没有进入事件循环时,就会跑那个标识符,再一次证明了,我们的标识符执行一早已开始,并不会进入事件循环,而是跑完所有同步标识符以后,才会早已开始。

/* 在最近的版中,bootstrap.js被拆分成了loader.js和node.js 再这里他们看到了v8读取javascript的方法 */ Local<String> loaders_name = FIXED_ONE_BYTE_STRING(env->isolate(), “internal/bootstrap/loaders.js”); Local<Function> loaders_bootstrapper = GetBootstrapper(env, LoadersBootstrapperSource(env), loaders_name); Local<String> node_name = FIXED_ONE_BYTE_STRING(env->isolate(), “internal/bootstrap/node.js”); Local<Function> node_bootstrapper = GetBootstrapper(env, NodeBootstrapperSource(env), node_name); // Add a reference to the global object Local<Object> global = env->context()->Global(); // Bootstrap internal loaders Local<Value> bootstrapped_loaders; if (!ExecuteBootstrapper(env, loaders_bootstrapper, arraysize(loaders_bootstrapper_args), loaders_bootstrapper_args, &bootstrapped_loaders)) { return; } // Bootstrap Node.js Local<Value> bootstrapped_node; Local<Value> node_bootstrapper_args[] = { env->process_object(), bootstrapped_loaders }; if (!ExecuteBootstrapper(env, node_bootstrapper, arraysize(node_bootstrapper_args), node_bootstrapper_args, &bootstrapped_node)) {

以上标识符只不过就是做了几件事情:

1. 初始化global对象

2. 读取bootstrap中的两个组件

3. 挂载bootstrap初始化之后的东西到global对象中

所以,神秘的bootstrap两个组件到底是什么呢?让他们一探究竟

bootstrap/loader.js

(function bootstrapInternalLoaders(process, getBinding, getLinkedBinding, getInternalBinding) { });

loader是一个表达式表达式,注意这里使用了(function(){})的方式将源标识符包住,究其原因是为的是让V8导出的时候,告诉V8把这段标识符导出成一个c++的表达式表达式,具体映射到c++,标识符主要是

//读取源标识符字符串,你看这是String类型 Local<String> loaders_name = FIXED_ONE_BYTE_STRING(env->isolate(), “internal/bootstrap/loaders.js”); on类型//说明loaders_bootstrapper就是一个表达式 Local<Function> loaders_bootstrapper = GetBootstrapper(env, LoadersBootstrapperSource(env), loaders_name); //注意看,这里是的参数只不过就是对应了bootstrapInternalLoaders中的4个参数 Local<Value> loaders_bootstrapper_args[] = { env->process_object(), get_binding_fn, get_linked_binding_fn, get_internal_binding_fn }; //执行ExecuteBootstrapper Local<Value> bootstrapped_loaders; if (!ExecuteBootstrapper(env, loaders_bootstrapper, arraysize(loaders_bootstrapper_args), loaders_bootstrapper_args, &bootstrapped_loaders)) {

思路很简单,就是标识符一大坨而已,接下来他们看看bootstrapInternalLoaders中四个重要的参数是什么

(function bootstrapInternalLoaders(process, getBinding, getLinkedBinding, getInternalBinding) { }); //process你没看错,就是他们所用的全局process对象 //getBinding只不过就是之后的process.binding//getLinkedBinding用于绑定在process._getLinkedBinding上用于载入c++组件,比如用户写的c++ addon //getInt

他们往下遍历标识符就会看到他们的老朋友

// Set up NativeModule function NativeModule(id) { this.filename = `${id}.js`; this.id = id; this.exports = {}; this.loaded = false; this.loading = false; } //构造一个loader,之后用于导出 const loaderExports = { internalBinding, NativeModule }; NativeModule.require = function(id) { const nativeModule = new NativeModule(id); nativeModule.cache(); nativeModule.compile(); return nativeModule.exports; } return loaderExports;

NativeModule组件。那个组件只不过就是他们用在Node.js中的module定义了,可以看见,里面的this.exports.他们往下看,终于见到了他们的老朋友require,所以在他们一早已开始调用node hello.js时的require是在这里被创建的,也就是Node.js开启的时候。而require之后的结果,永远是被编译过后的eports对象.

在最后,导出那个loader,还给c++层,然后将loader和process,传递给bootstrap/node.js.

bootstrap/node.js

(function bootstrapNodeJSCore(process, { internalBinding, NativeModule }){ NativeModule.require(internal/process/warning).setup(); NativeModule.require(internal/process/next_tick).setup(); NativeModule.require(internal/process/stdio).setup(); //….. evalScript(xxx)//执行他们的标识符 }

bootstrapNodeJSCore就是他们的开启表达式了,那个脚本跑完,所有的同步标识符会被执行完毕我,他们看到这里传递进来了process和他们需要的第一个NativeModule

在这段表达式中,只不过做的就是初始化,比如读取console,记载next_tick等等…他们用户指定的标识符由`evalScript进行调用

function evalScript(name) { const CJSModule = NativeModule.require(internal/modules/cjs/loader); const path = NativeModule.require(path); const cwd = tryGetCwd(path); const module = new CJSModule(name); module.filename = path.join(cwd, name); module.paths = CJSModule._nodeModulePaths(cwd); const body = wrapForBreakOnFirstLine(process._eval); const script = `global.__filename = ${JSON.stringify(name)};\n` + global.exports = exports;\n + global.module = module;\n + global.__dirname = __dirname;\n + global.require = require;\n + return require(“vm”).runInThisContext( + `${JSON.stringify(body)}, { filename: ` + `${JSON.stringify(name)}, displayErrors: true });\n`; const result = module._compile(script, `${name}-wrapper`); if (process._print_eval) console.log(result); // Handle any nextTicks added in the first tick of the program. process._tickCallback(); }

至此,他们揭开了所有谜团,在v8编译这段标识符运行的时候,就会被evalScript,以字符串拼接的方式,将他们的标识符拼接到这里,然后使用vm.runInThisContext()的办法去跑他们的code,所以他们的第一次跑就会执行了。在这里值得注意的是,在初始化的时候就会调用,_tickCallback方法。

从头梳理

node.js开启的时候,会从c++微观先开启,然后走到bootstrap里面初始化流程是初始化global对象->读取编译内置组件(process,c++等等)->运行用户指定脚本->跑一次nextTick->进入事件循环

本章内容最好和以后的一起看

1. 组件化js层:实现一个简单组件读取2. 组件化js层2:组件读取之谜

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务