npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

gitignore-filter

v0.1.1

Published

Respect .gitignore (and .prettierignore, etc.) exactly like git does, with tinyglobby, fdir or fs.glob. Tested against git itself.

Readme

gitignore-filter

npm CI

Respect .gitignore files exactly the way git does: with tinyglobby, fdir, Node's built-in fs.glob, or any list of paths. Every answer is tested against git itself.

It also handles other ignore files that use gitignore syntax, such as .prettierignore and .eslintignore.

import { glob } from "tinyglobby";
import { createGitignoreFilter } from "gitignore-filter";

const files = await glob("**/*.ts", {
  cwd,
  fs: createGitignoreFilter({ cwd }).createFs(),
});

Or, with a little sugar:

import { glob } from "gitignore-filter/tinyglobby";

const files = await glob("**/*.ts", { cwd }); // gitignore: true by default, like globby's option

Why this exists

globby has a gitignore: true option. tinyglobby, the lighter replacement recommended by e18e, deliberately leaves it out, so projects that move off globby lose gitignore support. Today the options are:

  • Stay on globby and keep its 24 installed packages.
  • Shell out to git ls-files, the documented workaround. That needs git installed and a .git folder present, so it breaks in Docker build contexts, unpacked tarballs and some CI checkouts.
  • Write your own .gitignore handling, which is harder than it looks (subfolder scoping, anchored patterns, re-includes, etc.).
  • Drop the feature, as several tools have, and let users trip over generated files, build output and secrets that suddenly show up.

