import-local执行流程与node模块路径解析流程

这篇具有很好参考价值的文章主要介绍了import-local执行流程与node模块路径解析流程。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

import-local 概述

当本地和全局同时存在两个脚手架命令时,使用 import-local 可以优先加载本地脚手架命令

const importLocal = require("import-local");

if (importLocal(__filename)) {
  require("npmlog").info("cli", "正在使用 jinhui-cli 本地版本");
} else {
  require(".")(process.argv.slice(2));
}

以上述代码为例:执行 jinhui 命令时实际执行的应该是

node C:\Program Files\nodejs\jinhui-cli\cli.js
所以将调试程序定位到全局下的 cli 文件,进入 import-local 源码

// __filename格式化 normalizedFilename => c:\\nvm\\v14.18.0\\node_modules\\jinhui-cli\\cli.js
const normalizedFilename = filename.startsWith("file://") ? fileURLToPath(filename) : filename;

// 向上依次查找package.json文件 如果目录存在该文件则判定全局主目录 => c:\\nvm\\v14.18.0\\node_modules\\jinhui-cli
const globalDir = pkgDir.sync(path.dirname(normalizedFilename));

// 全局主目录相对于运行文件的相对路径 => cli.js
const relativePath = path.relative(globalDir, normalizedFilename);

// 获取 package.json 内容
const pkg = require(path.join(globalDir, "package.json"));

// 本地目录的node_modules下是否存在运行文件
const localFile = resolveCwd.silent(path.join(pkg.name, relativePath));

// 本地 node_modules 绝对路径
const localNodeModules = path.join(process.cwd(), "node_modules");

// 执行的文件在当前的node_modules下
// 本地目录下node_modules 与 运行文件的相对路径如果以..开头则不是同一目录下
// 下列判定为:运行文件在本地目录下 且 运行文件根目录与本地目录根目录相同
const filenameInLocalNodeModules =
  !path.relative(localNodeModules, normalizedFilename).startsWith("..") &&
  path.parse(localNodeModules).root === path.parse(normalizedFilename).root;

// 运行文件不在当前node_modules下 且 本地目录node_modules下存在该运行文件 直接执行当前node_modules下的JS文件
return (
  !filenameInLocalNodeModules && localFile && path.relative(localFile, normalizedFilename) !== "" && require(localFile)
);

pkgDir.sync 内部调用 findUp 逐级向上查找

findUp.sync("package.json", { cwd: path.dirname(normalizedFilename) });

module.exports.sync = (name, options = {}) => {
  // 相对路径转绝对路径
  let directory = path.resolve(options.cwd || "");
  // 获取目录根路径
  const { root } = path.parse(directory);
  // 所有要查找的文件名
  const filenames = [].concat(name);

  while (true) {
    // locatePath 遍历数组中的文件名在指定目录下是否存在 如果存在则将查找到的第一个文件名返回
    const file = locatePath.sync(filenames, { cwd: directory });
    if (file) {
      return path.join(directory, name);
    }

    if (directory === root) {
      return null;
    }
    // 每次遍历逐级递减目录
    directory = path.dirname(directory);
  }
};

