fast-staged
v1.0.2
Published
A drop-in replacement for lint-staged, optimized for speed
Readme
[!NOTE] Test of lint-staged alternatives, with the original built by AI, and the new one partially built with AI. Would not recommend for large projects.
fast-staged
A drop-in replacement for lint-staged — optimized for speed.
Why faster?
| | lint-staged | fast-staged | | -------------- | ---------------------------------------- | ------------------------------ | | Dependencies | listr2, picomatch, string-argv, tinyexec | zero (bundled single file) | | Startup | ~180ms | ~15ms | | Concurrency | Sequential by default | Concurrent by default | | Stash strategy | Full stash | Lightweight index stash | | Bundle size | ~300KB unpacked | ~25KB |
Install
npm install --save-dev fast-staged
# or
yarn add -D fast-stagedUsage
fast-staged is a drop-in replacement — it reads the same config as lint-staged:
// package.json
{
"lint-staged": {
"*.{js,ts}": "eslint --fix",
"*.{css,scss}": ["stylelint --fix", "prettier --write"]
}
}Then in your .husky/pre-commit or lefthook.yml, just swap the binary:
# Before
npx lint-staged
# After (option 1 — explicit)
npx fast-staged
# After (option 2 — aliased, no changes needed)
# fast-staged also registers itself as `lint-staged` in node_modules/.binConfig formats
All lint-staged config formats are supported:
package.json→"lint-staged"or"fast-staged"keylint-staged.config.js/.mjs/.cjs.lintstagedrc/.lintstagedrc.json/.lintstagedrc.js.lintstagedrc.yaml/.ymlfast-staged.config.js/.json
// lint-staged.config.js ← works as-is
export default {
"*.{js,ts}": ["eslint --fix", "prettier --write"],
"*.md": "prettier --write",
};// Can also export a function (same as lint-staged)
export default async function (stagedFiles) {
return {
"*.ts": `tsc --noEmit`,
};
}CLI Options
fast-staged [options]
-c, --config <path> Path to config file
--no-stash Don't stash unstaged changes before running
--no-concurrent Run tasks sequentially
--allow-empty Run even when no files match
--lean Minimize git subprocesses (see Lean mode below)
--debug Enable debug output
-h, --help Show help
--version Print versionProgrammatic API
import { fastStaged } from "fast-staged";
const ok = await fastStaged({
cwd: process.cwd(), // working directory
concurrent: true, // run tasks concurrently (default)
stash: true, // stash unstaged changes (default)
diff: true, // re-stage files modified by commands
allowEmpty: false, // run even with no matched files
debug: false, // extra debug output
lean: false, // or set `lean` in config — see Lean mode
});
process.exit(ok ? 0 : 1);Lean mode
When you are fine with stricter tradeoffs, opt in so fast-staged avoids almost all git subprocesses besides listing the index (one git diff --cached per run):
- Set
"lean": truenext to your globs inpackage.json(lint-staged/fast-staged) or in any supported config file, or pass--lean/{ lean: true }to the API. - Skipped:
git rev-parse(repo root is found by walking up for.git, with a rare fallback togit rev-parse), the unstaged-dirty probe, stashing, and post-rungit addto re-stage formatter output.
Use lean mode only if a dirty working tree mixed with a partial stage will not confuse your tools, you do not rely on automatic re-staging after --write formatters, and a normal .git entry exists on disk (typical clones and worktrees).
{
"lint-staged": {
"lean": true,
"*.{js,ts}": "eslint --fix"
}
}Lower-level utilities
import {
loadConfig, // → { tasks, lean }; load + parse any lint-staged config format
getStagedFiles, // list staged files via git diff --cached
getGitRoot, // find git repository root (git subprocess)
getGitRootPreferFs, // walk `.git` then optional git fallback
peekPackageJsonLean, // read `lean` from package.json up the tree (sync)
runTasks, // run commands against matched files
buildMatcher, // glob → (file: string) => boolean
} from "fast-staged";File substitution
By default, matched filenames are appended to the end of commands:
{ "*.js": "eslint --fix" }
// runs: eslint --fix file1.js file2.jsUse {staged_files} to control placement:
{ "*.js": "eslint --fix {staged_files} --format compact" }
// runs: eslint --fix file1.js file2.js --format compactHow it works
- Git —
git diff --cached --name-onlylists staged files instantly - Match — each file is tested against your glob patterns (zero-dep, inlined)
- Stash — if you have unstaged changes, they're stashed with
--keep-indexso commands only see staged content - Run — all matching commands launch concurrently via
child_process.spawn(no shell, minimal overhead) - Re-stage — after formatters run, files are re-staged with
git add - Restore — the stash is popped
Building for npm
npm run build # outputs dist/index.js (ESM) + dist/index.cjs (CJS)
npm publishThe published package has zero runtime dependencies — everything is bundled by esbuild.
License
MIT
