import-local执行流程解析

素心~ / 2023-07-12 / 原文

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

// pkgDir内部调用
findUp.sync("package.json", { cwd: path.dirname(normalizedFilename) });

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

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

// 当前目录是否存在执行文件
const localFile = resolveCwd.silent(path.join(pkg.name, relativePath));

// 本地 node_modules 绝对路径
const localNodeModules = path.join(process.cwd(), "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)
);

findUp 逐级向上查找

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