// $PREFIX/lib/node, where $PREFIX is the root of the Node.js installation.
var prefixDir;
// process.execPath is $PREFIX/bin/nodeexcept on Windows where it is
// $PREFIX\node.exe.
if (isWindows) {
prefixDir = path.resolve(process.execPath, '..');
} else {
prefixDir = path.resolve(process.execPath, '..', '..');
}
哦,你好! 好久不见。
我从这篇系列博客中稍微休息了一下,享受假期并编写新版本的 Zarf。但我回来了,并准备继续推出这个系列博客。
我想先看看
module
模块(快速说三遍!)。Node.js 文档的模块页面包含了模块系统的大量信息。在深入module
代码库之前,我做了一个快速阅读。我将避免总结该文档页面的内容,因为我专注于代码,但如果在阅读本博文的其余部分之前仔细阅读,可能会有所帮助。像往常一样,我通过阅读 module.js 文件中的最后几行开始读取代码。
所以看起来好像在底部有一个变量映射,并且注释指出它是为了“向后兼容”。通过查看此文件上的提交历史,我深入了解了这部分。看起来这种变化在2011年初出现了(当时的Node 还只是一个 baby),并且是 CommonJS 模块系统某种重构的一部分。重构涉及一些文件的移动,所以变量映射实际上早于2011年初的日期。我似乎开始走入一个考古虫洞,所以我停下来回到代码。
module.js 始于调用
_initPaths
函数。如果我不得不尽力猜测,我会说它将负责初始化一些路径 - 呵呵呵。_initPaths
函数的前几行非常标准化,在我阅读过的 Node 代码库中很常见。逻辑很简单:如果我们在 Windows 上,请执行此操作;否则,那样做。在这部分,条件逻辑负责设置代表用户主目录的变量。接下来的一部分代码还使用了类似的条件逻辑来确定相关机器上 Node.js 安装的位置。
然后该函数将创建一个
paths
数组,它将存储可能存在 Node.js 的安装目录和用户的主目录中的 Node 模块位置。下一段代码获取
NODE_PATH
环境变量的值。我对这个环境变量的名字有点好奇,因为我以前没有遇到过。更彻底地重新阅读模块文档显示,这是一个“遗留”变量,来自于还没有一个如何在 Node 中加载模块的规范的时候。不管怎样。接下来的代码有趣一点了。
nodePath
是分号(或冒号,如果你在 Windows上)分隔的一串路径。下一部分代码通过冒号或分号将字符串分解,迭代该列表中的每个元素(即路径),并检查它们是否为真值,然后将它们添加到已有的paths
列表中。因此,现在我们的路径列表包含 Node.js 安装中的模块集合,我们的主目录以及NODE_PATH
环境变量中引用的目录。最后,该函数将
Module.globalPaths
的值设置为我们刚刚创建的paths
列表的副本。接下来我有兴趣看看的一个函数,就是
require
函数。所以看起来
require
需要做一些基本的断言来确认传递的path
的有效性,然后调用_load
函数。所以看起来我们正在检查
isMain
变量和experimentalModules
变量是否为真。isMain
是指(我猜测)require 是否来自主模块(译注:_load
函数通过isMain
的值来设定主模块),而experimentaModules
是指 Node 是否配置为支持 ES模块。看起来如果情况如此,函数调用 ES 模块加载器。我会在另一篇博客文章中多看看 ES 模块加载器,因此我目前放弃深入这部分。取而代之,我查看了_load
函数的其余部分。所以看起来
_load
所做的第一件事就是获取与我们要导入的模块相关的文件名。它通过调用
_resolveFilename
函数基于以下优先级搜索具有该模块名称的文件。如果您通过上面的链接阅读了模块文档,您会记得 Node 缓存它需要的模块。这使得代码中的下几行易于理解。
所以基本上,一旦它具有与模块相关联的文件名,它将检查该模块是否已经被加载并且在缓存中。如果是,它会调用
updateChildren
函数(我认为)更新了一些内部数据结构,以存储缓存模块现在已被新模块 required 的事实(译注:更新了 module.children 数组)。然后它返回该模块导出的 exports。下一段代码将检查所请求的模块是否是 NativeModule。如果是这样,它将调用原生模块加载器中的
require
函数。最后,如果模块尚未被 required ,并且它不是 Native 模块,则该函数调用
Module
构造函数为我们尝试加载的特定模块创建一个新的 Module 对象,并将其存储在缓存中。接下来它调用
module
上的tryModuleLoad
函数。tryModuleLoad
函数基本上是Module
对象中的load
原型函数的一个try-catch
小包装。load
函数的功能是检查模块文件名上的文件扩展名,然后将其传递给另一个加载器。例如,以.js
结尾的文件名由一个加载程序处理,以.json
结尾的文件名由另一个加载。快速浏览一看,我们可以导入以.js
,.json
,.node
和.mjs
(新ES模块系统的文件扩展名)结尾的文件。每个扩展加载程序最终负责填充module
对象中的exports
字段,作为 require 的结果返回。我将在这里停下代码阅读以保持文章的精简,简而言之,模块加载器负责管理我们系统上的模块(可能不同类型的模块)的缓存和查找。