nodejs之模块系统源码分析(上)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/THEANARKH/article/details/88610014

nodejs的模块分为几种,有内置的c++模块,内置的js模块,还有用户自定义的模块。下面我们先分析内置模块。然后再分析用户定义的模块。

1 内置模块

首先以注册tcp_wrap.cc模块为例子,一步步分析一下c++模块的注册。下面是tcp_wrap.cc模块的最后一句代码。

  1 NODE_BUILTIN_MODULE_CONTEXT_AWARE(tcp_wrap, node::TCPWrap::Initialize)

宏展开后

	#define NODE_BUILTIN_MODULE_CONTEXT_AWARE(modname, regfunc)                   \
	  NODE_MODULE_CONTEXT_AWARE_CPP(modname, regfunc, nullptr, NM_F_BUILTIN)
  
2   NODE_MODULE_CONTEXT_AWARE_CPP(tcp_wrap, node::TCPWrap::Initialize, nullptr, NM_F_BUILTIN)
	
	#define NODE_STRINGIFY(n) NODE_STRINGIFY_HELPER(n)
	#define NODE_STRINGIFY_HELPER(n) #n
	#define NODE_MODULE_CONTEXT_AWARE_CPP(modname, regfunc, priv, flags)          \
	  static node::node_module _module = {                                        \
	    NODE_MODULE_VERSION,                                                      \
	    flags,                                                                    \
	    nullptr,                                                                  \
	    __FILE__,                                                                 \
	    nullptr,                                                                  \
	    (node::addon_context_register_func) (regfunc),                            \
	    NODE_STRINGIFY(modname),                                                  \
	    priv,                                                                     \
	    nullptr                                                                   \
	  };                                                                          \
	  void _register_ ## modname() {                                              \
	    node_module_register(&_module);                                           \
	  }

3 static node::node_module _module = {                                        \
	    NODE_MODULE_VERSION,                                                      \
	    NM_F_BUILTIN,                                                                    \
	    nullptr,                                                                  \
	    __FILE__,                                                                 \
	    nullptr,                                                                  \
	    // 转成一个函数指针类型
	    (node::addon_context_register_func) (regfunc),                            \
	    'tcp_wrap',                                                  \
	    priv,                                                                     \
	    nullptr                                                                   \
	  };                                                                          \
	  void _register_ tcp_wrap) {                                              \
	    node_module_register(&_module);                                           \
	  }

所以上面的代码中的第三步的代码就是tcp_wrap.cc最后的一句代码宏展开后的样子。即定义了一个结构体和函数。其他内置的模块也类似。然后在nodejs启动的时候,通过RegisterBuiltinModules函数进行模块的注册。

RegisterBuiltinModules()

void RegisterBuiltinModules() {
#define V(modname) _register_##modname();
  NODE_BUILTIN_MODULES(V)
#undef V
}

#define NODE_BUILTIN_MODULES(V)                                               \
  NODE_BUILTIN_STANDARD_MODULES(V)                                            \
  NODE_BUILTIN_OPENSSL_MODULES(V)                                             \
  NODE_BUILTIN_ICU_MODULES(V)

#define NODE_BUILTIN_STANDARD_MODULES(V)                                      \
    V(async_wrap)                                                             \
    V(buffer)                                                                 \
    V(cares_wrap)                                                             \
    V(config)                                                                 \
    V(contextify)                                                             \
    V(domain)                                                                 \
    V(fs)                                                                     \
    V(fs_event_wrap)                                                          \
    V(http2)                                                                  \
    V(http_parser)                                                            \
    V(inspector)                                                              \
    V(js_stream)                                                              \
    V(module_wrap)                                                            \
    V(os)                                                                     \
    V(performance)                                                            \
    V(pipe_wrap)                                                              \
    V(process_wrap)                                                           \
    V(serdes)                                                                 \
    V(signal_wrap)                                                            \
    V(spawn_sync)                                                             \
    V(stream_wrap)                                                            \
    V(tcp_wrap)                                                               \
    V(timer_wrap)                                                             \
    V(trace_events)                                                           \
    V(tty_wrap)                                                               \
    V(udp_wrap)                                                               \
    V(url)                                                                    \
    V(util)                                                                   \
    V(uv)                                                                     \
    V(v8)                                                                     \
    V(zlib)
#

