首頁  >  文章  >  web前端  >  一文聊聊Node.js中的模組路徑解析

一文聊聊Node.js中的模組路徑解析

青灯夜游
青灯夜游轉載
2021-12-16 19:19:311937瀏覽

本篇文章帶大家了解Node.js中的模組路徑解析,介紹一下Node模組路徑解析方法,希望對大家有幫助!

一文聊聊Node.js中的模組路徑解析

require案例

  • #目前有一個項目
  • 目前項目路徑/Users/rainbow/Documents/前端/鷹架開發/rainbow-test
  • #專案bin目錄下方有一堆檔案

一文聊聊Node.js中的模組路徑解析

  • #/bin/index.js
console.log(require.resolve("."));
// /Users/rainbow/Documents/前端/脚手架开发/rainbow-test/bin/index.js  输出bin/index.js的绝对路径
console.log(require.resolve.paths("."));
// [ '/Users/rainbow/Documents/前端/脚手架开发/rainbow-test/bin' ] 输出的文件可能在的路径的数组
console.log(require.resolve("yargs"));
// /Users/rainbow/Documents/前端/脚手架开发/rainbow-test/node_modules/yargs/index.cjs
console.log(require.resolve.paths("yargs"));
/*
[
  '/Users/rainbow/Documents/前端/脚手架开发/rainbow-test/bin/node_modules',
  '/Users/rainbow/Documents/前端/脚手架开发/rainbow-test/node_modules',
  '/Users/rainbow/Documents/前端/脚手架开发/node_modules',
  '/Users/rainbow/Documents/前端/node_modules',
  '/Users/rainbow/Documents/node_modules',
  '/Users/rainbow/node_modules',
  '/Users/node_modules',
  '/node_modules',
  '/Users/rainbow/.node_modules',
  '/Users/rainbow/.node_libraries',
  '/usr/local/Cellar/node/14.3.0_1/lib/node'
]
*/

require解析並找到模組執行檔的流程

1、Nodejs專案模組路徑解析是透過require.resolve方式實現的。

  • require.resolve就是透過Module._resolveFileName方法實作的
  • Module._resolveFileName#核心流程是:
    • #判斷路徑是否為內建模組
    • 不是,則透過Module._resolveLookupPahts方法,產生node_modules可能存在的路徑,如果傳入的路徑是'/test/lerna/cli. js',在每一級路徑下加上node_moduels的路徑數組
    • #透過Module._findPath查詢模組的真實路徑,

#2、Module._findPath核心流程是:

  • 查詢快取(將request和paths透過\x00合併生成cacheKey)
  • 遍歷Module._resolveLookupPahts方法產生的paths數組,將pathrequest組成檔案路徑basePath
  • 如果basePath存在則呼叫fs.realPahtSync取得檔案的真實路徑
  • 將檔案真實路徑快取到Module._pathCache(key為cacheKey)(Module._pathCache就是一個map)

#3、fs.realPahtSync核心流程:

  • #查詢快取(快取的key為p。即Module._findPath中產生的路徑)
  • 從左往右遍歷路徑字串,查詢到/時,拆分路徑,判斷該路徑是否為軟鏈接,如果是軟連結則查詢真實鏈接,並產生新路徑p,然後繼續讓後遍歷,這裡有一個細節:
  • 遍歷過程中產生的子路徑base會快取在knownHard和cache中,避免重複查詢
  • 遍歷完成得到模組對應的真實路徑,此時會將原始路徑original作為key,真實路徑作為value,保存到快取中

4、require.resolve. paths等價於Module._resolveLookupPaths,此方法取得所有node_modules可能存在的路徑組成一個陣列。

5、require.resolve.paths實作原理是: 

  • 如果是/(根路徑)直接回傳 ['/node_modules']
  • 否則,將路徑字串從後往前遍歷,查詢到/時,拆分路徑,在後面加上node_modules,並傳入一個paths數組,直到查詢不到/後回傳paths陣列

require使用到內建模組的方法

當我們使用require('yargs')

require方法

  • #實際上使用的是Module._load方法
Module.prototype.require = function(id) { //id = 'yargs'
  validateString(id, 'id');
  if (id === '') {
    throw new ERR_INVALID_ARG_VALUE('id', id, 'must be a non-empty string');
  }
  requireDepth++;
  try {
    return Module._load(id, this, /* isMain */ false);
  } finally {
    requireDepth--;
  }
};
// 参数
id = 'yargs'
this={
 paths: Module._nodeModulePaths(process.cwd())
}

Module._nodeModulePaths方法

一文聊聊Node.js中的模組路徑解析##

