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

esgrep

v1.4.0

Published

Syntactically-aware grep for JavaScript and TypeScript

Readme

example

ESGrep

Syntactically-aware grep for JavaScript and TypeScript

Usage as a CLI

Install it with npm install --global esgrep or the equivalent using pnpm/yarn/etc, then use it as esgrep [OPTION...] PATTERN [FILE...]. If FILE is not precised, reads from stdin.

The CLI is basically a wrapper around find and accepts the same options and a few more that handle help print and output format. This means that these are logically equivalent:

Reading from stdin:

echo 'const x: number = 10' | esgrep 'const x = ES_ANY'
find("const x: number = 10", "const x = ES_ANY");

Reading from files:

esgrep 'fetch(ES_ANY, { method: "POST" })' api.js lib.ts
find('fetch(ES_ANY, { method: "POST" })', readFileSomehow("api.js"));
find('fetch(ES_ANY, { method: "POST" })', readFileSomehow("lib.ts"));

Passing arguments:

esgrep --statement -- '(() => {})()' file.js
find("(() => {})()", readFileSomehow("file.js"), { statement: true });

Note

When you pass esgrep --foo bar baz, esgrep has no way to know whether bar is the value of --foo (current behavior) or if it is a positional argument just like baz and --foo is a flag option.

You can tell esgrep to explicitly start parsing positional arguments by using --:

esgrep --foo bar baz
# Parsed options: { foo: 'bar' }
# Parsed positional arguments: ['baz']
esgrep --foo -- bar baz
# Parsed options: { foo: true }
# parsed positional arguments: ['bar', 'baz']

Usage as a library

Install it with npm install esgrep or the equivalent using pnpm/yarn/etc, then import it:

import { find, findStrings } from "esgrep";
// or
const { find, findStrings } = require("esgrep");

For now, the lib only targets Node but it's in the roadmap to target Deno and the Web.

Types should be included in the build so refer to them for the exact types of arguments and returned values. This doc focuses on esgrep scenari rather than spec details.

find(pattern, haystack, options?)

pattern and haystack are strings that represent javascript or typescript code which will be matched based on their AST (and not string representation).

options is detailed in options.

find is a Generator that yields estree nodes.

import { find } from "esgrep";

const pattern = "const x = 10";
const haystack = "const x = /* a number */ 10; const y = 20;";
const matches = find(pattern, haystack);
for (const match of matches) {
  console.log(match);
  // {
  //   type: 'VariableDeclaration',
  //   declarations: [ ... ],
  //   kind: 'const',
  //   range: [ 0, 28 ],
  //   loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 28 } }
  // }
}

If you're not comfortable with generators or don't want to use the perf and streaming capabilities they can provide, you can just spread it out into a plain array:

console.log([...matches]);
// [ { type: 'VariableDeclaration', ... }]

findStrings(pattern, haystack, options?)

Basically the same as find but iterates over matched strings rather that matched nodes.

import { findStrings } from "esgrep";

const pattern = "const x = 10";
const haystack = "const x = /* a number */ 10; const y = 20;";
console.log([...findStrings(pattern, haystack)]);
// [ 'const x = /* a number */ 10;' ]

Options

-h, --help (CLI only)

Prints the synopsis and lists CLI options.

user@host$ esgrep -h

-f, --format {pretty,oneline,jsonl} (CLI only)

Defines the output format of the search

pretty (default): easy to read output

user@host$ esgrep 'const tasks = ES_ANY' src/cli/main.ts
─────────────────────┐
src/cli/main.ts:20:2 │
   ┌─────────────────┘
20 │   const tasks =
21 │     paths.length === 0
22 │       ? [{ path: "stdin", read: readStdin }]
23 │       : paths.map((path) => ({ path, read: () => readFile(path) }));

oneline: streams out lines of the shape $path:$line$column:$match_in_one_line

user@host$ esgrep --format oneline 'const tasks = ES_ANY' src/cli/main.ts
src/cli/main.ts:18:2:const tasks = paths.length === 0 ? [{ path: "stdin", read: readStdin }] : paths.map((path) => ({ path, read: () => readFile(path) }));

jsonl: streams out lines of the shape { path: string, match: Node } where Node is an ESTree node

user@host$ esgrep user users.ts
{"path":"users.ts","match":{"type":"Identifier","name":"user","range":[148,152],"loc":{"start":{"line":4,"column":24},"end":{"line":4,"column":28}}}}

