站长家园(原代码之家)(www.adminjie.com)网站源码,微信源码,游戏源码,商业源码分享平台。
当前位置:网站首页 技术文章 网络编程 正文

一文聊聊Node.js中的模块路径解析

时间:2021-12-18 [网络编程]作者:fabuyuan 浏览:8 次

本篇文章带大家了解一下Node.js中的模块路径解析,介绍一下Node模块路径解析方法,希望对大家有所帮助!

一文聊聊Node.js中的模块路径解析  2

require案例

  • 当前有一个项目
  • 当前项目路径/Users/rainbow/Documents/前端/脚手架开发/rainbow-test
  • 项目bin目录下有一堆文件

一文聊聊Node.js中的模块路径解析  2

  • /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中的模块路径解析  2

// 进入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中的模块路径解析  2

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中的模块路径解析  2

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中的模块路径解析  2

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中的模块路径解析  2

fs.realPahtSync

一文聊聊Node.js中的模块路径解析  2

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

以上就是一文聊聊Node.js中的模块路径解析的详细内容,更多请关注站长家园其它相关文章!

本文标签:  Node.js模块路径解析

转载请注明来源:一文聊聊Node.js中的模块路径解析

本文永久链接地址:https://www.adminjie.com/post/6589.html

免责声明:
本站所发布的一切资源仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容。如果您喜欢该程序,请支持正版软件,购买注册,得到更好的正版服务。

附:
二○○二年一月一日《计算机软件保护条例》第十七条规定:为了学习和研究软件内含的设计思想和原理,通过安装、显示、传输或者存储软件等方式使用软件的,可以不经软件著作权人许可,不向其支付报酬!鉴于此,也希望大家按此说明研究软件!

版权声明:
一、本站致力于为软件爱好者提供国内外软件开发技术和软件共享,着力为用户提供优资资源。
二、本站提供的部分源码下载文件为网络共享资源,请于下载后的24小时内删除。如需体验更多乐趣,还请支持正版。
三、我站提供用户下载的所有内容均转自互联网。如有内容侵犯您的版权或其他利益的,若有侵犯你的权益请:提交版权证明文件到邮箱 2225329873#qq.com(#换为@) 站长会进行审查之后,情况属实的会在三个工作日内为您删除。

  • 站长家园(原代码之家)会员升级
  • 最新文章
    • oracle中not的用法是什么

      oracle中not的用法是什么

      在oracle中,not用于对指定的条件取反,当指定的条件为真时,not的结果就是假,当指定的条件为假时,not的结果就是真,语法为“Selectwhere...

    • Vue3中5个可以提高开发效率的小知识【整理分享】

      Vue3中5个可以提高开发效率的小知识【整理分享】

      本篇文章给大家聊聊Vue3开发,整理分享5个可以提高开发效率的小知识,让你在Vue3的项目开发中更加丝滑,希望对大家有所帮助!最近鼓捣了一下Vue3+...

    • php5.6.31如何编译安装

      php5.6.31如何编译安装

      php5.6.31编译安装的方法:1、添加epel源;2、安装依赖;3、下载并解压php5.6.31;4、修改php-fpm.conf;5、启动php-fpm即...

    • oracle怎样进行空值替换

      oracle怎样进行空值替换

      在oracle中,可利用nvl()函数进行空值替换,该函数用于从两个表达式中返回一个非null值,语法为“nvl(字段值,替换值)”;当字段值的结果为null值...

    • oracle数据库有什么用

      oracle数据库有什么用

      oracle数据库的作用:1、oracle可以按照数据结构来组织、存储和管理数据;2、oracle能够适用于大型、中型和微型计算机的关系数据库管理。本教程操作环...

    热门文章