跳到主要内容

Find a file or directory by walking up parent directories

在某个目录下向上找指定文件,找到则返回该文件路径,找不到返回 null

源码解析

"use strict";
const path = require("path");
const locatePath = require("locate-path");
const pathExists = require("path-exists");

const stop = Symbol("findUp.stop");

module.exports = async (name, options = {}) => {
let directory = path.resolve(options.cwd || "");
const { root } = path.parse(directory);
const paths = [].concat(name);

const runMatcher = async (locateOptions) => {
if (typeof name !== "function") {
return locatePath(paths, locateOptions);
}

const foundPath = await name(locateOptions.cwd);
if (typeof foundPath === "string") {
return locatePath([foundPath], locateOptions);
}

return foundPath;
};

// eslint-disable-next-line no-constant-condition
while (true) {
// eslint-disable-next-line no-await-in-loop
const foundPath = await runMatcher({ ...options, cwd: directory });

if (foundPath === stop) {
return;
}

if (foundPath) {
return path.resolve(directory, foundPath);
}

if (directory === root) {
return;
}

directory = path.dirname(directory);
}
};

module.exports.sync = (name, options = {}) => {
let directory = path.resolve(options.cwd || "");
const { root } = path.parse(directory);
const paths = [].concat(name);

const runMatcher = (locateOptions) => {
if (typeof name !== "function") {
return locatePath.sync(paths, locateOptions);
}

const foundPath = name(locateOptions.cwd);
if (typeof foundPath === "string") {
return locatePath.sync([foundPath], locateOptions);
}

return foundPath;
};

// eslint-disable-next-line no-constant-condition
while (true) {
const foundPath = runMatcher({ ...options, cwd: directory });

if (foundPath === stop) {
return;
}

if (foundPath) {
return path.resolve(directory, foundPath);
}

if (directory === root) {
return;
}

directory = path.dirname(directory);
}
};

module.exports.exists = pathExists;

module.exports.sync.exists = pathExists.sync;

module.exports.stop = stop;

我们来分析一下同步的源码部分

1. 解析路径为绝对路径

let directory = path.resolve(options.cwd || "");

通过path.resolve() 方法将路径解析为绝对路径

2. 解析根路径

const { root } = path.parse(directory);

通过 path.parse() 来解析绝对路径中的根路径

path.parse("/home/user/dir/file.txt");
// Returns:
// { root: '/',
// dir: '/home/user/dir',
// base: 'file.txt',
// ext: '.txt',
// name: 'file' }

3. 拼接传递过来的文件名

const paths = [].concat(name);

4. 死循环逐级向上查找文件

while (true) {
const foundPath = runMatcher({ ...options, cwd: directory });

if (foundPath === stop) {
return;
}

if (foundPath) {
return path.resolve(directory, foundPath);
}

if (directory === root) {
return;
}

directory = path.dirname(directory);
}

定义死循环逐级向上查找文件,找到则返回路径。这里查找路径定义了一个 runMatcher 方法

4.1 runMatcher 查找路径

通过 locate-path 查找当前路径是否存在

const runMatcher = (locateOptions) => {
if (typeof name !== "function") {
return locatePath.sync(paths, locateOptions);
}

const foundPath = name(locateOptions.cwd);
if (typeof foundPath === "string") {
return locatePath.sync([foundPath], locateOptions);
}

return foundPath;
};

locatePath: 在磁盘多个路径中查找第一个存在的路径

  • name 类型不为 function 直接调用 locatePath.sync() 方法查找当前路径中是否存在文件
  • 否则查找的路径为 name 返回值
  • 查找路径为 string 类型调用 locatePath.sync() 方法查找当前路径中是否存在文件
  • 否则返回 name 返回值

4.2 找到的路径为 stop 值则返回

if (foundPath === stop) {
return;
}

4.3 找到了路径返回绝对路径

if (foundPath) {
return path.resolve(directory, foundPath);
}

返回解析后的绝对路径

4.4 找到了根目录还没找到则返回

if (directory === root) {
return;
}

4.5 目录向上一级继续查找

directory = path.dirname(directory);