You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Module._findPath=function(request,paths,isMain){if(path.isAbsolute(request)){paths=[''];}elseif(!paths||paths.length===0){returnfalse;}// \x00 -> null,相当于空字符串varcacheKey=request+'\x00'+(paths.length===1 ? paths[0] : paths.join('\x00'));// 路径的缓存varentry=Module._pathCache[cacheKey];if(entry)returnentry;varexts;// 尾部是否带有/vartrailingSlash=request.length>0&&request.charCodeAt(request.length-1)===47/*/*/;// For each pathfor(vari=0;i<paths.length;i++){// Don't search further if path doesn't existconstcurPath=paths[i];// 当前路径if(curPath&&stat(curPath)<1)continue;varbasePath=path.resolve(curPath,request);varfilename;// 调用internalModuleStat方法来判断文件类型varrc=stat(basePath);// 如果路径不以/结尾,那么可能是文件,也可能是文件夹if(!trailingSlash){if(rc===0){// File. 文件if(preserveSymlinks&&!isMain){filename=path.resolve(basePath);}else{filename=toRealPath(basePath);}}elseif(rc===1){// Directory. 当提供的路径是文件夹的情况下会去这个路径下找package.json中的main字段对应的模块的入口文件if(exts===undefined)// '.js' '.json' '.node' '.ms'exts=Object.keys(Module._extensions);// 获取pkg内部的main字段对应的值filename=tryPackage(basePath,exts,isMain);}if(!filename){// try it with each of the extensionsif(exts===undefined)exts=Object.keys(Module._extensions);filename=tryExtensions(basePath,exts,isMain);// ${basePath}.(js|json|node)等文件后缀,看是否文件存在}}// 如果路径以/结尾,那么就是文件夹if(!filename&&rc===1){// Directory.if(exts===undefined)exts=Object.keys(Module._extensions);filename=tryPackage(basePath,exts,isMain)||// try it with each of the extensions at "index"tryExtensions(path.resolve(basePath,'index'),exts,isMain);}if(filename){// Warn once if '.' resolved outside the module dirif(request==='.'&&i>0){if(!warned){warned=true;process.emitWarning('warning: require(\'.\') resolved outside the package '+'directory. This functionality is deprecated and will be removed '+'soon.','DeprecationWarning','DEP0019');}}// 缓存路径Module._pathCache[cacheKey]=filename;returnfilename;}}returnfalse;}functiontryPackage(requestPath,exts,isMain){varpkg=readPackage(requestPath);// 获取package.json当中的main字段if(!pkg)returnfalse;varfilename=path.resolve(requestPath,pkg);// 解析路径returntryFile(filename,isMain)||// 直接判断这个文件是否存在tryExtensions(filename,exts,isMain)||// 判断这个分别以js,json,node等后缀结尾的文件是否存在tryExtensions(path.resolve(filename,'index'),exts,isMain);// 判断这个分别以 ${filename}/index.(js|json|node)等后缀结尾的文件是否存在}
Module.prototype._compile=function(content,filename)){content=internalModule.stripShebang(content);// create wrapper function// 将源码的文本包裹一层varwrapper=Module.wrap(content);// vm.runInThisContext在一个v8的虚拟机内部执行wrapper后的代码varcompiledWrapper=vm.runInThisContext(wrapper,{filename: filename,lineOffset: 0,displayErrors: true});varinspectorWrapper=null;if(process._breakFirstLine&&process._eval==null){if(!resolvedArgv){// we enter the repl if we're not given a filename argument.if(process.argv[1]){resolvedArgv=Module._resolveFilename(process.argv[1],null,false);}else{resolvedArgv='repl';}}// Set breakpoint on module startif(filename===resolvedArgv){deleteprocess._breakFirstLine;inspectorWrapper=process.binding('inspector').callAndPauseOnStart;}}vardirname=path.dirname(filename);// 构造require函数varrequire=internalModule.makeRequireFunction(this);vardepth=internalModule.requireDepth;if(depth===0)stat.cache=newMap();varresult;if(inspectorWrapper){result=inspectorWrapper(compiledWrapper,this.exports,this.exports,require,this,filename,dirname);}else{// 开始执行这个函数// 传入的参数依次是 module.exports / require / module / filename / dirnameresult=compiledWrapper.call(this.exports,this.exports,require,this,filename,dirname);}if(depth===0)stat.cache=null;returnresult;}Module.wrap=function(script){returnModule.wrapper[0]+script+Module.wrapper[1];};Module.wrapper=['(function (exports, require, module, __filename, __dirname) { ','\n});'];
Require源码粗读
最近一直在用
node.js
写一些相关的工具,对于node.js
的模块如何去加载,以及所遵循的模块加载规范的具体细节又是如何并不是了解。这篇文件也是看过node.js
源码及部分文章总结而来:在
es2015
标准以前,js
并没有成熟的模块系统的规范。Node.js
为了弥补这样一个缺陷,采用了CommonJS
规范中所定义的模块规范,它包括:1.require
require
是一个函数,它接收一个模块的标识符,用以引用其他模块暴露出来的API
。2.module context
module context
规定了一个模块当中,存在一个require变量,它遵从上面对于这个require
函数的定义,一个exports
对象,模块如果需要向外暴露API,即在一个exports
的对象上添加属性。以及一个module object
。3.module Identifiers
module Identifiers
定义了require
函数所接受的参数规则,比如说必须是小驼峰命名的字符串,可以没有文件后缀名,.
或者..
表明文件路径是相对路径等等。具体关于
commonJS
中定义的module
规范,可以参见wiki文档在我们的
node.js
程序当中,我们使用require
这个看起来是全局(后面会解释为什么看起来是全局的)的方法去加载其他模块。首先我们来看下关于这个方法,
node.js
内部是如何定义的:Module._load
方法是一个内部的方法,主要是:接下来我们来看下
node.js
是如何根据传入的模块路径字符串来查找对应的模块的:在这个方法内部,需要调用一个内部的方法:
Module._resolveLookupPaths
,这个方法会依据父模块的路径获取所有这个模块可能的路径:这个方法内部有以下几种情况的处理:
node xxx
启动的模块这个时候
node.js
会直接获取到你这个程序执行路径,并在这个方法当中返回require(xxx)
require一个存在于node_modules
中的模块这个时候会对执行路径上所有可能存在
node_modules
的路径进行遍历一遍require(./)
require一个相对路径或者绝对路径的模块直接返回父路径
当拿到需要找寻的路径后,调用
Module._findPath
方法去查找对应的文件路径。梳理下上面查询模块时的一个策略:
require
模块的时候,传入的字符串最后一个字符不是/
时:如果是个文件,那么直接返回这个文件的路径
如果是个文件夹,那么会找个这个文件夹下是否有
package.json
文件,以及这个文件当中的main
字段对应的路径(对应源码当中的方法为tryPackage
):.js
,.json
,.node
,.ms
后缀去加载对应文件index.js
,index.json
,index.node
文件如果以上2个方法都没有找到对应文件路径,那么就对文件路径后添加分别添加
.js
,.json
,.node
,.ms
后缀去加载对应的文件(对应源码当中的方法为tryExtensions
)require
模块的时候,传入的字符串最后一个字符是/
时,即require
的是一个文件夹时:index.js
,index.json
,index.node
等文件当找到文件的路径后就调用
tryModuleLoad
开始加载模块了,这个方法内部实际上是调用了模块实例的load
方法:调用
Module._extension
方法去加载不同格式的文件,就拿js
文件来说:内部调用了
Module.prototype._compile
这个方法:Module.wrap
将源码包裹一层(遵循commonJS
规范)vm
v8虚拟机暴露出来的方法来构造一个新的函数通过源码发现,
Module.wrapper
在对源码文本进行包裹的时候,传入了5个参数:是对于第三个参数
module
的exports
属性的引用这个
require
并非是Module.prototype.require
方法,而是通过internalModule.makeRequireFunction
重新构造出来的,这个方法内部还是依赖Module.prototype.require
方法去加载模块的,同时还对这个require
方法做了一些拓展。module
对象,如果需要向外暴露API
供其他模块来使用,需要在module.exports
属性上定义当前文件的绝对路径
当前文件的父文件夹的绝对路径
几个问题
exports 和 module.exports的关系
特别注意第一个参数和第三参数的联系:第一参数是对于第三个参数的
exports
属性的引用。一旦将某个模块exports
赋值给另外一个新的对象,那么就断开了exports
属性和module.exports
之间的引用关系,同时在其他模块当中也无法引用在当前模块中通过exports
暴露出去的API
,对于模块的引用始终是获取module.exports
属性。循环引用
官方示例:
a.js
b.js
main.js
在
a
模块加载时,需要加载b
模块,但是在实际加载a
模块之前,就已经将a
模块进行的缓存,具体参见Module._load
方法:因为在加载
b
模块的过程中再次去加载a
模块的时候,这时是直接从缓存中获取a
模块导出的API
,此时exports.done
的属性还是false
,未被设置为true
,只有当b
模块被完全加载后,a
模块exports
属性才被设置为true
。The text was updated successfully, but these errors were encountered: