资讯专栏INFORMATION COLUMN

require源码阅读

Batkid / 2509人阅读

require gitlab

mudule对象化

require最终会把每个模块都转化为对象

function Module(id, parent) {
    this.id = id;
    this.exports = {};
    this.parent = parent;
    updateChildren(parent, this, false);
    this.filename = null;
    this.loaded = false;
    this.children = [];
}
require方法

用assert断言输入的合法性并调用_load方法还有一个调用_load的是

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();
};

这个我不是特别确定,但基本确定是给node xxx.js 这条命令调用的

load方法

这里有很多是关于处理main的,核心的一段是

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

这个佐证了上面runMain是给node xxx.js 这条命令调用的这个论点,另外main模块的id一定是 ".",并且parent一定是空。
另外在调用前会先查找模块缓存的是否存在。
以下代码为了简化删去main模块的处理

//创建没有原型的空对象
Module._cache = Object.create(null);
Module._pathCache = Object.create(null);

Module._load = function(request, parent, isMain) {
  if (parent) {
    debug("Module._load REQUEST %s parent: %s", request, parent.id);
  }

  var filename = Module._resolveFilename(request, parent, isMain);

//缓存中是否存在
  var cachedModule = Module._cache[filename];
  if (cachedModule) {
    updateChildren(parent, cachedModule, true);
    return cachedModule.exports;
  }

//是否是native模块
  if (NativeModule.nonInternalExists(filename)) {
    debug("load native module %s", request);
    return NativeModule.require(filename);
  }

//其他模块处理
  var module = new Module(filename, parent);

  Module._cache[filename] = module;

  tryModuleLoad(module, filename);

  return module.exports;
};

这个部分说明了加载模块首先是判断模块名,之后是查找缓存,查找native 模块,然后是其他模块,最后的return是最为关键的,返回值永远是module的 exports

查找node_modules文件夹的规则
 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"];

    // note: this approach *only* works when the path is guaranteed
    // to be absolute.  Doing a fully-edge-case-correct path.split
    // that works on both Windows and Posix is non-trivial.
    const paths = [];
    var p = 0;
    var last = from.length;
    for (var i = from.length - 1; i >= 0; --i) {
      const code = from.charCodeAt(i);
      if (code === 47//*/*/) {
        if (p !== nmLen)
          paths.push(from.slice(0, last) + "/node_modules");
        last = i;
        p = 0;
      } else if (p !== -1) {
        if (nmChars[p] === code) {
          ++p;
        } else {
          p = -1;
        }
      }
    }

从from开始逐层向上查找node_modules文件夹

Module._extensions

js,json,node,mjs
每个后缀的文件都有对应的打开方式
js 清除可能的BOM头后加载
json json Parse
node .node 这是C/C++编写的扩展文件,通过dlopen()方法加载最后编译生成的文件,可以当作是一个系统调用

findPath

这部分代码比较多,只看一段注释即可
// given a module name, and a list of paths to test, returns the first
// matching file in the following precedence.
//
// require("a.")
// -> a.
//
// require("a")
// -> a
// -> a.
// -> a/index.

package.json

node会去寻找路径中的package.json文件并且会加载其中的main,并放入packagecache中,用main中指定的文件再去确认绝对路径然后加载

function readPackage(requestPath) {
  const entry = packageMainCache[requestPath];
  if (entry)
    return entry;

  const jsonPath = path.resolve(requestPath, "package.json");
  const json = internalModuleReadFile(path.toNamespacedPath(jsonPath));

  if (json === undefined) {
    return false;
  }

  try {
    var pkg = packageMainCache[requestPath] = JSON.parse(json).main;
  } catch (e) {
    e.path = jsonPath;
    e.message = "Error parsing " + jsonPath + ": " + e.message;
    throw e;
  }
  return pkg;
}
compile

如何查找到的文件基本清楚了,之后就是最常用的js的compile了
js模块的外层一定会被套上

Module.wrapper = [
  "(function (exports, require, module, __filename, __dirname) { ",
  "
});"
];

之后涉及了断点的处理,是在vm模块中的Srcript对象中的runInThisContext

后面有一句说明了为什么node中的所有文件也可以拥有Module的各种方法和特性 require 包括那些并不是main的文件,另外所有的模块也是公用的模块缓存,利用Module require中的return Module._load(path, this, /* isMain */ false);把自己的this作为parent作为Module对象的parent
var require = internalModule.makeRequireFunction(this);
makeRequireFunction的代码