node.js 模块路径解析流程

  • Nodejs 模块路径解析流程

    • Nqde.js 项目模块路径解析是通过 require.resolve 方法来实现的
  • require.resolve 就是通过 Module._resolveFileName 方法实现的

  • require.resolve 实现原理:

  • Module._resolveFileName 方法核心流程有 3 点:

    • 判断是否为内置模块
    • 通过 Module._resolveLookupPaths 方法生成 node_modules 可能存在的路径
    • 通过 Module._findPath 查询模块的真实路径
  • Module._findPath 核心流程有 4 点:

    • 查询缓存(将 request 和 paths 通过\x00 合并成 cacheKey)
    • 遍历 paths,将 path 与 request 组成文件路径 basePath
    • 如果 basePath 存在则调用 fs._realPathSync 获取文件真实路径
    • 将文件真实路径缓存到 Module._pathCache (key 就是前面生成的 cacheKey)
  • fs._realPathSync 核心流程有 3 点:

    • 查询缓存(缓存的 key 为 p,即 Module.findPath 中生成的文件路径)
    • 从左往右遍历路径字符串,查询到 / 时,拆分路径,判断该路径是否为软链接,如果是软链接则查询真实链接,并生成新路径 p,然继续往后遍历,这里有 1 个细节需要特别注意:
      • 遍历过程中生成的子路径 base 会缓存在 knownHard 和 cache 中,避免重复查询
    • 遍历完成得到模块对应的真实路径,此时会将原始路径 original 作为 key,真实路径作为 value,保存到缓存中
  • require.resolve.paths 等价于 Module._resolveLookupPaths,该方法用于获取所有 node_modules 可能存在的路径

    • require.resolve.paths 实现原理:
    • 如果路径为 / (根目录),直接返回[/node modules]
    • 否则,将路径字符串从后往前遍历,查询到 / 时,拆分路径,在后面加上 node_modules,并传入一个 paths 数组,直至查询不到 /后返回 paths 数组

_resolveFilename 查找文件真实路径流程图

import-local执行流程与node模块路径解析流程文章来源地址https://www.toymoban.com/news/detail-554238.html

resolveCwd.silent 源码调用 Module._resolveFilename 查找本地目录是否可加载到指定模块 如能则返回文件路径

module.exports.silent = (moduleId) => resolveFrom.silent(process.cwd(), moduleId);

const resolveFrom = (fromDirectory, moduleId, silent) => {
  const fromFile = path.join(fromDirectory, "noop.js");
  const resolveFileName = () =>
    // 调用Module模块内置方法_resolveFilename 加载模块如果存在则返回文件地址
    Module._resolveFilename(moduleId, {
      id: fromFile,
      filename: fromFile,
      // 调用Module模块内置方法_nodeModulePaths 生成所有可能存在node_modules的目录数组
      paths: Module._nodeModulePaths(fromDirectory),
    });

  // silent为静默方式不抛出异常返回undefined
  if (silent) {
    try {
      return resolveFileName();
    } catch (error) {
      return;
    }
  }
  return resolveFileName();
};

内置模块 Module._nodeModulePaths 逐级目录添加 node_modules

const nmChars = [115, 101, 108, 117, 100, 111, 109, 95, 101, 100, 111, 110];
const nmLen = nmChars.length;
Module._nodeModulePaths = function (from) {
  // 生成绝对路径
  from = path.resolve(from);
  // 如果传入路径为根路径直接返回["/node_modules"]
  if (from === "/") return ["/node_modules"];
  // 定义路径数组
  const paths = [];
  // 从最后一位开始遍历路径
  // p变量用来判断最后一项目录长度是否等于node_modules
  // last变量用来存储当前需要截取的路径长度
  for (let i = from.length - 1, p = 0, last = from.length; i >= 0; --i) {
    const code = StringPrototypeCharCodeAt(from, i);
    // 判断当前遍历位置是否为 \/字符
    if (code === "47") {
      // 如果遍历目录不是node_modules则添加node_modules
      if (p !== nmLen) ArrayPrototypePush(paths, StringPrototypeSlice(from, 0, last) + "/node_modules");
      last = i;
      p = 0;
    } else if (p !== -1) {
      // 遍历字符是否跟nmChars每个charCode相等 nmChars存放了node_modules倒序的charCode 如果相等p++
      if (nmChars[p] === code) {
        ++p;
      } else {
        p = -1;
      }
    }
  }
  // 根目录添加node_modules
  ArrayPrototypePush(paths, "/node_modules");
  return paths;
};

内置模块 Module._resolveFilename 读取文件真实路径

// 下列代码对node.js源码进行了部分删减
Module._resolveFilename = function (request, parent, isMain) {
  // 判断是否可以被加载的内置模块
  if (StringPrototypeStartsWith(request, "node:") || NativeModule.canBeRequiredByUsers(request)) {
    return request;
  }
  // 将paths和环境变量node_modules进行合并;
  const paths = Module._resolveLookupPaths(request, parent);
  // 查找路径文件
  const filename = Module._findPath(request, paths, isMain, false);
  if (filename) return filename;
};

内置模块 Module._findPath 读取文件真实路径

Module._findPath = function (request, paths, isMain) {
  // 判断是否为绝对路径
  const absoluteRequest = path.isAbsolute(request);
  // 如果为绝对路径则paths置空 如果paths不存在则返回false
  if (absoluteRequest) {
    paths = [""];
  } else if (!paths || paths.length === 0) {
    return false;
  }
  // 判断是否存在缓存如果存在返回缓存 缓存key为 模块名 + 模块path jinhui-cli/cli.js/C:/Users/xiabin
  const cacheKey = request + "\x00" + ArrayPrototypeJoin(paths, "\x00");
  // 存在缓存直接返回缓存
  const entry = Module._pathCache[cacheKey];
  if (entry) return entry;

  let exts;
  // 判断结尾是否为  \/字符
  let trailingSlash =
    request.length > 0 && StringPrototypeCharCodeAt(request, request.length - 1) === CHAR_FORWARD_SLASH;
  if (!trailingSlash) {
    // 判断结尾是否为相对路径 /.. /. .. .
    trailingSlash = RegExpPrototypeTest(trailingSlashRegex, request);
  }

  //  遍历所有path
  for (let i = 0; i < paths.length; i++) {
    // Don't search further if path doesn't exist
    const curPath = paths[i];
    // 当前path不是目录则直接跳过本次循环
    if (curPath && stat(curPath) < 1) continue;

    // 文件路径
    const basePath = path.resolve(curPath, request);
    let filename;

    const rc = stat(basePath);
    // 结尾是不是 \/ 不是相对路径
    if (!trailingSlash) {
      // basePath是否为文件
      if (rc === 0) {
        if (!isMain) {
          // 是否阻止做超链接
          if (preserveSymlinks) {
            filename = path.resolve(basePath);
          } else {
            // 将软连接转换为真实文件地址
            filename = toRealPath(basePath);
          }
          // 转绝对路径
        } else if (preserveSymlinksMain) {
          filename = path.resolve(basePath);
        } else {
          // 将软连接转换为真实文件地址
          filename = toRealPath(basePath);
        }
      }
    }

    // 文件未找到且查询路径为目录 则尝试生成文件路径
    if (!filename && rc === 1) {
      if (exts === undefined) exts = ObjectKeys(Module._extensions);
      filename = tryPackage(basePath, exts, isMain, request);
    }
    // 查询到文件真实路径将当前路径缓存 并返回真实路径
    if (filename) {
      Module._pathCache[cacheKey] = filename;
      return filename;
    }
  }

  return false;
};

内置模块 fs.realpathSync 将软连接转换为真实路径

//  toRealPath内部调用此方法并将软连 => 真实路径的所有缓存传入
function realpathSync(p, options) {
  // options不存在则置空
  options = getOptions(options, emptyObj);
  // 如果为URL路径则进行转换
  p = toPathIfFileURL(p);
  // 如果不是string字符串则转为string
  if (typeof p !== "string") {
    p += "";
  }
  // 是否为有效路径
  validatePath(p);
  // 将相对路径转为绝对路径
  p = pathModule.resolve(p);
  // 查询缓存如果存在缓存则直接返回缓存中的真实路径
  const cache = options[realpathCacheKey];
  const maybeCachedResult = cache && cache.get(p);
  if (maybeCachedResult) {
    return maybeCachedResult;
  }
  // 所有软连接缓存 通过Object.create(null)创建的对象没有原型链 是一个纯粹的对象
  const seenLinks = ObjectCreate(null);
  const knownHard = ObjectCreate(null);
  // 将软连接路径作为缓存key p表示真实路径
  const original = p;

  let pos;
  let current;
  let base;
  let previous;
  // 找到根目录
  current = base = splitRoot(p);
  pos = current.length;

  while (pos < p.length) {
    // 传入真实路径 与 查找下一级目录开始位置
    const result = nextPart(p, pos);
    previous = current;
    // 判断是否找到下一级目录 并将本级目录与之前目录做拼接
    if (result === -1) {
      const last = p.slice(pos);
      current += last;
      base = previous + last;
      pos = p.length;
    } else {
      current += p.slice(pos, result + 1);
      base = previous + p.slice(pos, result);
      pos = result + 1;
    }

    // 判断缓存中是否存在本级目录 如果存在则跳过本次循环
    if (knownHard[base] || (cache && cache.get(base) === base)) {
      if (isFileType(statValues, S_IFIFO) || isFileType(statValues, S_IFSOCK)) {
        break;
      }
      continue;
    }

    let resolvedLink;
    const maybeCachedResolved = cache && cache.get(base);
    if (maybeCachedResolved) {
      resolvedLink = maybeCachedResolved;
    } else {
      const baseLong = pathModule.toNamespacedPath(base);
      const ctx = { path: base };
      const stats = binding.lstat(baseLong, true, undefined, ctx);
      handleErrorFromBinding(ctx);
      // 通过文件状态判断是否为软连接 如果不是软连接则加入缓存并跳过
      if (!isFileType(stats, S_IFLNK)) {
        knownHard[base] = true;
        if (cache) cache.set(base, base);
        continue;
      }

      let linkTarget = null;
      let id;
      if (!isWindows) {
        // 获取设备ID
        const dev = stats[0].toString(32);
        // 获取文件ID
        const ino = stats[7].toString(32);
        // 生成唯一键 作为软连接缓存的key
        id = `${dev}:${ino}`;
        // 判断缓存是否存在
        if (seenLinks[id]) {
          linkTarget = seenLinks[id];
        }
      }
      // 缓存不存在
      if (linkTarget === null) {
        // 当前路径
        const ctx = { path: base };
        // 获取当前文件状态
        binding.stat(baseLong, false, undefined, ctx);
        handleErrorFromBinding(ctx);
        // 获取软链对应的真实路径
        linkTarget = binding.readlink(baseLong, undefined, undefined, ctx);
        handleErrorFromBinding(ctx);
      }
      // 相对路径转绝对路径
      resolvedLink = pathModule.resolve(previous, linkTarget);
      // 获取到的软链对应的真实路径存入缓存
      if (cache) cache.set(base, resolvedLink);
      if (!isWindows) seenLinks[id] = linkTarget;
    }
    // 重新生成文件真实路径
    p = pathModule.resolve(resolvedLink, p.slice(pos));

    // 生成新的路径重复每个目录是否为软链验证流程以确保完整路径每个目录都是真实路径
    current = base = splitRoot(p);
    pos = current.length;

    if (isWindows && !knownHard[base]) {
      const ctx = { path: base };
      binding.lstat(pathModule.toNamespacedPath(base), false, undefined, ctx);
      handleErrorFromBinding(ctx);
      knownHard[base] = true;
    }
  }

  if (cache) cache.set(original, p);
  return encodeRealpathResult(p, options);
}