// 进入mac电脑所在的逻辑:
// from => /Users/rainbow/Documents/前端/脚手架开发/lerna源码/lernas  //'from' is the __dirname of the module.
  Module._nodeModulePaths = function(from) {
    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'];

    const paths = [];
    
   // 关键算法代码
    for (let i = from.length - 1, p = 0, last = from.length; i >= 0; --i) {
      const code = from.charCodeAt(i);
      if (code === CHAR_FORWARD_SLASH) {
        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;
        }
      }
    }

    // Append /node_modules to handle root paths.
    paths.push('/node_modules');

    return paths;
  };

for迴圈的核心演算法解析:

一文聊聊Node.js中的模組路徑解析

#Module._load方法

Module._load(id, this, /* isMain */ false)

核心實作碼是:const filename = Module._resolveFilename(request, parent, isMain);

##require.resolve

Node.js

專案模組路徑解析是透過
    require.resolve
  • 方式實現的。
  • require.resolve就是透過
Module._resolveFileName

方法實現的,

// node.js内置模块require的源代码
function resolve(request, options) {
  validateString(request, 'request');
  return Module._resolveFilename(request, mod, false, options); //核心实现
}

require.resolve = resolve;

function paths(request) {
  validateString(request, 'request');
  return Module._resolveLookupPaths(request, mod); //核心代码
}

resolve.paths = paths;
Module._resolveFileName

核心流程
  • 判斷路徑是否為內建模組
  • #不是,則透過
  • Module._resolveLookupPahts方法,將paths和環境中的路徑結合起來
  • 透過
Module._findPath

查詢模組的真實路徑

return Module._resolveFilename(request, parent, isMain) ;一文聊聊Node.js中的模組路徑解析

##########
Module._resolveFilename = function(request, parent, isMain, options) {
  if (NativeModule.canBeRequiredByUsers(request)) { //是否为内置模块
    return request;
  }

  let paths;
  // 让paths和环境变量中的paths结合
  paths = Module._resolveLookupPaths(request, parent); //核心代码
  
  if (parent && parent.filename) {
    // 读取filename对应的package.json文件,看是否有exports字段,当前filename = false
    const filename = trySelf(parent.filename, request);
    if (filename) { //false
      const cacheKey = request + '\x00' +
          (paths.length === 1 ? paths[0] : paths.join('\x00'));
      Module._pathCache[cacheKey] = filename;
      return filename;
    }
  }

 //关键代码,找到本地执行文件 // Look up the filename first, since that's the cache key. 
  const filename = Module._findPath(request, paths, isMain, false);
  if (filename) return filename;
  // ...
};

Module._resolveLookupPahts方法

  • 生成要查找模块的所有路径上可能存在node_modules的路径数组
  • require.resolve.paths("yargs")核心实现方法

生成

[
  '/Users/rainbow/Documents/前端/脚手架开发/rainbow-test/bin/node_modules',
  '/Users/rainbow/Documents/前端/脚手架开发/rainbow-test/node_modules',
  '/Users/rainbow/Documents/前端/脚手架开发/node_modules',
  '/Users/rainbow/Documents/前端/node_modules',
  '/Users/rainbow/Documents/node_modules',
  '/Users/rainbow/node_modules',
  '/Users/node_modules',
  '/node_modules',
  '/Users/rainbow/.node_modules',
  '/Users/rainbow/.node_libraries',
  '/usr/local/Cellar/node/14.3.0_1/lib/node'
]

一文聊聊Node.js中的模組路徑解析

Module._resolveLookupPaths = function(request, parent) {
  if (NativeModule.canBeRequiredByUsers(request)) {
    debug('looking for %j in []', request);
    return null;
  }

  // Check for node modules paths.
  if (request.charAt(0) !== '.' ||
      (request.length > 1 &&
      request.charAt(1) !== '.' &&
      request.charAt(1) !== '/' &&
      (!isWindows || request.charAt(1) !== '\'))){
     let paths = modulePaths;
     if (parent != null && parent.paths && parent.paths.length) {
      paths = parent.paths.concat(paths);
    }

    debug('looking for %j in %j', request, paths);
    return paths.length > 0 ? paths : null;
  }
  
  // In REPL, 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 in REPL there is no filename
    const mainPaths = ['.'];

    debug('looking for %j in %j', request, mainPaths);
    return mainPaths;
  }

  debug('RELATIVE: requested: %s from parent.id %s', request, parent.id);

  const parentDir = [path.dirname(parent.filename)];
  debug('looking for %j', parentDir);
  return parentDir;
};

Module._findPath核心流程

  • 查询缓存(将request和paths通过\x00合并生成cacheKey)(\x00是空格的16进制)
  • 遍历Module._resolveLookupPahts方法生成的paths数组,将pathrequest组成文件路径basePath
  • 如果basePath存在则调用fs.realPahtSync获取文件的真实路径

一文聊聊Node.js中的模組路徑解析

fs.realPahtSync

一文聊聊Node.js中的模組路徑解析

更多node相关知识,请访问:nodejs 教程!!

以上是一文聊聊Node.js中的模組路徑解析的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:juejin.cn。如有侵權,請聯絡admin@php.cn刪除