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

2023-05-16

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

require案例

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

  • /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方法

// 进入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循环的核心算法解析:

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);

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'
]

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获取文件的真实路径

fs.realPahtSync

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

  • 分享一波js,node,vue等相关的书籍

    作者在空闲时间看了一些关于js node vue等相关的一些书籍 为了方便有需要的朋友一起学习 所以将这些书籍分享给大家 大家如果有需要的可以根据自己的需要下载 书籍清单 css相关 CSS那些事儿 flex布局相关 Flex 3权威指南
  • Error: ER_NOT_SUPPORTED_AUTH_MODE: Client does not support authentication解决办法

    node连接数据库报错 Error ER NOT SUPPORTED AUTH MODE Client does not support authentication protocol requested by server conside
  • egg:Error: controller not exists

    前言 在学习和运用node的egg时的报错信息记录 报错 解决办法 确定router js里面的路由和自己写的Controller相匹配 确定你在controller最后一行写了module exports 个人问题 router js里面
  • axios 的理解和使用 axios.create(对axios请求进行二次封装) 拦截器 取消请求(axios.CancelToken)

    目录 axios是什么 axios特点 axios中文文档 axios常用语法 axios安装 axios简单使用 默认get请求 post put delete axios难点语法 axios create config 对axios请求
  • 编译opencv.js

    opencv 支持编译多个平台 其中还支持JavaScript 不过编译需要emscripten 编译环境 centos7 Python2 7 1 下载OpenCV源码 官网 https opencv org releases 例如下载4
  • node写可选参数接口

    个人网站 紫陌 笔记分享网 想寻找共同学习交流 共同成长的伙伴 请点击 前端学习交流群 今天写项目接口看到接口文档要求带四个参数两个参数必选两个可选 当时在想可选参数要怎么做 毕竟自己也没有写过 然后想了一天终于想出一个感觉不是最佳的方案
  • Async详解之一:流程控制

    转载 http freewind me blog 20120515 917 html 为了适应异步编程 减少回调的嵌套 我尝试了很多库 最终觉得还是async最靠谱 地址 https github com caolan async Asyn
  • 如何在 Debian 11 上安装 NVM

    NVM 代表 Node Version Manager 它是一个用于安装 Node js 的命令行实用程序 它只允许程序员在他们的帐户中安装 Node js 这意味着安装是根据用户特定完成的 单个系统中的所有用户都有自己的 Node js
  • npm 实现原理

    输入 npm install 命令并敲下回车后 会经历如下几个阶段 以 npm 5 5 1 为例 1 执行工程自身 preinstall 当前 npm 工程如果定义了 preinstall 钩子此时会被执行 2 确定首层依赖模块 首先需要做
  • Node =》 nodemon

    在编写调试Node js的时候 如果修改了项目的代码 则需要频繁的手动close掉 然后再重新启动 非常频繁 现在可通过nodemon这个工具 能够监听项目文件的变动 当代码被修改后 nodemon会帮我们重新启动项目 极大方便了开发和调试
  • Node.js——回调函数及事件处理机制

    目录 回调函数 定义 理解 事件处理机制 补充 回调函数 定义 什么是回调函数呢 通俗的讲 将一个函数A作为参数传递给函数B 在函数B内对函数A进行调用 函数A就是回调函数 Node js 异步编程的直接体现就是回调 回调函数在完成任务后就
  • 通过NodeJS对接微信客服实现第三方API管理消息

    最近项目有个需求 要对接微信客服 之前也没做过 一脸懵逼 我属于那种不知其然 就心里很没底的人 感觉不知道怎么去开发了 所以就趁着后端还没开发完接口 先自己对接一下试试 接下来就是整个流程 首先我们先注册一个企业微信 这一步是必须的 因为我
  • node基础之三:http 模块

    1 导入模块 const http require http 2 创建服务 const server http createServer request response gt 获取请求方法 request method 获取请求 url
  • npm指令执行前执行自定义代码

    1 基本逻辑 npm start执行前执行node bin wb handle scripts wb node bin wb handle npm start wb handle源码 删除deleteNodeModules配置的最后一级目录
  • Express初学之入门

    1 Express简介 基于Node的极简MVC框架 提供灵活的路由功能 提供强大的中间件机制 内核极小 扩展性很好 使用广泛 2 路由机制 Express概念 定义了Api的地址 请求 响应方式 方法 天然支持HTTP Method 中的
  • MongoDB的安装与基本使用

    首先 得从MongoDB官网下载到对应操作系统的数据库安装包 MongoDB官网 https www mongodb com MongoDB下载地址 https www mongodb com download center communi
  • express脚手架安装和使用,node添加跨域处理

    前言 node的脚手架 express generator 的安装和使用 安装 步骤一 全局安装脚手架 npm i express generator g cnpm i express generator g 可以运行命令 express
  • 如何在 Windows 10 上安装 Node.js

    Node js 是一个开源的 JavaScript 运行时环境 它允许在 Web 浏览器之外运行 JavaScript 本教程将帮助您在 Windows 系统上安装 Node js 在 Windows 上安装节点 以下是在 Windows
  • 文字转png图片

    body中的数据格式 Convert text to PNG image param text param options param options font 30px sans serif css style font param op
  • 大数据毕业设计之前端02:架构布局和aside的设计

    前言 上一篇主要讲了我学习前端的一个经历 以及为什么选择BuildAdmin作为深入前端学习的原因 同事也大致聊了一下学习前端需要使用哪些技术栈 本篇文章来拆解一下BuildAdmin的前端代码结构 和布局实现的细节 前端代码结构 必须先了