gitignore-filter is that missing piece and lets tinyglobby stay tiny (tinyglobby#92).

What does this add over the ignore package?

ignore is the rule engine: give it gitignore rules and it tells you whether a path matches them. This package uses it, and does everything around it that git does and ignore leaves to you:

| | ignore | gitignore-filter | | ------------------------------------------------------------------------------------------- | :------: | :----------------: | | Matches a path against gitignore rules | ✓ | ✓ (via ignore) | | Finds every .gitignore in the tree, root and subfolders | | ✓ | | Scopes each file's rules to its own folder (/build in src/.gitignore means src/build) | | ✓ | | Lets deeper files override shallower ones, in git's precedence order | | ✓ | | Reads .git/info/exclude and the global gitignore (core.excludesFile) | | ✓ | | Reads core.ignorecase, so matching is case-insensitive where git's is | | ✓ | | Stops at repository boundaries; nested repos and submodules keep their own rules | | ✓ | | Knows which paths are folders, so build/ only matches directories | | ✓ | | Skips ignored folders while crawling instead of filtering afterwards | | ✓ | | Works the same with .prettierignore, .eslintignore and other gitignore-style files | | ✓ | | Checked against git on hundreds of generated repositories on every CI run | | ✓ |

In short, ignore answers "do these rules match this path?" and gitignore-filter answers "would git ignore this file in this project?"

Is tinyglobby + this still smaller than globby?

Yes, by a wide margin. Measured from a clean install of production dependencies (pnpm size):

| What you install | gitignore support | Packages | Size on disk | | ---------------------------------------- | :---------------: | -------: | -----------: | | globby 16.2.4 | ✓ | 24 | 773 kB | | tinyglobby 0.2.17 + gitignore-filter | ✓ | 5 | 364 kB | | tinyglobby 0.2.17 alone | | 3 | 183 kB | | fast-glob 3.3.3 | | 18 | 513 kB | | glob 13.0.6 | | 7 | 6,334 kB |

That's about a fifth of globby's packages and less than half its size. glob and fast-glob are shown for reference only: neither has ever supported .gitignore. (The names are easy to mix up. glob is the original Node globbing library; fast-glob is a faster rewrite; globby is built on fast-glob and adds conveniences, including gitignore: true; tinyglobby is the lightweight replacement for fast-glob and globby, without that option.) The two packages added on top of tinyglobby are this one (about 63 kB including this README) and ignore (about 121 kB, with no dependencies of its own). ignore is often already in a project's tree anyway: it has hundreds of millions of weekly downloads.

It's also faster. On a synthetic monorepo with about 25,000 files, most of them in node_modules, dist and coverage folders (pnpm bench):

| Approach | Median time | Matches git | | ------------------------------------------------- | ----------: | :-----------------------------------: | | globby { gitignore: true } | 122 ms | ✓ | | tinyglobby + documented git ls-files workaround | 100 ms | ✓ | | tinyglobby + gitignore-filter | 28 ms | ✓ | | tinyglobby with no gitignore support (baseline) | 28 ms | returns 24,951 files instead of 5,151 |

Measured on an Apple M2 Max, Node 24.21, median of 15 runs. Your numbers will differ; the ordering was the same on Linux and macOS.

tinyglobby + gitignore-filter is so much faster than globby because:

  1. It crawls once. globby crawls the tree once just to find the .gitignore files, then again to glob. gitignore-filter reads each folder's .gitignore when the crawl reaches that folder.
  2. It doesn't stat every result. globby calls stat on each result (about 5,000 here) to check whether it's a folder. The directory listing already says which entries are folders, so gitignore-filter doesn't need to.

It's as fast as tinyglobby with no gitignore support because ignored folders are never opened: the crawl opens about 300 directories instead of about 1,600, which saves about as much time as checking the rules costs.

Install

npm install gitignore-filter

To use it with tinyglobby, install tinyglobby too (an optional peer dependency). Requires Node.js 20 or later.

Usage

With tinyglobby

Pass createFs() as tinyglobby's fs option. It drops ignored entries as each folder is read, so ignored folders are never crawled, and every tinyglobby option works as usual.

import { glob } from "tinyglobby";
import { createGitignoreFilter } from "gitignore-filter";

const filter = createGitignoreFilter({ cwd });
const files = await glob("**/*.ts", { cwd, fs: filter.createFs() });

If you already pass your own fs, give it to createFs(yourFs) and it will be wrapped instead of replaced.

Migrating from globby

gitignore-filter/tinyglobby is the same thing with less typing: it exports tinyglobby's glob and globSync with globby's gitignore and ignoreFiles options added. gitignore defaults to true:

- import { globby } from 'globby'
+ import { glob } from 'gitignore-filter/tinyglobby'

- const files = await globby('src/**/*.ts', { cwd, gitignore: true })
+ const files = await glob('src/**/*.ts', { cwd })

All tinyglobby options work as usual.

import { glob, globSync } from "gitignore-filter/tinyglobby";

await glob(["**/*.js", "!**/*.test.js"], {
  cwd,
  ignoreFiles: [".prettierignore"],
});
globSync({
  patterns: ["**/*"],
  cwd,
  dot: true,
  gitignore: false,
  ignoreFiles: ".eslintignore",
});

Migrating from tinyglobby's git ls-files workaround? Delete the helper and use either form above.

Checking paths directly

import { createGitignoreFilter } from "gitignore-filter";

const filter = createGitignoreFilter({ cwd });

filter.isIgnored("dist/app.js"); // true
filter.isIgnored("src/index.ts"); // false
filter.isIgnored("build", true); // pass true when you know it's a directory
filter.isIgnored("build/"); // or end the path with /

filter.filter(["src/a.ts", "dist/a.js", "README.md"]); // ['src/a.ts', 'README.md']

Relative paths are resolved from cwd; absolute paths and file: URLs work too. If you don't say whether a path is a directory and the answer depends on it (build/ only matches directories), the path is checked on disk once.

With fdir

createFs() works as fdir's fs option too.

import { fdir } from "fdir";
import { createGitignoreFilter } from "gitignore-filter";

const filter = createGitignoreFilter({ cwd });
const files = new fdir({ fs: filter.createFs() })
  .withRelativePaths()
  .crawl(cwd)
  .sync();

// Or only prune directories:
new fdir().exclude(filter.fdirExclude).crawl(cwd).sync();

With Node's fs.glob

Use withFileTypes: true. In string mode Node sometimes passes only a base name to exclude, which can't be located reliably.

import { globSync } from "node:fs";

const filter = createGitignoreFilter({ cwd });
const files = globSync("**/*", {
  cwd,
  withFileTypes: true,
  exclude: filter.fsGlobExclude,
})
  // Node's fs.glob can still return a few files that `exclude` rejected
  // (nodejs/node#63049); this line makes the result exact until that's fixed.
  .filter((entry) => !filter.fsGlobExclude(entry));

Options

createGitignoreFilter(options) and the tinyglobby helper accept:

| Option | Default | Description | | ----------------- | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | cwd | process.cwd() | Where relative paths are resolved from, and where the search for the repository starts. | | gitignore | true | Respect .gitignore files, .git/info/exclude and the global excludes file. | | ignoreFiles | none | Extra gitignore-syntax files to respect, by name: '.prettierignore' or ['.prettierignore', '.eslintignore']. globby-style '**/.prettierignore' is accepted too. They're found and scoped exactly like .gitignore, and layered on top of it. | | globalGitignore | true | Respect the user's global excludes file, as git does. Set it to false for results that don't depend on the machine. | | ignoreCase | repo setting | Match case-insensitively. Defaults to the repository's core.ignorecase, which git sets to true on macOS and Windows. |

How it decides

It follows git's own rules:

  1. Every .gitignore applies only within its own folder, with patterns relative to that folder.
  2. A pattern with no slash (*.log) matches at any depth; a leading or middle slash (/dist, a/b) anchors it; a trailing slash (build/) matches only directories.
  3. Precedence, lowest to highest: the global excludes file, then .git/info/exclude, then .gitignore files from the root down. The last matching rule wins, so a deeper file can override a shallower one.
  4. Nothing inside an ignored folder can be re-included (logs/ followed by !logs/keep.log still ignores keep.log; use logs/* instead), and git doesn't read ignore files inside ignored folders.
  5. A nested repository or submodule uses only its own rules, though the outer repository can still ignore its folder.
  6. Outside a git repository (for example, a Docker build context with no .git), .gitignore files under cwd are still applied the same way.
  7. The .git folder itself is always treated as ignored, since git never treats it as part of the working tree.

Ignore files are read lazily and cached, so only the folders you ask about are touched. The filter doesn't watch for changes; create a new one if ignore files change.

Tested against git

The test suite never checks answers against a hand-written expectation of how gitignore works. Each test builds a real repository, asks git (git ls-files --others --ignored --exclude-standard) which files it ignores, and requires this package to give the same answer for every file. It also checks that the tinyglobby helper returns exactly git's list of non-ignored files.

On top of hand-written scenarios, a randomized test generates hundreds of repositories with random folders and random ignore files and compares each against git. Runs are reproducible from a seed. CI runs everything on Linux, macOS and Windows across Node 20, 22 and 24, plus a weekly run of 5,000 random repositories with a fresh seed.

pnpm test                                  # everything, 300 random repositories
FUZZ_RUNS=5000 pnpm test:only              # more random repositories
FUZZ_SEED=12345 FUZZ_RUNS=1 pnpm test:only  # reproduce one case

Limits

  • It answers "is this path ignored?", not "is it tracked?" Like globby's gitignore option and git check-ignore --no-index, it doesn't read git's index. A file that's already committed but matches an ignore pattern is reported as ignored.
  • The system-wide git config (/etc/gitconfig) and [include] sections are not read when looking up core.excludesFile. The repository config, ~/.gitconfig, $XDG_CONFIG_HOME/git/config and GIT_CONFIG_GLOBAL are.
  • Only gitignore-syntax files belong in ignoreFiles. .dockerignore, for example, follows different rules.

License

MIT