原文见我的公众号文章 优雅的让你晓得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);}