count: do not show matches, only show the number of matches per file. column-separated

user@host$ esgrep user ./src/**/*.{ts,js}
./src/api.js:10
./src/users.js:4
./src/types.ts:0

Note

It is by design that ESGrep doesn't exactly match Grep output options, either because some option didn't make sense anymore, was not useful, or could be achieved by a little bit of bash-fu

  • grep --count ...: esgrep --format count ...
  • grep --files-without-match ...: esgrep --format count ... | grep ':0$' | cut -d ':' -f 1
  • grep --files-with-matches ...: esgrep --format count ... | grep -v ':0$' | cut -d ':' -f 1
  • grep --with-filename ...: always on
  • grep --no-filename ...: always off
  • grep --line-number ...: always on
  • grep --invert-match ...: not useful
  • grep --only-matching ...: not useful

-t, --ts

Include type annotations in the comparison

import { findStrings } from "esgrep";

const withTS = "const x: number = 10";
const withoutTS = "const x = 10";

console.log([...findStrings(withoutTS, withTS)]);
// [ 'const x: number = 10' ]
console.log([...findStrings(withoutTS, withTS, { ts: true })]);
// []

-r, --raw

Differentiate between strings in single quotes, double quotes, and template literals

import { findStrings } from "esgrep";

const haystack = `
const single = 'hello';
const double = "hello";
const template = \`hello\`
`;
console.log([...findStrings('"hello"', haystack)]);
// [ "'hello'", '"hello"', '`hello`' ]
console.log([...findStrings('"hello"', haystack, { raw: true })]);
// [ "'hello'" ]

-s, --statement

If the pattern is an expression statement, lookup the statement itself, and not the expression statement.

import { findStrings } from "esgrep";

const pattern = "10";
const haystack = "const x = 10";
console.log([...findStrings(pattern, haystack)]);
// [ '10' ]
console.log([...findStrings(pattern, haystack, { statement: true })]);
// []

Note The first search matches the 10 in const x = 10 because it looks up all expressions of 10. In plain English, it looks up all the occurrences of "just" the number 10. That is the most intuitive and default behavior.

The second search does not match the 10 in const x = 10 because it looks up all statements of consisting of only the expression 10. In plain English, statements are anything that makes the exact same sense when adding a ; at the end.

The difference is subtle and it usually takes people not familiar with the concept a few articles to have a good grasp on statements vs expressions. Why not start with this one?

ES Expressions

Additionally to its options, patterns are allowed to be flexible thanks to ES expressions - special expressions that allow for something else than perfect matches.

ES_ANY

Matches anything

import { findStrings } from "esgrep";

console.log([...findStrings("ES_ANY", "fn('foo', 'bar')")]);
// [
//   "fn('foo', 'bar')", // matches the expression
//   "fn('foo', 'bar')", // matches the statement (string is the same, not the node)
//   "fn",
//   "'foo'",
//   "'bar'"
// ];
console.log([...findStrings("x = ES_ANY", "x = 10; x = 'hello'")]);
// [
//   "x = 10",
//   "x = 'hello'"
// ];

ES_NOT

Matches anything but the first argument

import { findStrings } from "esgrep";

const pattern = "const x = ES_NOT(10)";
const haystack = "const x = 20";
console.log([...findStrings(pattern, haystack)]);
// [ 'const x = 20' ]

ES_EVERY

Matches if all expressions passed as argument match

import { findStrings } from "esgrep";

const pattern = `const x = ES_EVERY(
  'hello',
  "hello",
  ES_ANY
)`;
const haystack = "const x = 'hello'";
console.log([...findStrings(pattern, haystack)]);
// [ "const x = 'hello'" ]

ES_SOME

Matches if at least one expression passed as argument matches

import { findStrings } from "esgrep";

const pattern = `const x = ES_SOME(
  "hello",
  "goodbye"
)`;
const haystack = "const x = 'hello'";
console.log([...findStrings(pattern, haystack)]);
// [ "const x = 'hello'" ]

About

Want to report a bug? Don't understant the doc? Suggest an improvement? Open an issue!

Contributions are welcomed, the code is still small, clean, and all in TS, so it should be readable and extendable without additional guidance. For big changes, consider opening an issue before opening a PR.

Coded w/ ♡ by Nino Filiu