- varmath = require('math');
- math.add(2, 3);
从 require 方法本身是如何实现的入手,一步一步看:(代码全部来自 node.js [https://github.com/nodejs/node] 源码)
require 方法封装在 node 源码中的 lib 文件夹里的 module.js 中
- 1 // Loads a module at the given file path. Returns that module's
- 2 // `exports` property.
- 3 // 给定一个模块目录,返回该模块的 exports 属性
- 4 Module.prototype.require = function(path) {
- 5 // assert() 头部引入,主要用于断言,如果表达式不符合预期,就抛出一个错误。
- 6 // assert方法接受两个参数,当第一个参数对应的布尔值为true时,不会有任何提示,返回undefined。
- 7 // 当第一个参数对应的布尔值为false时,会抛出一个错误,该错误的提示信息就是第二个参数设定的字符串。
- 8 assert(path, 'missing path'); //断言是否有path
- 9 assert(typeof path === 'string', 'path must be a string'); //断言 path是否是个字符串
- 10 11
- return Module._load(path, this,
- /* isMain */
- false); //require方法主要是为了引出_load方法。
- 12 //_load函数三个参数: path 当前加载的模块名称,parent 父亲模块,其实是谁导入了该模块,
- 13 // /* isMain */ false 是不是主入口文件
- 14
- };
require 中调用了 Module._load() 方法:
- 1 // Check the cache for the requested file.
- 2 // 1. If a module already exists in the cache: return its exports object.
- 3 // 2. If the module is native: call `NativeModule.require()` with the
- 4 // filename and return the result.
- 5 // 3. Otherwise, create a new module for the file and save it to the cache.
- 6 // Then have it load the file contents before returning its exports
- 7 // object.
- 8 // 从缓存中查找所要加载的模块
- 9 // 1. 如果一个模块已经存在于缓存中:直接返回它的exports对象
- 10 // 2. 如果模块是一个本地模块,调用'NativeModule.require()'方法,filename作为参数,并返回结果
- 11 // 3. 否则,使用这个文件创建一个新模块并把它加入缓存中。在加载它只会返回exports对象。
- 12 // _load函数三个参数: path 当前加载的模块名称,parent 父亲模块,/* isMain */ false 是不是主入口文件
- 13 Module._load = function(request, parent, isMain) {
- 14
- if (parent) {
- 15 //头部引入了 Module._debug = util.debuglog('module');const debug = Module._debug;
- 16 // 这个方法用来打印出调试信息,具体可以看 https://chyingp.gitbooks.io/nodejs/模块/util.html
- 17 debug('Module._load REQUEST %s parent: %s', request, parent.id);
- 18 19
- }
- 20 21 // 找到当前的需要解析的文件名
- 22
- var filename = Module._resolveFilename(request, parent, isMain);
- 23 24 //如果已经有的缓存,直接返回缓存的exports
- 25
- var cachedModule = Module._cache[filename];
- 26
- if (cachedModule) {
- 27
- return cachedModule.exports;
- 28
- }
- 29 30 //如果模块是一个内部模块,调用内部方法'NativeModule.require()'方法,filename作为参数,并返回结果
- 31
- if (NativeModule.nonInternalExists(filename)) {
- 32 debug('load native module %s', request);
- 33
- return NativeModule.require(filename);
- 34
- }
- 35 36 //创建一个新模块
- 37
- var module = new Module(filename, parent);
- 38 39 //是否为主模块,
- 40
- if (isMain) {
- 41 //主模块的话,需要将当前的module赋值给process.mainModule
- 42 process.mainModule = module;
- 43 //主模块的id特殊的赋值为"."
- 44 module.id = '.';
- 45
- }
- 46 47 //并把新模块加入缓存中
- 48 Module._cache[filename] = module;
- 49 50 //尝试导入模块的操作
- 51 tryModuleLoad(module, filename);
- 52 53 // 返回新创建模块中的exports,也就是暴露在外面的方法属性等。
- 54
- return module.exports;
- 55
- };
Module._load 中调用了 Module._resolveFilename() 方法
- 1 // 负责具体filename的文件查找
- 2 // 参数 request 当前加载的模块名称,parent 父亲模块,/* isMain */ false 是不是主入口文件
- 3 Module._resolveFilename = function(request, parent, isMain) {
- 4 5 //NativeModule用于管理js模块,头部引入的。
- 6 //NativeModule.nonInternalExists()用来判断是否是原生模块且不是内部模块,
- 7 //所谓内部模块就是指 lib/internal 文件目录下的模块,像fs等。
- 8 //满足 是原生模块且不是内部模块,则直接返回 当前加载的模块名称request。
- 9
- if (NativeModule.nonInternalExists(request)) {
- 10
- return request;
- 11
- }
- 12 13 // Module._resolveLookupPaths()函数返回一个数组[id , paths],
- 14 // paths是一个 可能 包含这个模块的文件夹路径(绝对路径)数组
- 15
- var paths = Module._resolveLookupPaths(request, parent, true);
- 16 17 // look up the filename first, since that's the cache key.
- 18 // 确定哪一个路径为真,并且添加到缓存中
- 19
- var filename = Module._findPath(request, paths, isMain);
- 20 21 // 如果没有找到模块,报错
- 22
- if (!filename) {
- 23
- var err = new Error(`Cannot find module '${request}'`);
- 24 err.code = 'MODULE_NOT_FOUND';
- 25
- throw err;
- 26
- }
- 27 28 // 找到模块则直接返回
- 29
- return filename;
- 30
- };
Module._resolveFilename 调用了 Module._resolveLookupPaths() 方法 和 Module._findPath() 方法。
这两个方法主要是对模块路径的查找,这里要说一下 node 模块路径解析,方便对下面两个函数的理解,大家可以对照着理解。
根据 require 函数的参数形式的不同,比如说直接引一个文件名 require("moduleA"),或者是路径 require("./moduleA") 等, 查找方式会有一些变化:
- 从 Y 路径的模块 require(X)
- 1. 如果 X 是一个核心模块,
- a. 返回核心模块 //核心模块是指node.js下lib的内容
- b. 结束
- 2. 如果 X 是以 './' 或 '/' 或 '../'开头
- a.加载文件(Y+X)
- b.加载目录(Y+ X)
- 3. 加载Node模块(X, dirname(Y))// 导入一个NODE_MODULE,返回 4. 抛出 "未找到"// 上述都没找到,直接排出没找到的异常。
- 加载文件(X)1. 如果 X 是一个文件,加载 X 作为 JavaScript 文本。结束
- 2. 如果 X.js 是一个文件,加载 X.js 作为 JavaScript 文本。结束
- 3. 如果 X.json 是一个文件,解析 X.json 成一个 JavaScript 对象。结束
- 4. 如果 X.node 是一个文件,加载 X.node 作为二进制插件。结束加载目录(X)1. 如果 X/package.json 是一个文件,
- a. 解析 X/package.json,查找 "main" 字段
- b. let M = X + (json main 字段)
- c. 加载文件(M)
- 2. 如果 X/index.js 是一个文件,加载 X/index.js 作为 JavaScript 文本。结束
- 3. 如果 X/index.json 是一个文件,解析 X/index.json 成一个 JavaScript 对象。结束
- 4. 如果 X/index.node 是一个文件,加载 X/index.node 作为二进制插件。结束
- 加载Node模块(X, START)
- 1. let DIRS=NODE_MODULES_PATHS(START)//得到 node_module 文件目录 2.foreach DIRinDIRS:// 遍历所有的路径 直到找到 x ,x 可能是 文件或者是目录 a.加载文件(DIR/X)
- b.加载目录(DIR/X)
- NODE_MODULES_PATHS(START) //具体NODE_MODULES文件目录算法 1. let PARTS = path split(START)
- 2. let I = count of PARTS - 1
- 3. let DIRS = []
- 4.whileI >= 0,
- a. ifPARTS[I] = "node_modules" CONTINUE
- b. DIR = path join(PARTS[0 .. I] + "node_modules")
- c. DIRS = DIRS + DIR
- d. let I = I - 1 5.returnDIRS
1、Module._resolveLookupPaths() 方法
- 1 // 'index.' character codes
- 2
- var indexChars = [105, 110, 100, 101, 120, 46];
- 3
- var indexLen = indexChars.length;
- 4 //_resolveLookupPaths() 方法用来查找模块,返回一个数组,数组第一项为模块名称即request,数组第二项返回一个可能包含这个模块的文件夹路径数组
- 5 //
- 6 //处理了如下几种情况:
- 7 // 1、是原生模块且不是内部模块
- 8 // 2、如果路径不以"./" 或者'..'开头或者只有一个字符串,即是引用模块名的方式,即require('moduleA');
- 9 // 2.1以 '/' 为前缀的模块是文件的绝对路径。 例如,require('/home/marco/foo.js') 会加载 /home/marco/foo.js 文件。
- 10 // 2.2以 './' 为前缀的模块是相对于调用 require() 的文件的。 也就是说,circle.js 必须和 foo.js 在同一目录下以便于 require('./circle') 找到它。
- 11 // 2.3当没有以 '/'、'./' 或 '../' 开头来表示文件时,这个模块必须是一个核心模块或加载自 node_modules 目录。
- 12 Module._resolveLookupPaths = function(request, parent, newReturn) { //request 当前加载的模块名称,parent 父亲模块
- 13 14 //NativeModule用于管理js模块,头部引入的。
- 15 //NativeModule.nonInternalExists()用来判断是否是原生模块且不是内部模块,所谓内部模块就是指 lib/internal 文件目录下的模块,像fs等。
- 16
- if (NativeModule.nonInternalExists(request)) {
- 17 debug('looking for %j in []', request);
- 18 19 //满足 是原生模块且不是内部模块,也就是说是node.js下lib文件夹下的模块,
- 20 //但不包含lib/internal 文件目录下的模块,并且newReturn 为true,则返回null ,
- 21 //如果newReturn 为false 则返回[request, []]。
- 22
- return (newReturn ? null: [request, []]);
- 23
- }
- 24 25 // Check for relative path
- 26 // 检查相关路径
- 27 // 如果路径不以"./"或者'..'开头或者只有一个字符串,即是引用模块名的方式,即require('moduleA');
- 28
- if (request.length < 2 || 29 request.charCodeAt(0) !== 46
- /*.*/
- || 30(request.charCodeAt(1) !== 46
- /*.*/
- && 31 request.charCodeAt(1) !== 47
- /*/*/
- )) {
- 32 //全局变量,在Module._initPaths 函数中赋值的变量,modulePaths记录了全局加载依赖的根目录
- 33
- var paths = modulePaths;
- 34 35 // 设置一下父亲的路径,其实就是谁导入了当前模块
- 36
- if (parent) {
- 37
- if (!parent.paths) 38 paths = parent.paths = [];
- 39
- else 40 paths = parent.paths.concat(paths);
- 41
- }
- 42 43 // Maintain backwards compat with certain broken uses of require('.')
- 44 // by putting the module's directory in front of the lookup paths.
- 45 // 如果只有一个字符串,且是 .
- 46
- if (request === '.') {
- 47
- if (parent && parent.filename) {
- 48 paths.unshift(path.dirname(parent.filename));
- 49
- } else {
- 50 paths.unshift(path.resolve(request));
- 51
- }
- 52
- }
- 53 54 debug('looking for %j in %j', request, paths);
- 55 56 //直接返回
- 57
- return (newReturn ? (paths.length > 0 ? paths: null) : [request, paths]);
- 58
- }
- 59 60 // with --eval, parent.id is not set and parent.filename is null
- 61 // 处理父亲模块为空的情况
- 62
- if (!parent || !parent.id || !parent.filename) {
- 63 // make require('./path/to/foo') work - normally the path is taken
- 64 // from realpath(__filename) but with eval there is no filename
- 65 // 生成新的目录, 在系统目录 modulePaths,当前目录 和 "node_modules" 作为候选的路径
- 66
- var mainPaths = ['.'].concat(Module._nodeModulePaths('.'), modulePaths);
- 67 68 debug('looking for %j in %j', request, mainPaths);
- 69 //直接返回
- 70
- return (newReturn ? mainPaths: [request, mainPaths]);
- 71
- }
- 72 73 // Is the parent an index module?
- 74 // We can assume the parent has a valid extension,
- 75 // as it already has been accepted as a module.
- 76 // 处理父亲模块是否为index模块,即 path/index.js 或者 X/index.json等 带有index字样的module
- 77 const base = path.basename(parent.filename); // path.basename()返回路径中的最后一部分
- 78
- var parentIdPath;
- 79
- if (base.length > indexLen) {
- 80
- var i = 0;
- 81 82 //检查 引入的模块名中是否有 "index." 字段,如果有, i === indexLen。
- 83
- for (; i < indexLen; ++i) {
- 84
- if (indexChars[i] !== base.charCodeAt(i)) 85
- break;
- 86
- }
- 87 88 // 匹配 "index." 成功,查看是否有多余字段以及剩余部分的匹配情况
- 89
- if (i === indexLen) {
- 90 // We matched 'index.', let's validate the rest
- 91
- for (; i < base.length; ++i) {
- 92 const code = base.charCodeAt(i);
- 93 94 // 如果模块名中有 除了 _, 0-9,A-Z,a-z 的字符 则跳出,继续下一次循环
- 95
- if (code !== 95
- /*_*/
- && 96(code < 48
- /*0*/
- || code > 57
- /*9*/
- ) && 97(code < 65
- /*A*/
- || code > 90
- /*Z*/
- ) && 98(code < 97
- /*a*/
- || code > 122
- /*z*/
- )) 99
- break;
- 100
- }
- 101 102 103
- if (i === base.length) {
- 104 // Is an index module
- 105 parentIdPath = parent.id;
- 106
- } else {
- 107 // Not an index module
- 108 parentIdPath = path.dirname(parent.id); //path.dirname() 返回路径中代表文件夹的部分
- 109
- }
- 110
- } else {
- 111 // Not an index module
- 112 parentIdPath = path.dirname(parent.id);
- 113
- }
- 114
- } else {
- 115 // Not an index module
- 116 parentIdPath = path.dirname(parent.id);
- 117
- }
- 118 119 //拼出绝对路径
- 120 //path.resolve([from ...], to) 将 to 参数解析为绝对路径。
- 121 //eg:path.resolve('/foo/bar', './baz') 输出'/foo/bar/baz'
- 122
- var id = path.resolve(parentIdPath, request);
- 123 124 // make sure require('./path') and require('path') get distinct ids, even
- 125 // when called from the toplevel js file
- 126 // 确保require('./path')和require('path')两种形式的,获得不同的 ids
- 127
- if (parentIdPath === '.' && id.indexOf('/') === -1) {
- 128 id = './' + id;
- 129
- }
- 130 131 debug('RELATIVE: requested: %s set ID to: %s from %s', request, id, 132 parent.id);
- 133 //path.dirname() 返回路径中代表文件夹的部分
- 134
- var parentDir = [path.dirname(parent.filename)];
- 135 136 debug('looking for %j in %j', id, parentDir);
- 137 138 // 当我们以"./" 等方式require时,都是以当前引用他的模块,也就是父亲模块为对象路径的
- 139
- return (newReturn ? parentDir: [id, parentDir]);
- 140
- };
2、Module._findPath() 方法
- 1
- var warned = false;
- 2 //_findPath用于从可能的路径中确定哪一个路径为真,并且添加到缓存中
- 3 //参数request 当前加载的模块名称,
- 4 //paths ,Module._resolveLookupPaths()函数返回一个数组[id , paths],即模块可能在的所有路径,
- 5 // /* isMain */ false 是不是主入口文件
- 6 Module._findPath = function(request, paths, isMain) {
- 7 8 //path.isAbsolute()判断参数 path 是否是绝对路径。
- 9
- if (path.isAbsolute(request)) {
- 10 paths = [''];
- 11
- } else if (!paths || paths.length === 0) {
- 12
- return false;
- 13
- }
- 14 15 16
- var cacheKey = request + '\x00' + 17(paths.length === 1 ? paths[0] : paths.join('\x00'));
- 18
- var entry = Module._pathCache[cacheKey];
- 19 20 //判断是否在缓存中,如果有则直接返回
- 21
- if (entry) 22
- return entry;
- 23 24 //如果不在缓存中,则开始查找
- 25
- var exts;
- 26 // 当前加载的模块名称大于0位并且最后一位是 / ,即是否有后缀的目录斜杠
- 27
- var trailingSlash = request.length > 0 && 28 request.charCodeAt(request.length - 1) === 47
- /*/*/
- ;
- 29 30 // For each path
- 31 // 循环每一个可能的路径paths
- 32
- for (var i = 0; i < paths.length; i++) {
- 33 34 // Don't search further if path doesn't exist
- 35 // 如果路径存在就继续执行,不存在就继续检验下一个路径 stat 获取路径状态
- 36 const curPath = paths[i];
- 37
- if (curPath && stat(curPath) < 1) continue;
- 38
- var basePath = path.resolve(curPath, request); //生成绝对路径
- 39
- var filename;
- 40 41 //stat 头部定义的函数,用来获取路径状态,判断路径类型,是文件还是文件夹
- 42
- var rc = stat(basePath);
- 43 //如果没有后缀的目录斜杠,那么就有可能是文件或者是文件夹名
- 44
- if (!trailingSlash) {
- 45 // 若是文件
- 46
- if (rc === 0) { // File.
- 47 48 // 如果是使用模块的符号路径而不是真实路径,并且不是主入口文件
- 49
- if (preserveSymlinks && !isMain) {
- 50 filename = path.resolve(basePath);
- 51
- } else {
- 52 filename = toRealPath(basePath); //获取当前执行文件的真实路径
- 53
- }
- 54 55 // 若是目录
- 56
- } else if (rc === 1) { // Directory.
- 57
- if (exts === undefined) 58 //目录中是否存在 package.json
- 59 //通过package.json文件,返回相应路径
- 60 exts = Object.keys(Module._extensions);
- 61 filename = tryPackage(basePath, exts, isMain);
- 62
- }
- 63 64 // 如果尝试了上面都没有得到filename 匹配所有扩展名进行尝试,是否存在
- 65
- if (!filename) {
- 66 // try it with each of the extensions
- 67
- if (exts === undefined) 68 exts = Object.keys(Module._extensions);
- 69 // 该模块文件加上后缀名js .json .node进行尝试,是否存在
- 70 filename = tryExtensions(basePath, exts, isMain);
- 71
- }
- 72
- }
- 73 74 // 如果仍然没有得到filename,并且路径类型是文件夹
- 75
- if (!filename && rc === 1) { // Directory.
- 76
- if (exts === undefined) 77 // 目录中是否存在 package.json
- 78 // 通过package.json文件,返回相应路径
- 79 exts = Object.keys(Module._extensions);
- 80 filename = tryPackage(basePath, exts, isMain);
- 81
- }
- 82 83 // 如果仍然没有得到filename,并且路径类型是文件夹
- 84
- if (!filename && rc === 1) { // Directory.
- 85 // try it with each of the extensions at "index"
- 86 // 是否存在目录名 + index + 后缀名
- 87 // 尝试 index.js index.json index.node
- 88
- if (exts === undefined) 89 exts = Object.keys(Module._extensions);
- 90 91 //tryExtensions()头部定义方法,用来检查文件加上js node json后缀是否存在
- 92 filename = tryExtensions(path.resolve(basePath, 'index'), exts, isMain);
- 93
- }
- 94 95 96
- if (filename) {
- 97 // Warn once if '.' resolved outside the module dir
- 98
- if (request === '.' && i > 0) {
- 99
- if (!warned) {
- 100 warned = true;
- 101 process.emitWarning(102 'warning: require(\'.\') resolved outside the package ' + 103 'directory. This functionality is deprecated and will be removed ' + 104 'soon.', 105 'DeprecationWarning', 'DEP0019');
- 106
- }
- 107
- }
- 108 109 // 将找到的文件路径存入返回缓存,然后返回
- 110 Module._pathCache[cacheKey] = filename;
- 111
- return filename;
- 112
- }
- 113
- }
- 114 115 // 所以从这里可以看出,对于具体的文件的优先级:
- 116 // 1. 具体文件。
- 117 // 2. 加上后缀。
- 118 // 3. package.json
- 119 // 4 index加上后缀
- 120 // 可能的路径以当前文件夹,nodejs系统文件夹和node_module中的文件夹为候选,以上述顺序找到任意一个,
- 121 // 就直接返回
- 122 123 // 没有找到文件,返回false
- 124
- return false;
- 125
- };
Module._load 中还调用了 tryModuleLoad() 方法
- 1
- function tryModuleLoad(module, filename) {
- 2
- var threw = true;
- 3 4 //try catch一下,如果装载失败,就会从cache中将这个模块删除。
- 5
- try {
- 6 7 //做真正的导入模块的操作
- 8 module.load(filename);
- 9 threw = false;
- 10
- } finally {
- 11
- if (threw) {
- 12 delete Module._cache[filename];
- 13
- }
- 14
- }
- 15
- }
tryModuleLoad() 中调用了 Module.prototype.load() 方法
- 1 // Given a file name, pass it to the proper extension handler.
- 2 // 指定一个文件名,导入模块,调用适当扩展处理函数,当前主要是js,json,和node
- 3Module.prototype.load=function(filename) {
- 4debug('load %j for module %j', filename,this.id);
- 5
- 6assert(!this.loaded);//断言 确保当前模块没有被载入
- 7 this.filename = filename;// 赋值当前模块的文件名
- 8
- 9 // Module._nodeModulePaths主要决定paths参数的值的方法。获取node_modules文件夹所在路径。
- 10 // path.dirname() 方法返回一个 path 的目录名 path.dirname('/foo/bar/baz/asdf/quux')
- 11 // 返回: '/foo/bar/baz/asdf'
- 12 this.paths =Module._nodeModulePaths(path.dirname(filename));13
- 14 //当前文件的后缀
- 15 varextension = path.extname(filename) || '.js';
- 16
- 17 //如果没有后缀,默认为 .js
- 18 if(!Module._extensions[extension]) extension = '.js';
- 19
- 20 //根据不同的后缀,执行不同的函数
- 21Module._extensions[extension](this, filename);
- 22 this.loaded =true;
- 23};
Module.prototype.load() 中调用了 Module._nodeModulePaths() 和 Module._extensions 方法
1、Module._nodeModulePaths() 根据操作系统的不同,返回不同的函数
- 1 //path 模块的默认操作会根据 Node.js 应用程序运行的操作系统的不同而变化。
- 2 //比如,当运行在 Windows 操作系统上时,path 模块会认为使用的是 Windows 风格的路径。
- 3 //例如,对 Windows 文件路径 C:\temp\myfile.html 使用 path.basename() 函数,
- 4 //运行在 POSIX 上与运行在 Windows 上会产生不同的结果:
- 5 //在 POSIX 上:
- 6 //path.basename('C:\\temp\\myfile.html');
- 7 // 返回: 'C:\\temp\\myfile.html'
- 8 //
- 9 // 在 Windows 上:
- 10 //path.basename('C:\\temp\\myfile.html');
- 11 // 返回: 'myfile.html'
- 12 //
- 13 // 以下就是根据不同的操作系统返回不同的路径格式 ,具体可以了解http://nodejs.cn/api/path.html
- 14 //
- 15 //
- 16 // Module._nodeModulePaths主要决定paths参数的值的方法。获取node_modules文件夹所在路径。
- 17 // 'node_modules' character codes reversed
- 18
- var nmChars = [115, 101, 108, 117, 100, 111, 109, 95, 101, 100, 111, 110];
- 19
- var nmLen = nmChars.length;
- 20
- if (process.platform === 'win32') {
- 21 // 'from' is the __dirname of the module.
- 22 Module._nodeModulePaths = function(from) {
- 23 // guarantee that 'from' is absolute.
- 24 from = path.resolve(from);
- 25 26 // note: this approach *only* works when the path is guaranteed
- 27 // to be absolute. Doing a fully-edge-case-correct path.split
- 28 // that works on both Windows and Posix is non-trivial.
- 29 30 // return root node_modules when path is 'D:\\'.
- 31 // path.resolve will make sure from.length >=3 in Windows.
- 32
- if (from.charCodeAt(from.length - 1) === 92
- /*\*/
- && 33 from.charCodeAt(from.length - 2) === 58
- /*:*/
- ) 34
- return [from + 'node_modules'];
- 35 36 const paths = [];
- 37
- var p = 0;
- 38
- var last = from.length;
- 39
- for (var i = from.length - 1; i >= 0; --i) {
- 40 const code = from.charCodeAt(i);
- 41 // The path segment separator check ('\' and '/') was used to get
- 42 // node_modules path for every path segment.
- 43 // Use colon as an extra condition since we can get node_modules
- 44 // path for dirver root like 'C:\node_modules' and don't need to
- 45 // parse driver name.
- 46
- if (code === 92
- /*\*/
- || code === 47
- /*/*/
- || code === 58
- /*:*/
- ) {
- 47
- if (p !== nmLen) 48 paths.push(from.slice(0, last) + '\\node_modules');
- 49 last = i;
- 50 p = 0;
- 51
- } else if (p !== -1) {
- 52
- if (nmChars[p] === code) {
- 53++p;
- 54
- } else {
- 55 p = -1;
- 56
- }
- 57
- }
- 58
- }
- 59 60
- return paths;
- 61
- };
- 62
- } else { // posix
- 63 // 'from' is the __dirname of the module.
- 64 Module._nodeModulePaths = function(from) {
- 65 // guarantee that 'from' is absolute.
- 66 from = path.resolve(from);
- 67 // Return early not only to avoid unnecessary work, but to *avoid* returning
- 68 // an array of two items for a root: [ '//node_modules', '/node_modules' ]
- 69
- if (from === '/') 70
- return ['/node_modules'];
- 71 72 // note: this approach *only* works when the path is guaranteed
- 73 // to be absolute. Doing a fully-edge-case-correct path.split
- 74 // that works on both Windows and Posix is non-trivial.
- 75 const paths = [];
- 76
- var p = 0;
- 77
- var last = from.length;
- 78
- for (var i = from.length - 1; i >= 0; --i) {
- 79 const code = from.charCodeAt(i);
- 80
- if (code === 47
- /*/*/
- ) {
- 81
- if (p !== nmLen) 82 paths.push(from.slice(0, last) + '/node_modules');
- 83 last = i;
- 84 p = 0;
- 85
- } else if (p !== -1) {
- 86
- if (nmChars[p] === code) {
- 87++p;
- 88
- } else {
- 89 p = -1;
- 90
- }
- 91
- }
- 92
- }
- 93 94 // Append /node_modules to handle root paths.
- 95 paths.push('/node_modules');
- 96 97
- return paths;
- 98
- };
- 99
- }
2、Module._extensions 方法
- 1 // 根据不同的文件类型,三种后缀,Node.js会进行不同的处理和执行
- 2 // 对于.js的文件会,先同步读取文件,然后通过module._compile解释执行。
- 3 // 对于.json文件的处理,先同步的读入文件的内容,无异常的话直接将模块的exports赋值为json文件的内容
- 4 // 对于.node文件的打开处理,通常为C/C++文件。
- 5 // Native extension for .js
- 6 Module._extensions['.js'] = function(module, filename) {
- 7 // 同步读取文件
- 8
- var content = fs.readFileSync(filename, 'utf8');
- 9 10 // internalModule.stripBOM()剥离 utf8 编码特有的BOM文件头,
- 11 // 然后通过module._compile解释执行
- 12 module._compile(internalModule.stripBOM(content), filename);
- 13
- };
- 14 15 16 // Native extension for .json
- 17 Module._extensions['.json'] = function(module, filename) {
- 18 // 同步的读入文件的内容
- 19
- var content = fs.readFileSync(filename, 'utf8');
- 20
- try {
- 21 // internalModule.stripBOM()剥离 utf8 编码特有的BOM文件头,
- 22 // 然后将模块的exports赋值为json文件的内容
- 23 module.exports = JSON.parse(internalModule.stripBOM(content));
- 24
- } catch(err) {
- 25 // 异常处理
- 26 err.message = filename + ': ' + err.message;
- 27
- throw err;
- 28
- }
- 29
- };
- 30 31 32 //Native extension for .node
- 33 Module._extensions['.node'] = function(module, filename) {
- 34 // 对于.node文件的打开处理,通常为C/C++文件。
- 35
- return process.dlopen(module, path._makeLong(filename));
- 36
- };
针对 .js 后缀的,在 Module._extensions 还调用了 module._compile() 方法
- 1 // Resolved path to process.argv[1] will be lazily placed here
- 2 // (needed for setting breakpoint when called with --debug-brk)
- 3
- var resolvedArgv;
- 4 // Run the file contents in the correct scope or sandbox. Expose
- 5 // the correct helper variables (require, module, exports) to
- 6 // the file.
- 7 // Returns exception, if any.
- 8 // 此方法用于模块的编译。
- 9 // 参数content 主要是模块js文件的主要内容,filename 是js文件的文件名
- 10 Module.prototype._compile = function(content, filename) {
- 11 // Remove shebang
- 12 // Shebang(也称为 Hashbang )是一个由井号和叹号构成的字符序列 #!
- 13
- var contLen = content.length;
- 14
- if (contLen >= 2) {
- 15 // 如果content 开头有Shebang
- 16
- if (content.charCodeAt(0) === 35
- /*#*/
- && 17 content.charCodeAt(1) === 33
- /*!*/
- ) {
- 18
- if (contLen === 2) {
- 19 // Exact match
- 20 content = '';
- 21
- } else {
- 22 // Find end of shebang line and slice it off
- 23 // 找到以shebang开头的句子的结尾,并将其分开,留下剩余部分 赋值给content
- 24
- var i = 2;
- 25
- for (; i < contLen; ++i) {
- 26
- var code = content.charCodeAt(i);
- 27
- if (code === 10
- /*\n*/
- || code === 13
- /*\r*/
- ) 28
- break;
- 29
- }
- 30
- if (i === contLen) 31 content = '';
- 32
- else {
- 33 // Note that this actually includes the newline character(s) in the
- 34 // new output. This duplicates the behavior of the regular expression
- 35 // that was previously used to replace the shebang line
- 36 content = content.slice(i);
- 37
- }
- 38
- }
- 39
- }
- 40
- }
- 41 42 // create wrapper function
- 43 // Module.wrap头部引入,主要用来给content内容包装头尾,类似于
- 44 // (function (exports, require, module, __filename, __dirname) {
- 45 // -----模块源码 content-----
- 46 // });
- 47
- var wrapper = Module.wrap(content);
- 48 49 // 包装好的文本就可以送到vm中执行了,这部分就应该是v8引擎的事情,
- 50 // runInThisContext将被包装后的源字符串转成可执行函数,runInThisContext的作用,类似eval
- 51
- var compiledWrapper = vm.runInThisContext(wrapper, {
- 52 filename: filename,
- 53 lineOffset: 0,
- 54 displayErrors: true 55
- });
- 56 57
- var inspectorWrapper = null;
- 58 // 处理debug模式,
- 59
- if (process._debugWaitConnect && process._eval == null) {
- 60
- if (!resolvedArgv) {
- 61 // we enter the repl if we're not given a filename argument.
- 62
- if (process.argv[1]) {
- 63 resolvedArgv = Module._resolveFilename(process.argv[1], null, false);
- 64
- } else {
- 65 resolvedArgv = 'repl';
- 66
- }
- 67
- }
- 68 69 // Set breakpoint on module start
- 70
- if (filename === resolvedArgv) {
- 71 delete process._debugWaitConnect;
- 72 inspectorWrapper = getInspectorCallWrapper();
- 73
- if (!inspectorWrapper) {
- 74 const Debug = vm.runInDebugContext('Debug');
- 75 Debug.setBreakPoint(compiledWrapper, 0, 0);
- 76
- }
- 77
- }
- 78
- }
- 79 80 // 获取当前的文件的路径
- 81
- var dirname = path.dirname(filename);
- 82 83 //生成require方法
- 84
- var require = internalModule.makeRequireFunction(this);
- 85 86 //依赖模块
- 87
- var depth = internalModule.requireDepth;
- 88
- if (depth === 0) stat.cache = new Map();
- 89
- var result;
- 90 91 //直接调用content经过包装后的wrapper函数,将module模块中的exports,生成的require,
- 92 //this也就是新创建的module,filename, dirname作为参数传递给模块
- 93 //类似于
- 94 //(function (exports, require, module, __filename, __dirname) {
- 95 // -----模块源码 content-----
- 96 // })( this.exports, require, this, filename, dirname);
- 97 // 这就是为什么我们可以直接在module文件中,直接访问exports, module, require函数的原因
- 98
- if (inspectorWrapper) {
- 99 result = inspectorWrapper(compiledWrapper, this.exports, this.exports, 100 require, this, filename, dirname);
- 101
- } else {
- 102 result = compiledWrapper.call(this.exports, this.exports, require, this, 103 filename, dirname);
- 104
- }
- 105
- if (depth === 0) stat.cache = null;
- 106
- return result;
- 107
- };
Module.prototype._compile 中调用了 Module.wrap 这个方法就是用了给 content 包装的主要函数, 它来自头部的引用:
- 1 //Module.wrapper和Module.wrap的方法写在下面,
- 2 //给传入进去的script也就是咱们的content --js文件内容套了一个壳,使其最后变成类似于如下的样子:
- 3 //
- 4 //(function (
来源: http://www.cnblogs.com/lijiayi/p/js_node_module.html