#if HAVE_OPENSSL
#define NODE_BUILTIN_OPENSSL_MODULES(V) V(crypto) V(tls_wrap)
#else
#define NODE_BUILTIN_OPENSSL_MODULES(V)
#endif

#if NODE_HAVE_I18N_SUPPORT
#define NODE_BUILTIN_ICU_MODULES(V) V(icu)
#else
#define NODE_BUILTIN_ICU_MODULES(V)
#endif

上面是nodejs内置的c++模块初始化过程。里面用了大量的宏,通过宏展开后,就是

  _register_async_wrap()
  _register_buffer()  

就是执行各个内置模块里最后一句代码宏展开之后的函数。每个函数的内容都是

  node_module_register(&_module)

不同的模块&_module对应的内容不一样。接下来我们看一下node_module_register函数。

static node_module* modlist_builtin;
static node_module* modlist_internal;
static node_module* modlist_linked
extern "C" void node_module_register(void* m) {
  struct node_module* mp = reinterpret_cast<struct node_module*>(m);

  if (mp->nm_flags & NM_F_BUILTIN) {
    mp->nm_link = modlist_builtin;
    modlist_builtin = mp;
  } else if (mp->nm_flags & NM_F_INTERNAL) {
    mp->nm_link = modlist_internal;
    modlist_internal = mp;
  } else if (!node_is_initialized) {
    // "Linked" modules are included as part of the node project.
    // Like builtins they are registered *before* node::Init runs.
    mp->nm_flags = NM_F_LINKED;
    mp->nm_link = modlist_linked;
    modlist_linked = mp;
  } else {
    modpending = mp;
  }
}

把内置模块对应的module结构体逐个加到modlist_builtin链表里。即内置模块最后形成一条链表。然后我们看看nodejs是怎么使用这些内置的c++模块的。我们看到nodejs里的内置的js模块都是通过process.binding函数引用c++模块的。所以我们首先看看binding函数。该函数是在bootstrap_node.js里定义的。

  const bindingObj = Object.create(null);
   // 保存c++的binding函数
    const getBinding = process.binding;
    process.binding = function binding(module) {
      module = String(module);
      let mod = bindingObj[module];
      if (typeof mod !== 'object') {
        mod = bindingObj[module] = getBinding(module);
        moduleLoadList.push(`Binding ${module}`);
      }
      return mod;
    };

我们看到,nodejs在执行bootstrap_node.js之前就给process挂载了binding函数,然后在bootstrap_node.js里,首先保存了c++层的binding函数,然后重写了binding函数,这是因为c++层的binding函数没有做缓存处理,这里封装了一下,加了一个缓存。我们接着看c++的binding函数。该函数是在node.cc的SetupProcessObject函数定义的。SetupProcessObject在nodejs初始化的时候会调用。

  	   env->SetMethod(process, "binding", Binding);
static void Binding(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);

  CHECK(args[0]->IsString());

  Local<String> module = args[0].As<String>();
  node::Utf8Value module_v(env->isolate(), module);

  node_module* mod = get_builtin_module(*module_v);
  Local<Object> exports;
  if (mod != nullptr) {
    exports = InitModule(env, mod, module);
  } else if (!strcmp(*module_v, "constants")) {
    exports = Object::New(env->isolate());
    CHECK(exports->SetPrototype(env->context(),
                                Null(env->isolate())).FromJust());
    DefineConstants(env->isolate(), exports);
  } else if (!strcmp(*module_v, "natives")) {
    exports = Object::New(env->isolate());
    DefineJavaScript(env, exports);
  } else {
    return ThrowIfNoSuchModule(env, *module_v);
  }

  args.GetReturnValue().Set(exports);
}

node_module* get_builtin_module(const char* name) {
  return FindModule(modlist_builtin, name, NM_F_BUILTIN);
}

inline struct node_module* FindModule(struct node_module* list,
                                      const char* name,
                                      int flag) {
  struct node_module* mp;

  for (mp = list; mp != nullptr; mp = mp->nm_link) {
    if (strcmp(mp->nm_modname, name) == 0)
      break;
  }

  CHECK(mp == nullptr || (mp->nm_flags & flag) != 0);
  return mp;
}

其实就是根据模块名在链表里查找这个模块,找到之后执行了initModule函数。

