原文见我的公众号文章 优雅的让你晓得 Node.js 的 fs 模块扫描目录何时完结了
需要
提交软件著作权申请须要的软件源代码一份。
问题剖析
读取我的项目目录,过滤某些文件夹(如:.git,.vscode 等),只提取指定类型的文件的内容(如:js,wxml,wxss)。
即把某我的项目下指定类型的代码提取进去,写入到同一个文件中。
封装满足以下特点的工具函数:
- 可主动扫描指定门路下的所有文件及文件夹
- 提供指定过滤(不扫描)某些文件夹的参数
ignoreDirs
- 提供指定须要的文件类型的参数
allowExts
- 提供读取到文件的监听事件
onFile
- 提供读取门路失败的监听事件
onError
- 提供当指定门路下无可扫描文件(扫描完结)的监听事件,办法外部扫描过程是同步执行的
onComplete
- 函数自身只提供指定目录扫码工作,不含文件自身的读写操作
先上代码
/**
* [printDirSync 同步遍历指定文件夹下的所有文件(夹),反对遍历完结回调 onComplete]
* @param {*} dirPath
* @param {*} options {allowExts: [], // 指定须要的文件类型的门路,不指定则默认容许所有类型(不同于文件夹,文件类型太多,用疏忽的形式太麻烦,所以用了容许)ignoreDirs: [],// 指定不须要读取的文件门路,不指定则默认读取所有文件夹
onFile: (fileDir, ext, stats) => {},
onError: (fileDir, err) => {},
onComplete: (fileNum) => {},}
*/
function printDirSync(
dirPath,
options = {allowExts: [],
ignoreDirs: [],
onFile: (fileDir, ext, stats) => {},
onError: (fileDir, err) => {},
onComplete: (filePaths) => {},}
) {const { allowExts, ignoreDirs, onFile, onComplete, onError} = options;
let onPrintingNum = 0; // 记录正在遍历的文件夹数量,用来判断是否所有文件遍历完结
let findFiles = []; // 统计所有文件,在 onComplete 中返回
// 因为 fs.stat 是异步办法,通过回调的形式返回后果,不可控的执行程序影响是【否遍历完结】的判断
// 所以这里返回 promise,配合 sync/await 模仿同步执行
const stat = (path) => {return new Promise((resolve, reject) => {fs.stat(path, function (err, stats) {if (err) {console.warn("获取文件 stats 失败");
if (onError && typeof onError == "function") {onError(path, err);
}
} else {if (stats.isFile()) {const names = path.split(".");
const ext = names[names.length - 1];
// 对文件的解决回调,可通过 allowExts 数组过滤指定须要的文件类型的门路,不指定则默认容许所有类型
if (
!allowExts ||
allowExts.length == 0 ||
(allowExts.length && allowExts.includes(ext))
) {if (onFile && typeof onFile == "function") {findFiles.push(path);
onFile(path, ext, stats);
}
}
}
// 这里是对文件夹的回调,可通过 ignoreDirs 数组过滤不想遍历的文件夹门路
if (stats.isDirectory()) {
if (
!ignoreDirs ||
ignoreDirs.length == 0 ||
(ignoreDirs.length && !ignoreDirs.includes(path))
) {print(path); // 递归遍历
}
}
}
resolve(path + "stat 完结");
});
});
};
// 解决正在遍历的文件夹遍历完结的逻辑:onPrintingNum-1 且 判断整体遍历是否完结
const handleOnPrintingDirDone = () => {if (--onPrintingNum == 0) {if (onComplete && typeof onComplete == "function") {onComplete(findFiles);
}
}
};
// 遍历门路,记录正在遍历门路的数量
const print = async (filePath) => {
onPrintingNum++; // 进入到这里,阐明以后至多有一个正在遍历的文件夹,因而 onPrintingNum+1
let files = fs.readdirSync(filePath); // 同步读取 filePath 的内容
let fileLen = files.length;
// 如果是空文件夹,不须要遍历,也阐明以后正在遍历的文件夹完结了,onPrintingNum-1
if (fileLen == 0) {handleOnPrintingDirDone();
}
// 遍历目录下的所有文件
for (let index = 0; index < fileLen; index++) {let file = files[index];
let fileDir = path.join(filePath, file); // 获取以后文件绝对路径
try {await stat(fileDir); // 同步执行门路信息的判断
// 当该文件夹下所有文件(门路)都遍历结束,也阐明以后正在遍历的文件夹完结了,onPrintingNum-1
if (index == fileLen - 1) {handleOnPrintingDirDone();
}
} catch (err) {}}
};
print(dirPath);
}
再看咋用
const {fs, printDir, printDirSync} = require("./file-tools");
let dirPath = "/Users/yourprojectpath/yourprojectname";
let allowExts = ["wxml", "wxss", "js", "txt", "md"];
let ignoreDirs = [`${dirPath}/.git`, `${dirPath}/.DS_Store`, `${dirPath}/dist`];
printDirSync(dirPath, {
allowExts,
ignoreDirs,
onFile: (fileDir, ext, stats) => {let fileContent = fs.readFileSync(fileDir, "utf-8"); // 同步读取文件内容
writeFile(` 文件门路:${fileDir.replace("/Users/yourprojectpath/","")}\n${fileContent}\n\n`);
},
onComplete: (files) => {console.log("疏忽的文件夹:", ignoreDirs);
console.log("指定的文件类型:", allowExts);
console.log(` 门路 [${dirPath}] 遍历完结,发现 ${files.length}个 文件如下 \n`,
files
);
},
});
function writeFile(data = "") {
let outDir = "./dist/codes.txt";
fs.appendFileSync(outDir, data, {encoding: "utf8",});
}
最初👂我解释
扫描指定门路下所有文件的根本流程
- 通过
fs.readdir
办法读取指定的门路,在此方的回调函数里会返回该门路下的files
; - 遍历这些
files
并通过fs.stat
办法判断类型(是文件还是文件夹); - 如果是文件夹,则反复 1 和 2;
- 如果是文件,则记录下来;
- 直到程序完结,阐明扫描结束!
如何晓得扫描完结?
要做到这点,首先要保障办法外部扫描过程是同步执行的。
异步过于不可控,它会让代码的执行程序变得凌乱,所以就会选用同步扫描办法 fs.readdirSync
。
再者,因为在扫描过程中须要获取文件的 stats
信息来判断是 文件
还是 文件夹
,
而这个办法也是异步的(通过回调办法获取后果),同样会给执行程序带来不确定性。
所以能够将 fs.stat
这部分性能提取进去,并通过 Promise
的模式返回后果,联合 async/awat
,
达到同步执行文件信息判断的成果。
这样便保障了代码执行流程的可控性,然而这样还不够!
到底如何能力晓得所有文件都曾经扫描完结呢?
外围的一步,这里我通过设定一个监测变量 onPrintingNum
,它记录正在扫描的文件夹数量,用来判断是否所有文件扫描完结。
当咱们指定了一个门路,并执行 printDirSync
办法,onPrintingNum
初始化为 0,在办法外部,独立封装了负责扫描的办法 print
。
主动执行一次 print
办法来扫描门路下的所有文件和文件夹,这个办法每次被调用,onPrintingNum
就会累加 1,
当遇到空文件夹(fs.readdirSync
返回的 files
为空数组)或者遍历到 files
的结尾,onPrintingNum
先缩小 1;
而后紧接着判断 onPrintingNum
是否为 0,若为 0,则阐明遍历完结了。
能够联合代码及外面的正文了解下,通过自己大量测试(简单的文件构造和高频扫描)未发现问题。若有破绽或有余请评论指出,一起探讨。
在解决这个问题的过程中也发现了一些 fs.readdirSync
办法读取文件的特点:输入的 files
数组(读取文件遍历)程序并非固定,
这里没有深入研究。
若不关怀扫描完结的动作,这里再提供一版异步的扫描办法,实践上效率更高。
/**
* [printDir 异步遍历文件夹的所有文件,只关注遇到文件的解决,不关注是否全副遍历完结]
* @param {String} dirPath [要遍历的文件夹门路]
* @return {Object} options {allowExts: [], // 指定须要的文件类型的门路,不指定则默认容许所有类型(不同于文件夹,文件类型太多,用疏忽的形式太麻烦,所以用了容许)ignoreDirs: [], // 指定不须要读取的文件门路,不指定则默认读取所有文件夹
onFile: (fileDir, ext, stats) => {},
onError: (fileDir, err) => {},
onComplete: (fileNum) => {},}
*/
function printDirAsync(
dirPath,
options = {allowExts: [],
ignoreDirs: [],
onFile: (fileDir, ext, stats) => {},
onError: (fileDir, err) => {},}
) {const { ignoreDirs, onFile, onError} = options;
const print = (filePath) => {fs.readdir(filePath, function (err, files) {if (err) {return console.error(err);
}
let fileLen = files.length;
// 遍历目录下的所有文件
for (let index = 0; index < fileLen; index++) {let file = files[index];
let path = path.join(filePath, file); // 获取以后文件绝对路径
fs.stat(path, function (err, stats) {if (err) {console.warn("获取文件 stats 失败");
if (onError && typeof onError == "function") {onError(path, err);
}
} else {
// 对文件的解决回调,可通过 allowExts 数组过滤指定须要的文件类型的门路,不指定则默认容许所有类型
if (stats.isFile()) {const names = path.split(".");
const ext = names[names.length - 1];
if (
!allowExts ||
allowExts.length == 0 ||
(allowExts.length && allowExts.includes(ext))
) {findFiles.push(path);
if (onFile && typeof onFile == "function") {onFile(path, ext, stats);
}
}
}
// 这里是对文件夹的回调,可通过 ignoreDirs 数组过滤不想遍历的文件夹门路
if (stats.isDirectory()) {
if (
ignoreDirs.length == 0 ||
(ignoreDirs.length && !ignoreDirs.includes(path))
) {print(path); // 递归遍历
}
}
}
});
}
});
};
print(dirPath);
}