fdir

The fastest directory crawler and globbing library for Node.js, built around a chainable builder API with optional picomatch-powered glob support.

Library
npm
v6.5.0
1,734stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
47/100Fair
Development Activity0
Maintenance32
Community56
Maturity60
Momentum40

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
83/100Excellent
Architecture85
Code Quality78
Innovation80
Learning Curve90

fdir is a directory-crawling and globbing library for Node.js designed from the ground up for raw throughput — it can walk a directory tree containing one million files in under a second. Rather than exposing a single monolithic function, fdir uses an expressive Builder pattern (new fdir().withFullPaths().crawl(path)) that lets consumers opt into exactly the behavior they need — full paths, relative paths, symlink resolution, depth limits, file/directory filtering, and glob matching — without paying for features they don’t use.

Under the hood, fdir builds its walking, filtering, and path-joining functions conditionally based on which builder options were set, so the JavaScript engine can inline the resulting tiny functions instead of branching on every file it visits. It ships with zero required dependencies (picomatch is an optional peer dependency, only needed for glob matching), works in synchronous, Promise-based, and callback-based modes, and is used in production by projects including Rollup’s plugin suite, Pulumi, dotenvx, and MDN’s Yari.

What You Get

  • A chainable Builder API (new fdir().withXyz().crawl(path)) for composing crawl behavior declaratively
  • Synchronous (.sync()), Promise-based (.withPromise()), and callback-based crawling modes
  • Optional glob matching via the picomatch peer dependency, with compiled matchers cached per pattern set
  • Fine-grained control over output shape: full paths, relative paths, directories-only, grouped-by-directory, or counts-only
  • Symlink resolution, max-depth limits, max-file limits, and AbortSignal support for cancellable crawls

Common Use Cases

  • Build tools and bundlers (e.g. Rollup plugins) enumerating source files to process
  • CLI tools and linters that need to glob-match files across large repositories quickly
  • Static site generators and documentation tools (e.g. MDN’s Yari) walking content directories
  • Package managers and dependency tools scanning node_modules trees for manifests

Under The Hood

Architecture fdir separates its public API (src/builder/index.ts’s Builder class plus api-builder.ts) from its execution core (src/api/walker.ts’s Walker class), with the walker composed from a set of small, independently swappable functions in src/api/functions/ (join-path, push-directory, push-file, get-array, group-files, resolve-symlink, walk-directory, invoke-callback) that are selected and bound once per Walker construction based on the active Options object, then invoked directly inside the hot per-entry loop in walk(); a Queue (src/api/queue.ts) tracks in-flight async directory reads via a counter so the walk only invokes its result callback once every recursive fs.readdir call has resolved, and an Aborter (src/api/aborter.ts) provides a shared abort flag checked at the top of each walk() invocation. Because the “conditional function” strategy pushes option-branching to construction time rather than per-file, changing the core Options shape (in src/types.ts) is the one change that ripples through nearly every function builder in src/api/functions/.

Tech Stack fdir is written in TypeScript (strict mode, ESNext target) and builds dual ESM/CJS bundles via tsdown, targeting Node >=12, with zero required runtime dependencies — its only dependency-like relationship is an optional peer dependency on picomatch for glob support, loaded lazily via require.resolve with a try/catch fallback so its absence never causes a hard failure. Tests run on Vitest with @vitest/coverage-v8 for coverage and mock-fs for filesystem fixtures, formatting is enforced via Prettier, and an extensive benchmarks suite (using benny, ts-node, and a long list of competing crawler/glob libraries as devDependencies, including pinned historical versions of fdir itself) tracks performance regressions across releases.

Code Quality Two test files exercise the public Builder/APIBuilder surface plus a dedicated symlink-resolution suite, backed by mock-fs fixtures, with coverage tracked via @vitest/coverage-v8 and explicit c8 ignore annotations marking deliberately-untested defensive branches. The codebase is fully typed under strict: true TypeScript, uses consistent naming and small single-responsibility modules (one file per builder function), and CI runs tests and the build on every push. No typed error classes are used — filesystem errors pass through largely as-is and are suppressed by default, a documented, deliberate default rather than an oversight.

API Design The chainable Builder API is fdir’s main ergonomic decision — a call like new fdir().withFullPaths().withMaxDepth(2).crawl(path).sync() reads as a declarative configuration pipeline rather than a single options object, method names map directly to documented behavior, and the same builder supports three execution modes without re-specifying options. Getting started requires zero configuration, and glob support is opt-in via a single .glob() call rather than a separate API surface, though the peer-dependency-based picomatch wiring is a minor discoverability rough edge for a caller who forgets to install it.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search