歡迎來個人博客閱讀:《Node.js源碼解析-require背後》node
在編寫 Node.js 應用的過程當中,咱們或多或少的都寫過相似 const xxx = require('xxx')
的代碼,其做用是引入模塊。不知你們有沒有想過,這段代碼是如何肯定咱們要引入的模塊?又是以怎樣的上下文來執行模塊代碼的呢?git
讓咱們來翻開 Node.js 源碼,先找到 lib/module.js
中的 Module.prototype.require()
函數github
// lib/module.js Module.prototype.require = function(path) { assert(path, 'missing path'); assert(typeof path === 'string', 'path must be a string'); return Module._load(path, this, /* isMain */ false); }; // Check the cache for the requested file. // 1. If a module already exists in the cache: return its exports object. // 2. If the module is native: call `NativeModule.require()` with the // filename and return the result. // 3. Otherwise, create a new module for the file and save it to the cache. // Then have it load the file contents before returning its exports // object. 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) { return cachedModule.exports; } if (NativeModule.nonInternalExists(filename)) { debug('load native module %s', request); return NativeModule.require(filename); } var module = new Module(filename, parent); if (isMain) { process.mainModule = module; module.id = '.'; } Module._cache[filename] = module; tryModuleLoad(module, filename); return module.exports; };
Module.prototype.require()
對傳入的 path 簡單斷言後調用 Module._load()
來導入模塊json
Module._load()
的執行思路以下:緩存
查找模塊: Module._resolveFilename()
app
存在緩存: 返回 cachedModule.exports
ide
是內置模塊: 見 NativeModule.require()函數
加載模塊: tryModuleLoad()
優化
所以,Module.prototype.require()
的源碼能夠分爲兩大塊: 查找模塊和加載模塊ui
查找模塊的關鍵在於定位模塊的具體路徑,這個功能由 Module._resolveFilename()
函數實現
// lib/module.js Module._resolveFilename = function(request, parent, isMain) { if (NativeModule.nonInternalExists(request)) { return request; } // 可能存在要加載模塊的目錄 var paths = Module._resolveLookupPaths(request, parent, true); // 具體的模塊路徑 var filename = Module._findPath(request, paths, isMain); if (!filename) { var err = new Error(`Cannot find module '${request}'`); err.code = 'MODULE_NOT_FOUND'; throw err; } return filename; };
Module._resolveFilename()
函數先調用 Module._resolveLookupPaths()
計算出模塊可能存在的目錄,而後調用 Module._findPath()
獲得模塊路徑
經過調用 Module._resolveLookupPaths()
函數能夠計算出模塊可能存在的目錄,在調用時存在三種狀況:
直接 require('xxx')
: 須要遞歸查詢路徑上的 node_modules
目錄和全局 node_modules
目錄
經過 Module.runMain()
或 --eval
參數: 返回執行命令行的目錄
使用相對 / 絕對路徑導入: 這時,直接返回父模塊目錄便可
// lib/module.js Module._resolveLookupPaths = function(request, parent, newReturn) { if (NativeModule.nonInternalExists(request)) { debug('looking for %j in []', request); return (newReturn ? null : [request, []]); } // 相似 require('xxx') if (request.length < 2 || request.charCodeAt(0) !== 46 || // 非 . 開頭 (request.charCodeAt(1) !== 46 // 非 .. 開頭 && request.charCodeAt(1) !== 47)) { // 非 / 開頭 var paths = modulePaths; if (parent) { if (!parent.paths) paths = parent.paths = []; else paths = parent.paths.concat(paths); } // Maintain backwards compat with certain broken uses of require('.') // by putting the module's directory in front of the lookup paths. if (request === '.') { if (parent && parent.filename) { paths.unshift(path.dirname(parent.filename)); } else { paths.unshift(path.resolve(request)); } } debug('looking for %j in %j', request, paths); return (newReturn ? (paths.length > 0 ? paths : null) : [request, paths]); } // 執行 Module.runMain() 進入,此時 request 是絕對路徑 // with --eval, parent.id is not set and parent.filename is null if (!parent || !parent.id || !parent.filename) { // make require('./path/to/foo') work - normally the path is taken // from realpath(__filename) but with eval there is no filename var mainPaths = ['.'].concat(Module._nodeModulePaths('.'), modulePaths); debug('looking for %j in %j', request, mainPaths); return (newReturn ? mainPaths : [request, mainPaths]); } // ... var parentDir = [path.dirname(parent.filename)]; debug('looking for %j in %j', id, parentDir); return (newReturn ? parentDir : [id, parentDir]); };
使用 Module._resolveLookupPaths()
函數找到模塊可能存在的目錄後,調用 Module._findPath()
函數,遞歸查找模塊
Module._findPath()
函數在查找模塊時,存在如下幾種狀況:
require
的是文件 ==> .js
/ .json
/ .node
require
的文件夾 ==> 找 index.js
/ index.json
/ index.node
require
的包 ==> 找 package.json
// lib/module.js Module._findPath = function(request, paths, isMain) { if (path.isAbsolute(request)) { paths = ['']; } else if (!paths || paths.length === 0) { return false; } // 計算 cacheKey // 對於同一模塊,每一個目錄的 cacheKey 不一樣 var cacheKey = request + '\x00' + (paths.length === 1 ? paths[0] : paths.join('\x00')); var entry = Module._pathCache[cacheKey]; // 有緩存就走緩存 if (entry) return entry; var exts; var trailingSlash = request.length > 0 && request.charCodeAt(request.length - 1) === 47; // / // For each path for (var i = 0; i < paths.length; i++) { const curPath = paths[i]; // 不存在就跳過 if (curPath && stat(curPath) < 1) continue; var basePath = path.resolve(curPath, request); var filename; var rc = stat(basePath); if (!trailingSlash) { if (rc === 0) { // 文件 if (preserveSymlinks && !isMain) { filename = path.resolve(basePath); } else { filename = toRealPath(basePath); } } else if (rc === 1) { // 目錄或 package if (exts === undefined) exts = Object.keys(Module._extensions); // 若是是目錄,則 filename 爲 false filename = tryPackage(basePath, exts, isMain); } // 對應是文件但未給出文件後綴的狀況 if (!filename) { // try it with each of the extensions if (exts === undefined) exts = Object.keys(Module._extensions); filename = tryExtensions(basePath, exts, isMain); } } if (!filename && rc === 1) { // 目錄或 package if (exts === undefined) exts = Object.keys(Module._extensions); // 若是是目錄,則 filename 爲 false filename = tryPackage(basePath, exts, isMain); } if (!filename && rc === 1) { // 目錄 // try it with each of the extensions at "index" if (exts === undefined) exts = Object.keys(Module._extensions); filename = tryExtensions(path.resolve(basePath, 'index'), exts, isMain); } if (filename) { // Warn once if '.' resolved outside the module dir if (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; return filename; } } return false; };
上面的 for 循環內有許多重複代碼,能夠優化爲:
var exts = Object.keys(Module._extensions); var trailingSlash = request.length > 0 && request.charCodeAt(request.length - 1) === 47; // / for (var i = 0; i < paths.length; i++) { const curPath = paths[i]; // 不存在就跳過 if (curPath && stat(curPath) < 1) continue; var basePath = path.resolve(curPath, request); var filename; var rc = stat(basePath); if (!trailingSlash && rc !== 1) { // 文件或啥也沒有 if (rc === 0) { // 文件 if (preserveSymlinks && !isMain) { filename = path.resolve(basePath); } else { filename = toRealPath(basePath); } } else { // 對應是文件但未給出文件後綴的狀況 filename = tryExtensions(basePath, exts, isMain); } } if(!filename && rc === 1){ // 目錄或 package filename = tryPackage(basePath, exts, isMain); // 若是是目錄,則 filename 爲 false if (!filename) { // try it with each of the extensions at "index" filename = tryExtensions(path.resolve(basePath, 'index'), exts, isMain); } } if (filename) { // Warn once if '.' resolved outside the module dir if (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; return filename; } }
Module._findPath()
函數內部依賴下面幾個方法:
// lib/module.js function tryPackage(requestPath, exts, isMain) { // 讀取目錄下的 package.json 並返回 package.main,沒有則返回 false var pkg = readPackage(requestPath); if (!pkg) return false; var filename = path.resolve(requestPath, pkg); return tryFile(filename, isMain) || // package.main 有後綴 tryExtensions(filename, exts, isMain) || // package.main 沒有後綴 tryExtensions(path.resolve(filename, 'index'), exts, isMain); // package.main 不存在則默認加載 index.js / index.json / index.node } function readPackage(requestPath) { const entry = packageMainCache[requestPath]; if (entry) return entry; const jsonPath = path.resolve(requestPath, 'package.json'); const json = internalModuleReadFile(path._makeLong(jsonPath)); // 沒有 package.json,說明不是一個包 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; } // given a path check a the file exists with any of the set extensions function tryExtensions(p, exts, isMain) { for (var i = 0; i < exts.length; i++) { const filename = tryFile(p + exts[i], isMain); if (filename) { return filename; } } return false; } // check if the file exists and is not a directory // if using --preserve-symlinks and isMain is false, // keep symlinks intact, otherwise resolve to the // absolute realpath. function tryFile(requestPath, isMain) { const rc = stat(requestPath); if (preserveSymlinks && !isMain) { return rc === 0 && path.resolve(requestPath); } return rc === 0 && toRealPath(requestPath); } function toRealPath(requestPath) { return fs.realpathSync(requestPath, { [internalFS.realpathCacheKey]: realpathCache }); }
經過以上步驟,咱們找到了對應的模塊文件,下面開始加載模塊
經過 Module._resolveFilename()
函數找到具體的模塊文件路徑後,就能夠開始加載模塊了
Module._load()
調用 tryModuleLoad()
函數來加載模塊。tryModuleLoad()
則將 module.load()
函數( 真正加載模塊的函數 )包裹在一個 try-finally 塊中
// lib/module.js function tryModuleLoad(module, filename) { var threw = true; try { module.load(filename); threw = false; } finally { if (threw) { delete Module._cache[filename]; } } }
對於 Module.prototype.load()
函數來講,模塊文件有三種類型:
.js
: 讀取文件而後調用 module._compile()
編譯執行,這是默認狀況
.json
: 做爲 json
文件讀取
.node
: 直接執行編譯後的二進制文件
// lib/module.js // Given a file name, pass it to the proper extension handler. Module.prototype.load = function(filename) { debug('load %j for module %j', filename, this.id); assert(!this.loaded); this.filename = filename; // 全局 node_modules 和文件路徑上全部的 node_modules this.paths = Module._nodeModulePaths(path.dirname(filename)); // 經過文件擴展名肯定加載方式,默認採用 .js 的方式加載 var extension = path.extname(filename) || '.js'; if (!Module._extensions[extension]) extension = '.js'; Module._extensions[extension](this, filename); this.loaded = true; }; // Native extension for .js Module._extensions['.js'] = function(module, filename) { var content = fs.readFileSync(filename, 'utf8'); module._compile(internalModule.stripBOM(content), filename); }; // Native extension for .json Module._extensions['.json'] = function(module, filename) { var content = fs.readFileSync(filename, 'utf8'); try { module.exports = JSON.parse(internalModule.stripBOM(content)); } catch (err) { err.message = filename + ': ' + err.message; throw err; } }; //Native extension for .node Module._extensions['.node'] = function(module, filename) { return process.dlopen(module, path._makeLong(filename)); }; // Run the file contents in the correct scope or sandbox. Expose // the correct helper variables (require, module, exports) to // the file. // Returns exception, if any. Module.prototype._compile = function(content, filename) { content = internalModule.stripShebang(content); // 使用下面這個結構包裹 // (function (exports, require, module, __filename, __dirname) { // ... // }); var wrapper = Module.wrap(content); // 在沙箱內生成函數對象 var compiledWrapper = vm.runInThisContext(wrapper, { filename: filename, lineOffset: 0, displayErrors: true }); // ... var dirname = path.dirname(filename); var require = internalModule.makeRequireFunction(this); var depth = internalModule.requireDepth; if (depth === 0) stat.cache = new Map(); var result; if (inspectorWrapper) { result = inspectorWrapper(compiledWrapper, this.exports, this.exports, require, this, filename, dirname); } else { result = compiledWrapper.call(this.exports, this.exports, require, this, filename, dirname); } if (depth === 0) stat.cache = null; return result; };
從上面的代碼能夠看出,執行模塊時,模塊中的 require,不是 module.js
中的 require,而是由 internalModule.makeRequireFunction()
生成的一個新的函數對象,其隱藏了 module 的實現細節,方便使用
// lib/internal/module.js // 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) { return Module._resolveFilename(request, mod); } require.resolve = resolve; require.main = process.mainModule; // Enable support to add extra extension types. require.extensions = Module._extensions; require.cache = Module._cache; return require; }
各個模塊文件中 require
的區別:
內置模塊( module.js
/ fs.js
等 ): 對應 NativeModule.require
函數,僅供 node 內部使用
第三方模塊: 對應 internalModule.makeRequireFunction()
函數的執行結果,底層依賴 Module.prototype.require()
,遵循 CommonJS 規範
當執行 const xxx = require('xxx')
這段代碼時
先根據 'xxx'
和模塊所在目錄得出被 require 的模塊可能存在的目錄 - Module._resolveLookupPaths
再根據 'xxx'
和 1 的結果得出被 require 的模塊的文件路徑 - Module._findPath
而後根據其拓展名肯定加載方式 - Module.prototype.load
最後將 module.exports
導出
參考: