Node.js源码学习-模块

Node.js底层有三个重要部分

node

libuv 建立事件循环

v8 引擎以及JS/C++对象转换

node提供的模块 fs os net process等

libuv是一个神器,事件循环,适合状态机驱动的架构,单独学习。

v8的请看google的官方API文档。

今天主要看一下node的模块。

 

1.js2c.py

node内置了很多js文件,包括主程序 src/node.js和模块文件lib/*.js。js2py是把每一个js文件都生成一个源码数组,存放在node_natives.h中。

node_native

node_native.h中,每个js文件对应一个数组,源码以const char数组的形式存放。

 

node_native在映射完毕后,变成了这样一个数组。

2.node_javascript.cc

node_javascript.cc include了node_native.h,并提供两个方法:

MainSource(),只有一句

return OneByteString(env->isolate(), node_native, sizeof(node_native) - 1);

返回node_native映射的js文件,即node.js,转换成了一个v8::handle类型。node_native_1

 

 

图:node_native,node.js映射后的数组。

DefineJavaScript(env, target)

将所有js文件映射到Object型的target(不包括node.js)

target->Set(name, source)

综上,node_javascript.cc做了两件事,返回node.js的v8::handle,

返回lib/*.js的Object,{name: v8::handle, name: v8::handle .....}

现在所有js文件都被转换成了v8::handle。

3.node.cc

node.cc是nodejs的main文件,这里我们先认识一个node世界中的风云人物,process。

 

 

在node.cc的CreateEnvironment函数中,process出生了,下面我们从源码看看process是个什么?被赋予了哪些属性?

Local<FunctionTemplate> process_template = FunctionTemplate::New(isolate);
process_template->SetClassName(FIXED_ONE_BYTE_STRING(isolate, "process"));

Local<Object> process_object = process_template->GetFunction()->NewInstance();
env->set_process_object(process_object);

SetupProcessObject(env, argc, argv, exec_argc, exec_argv);

从上面这段代码可以看出process的原始属性,FunctionTemplate,很明显,是js里的一个function,function的name为process。

现在JS世界中已经多了一个叫process的function,那么它是在哪里成长的呢?

 

在node.cc的LoadEnvironment函数中,

Local<String> script_name = FIXED_ONE_BYTE_STRING(env->isolate(), "node.js");
Local<Value> f_value = ExecuteString(env, MainSource(env), script_name);
CHECK(f_value->IsFunction());
Local<Function> f = Local<Function>::Cast(f_value);
Local<Object> global = env->context()->Global();
Local<Value> arg = env->process_object();
f->Call(global, 1, &arg);

运行MainSource

拿到了node.js的返回f_value,即node.js函数,入参为process的闭包函数,

(function(process) {})

验明正身,执行node.js闭包函数,最后一句做JS的同学看着肯定很眼熟,这不就是

(function(process){}).call(global, process)

 

4.fs.js

这里通过一个fs模块的创建与加载,来展示process呼风唤雨的能力。

const SlowBuffer = require('buffer').SlowBuffer;
const util = require('util');
const pathModule = require('path');

const binding = process.binding('fs');
const constants = require('constants');
const fs = exports;
const Buffer = require('buffer').Buffer;
const Stream = require('stream').Stream;
const EventEmitter = require('events');
const FSReqWrap = binding.FSReqWrap;
const FSEvent = process.binding('fs_event_wrap').FSEvent;

首先规规矩矩的加载了几个模块,然后突然出现了binding

Binding在node.cc中实现

主要逻辑为,查看cache中是否有这个模块,如果没有就新建一个。

新建模块:如果存在builtin,则使用nm_context_register_func方法注册到cache中,如果是常量,cache中注册常量,如果是natives,绑定所有lib/*.js。

在fs中,process.binding找到了fs,是一个builtin模块。这个模块在node_file.cc中实现,最后通过NODE_MODULE_CONTEXT_AWARE_BUILTIN注册到builtin。

Binding函数完成了js上调用C++模块。

 

5.node.js

node.js中通过一个NativeModules来管理js模块,

function NativeModule(id) {
   this.filename = id + '.js';
   this.id = id;
   this.exports = {};
   this.loaded = false;
}

NativeModule._source = process.binding('natives');
NativeModule._cache = {};

 

process.binding('natives'),将所有内置js模块绑定到_source上。

之后可以通过require方法来拿到这些模块。

NativeModule.require = function(id) {
   if (id == 'native_module') {
      return NativeModule;
   }

   var cached = NativeModule.getCached(id);
   if (cached) {
      return cached.exports;
   }

   if (!NativeModule.exists(id)) {
      throw new Error('No such native module ' + id);
   }

   process.moduleLoadList.push('NativeModule ' + id);

   var nativeModule = new NativeModule(id);

   nativeModule.cache();
   nativeModule.compile();

   return nativeModule.exports;
};

先检查cache,如果cache没有,就在process.moduleLoadList中增加一个,然后缓存,执行.

缓存就是把模块放到_cache中,compile函数

NativeModule.prototype.compile = function() {
   var source = NativeModule.getSource(this.id);
   source = NativeModule.wrap(source);

   var fn = runInThisContext(source, {
      filename: this.filename,
      lineOffset: 0
   });
   fn(this.exports, NativeModule.require, this, this.filename);

   this.loaded = true;
};


 var ContextifyScript = process.binding('contextify').ContextifyScript;
 function runInThisContext(code, options) {
 var script = new ContextifyScript(code, options);
 return script.runInThisContext();
 }

通过C++模块contextify运行了这段js。

 

 

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注