随机推荐

  • 软件测试 | 测试开发 | Android App 保活服务的配置与禁用

    Android应用保活是应用 系统 用户三个角色相互影响的产物 几乎每一款应用都希望自己能实现永久保活 xff0c 并通过保活实现消息推送 信息上报等等交互行为 xff1b 几乎所有的系统厂商都想把应用关在笼子里 xff0c 通过控制应用的
  • JavaScript中拷贝数组的14个小技巧

    相关推荐 xff1a javascript视频教程 数组拷贝经常被误解 xff0c 但这并不是因为拷贝过程本身 xff0c 而是因为缺乏对 JS 如何处理数组及其元素的理解 JS 中的数组是可变的 xff0c 这说明在创建数组之后还可以修改
  • JavaScript中的内存管理

    相关推荐 xff1a javascript视频教程 大多数时候 xff0c 我们在不了解有关内存管理的知识下也只开发 xff0c 因为 JS 引擎会为我们处理这个问题 不过 xff0c 有时候我们会遇到内存泄漏之类的问题 xff0c 这个只
  • 详解bootstrap自定义侧边导航栏的方法

    本篇文章给大家介绍一下bootstrap自定义侧边导航栏的方法 有一定的参考价值 xff0c 有需要的朋友可以参考一下 xff0c 希望对大家有所帮助 bootstrap自带的响应式导航栏是向下滑动的 xff0c 有时满足不了个性化的需求
  • 使用VSCode调试Golang工程

    本篇文章给大家介绍一下使用VSCode调试Golang工程的方法 有一定的参考价值 xff0c 有需要的朋友可以参考一下 xff0c 希望对大家有所帮助 推荐学习 xff1a vscode教程 关键字 最简单的调试攻略多项目调试 适用个人开
  • 深入解析JavaScript中的作用域

    本篇文章带大家深入理解JavaScript作用域 有一定的参考价值 xff0c 有需要的朋友可以参考一下 xff0c 希望对大家有所帮助 这篇文章称为笔记更为合适一些 xff0c 内容来源于 你不知道的JavaScript xff08 上卷
  • 12个提升程序员软技能与效率的开发工具(转载)

    本篇文章给大家推荐12个提升程序员软技能与效率的开发工具 有一定的参考价值 xff0c 有需要的朋友可以参考一下 xff0c 希望对大家有所帮助 大家好 xff0c 我是你们的 猫哥 xff0c 那个不喜欢吃鱼 又不喜欢喵 的超级猫 这一期
  • 一文了解Nodejs中的模块化和事件循环

    本篇文章带大家了解一下Nodejs中的模块化和事件循环 有一定的参考价值 xff0c 有需要的朋友可以参考一下 xff0c 希望对大家有所帮助 5 20出了一款线上Ide xff0c 能够在浏览器上边运行 Node js WebContai
  • 浅谈php输出数组的4种方法

    在之前的文章 PHP数组学习之一维数组如何创建和初始化 xff08 代码详解 xff09 和 PHP数组学习之二维数组创建方法浅析 中我们通过代码实例介绍了一维数组和二维数组的定义方法 xff0c 简单易懂 既然数组创建并初始化了 xff0
  • 浅谈PHP遍历数组之for循环语句

    数组是一个非常基础和重要的数据结构 xff0c 当我们创建好一个数组后 xff0c 就需要去操作它 xff1b 而数组最常见的一个操作就是循环遍历 PHP中支持多种遍历数组的方法 xff0c 今天我们就来学习如何利用for循环语句来遍历数组
  • 软件测试 | 测试开发 | Python中日志异步发送到远程服务器

    背景 在Python中使用日志最常用的方式就是在控制台和文件中输出日志了 logging模块也很好的提供的相应 的类 使用起来也非常方便 但是有时我们可能会有一些需求 如还需要将日志发送到远端 或者直接写入数 据库 这种需求该如何实现呢 S
  • 笔记本外接显示器闪烁问题

    笔记本外接显示器闪烁问题 最简单的一个解决方法 xff1a 就是不要把显示器和笔记本的电源插到同一个排插上
  • 8种CSS实现loading加载特效的小技巧(分享)

    本篇文章给大家分享8种CSS实现loading加载特效的小技巧 xff0c 希望对大家有所帮助 xff01 为什么会写这种文章呢 xff1f 平时开发的时候 xff0c 我们遇到加载 xff0c 要么是UI框架中自带 xff0c 要么就是百
  • 值得收藏的26个css面试题,增强你的CSS基础!

    CSS在网页设计中非常流行 xff0c 可以减少结构内容中的复杂性和重复 本篇文章给大家分享26个基于css的面试题 xff0c 可以增强你的CSS基础 xff0c 快来学习吧 xff08 学习视频分享 xff1a css视频教程 xff0
  • JavaScript中关于“this”的7个有趣面试题,你能全答对吗?

    相关推荐 xff1a 2021年大前端面试题汇总 xff08 收藏 xff09 在 JavaScript 中 xff0c this 是函数调用上下文 正是由于 this 的行为很复杂 xff0c 所以在 JavaScript 面试中 xff
  • 分享几种实用的Node.js调试方法,快来收藏吧!!

    本篇文章给大家介绍Nodejs调试的几种方式 有一定的参考价值 xff0c 有需要的朋友可以参考一下 xff0c 希望对大家有所帮助 相关推荐 xff1a nodejs 教程 第一种 1 打开 vscode 内置终端 xff0c 右上角选择
  • 聊聊在VSCode中怎么点击DOM 自动定位到相应代码行?

    如何在 Vue 项目中 xff0c 通过点击 DOM 自动定位VSCode中的代码行 xff1f 下面本篇文章就来给大家分享一个插件 xff0c 并聊聊实现原理 xff0c 快来收藏吧 xff01 现在大型的 Vue项目基本上都是多人协作开
  • 一文深入了解 Node 中的事件循环

    Node js是单线程的语言 xff0c 是通过事件循环处理非阻塞I O操作的 下面本篇文章带大家详细了解 Node 中的事件循环 xff0c 希望对大家有所帮助 xff01 Node js 做为 JavaScript 的服务端运行时 xf
  • 深入解析vue中的key,看看key到底能拿来干嘛!

    key到底有什么用途 xff1f 下面本篇文章给大家深度解析一下vue中的key xff0c 看看key到底能拿来干嘛 xff0c 希望对大家有所帮助 xff01 vue中关于key的深度解析 key到底有什么用途 先来看官方解释 key属
  • 一文聊聊Node.js中的模块路径解析

    本篇文章带大家了解一下Node js中的模块路径解析 xff0c 介绍一下Node模块路径解析方法 xff0c 希望对大家有所帮助 xff01 require案例 当前有一个项目当前项目路径 Users rainbow Documents