static Local<Object> InitModule(Environment* env,
                                 node_module* mod,
                                 Local<String> module) {
  Local<Object> exports = Object::New(env->isolate());
  // Internal bindings don't have a "module" object, only exports.
  CHECK_EQ(mod->nm_register_func, nullptr);
  CHECK_NE(mod->nm_context_register_func, nullptr);
  Local<Value> unused = Undefined(env->isolate());
  mod->nm_context_register_func(exports,
                                unused,
                                env->context(),
                                mod->nm_priv);
  return exports;
}

执行了模块的nm_context_register_func执行的函数,对应的就是每个内置模块最后一句代码里的Initialize函数。他会导出一个对象给调用方。比如

const { TCP, constants: TCPConstants } = process.binding('tcp_wrap');

这就是内置的js模块调用c++模块的过程。

2 用户自定义模块

我们在写一个模块的时候,一般会用require函数,那这个函数是哪里来的呢?一个模块的代码,在nodejs看来其实只是一个字符串,nodejs在require这个模块的时候,会在整个模块代码包裹在一个函数里。

Module.wrapper = [
  '(function (exports, require, module, __filename, __dirname) { ',
  	your code
  '\n});'
];

所以我们要首先看看这个这里的require函数和其他参数是哪里来的。这时候我们就要找到第一个被执行的用户js是如何被处理的。我们看看bootstrap_node.js的代码 。里面有个NativeModule函数。这个函数也是加载模块的,但是他是用来加载内置的js模块的,这些模块在lib下。比如http模块。我们看一下他的核心代码。

function NativeModule(id) {
    this.filename = `${id}.js`;
    this.id = id;
    this.exports = {};
    this.loaded = false;
    this.loading = false;
  }
  // lib下的js,是一个模块路径到源码的对象
  NativeModule._source = process.binding('natives');
  // 缓存管理
  NativeModule._cache = {};

  const config = process.binding('config');

  NativeModule.require = function(id) {
    if (id === 'native_module') {
      return NativeModule;
    }
    // 先查缓存
    const cached = NativeModule.getCached(id);
    if (cached && (cached.loaded || cached.loading)) {
      return cached.exports;
    }
    // 判断是否在native对象里
    if (!NativeModule.exists(id)) {
      ...
    }

    moduleLoadList.push(`NativeModule ${id}`);

    const nativeModule = new NativeModule(id);
    // 加载然后执行,导出
    nativeModule.cache();
    nativeModule.compile();

    return nativeModule.exports;
  };

  NativeModule.requireForDeps = function(id) {
    if (!NativeModule.exists(id) ||
        // TODO(TimothyGu): remove when DEP0084 reaches end of life.
        id.startsWith('node-inspect/') ||
        id.startsWith('v8/')) {
      id = `internal/deps/${id}`;
    }
    return NativeModule.require(id);
  };

  NativeModule.getCached = function(id) {
    return NativeModule._cache[id];
  };

  NativeModule.exists = function(id) {
    return NativeModule._source.hasOwnProperty(id);
  };

  NativeModule.getSource = function(id) {
    return NativeModule._source[id];
  };

  NativeModule.wrap = function(script) {
    return NativeModule.wrapper[0] + script + NativeModule.wrapper[1];
  };

  NativeModule.wrapper = [
    '(function (exports, require, module, internalBinding, process) {',
    '\n});'
  ];

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

    this.loading = true;

    try {
      const fn = runInThisContext(source, {
        filename: this.filename,
        lineOffset: 0,
        displayErrors: true
      });
      const requireFn = this.id.startsWith('internal/deps/') ?
        NativeModule.requireForDeps :
        NativeModule.require;
      // require函数只会在lib路径下的js里找模块
      fn(this.exports, requireFn, this, internalBinding, process);

      this.loaded = true;
    } finally {
      this.loading = false;
    }
  };

  NativeModule.prototype.cache = function() {
    NativeModule._cache[this.id] = this;
  };

从上面的代码我们知道,NativeModule模块根据传进去的id找到lib下的一个文件,然后执行,返回导出的对象。接下来我们看我们执行node app.js时是怎么被执行的。

bootstrap_node.js
	const Module = NativeModule.require('module');
	Module.runMain();

runMain就是执行app.js的入口。

