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 Parsenode .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.<ext>")// -> a.<ext>//// require("a")// -> a// -> a.<ext>// -> a/index.<ext>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) { ', '\n});'];
之后涉及了断点的处理,是在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同步的特点也是在这体现出来的