fs.walk for node (as a port of Go's filepath.Walk)
Walk a directory recursively and handle each entity (files, directories, symlnks, etc).
(a port of Go's filepath.Walk
using Node.js v10+'s fs.readdir's withFileTypes and ES 2021)
``js
await Walk.walk(pathname, walkFunc);
async function walkFunc(err, pathname, dirent) {
// err is failure to lstat a file or directory
// pathname is relative path, including the file or folder name
// dirent = { name, isDirectory(), isFile(), isSymbolicLink(), ... }
if (err) {
return false;
}
console.log(pathname);
}
`
- Install
- Usage
- CommonJS
- ES Modules
- API
- Walk.walk
- walkFunc
- Example: filter dotfiles
- Walk.create
- withFileStats
- sort (and filter)
- Node walk in <50 Lines of Code
- License (MPL-2.0)
`bash`
npm install --save @root/walk
You can use this with Node v12+ using Vanilla JS (CommonJS) or ES2021 (ES Modules).
`js
var Walk = require("@root/walk");
var path = require("path");
Walk.walk("./", walkFunc).then(function () {
console.log("Done");
});
// walkFunc must be async, or return a Promise
function walkFunc(err, pathname, dirent) {
if (err) {
// throw an error to stop walking
// (or return to ignore and keep going)
console.warn("fs stat error for %s: %s", pathname, err.message);
return Promise.resolve();
}
// return false to skip a directory
// (ex: skipping "dot file" directories)
if (dirent.isDirectory() && dirent.name.startsWith(".")) {
return Promise.resolve(false);
}
// fs.Dirent is a slimmed-down, faster version of fs.Stats
console.log("name:", dirent.name, "in", path.dirname(pathname));
// (only one of these will be true)
console.log("is file?", dirent.isFile());
console.log("is link?", dirent.isSymbolicLink());
return Promise.resolve();
}
`
@root/walk can be used with async/await or Promises.
`js
import { walk } from "@root/walk";
import path from "path";
const walkFunc = async (err, pathname, dirent) => {
if (err) {
throw err;
}
if (dirent.isDirectory() && dirent.name.startsWith(".")) {
return false;
}
console.log("name:", dirent.name, "in", path.dirname(pathname));
};
await walk("./", walkFunc);
console.log("Done");
`
Walk.walk walks pathname (inclusive) and calls walkFunc for each file system entity.
It can be used with Promises:
`js`
Walk.walk(pathname, promiseWalker).then(doMore);
Or with async / await:
`js`
await Walk.walk(pathname, asyncWalker);
The behavior should exactly match Go's
filepath.Walk with a few exceptions:
- uses JavaScript Promises/async/await
- receives dirent rather than lstat (for performance, see withFileStats)
- optional parameters to change stat behavior and sort order
Handles each directory entry
`jserr
async function walkFunc(err, pathname, dirent) {
// is a file system stat errorpathname
// is the full pathname, including the file namedirent
// is an fs.Dirent with a name, isDirectory, isFile, etc`
return null;
}
Create a custom walker with these options:
- withFileStats: true walkFunc will receive fs.Stats[] from fs.lstat instead of fs.Dirent[]sort: (entities) => entities.sort()
- sort and/or filter entities before walking them
`js`
const walk = Walk.create({
withFileStats: true,
sort: (entities) => entities.sort()),
});
By default walk will use fs.readdir(pathname, { withFileTypes: true }) which returns fs.Dirent[],fs.Stats
which only has name and file type info, but is much faster when you don't need the complete .
Enable withFileStats to use get full fs.Stats. This will use fs.readdir(pathname) (returning String[])fs.lstat(pathname)
and then call - including mtime, birthtime, uid, etc - right after.
`js
const walk = Walk.create({
withFileStats: true,
});
walk(".", async function (err, pathname, stat) {
console.log(stat.name, stat.uid, stat.birthtime, stat.isDirectory());
});
`
Sometimes you want to give priority to walking certain directories first.
The sort option allows you to specify a funciton that modifies the fs.Dirent[] entities (default) or String[] filenames (withFileStats: true).
Since you must return the sorted array, you can also filter here if you'd prefer.
`js
const byNameWithoutDotFiles = (entities) => {
// sort by name
// filter dot files
return entities
.sort((a, b) => {
if (a.name > b.name) {
return 1;
}
if (a.name < b.name) {
return -1;
}
return 0;
})
.filter((ent) => !ent.name.startsWith("."));
};
const walk = Walk.create({ sort: byNameWithoutDotFiles });
walk(".", async function (err, pathname, stat) {
// each directories contents will be listed alphabetically
console.log(pathname);
});
`
Note: this gets the result of fs.readdir(). If withFileStats is true you will get a String[] of filenames - because this hapens BEFORE fs.lstat() is called - otherwise you will get fs.Dirent[]`.
If you're like me and you hate dependencies,
here's the bare minimum node fs walk function:
See snippet.js or
The main module, as published to NPM, is licensed the MPL-2.0.
The ~50 line snippet is licensed CC0-1.0 (Public Domain).