// bootstrap main module.process.argv[1]即app.js
Module.runMain = function() {
  // Load the main module--the command line argument.
  Module._load(process.argv[1], null, true);
  // Handle any nextTicks added in the first tick of the program
  process._tickCallback();
};
Module._load = function(request, parent, isMain) {
  ...
  // 解析出文件绝对路径
  var filename = Module._resolveFilename(request, parent, isMain);
  // 缓存
  var cachedModule = Module._cache[filename];
  if (cachedModule) {
    updateChildren(parent, cachedModule, true);
    return cachedModule.exports;
  }
  /*
    如果是原生的js模块(除了lib/internal文件夹下的)则优先取原生的,比如我们require('fs'),
    即使我们也写了一个fs.js也不会被加载到。
  */
  if (NativeModule.nonInternalExists(filename)) {
    debug('load native module %s', request);
    return NativeModule.require(filename);
  }

  // Don't call updateChildren(), Module constructor already does.
  var module = new Module(filename, parent);

  if (isMain) {
    process.mainModule = module;
    module.id = '.';
  }

  Module._cache[filename] = module;

  tryModuleLoad(module, filename);

  return module.exports;
};


function tryModuleLoad(module, filename) {
  var threw = true;
  try {
    module.load(filename);
    threw = false;
  } finally {
    if (threw) {
      delete Module._cache[filename];
    }
  }
}
Module.prototype.load = function(filename) {
  debug('load %j for module %j', filename, this.id);

  assert(!this.loaded);
  this.filename = filename;
  /**
   * /dsasa/dsa/b.js
   * path.dirname(filename) => /dsasa/dsa/
   * 
   * 目录路径:/dsasa/dsa
    ["/dsasa/dsa/node_modules", "/dsasa/node_modules", "/node_modules"]
   */
  // 解析出文件可能存在的路径,如果dirname解析出的当前目录下不存在b.js,则会在paths里继续找
  this.paths = Module._nodeModulePaths(path.dirname(filename));
  // 默认拓展名是js,如果传的文件名有拓展名但是不合法则取拓展名为js
  var extension = path.extname(filename) || '.js';
  if (!Module._extensions[extension]) extension = '.js';
  // 找到对应的函数执行
  Module._extensions[extension](this, filename);
  this.loaded = true;
}
 Module._nodeModulePaths = function(from) {
    // guarantee that 'from' is absolute.
    from = path.resolve(from);
    // Return early not only to avoid unnecessary work, but to *avoid* returning
    // an array of two items for a root: [ '//node_modules', '/node_modules' ]
    if (from === '/')
      return ['/node_modules'];
      
    const paths = [];
    var p = 0;
    var last = from.length;
    // 从路径的最后一个字符开始遍历,假设from是/a/b
    for (var i = from.length - 1; i >= 0; --i) {
      const code = from.charCodeAt(i);
      // 找到一个/,并且/后面的字符串不是node_modules,追加一个path,如果本身node_modules就是则不需要追加了
      if (code === 47/*/*/) {
        if (p !== nmLen)
          paths.push(from.slice(0, last) + '/node_modules');
        // 更新还没有遍历的字符的末位置
        last = i;
        // 重置
        p = 0;
      } else if (p !== -1) {
        // 逐个字符比较,判断连起来是否是node_modules
        if (nmChars[p] === code) {
          ++p;
        } else {
          p = -1;
        }
      }
    }
    // Append /node_modules to handle root paths.
    paths.push('/node_modules');

    return paths;
  };

解析完文件的路径后继续读取文件内容然后执行。

Module._extensions['.js'] = function(module, filename) {
  var content = fs.readFileSync(filename, 'utf8');
  module._compile(internalModule.stripBOM(content), filename);
};
Module.prototype._compile = function(content, filename) {
 
  content = internalModule.stripShebang(content);

  // create wrapper function
  var wrapper = Module.wrap(content);

  var compiledWrapper = vm.runInThisContext(wrapper, {
    filename: filename,
    lineOffset: 0,
    displayErrors: true
  });
  ...
  var dirname = path.dirname(filename);
  // 带有上下文的require
  var require = internalModule.makeRequireFunction(this);
  // 从这里可以知道我们在模块里拿到的require到底是什么,还有模块里的exports,module.exports
compiledWrapper.call(this.exports, this.exports, require, this,filename, dirname);
  return result;
};

require函数返回了的module.exports,就这样我们拿到了模块里导出的内容。模块系统的模块查找和缓存还有很多细节,有时间继续分析。

猜你喜欢

转载自blog.csdn.net/THEANARKH/article/details/88610014