到了这里,关于import-local执行流程与node模块路径解析流程的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • 【Node.js】path 模块进行路径处理

    【Node.js】path 模块进行路径处理

    Node.js 执行 JS 代码时,代码中的路径都是以终端所在文件夹出发查找相对路径,而不是以我们认为的从代码本身出发,会遇到问题,所以在 Node.js 要执行的代码中,访问其他文件,建议使用绝对路径 实例: 问题原因:就是从代码文件夹出发,使用 ../text.txt 解析路径,找不到

    2024年02月20日
    浏览(15)
  • 【Node.js】深度解析常用核心模块-path模块

    【Node.js】深度解析常用核心模块-path模块

    ✅ 作者简介:一名将要迈入大三的大学生,致力于提高前端开发能力 ✨ 个人主页:前端小白在前进的主页 🔥 系列专栏 : node.js学习专栏 ⭐️ 个人社区 : 个人交流社区 🍀 学习格言: ☀️ 打不倒你的会使你更强!☀️ 🔥前言 在node.js中常用的三大模块中还有一个是path模块

    2024年02月04日
    浏览(11)
  • ListView中requestLayout执行流程解析

    那么requestLayout方法的执行流程是怎样的,它到底做了什么事呢? 另外,我们也执行在setAdapter里面其实也执行了requestLayout这个方法,所以我们知道这个方法对整个ListView的绘制非常重要,其实不仅是ListView,而是所有的View都是如此。 我们先来看看ListView中的setAdapter源码,看看

    2024年02月06日
    浏览(6)
  • python pyinstaller运行可执行exe文件,在服务器上出错:importError: DLL load failed while importing cv2:找不到指定的模块 解决方法

    python pyinstaller运行可执行exe文件,在服务器上出错:importError: DLL load failed while importing cv2:找不到指定的模块 解决方法

    目录 一、问题描述 二、原因分析  三、解决步骤 1.远程服务器上,打开“服务管理器”-“功能”-“添加功能” 2.勾选“桌面体验” 3.安装,自动安装“墨迹和手写服务” 4.重启后安装成功 5. 再去cmd中执行 exe文件不报错 四、其他方法 我的电脑:win10+python3.8+pycharm; 远程服务

    2024年02月14日
    浏览(15)
  • 【Mybatis源码解析】mapper实例化及执行流程源码分析

    基础环境:JDK17、SpringBoot3.0、mysql5.7 储备知识:《【Spring6源码・AOP】AOP源码解析》、《JDBC详细全解》 基于SpringBoot的Mybatis源码解析: 1.如何对mapper实例化bean 在加载BeanDefinition时,会将SqlSessionFactory、SqlSessionTemplate、MapperScannerConfigurer加载到注册表中,以供后续进行实例化。

    2024年02月01日
    浏览(15)
  • 【Spring 】执行流程解析:了解Bean的作用域及生命周期

    【Spring 】执行流程解析:了解Bean的作用域及生命周期

     哈喽,哈喽,大家好~ 我是你们的老朋友: 保护小周ღ    今天给大家带来的是 Spring 项目的执行流程解析  和 Bean 对象的6 种作用域 以及 生命周期 , 本文将为大家讲解,一起来看看叭~ 本期收录于博主的专栏 :JavaEE_保护小周ღ的博客-CSDN博客 适用于编程初学者,感兴趣

    2024年02月16日
    浏览(13)
  • (2)前端控制器的扩展配置, 视图解析器类型以及MVC执行流程的概述

    注册前端控制器的细节 在web.xml文件注册SpringMVC的前端控制器 DispatcherServlet 时在url-pattern标签中使用 / 和 /* 的区别 / 可以匹配.html或.js或.css等方式的请求路径,但不匹配*.jsp的请求路径 /* 可以匹配所有请求(包括.jsp请求), 例如在过滤器中使用 /* 表示匹配所有请求 DispatcherServle

    2024年02月15日
    浏览(42)
  • openxr runtime Monado 源码解析 源码分析:整体介绍 模块架构 模块作用 进程 线程模型 整体流程

    openxr runtime Monado 源码解析 源码分析:整体介绍 模块架构 模块作用 进程 线程模型 整体流程

    monado系列文章索引汇总: openxr runtime Monado 源码解析 源码分析:源码编译 准备工作说明 hello_xr解读 openxr runtime Monado 源码解析 源码分析:整体介绍 模块架构 模块作用 进程 线程模型 整体流程 openxr runtime Monado 源码解析 源码分析:CreateInstance流程(设备系统和合成器系统)C

    2024年02月11日
    浏览(15)
  • 深入理解flinksql执行流程,calcite与catalog相关概念,扩展解析器实现语法的扩展

    深入理解flinksql执行流程,calcite与catalog相关概念,扩展解析器实现语法的扩展

    flink在执行sql语句时,是无法像java/scala代码一样直接去使用的,需要解析成电脑可以执行的语言,对sql语句进行解析转化。 这里说的我感觉其实不是特别准确,应该是 flink使用的是一款开源SQL解析工具Apache Calcite ,Calcite使用Java CC对sql语句进行了解析 。 那么我们先来简单说

    2024年02月21日
    浏览(14)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包