源码解读:find-up

3 阅读2分钟

find-up通过查找父目录来查找文件或者目录,官方给的说明是:Find a file or directory by walking up parent directories 安装

$ npm install find-up

使用示例 example.js

const path = require('path');
const findUp = require('find-up');

(async () => {
	console.log(await findUp('unicorn.png'));
	//=> '/Users/sindresorhus/unicorn.png'

	console.log(await findUp(['rainbow.png', 'unicorn.png']));
	//=> '/Users/sindresorhus/unicorn.png'

	console.log(await findUp(async directory => {
		const hasUnicorns = await findUp.exists(path.join(directory, 'unicorn.png'));
		return hasUnicorns && directory;
	}, {type: 'directory'}));
	//=> '/Users/sindresorhus'
})();

话不多说,直接上干货,源码:

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

我们看到,总共导出了5个方法,我们逐句分析:

  1. 模块引入:
    • path:Node.js的内置模块,提供了处理文件和目录路径的功能。
    • locatePath:用于查找给定路径数组中的第一个存在的路径。
    • pathExists:用于检测路径是否存在。
  2. stop符号:
    • 定义了一个特殊符号stop,用于在查找过程中停止查找操作。
  3. 异步查找函数:
    • module.exports = async (name, options = {}) => { ... }:导出了一个异步函数,这个函数需要两个参数:name(需要查找的文件或路径名称)和 options(可选配置)。
    • let directory = path.resolve(options.cwd || ''):将当前工作目录解析为绝对路径。
    • const {root} = path.parse(directory):从当前目录中获取文件系统的根目录。
  4. 路径查找的主要逻辑:
    • const paths = [].cancat(name):将name包装为数组,支持单个字符串和数组形式的输入。
    • 定义了一个异步函数runMatcher,用于匹配路径。如果name不是一个函数,则调用locatePath来查找路径;如果是函数,则调用该函数并传入当前工作目录以获取路径。
    • 使用无限循环while(true)来逐层查找路径,直到找到符合条件的路径或到达根目录。
  5. 查找过程:
    • 在循环中,调用 runMatcher 来查找当前目录的路径。
    • 如果找到的路径为 stop,则终止查找。
    • 如果找到有效路径,则返回该路径;如果当前目录是根目录且仍未找到,则返回 undefined。
    • 若未找到,则将目录更改为其父目录,持续进行查找。

整体来看,该模块实现了一个向上查找文件或路径的功能,支持通过异步方式进行查找,便于在文件系统中寻找特定的文件或目录。