// Invoke with makeRequireFunction(module) where |module| is the Module object
// to use as the context for the require() function.
function makeRequireFunction(mod) {
  const Module = mod.constructor;

  function require(path) {
    try {
      exports.requireDepth += 1;
      return mod.require(path);
    } finally {
      exports.requireDepth -= 1;
    }
  }

  function resolve(request, options) {
    return Module._resolveFilename(request, mod, false, options);
  }

  require.resolve = resolve;

  function paths(request) {
    return Module._resolveLookupPaths(request, mod, true);
  }

  resolve.paths = paths;

  require.main = process.mainModule;

  // Enable support to add extra extension types.
  require.extensions = Module._extensions;

  require.cache = Module._cache;

  return require;
}

执行compiledWrapper,对应wrapper插入的js
result = compiledWrapper.call(this.exports, this.exports, require, this, filename, dirname);
首先是compiledWrapper的this绑定在了Module自己exports上,自己的exports也作为了参数被注入,相当于隐式的赋值exports就是compiledWrapper中的exports了这个就成了对外唯一的输出了,其他的所有值都成了compiledWrapper的私有不会污染全局,require作为参数被注入,另外就是文件名 和路径的注入

从上述处理加载的过程我们可以发现只要有require node就会继续加载,另外CommonJs同步的特点也是在这体现出来的

对照学习

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/93763.html

相关文章

  • koa源码阅读之目录结构与辅助库相关

    摘要:从一个对象里面提取需要的属性这篇文章一直想写了还想起那一夜我看到白天的代码,实在太美了。 koa源码lib主要文件有 application.js context.js request.js response.js application.js koa主要的逻辑处理代码整个koa的处理 context.js 将req,res方法 挂载在这,生成ctx上下文对象 requests....

    sherlock221 评论0 收藏0
  • 阅读sea.js源码小结

    摘要:依赖信息是一个数组,比如上面的依赖数组是源码如下是利用正则解析依赖的一个函数时间出发函数主要看这个部分注释是防止拷贝该时间的回调函数,防止修改,困惑了一下。对的赋值需要同步执行,不能放在回调函数里。 sea.js想解决的问题 恼人的命名冲突 烦琐的文件依赖 对应带来的好处 Sea.js 带来的两大好处: 通过 exports 暴露接口。这意味着不需要命名空间了,更不需要全局变量。...

    chavesgu 评论0 收藏0
  • 一步步去阅读koa源码,整体架构分析

    摘要:阅读好的框架的源码有很多好处,从大神的视角去理解整个框架的设计思想。使用其实某个框架阅读源码的时候,首先我们要会去用这个框架,因为用了我们才知道,某个是怎么用,哪里有坑,哪里设计的精妙。 阅读好的框架的源码有很多好处,从大神的视角去理解整个框架的设计思想。大到架构设计,小到可取的命名风格,还有设计模式、实现某类功能使用到的数据结构和算法等等。 使用koa 其实某个框架阅读源码的时候,首...

    haoguo 评论0 收藏0
  • 一步步去阅读koa源码,整体架构分析

    摘要:阅读好的框架的源码有很多好处,从大神的视角去理解整个框架的设计思想。使用其实某个框架阅读源码的时候,首先我们要会去用这个框架,因为用了我们才知道,某个是怎么用,哪里有坑,哪里设计的精妙。 阅读好的框架的源码有很多好处,从大神的视角去理解整个框架的设计思想。大到架构设计,小到可取的命名风格,还有设计模式、实现某类功能使用到的数据结构和算法等等。 使用koa 其实某个框架阅读源码的时候,首...

    chaos_G 评论0 收藏0
  • Koa源码阅读笔记(2) -- compose

    摘要:于是抱着知其然也要知其所以然的想法,开始阅读的源代码。问题读源代码时,自然是带着诸多问题的。源代码如下在被处理完后,每当有新请求,便会调用,去处理请求。接下来会继续写一些阅读笔记,因为看的源代码确实是获益匪浅。 本笔记共四篇Koa源码阅读笔记(1) -- coKoa源码阅读笔记(2) -- composeKoa源码阅读笔记(3) -- 服务器の启动与请求处理Koa源码阅读笔记(4) -...

    roland_reed 评论0 收藏0

发表评论

0条评论

Batkid

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<