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

grep-wasm

v0.1.0

Published

WebAssembly bindings for ripgrep - high-performance text search

Readme

grep-wasm

WebAssembly bindings for ripgrep - blazing-fast text search for browsers and Node.js.

npm version GitHub License: MIT OR Unlicense

🚀 Features

  • High Performance - Powered by ripgrep's efficient search engine compiled to WebAssembly
  • 🎯 Full-Featured - Regex patterns, case-insensitive matching, whole-word search, and more
  • 📦 TypeScript Native - Complete type definitions with modern, intuitive API
  • 🌐 Universal - Works in browsers, Node.js, and WebContainer environments
  • 🔍 Advanced Filtering - File type filters, .gitignore support, depth limits
  • Simple API - Clean, consistent interface with automatic initialization
  • 🛡️ Structured Errors - Detailed error information for better debugging

📦 Installation

npm install grep-wasm

或使用其他包管理器:

# pnpm
pnpm add grep-wasm

# yarn
yarn add grep-wasm

⚡ Quick Start

Basic Search

import { ripgrep } from 'grep-wasm';

// Search for a pattern in files
const results = await ripgrep.search('function', [
  { path: 'main.js', content: 'function main() { console.log("Hello"); }' },
  { path: 'utils.js', content: 'export function helper() { return 42; }' }
], {
  caseInsensitive: true
});

console.log(`Found ${results.totalMatches} matches in ${results.filesWithMatches} files`);

// Display results
results.matches.forEach(match => {
  console.log(`${match.path}:${match.lineNumber}: ${match.line}`);
});

Node.js Directory Search

import { searchInDirectory } from 'grep-wasm/node';

// Automatically search all files in a directory
const results = await searchInDirectory(
  './src',              // Directory path
  'TODO',               // Search pattern
  {
    fileTypes: ['*.js', '*.ts'],
    ignorePatterns: ['node_modules', '*.test.js'],
    caseInsensitive: true
  }
);

console.log(`Found ${results.totalMatches} TODOs`);

📚 Core API

RipgrepWasm Class

The main class providing high-level search functionality.

search(pattern, files, options?)

Search for a pattern in files with detailed results including line numbers and matched content.

Parameters:

1. pattern: string

The search pattern. Supports both literal strings and regular expressions.

Examples:

// Literal string
'TODO'

// Regular expression
'function\\s+\\w+'

// Multiple patterns (OR)
'TODO|FIXME|XXX'

// Word with boundaries
'\\bclass\\b'
2. files: FileEntry[]

Array of files to search. Each file must include path and content.

interface FileEntry {
  /** 
   * Full file path (absolute or relative)
   * Used in search results to identify where matches were found
   * 
   * Examples:
   * - 'src/main.ts'
   * - '/project/src/utils.js'
   * - './README.md'
   */
  path: string;
  
  /** 
   * File content as UTF-8 string
   * The actual text content to search within
   * Must be valid UTF-8 (binary files not supported)
   * 
   * Example:
   * 'function main() {\n  console.log("Hello");\n}'
   */
  content: string;
}

Example:

const files: FileEntry[] = [
  {
    path: 'src/index.ts',
    content: 'import { app } from "./app";\n\napp.start();'
  },
  {
    path: 'src/app.ts',
    content: 'export const app = {\n  start() { console.log("Started"); }\n};'
  }
];
3. options?: SearchOptions

Optional configuration to control search behavior.

interface SearchOptions {
  /**
   * Case-insensitive search (equivalent to grep -i)
   * 
   * Default: false
   * 
   * When true:
   * - 'TODO' matches 'TODO', 'todo', 'Todo', 'tOdO', etc.
   * 
   * Example:
   * await search('hello', files, { caseInsensitive: true });
   * // Matches: "Hello", "HELLO", "hello"
   */
  caseInsensitive?: boolean;
  
  /**
   * Treat pattern as fixed string, not regex (equivalent to grep -F)
   * 
   * Default: false
   * 
   * When true:
   * - Special regex characters are treated literally
   * - Faster than regex matching
   * 
   * Example:
   * await search('[test]', files, { fixedStrings: true });
   * // Matches literal "[test]", not regex character class
   */
  fixedStrings?: boolean;
  
  /**
   * Match whole words only (equivalent to grep -w)
   * 
   * Default: false
   * 
   * When true:
   * - Pattern must match complete words (bounded by non-word characters)
   * - Automatically adds \b word boundaries to pattern
   * 
   * Example:
   * await search('main', files, { wordBoundary: true });
   * // Matches: "main()" but NOT "remain" or "domain"
   */
  wordBoundary?: boolean;
  
  /**
   * Include line numbers in results
   * 
   * Default: true
   * 
   * When false:
   * - lineNumber field still present but may be 0
   * - Slightly faster search
   * 
   * Example:
   * await search('TODO', files, { lineNumbers: true });
   */
  lineNumbers?: boolean;
  
  /**
   * Output format control
   * 
   * Default: 'detailed'
   * 
   * Options:
   * - 'detailed': Full match details (path, line number, line content)
   * - 'files_only': Only file paths (matches array still contains items, use for path extraction)
   * 
   * Example:
   * const results = await search('TODO', files, { outputFormat: 'detailed' });
   * // results.matches contains full MatchResult objects
   * 
   * const results2 = await search('TODO', files, { outputFormat: 'files_only' });
   * const paths = [...new Set(results2.matches.map(m => m.path))];
   */
  outputFormat?: 'detailed' | 'files_only';
}

Complete Options Example:

const options: SearchOptions = {
  caseInsensitive: true,   // Match any case
  fixedStrings: false,     // Use regex (default)
  wordBoundary: true,      // Match whole words only
  lineNumbers: true,       // Include line numbers (default)
  outputFormat: 'detailed' // Full results (default)
};

const results = await ripgrep.search('function', files, options);

Returns: Promise<SearchResult>

Complete search results with match details.

interface SearchResult {
  /**
   * All matches found across all files
   * Array of individual match results
   * Ordered by file path, then line number
   * 
   * Empty array if no matches found
   */
  matches: MatchResult[];
  
  /**
   * Total number of matches found
   * Count of all occurrences across all files
   * 
   * Example:
   * - If 'TODO' appears 3 times in file1 and 2 times in file2
   * - totalMatches = 5
   */
  totalMatches: number;
  
  /**
   * Number of unique files containing matches
   * 
   * Example:
   * - If 'TODO' appears in 3 different files
   * - filesWithMatches = 3
   */
  filesWithMatches: number;
}

interface MatchResult {
  /**
   * Full file path where match was found
   * Same as the path provided in FileEntry
   * 
   * Example: 'src/main.ts'
   */
  path: string;
  
  /**
   * Line number where match was found (1-indexed)
   * First line of file is line 1
   * 
   * Example: 42 (means match is on line 42)
   * 
   * Note: Will be 0 if lineNumbers option is false
   */
  lineNumber: number;
  
  /**
   * Full line content containing the match
   * The complete text of the line (trimmed of trailing whitespace)
   * 
   * Example: '  // TODO: implement this feature'
   */
  line: string;
  
  /**
   * Byte offset of the match start position
   * Currently always 0 (reserved for future use)
   * 
   * Future: Will indicate exact position of match within line
   */
  byteOffset: number;
}

Result Example:

const results = await ripgrep.search('TODO', files);

console.log(results);
// Output:
// {
//   matches: [
//     {
//       path: 'src/main.ts',
//       lineNumber: 5,
//       line: '  // TODO: implement error handling',
//       byteOffset: 0
//     },
//     {
//       path: 'src/utils.ts',
//       lineNumber: 12,
//       line: '// TODO: optimize this function',
//       byteOffset: 0
//     }
//   ],
//   totalMatches: 2,
//   filesWithMatches: 2
// }

Usage Examples:

// Example 1: Basic case-sensitive search
const results = await ripgrep.search('TODO', files);
console.log(`Found ${results.totalMatches} TODOs`);

// Example 2: Case-insensitive search (grep -i)
const results = await ripgrep.search('function', files, { 
  caseInsensitive: true 
});
// Matches: "function", "Function", "FUNCTION"

// Example 3: Whole word matching (grep -w)
const results = await ripgrep.search('test', files, {
  wordBoundary: true
});
// Matches: "test()" but NOT "testing" or "latest"

// Example 4: Literal string search (grep -F)
const results = await ripgrep.search('[test]', files, {
  fixedStrings: true
});
// Matches literal "[test]", not regex pattern

// Example 5: Regular expression search
const results = await ripgrep.search('function\\s+\\w+', files);
// Matches: "function main", "function helper", etc.

// Example 6: Multiple patterns (OR)
const results = await ripgrep.search('TODO|FIXME|XXX', files);
// Matches any of the three patterns

// Example 7: Extract unique file paths
const results = await ripgrep.search('import', files);
const uniqueFiles = [...new Set(results.matches.map(m => m.path))];
console.log('Files with imports:', uniqueFiles);

// Example 8: Combined options
const results = await ripgrep.search('error', files, {
  caseInsensitive: true,  // Match any case
  wordBoundary: true,     // Whole word only
  lineNumbers: true       // Include line numbers
});

// Example 9: Find function definitions
const results = await ripgrep.search(
  '^\\s*function\\s+\\w+\\s*\\(',  // Regex for function definitions
  files
);

// Example 10: Count matches per file
const results = await ripgrep.search('console.log', files);
const countsPerFile = results.matches.reduce((acc, match) => {
  acc[match.path] = (acc[match.path] || 0) + 1;
  return acc;
}, {} as Record<string, number>);

filterDirectoryFiles(config, filePaths)

Filter file paths based on directory search configuration. Useful for applying .gitignore rules, file type filters, and depth limits.

Parameters:

1. config: DirectorySearchConfig

Configuration for filtering files.

interface DirectorySearchConfig {
  /**
   * Root directory path for calculating relative paths and depths
   * 
   * Required: true
   * 
   * Example: '/project' or './src'
   */
  rootPath: string;
  
  /**
   * Maximum recursion depth from root
   * 
   * Default: undefined (unlimited)
   * 
   * Examples:
   * - 0: Only files in root directory
   * - 1: Root + 1 level of subdirectories
   * - undefined: No limit
   * 
   * Usage:
   * { rootPath: '/project', maxDepth: 2 }
   * // Includes: /project/file.js, /project/src/app.js
   * // Excludes: /project/src/components/Button.js (depth 3)
   */
  maxDepth?: number;
  
  /**
   * File type patterns to include (glob patterns)
   * 
   * Default: [] (all files)
   * 
   * Supports:
   * - Wildcards: *.js, *.ts
   * - Multiple extensions: *.{js,ts}
   * - Subdirectories: **\/*.js
   * 
   * Examples:
   * ['*.ts']           // Only .ts files
   * ['*.js', '*.jsx']  // JavaScript files
   * ['*.{ts,tsx}']     // TypeScript files
   * 
   * Note: If specified, ONLY matching files are included
   */
  fileTypes?: string[];
  
  /**
   * Patterns to ignore (glob patterns)
   * 
   * Default: []
   * 
   * Common examples:
   * ['node_modules']         // Exclude node_modules directory
   * ['*.min.js']            // Exclude minified files
   * ['dist', 'build']       // Exclude build directories
   * ['*.log']               // Exclude log files
   * ['**\/__tests__/**']     // Exclude test directories
   * 
   * Note: These patterns are applied AFTER fileTypes filter
   */
  ignorePatterns?: string[];
  
  /**
   * Whether to respect .gitignore rules
   * 
   * Default: true
   * 
   * When true:
   * - Files matching .gitignore patterns are excluded
   * - Requires gitignoreFiles to be provided
   * 
   * Example:
   * { 
   *   respectGitignore: true,
   *   gitignoreFiles: [
   *     { path: '/project', content: 'node_modules/\n*.log' }
   *   ]
   * }
   */
  respectGitignore?: boolean;
  
  /**
   * Whether to include hidden files (starting with .)
   * 
   * Default: false
   * 
   * When false:
   * - Files like .env, .gitignore are excluded
   * - Directories like .git are excluded
   * 
   * When true:
   * - All files included (subject to other filters)
   */
  includeHidden?: boolean;
  
  /**
   * .gitignore file contents to apply
   * 
   * Default: []
   * 
   * Structure:
   * - path: Directory containing the .gitignore
   * - content: Raw .gitignore file content
   * 
   * Example:
   * gitignoreFiles: [
   *   {
   *     path: '/project',
   *     content: `
   *       node_modules/
   *       dist/
   *       *.log
   *       .env
   *     `
   *   },
   *   {
   *     path: '/project/packages/app',
   *     content: 'build/\n*.test.js'
   *   }
   * ]
   * 
   * Note: Multiple .gitignore files can be specified for monorepos
   */
  gitignoreFiles?: GitignoreFile[];
  
  /**
   * Override patterns (whitelist)
   * Forces inclusion of matching files even if ignored
   * 
   * Default: []
   * 
   * Example:
   * {
   *   ignorePatterns: ['dist/**'],
   *   overridePatterns: ['dist/important.js']
   * }
   * // All dist/** files ignored EXCEPT dist/important.js
   */
  overridePatterns?: string[];
  
  /**
   * Exclude patterns (blacklist)
   * Additional exclusion rules
   * 
   * Default: []
   * 
   * Example:
   * {
   *   fileTypes: ['*.js'],
   *   excludePatterns: ['*.test.js', '*.spec.js']
   * }
   * // Include .js files but exclude test files
   */
  excludePatterns?: string[];
}

interface GitignoreFile {
  /** Directory path where .gitignore is located */
  path: string;
  /** Content of .gitignore file */
  content: string;
}

Config Example:

const config: DirectorySearchConfig = {
  rootPath: '/project',
  maxDepth: 5,
  fileTypes: ['*.ts', '*.tsx', '*.js', '*.jsx'],
  ignorePatterns: [
    'node_modules',
    'dist',
    'build',
    '*.min.js',
    '*.map',
    'coverage'
  ],
  respectGitignore: true,
  includeHidden: false,
  gitignoreFiles: [
    {
      path: '/project',
      content: `
node_modules/
dist/
.env
*.log
.DS_Store
      `.trim()
    }
  ],
  overridePatterns: ['src/**/*'],
  excludePatterns: ['**/*.test.ts']
};
2. filePaths: string[]

Array of file paths to filter.

Example:

const filePaths = [
  '/project/src/main.ts',
  '/project/src/utils.ts',
  '/project/node_modules/react/index.js',
  '/project/dist/bundle.js',
  '/project/.env',
  '/project/src/app.test.ts'
];

Returns: Promise<FilePathEntry[]>

Array of filtered file path entries with metadata.

interface FilePathEntry {
  /**
   * Full file path (same as input)
   * 
   * Example: '/project/src/main.ts'
   */
  path: string;
  
  /**
   * Path relative to rootPath
   * 
   * Example: 'src/main.ts' (if rootPath is '/project')
   */
  relativePath: string;
  
  /**
   * Depth level from root (0-indexed)
   * 
   * Examples:
   * - '/project/file.js' -> depth: 0
   * - '/project/src/file.js' -> depth: 1
   * - '/project/src/components/Button.js' -> depth: 2
   */
  depth: number;
}

Result Example:

const filtered = await ripgrep.filterDirectoryFiles(config, filePaths);

console.log(filtered);
// Output:
// [
//   {
//     path: '/project/src/main.ts',
//     relativePath: 'src/main.ts',
//     depth: 1
//   },
//   {
//     path: '/project/src/utils.ts',
//     relativePath: 'src/utils.ts',
//     depth: 1
//   }
// ]
// Excluded:
// - /project/node_modules/react/index.js (ignorePatterns)
// - /project/dist/bundle.js (ignorePatterns)
// - /project/.env (includeHidden: false)
// - /project/src/app.test.ts (excludePatterns)

Example:

const filtered = await ripgrep.filterDirectoryFiles({
  rootPath: '/project',
  maxDepth: 3,
  fileTypes: ['*.js', '*.ts'],
  ignorePatterns: ['node_modules', 'dist', '*.min.js'],
  respectGitignore: true
}, allFilePaths);

console.log(`Filtered ${filtered.length} files`);

Convenience Functions

import { search, filterDirectoryFiles } from 'grep-wasm';

// Same as ripgrep.search(...)
const results = await search(pattern, files, options);

// Same as ripgrep.filterDirectoryFiles(...)
const filtered = await filterDirectoryFiles(config, filePaths);

🟢 Node.js Helper Functions

The grep-wasm/node module provides convenient directory search functions that automatically handle file system access.

searchInDirectory(dirPath, pattern, options?)

Automatically read and search all files in a directory.

import { searchInDirectory } from 'ripgrep-wasm/node';

const results = await searchInDirectory(
  '/project/src',
  'function',
  {
    fileTypes: ['*.js', '*.ts'],
    ignorePatterns: ['*.test.js', 'node_modules'],
    caseInsensitive: true,
    maxDepth: 5
  }
);

results.matches.forEach(match => {
  console.log(`${match.path}:${match.lineNumber}: ${match.line}`);
});

readDirectoryFiles(dirPath, options?)

Recursively read all files from a directory.

import { readDirectoryFiles } from 'grep-wasm/node';

const files = await readDirectoryFiles('/project', {
  fileTypes: ['*.js'],
  ignorePatterns: ['node_modules', 'dist'],
  maxDepth: 3
});

// Use with core search
const results = await search('pattern', files);

⚙️ Supported Features & Limitations

✅ Currently Supported

The following features are currently implemented and tested:

| Feature | Option | Equivalent | Description | |---------|--------|------------|-------------| | Case-insensitive | caseInsensitive: true | grep -i | Match regardless of case | | Fixed strings | fixedStrings: true | grep -F | Literal string matching (no regex) | | Word boundary | wordBoundary: true | grep -w | Match whole words only | | Line numbers | lineNumbers: true | grep -n | Include line numbers (default: ON) | | Regex patterns | (default) | grep -E | Full regular expression support | | Multiple patterns | pattern1\|pattern2 | grep -e | OR multiple patterns | | File filtering | fileTypes | --include | Filter by file extensions | | Ignore patterns | ignorePatterns | --exclude | Exclude files/directories | | .gitignore | respectGitignore | - | Honor .gitignore rules | | Max depth | maxDepth | --max-depth | Limit directory recursion | | Hidden files | includeHidden | - | Include/exclude hidden files |

❌ Not Yet Supported

The following common grep features are not yet implemented. We're tracking these for future releases:

| Feature | grep Option | Workaround | Priority | |---------|-------------|------------|----------| | Invert match | -v, --invert-match | Filter results in JS | Medium | | Max count | -m, --max-count | Slice results array | Low | | Count only | -c, --count | Use results.totalMatches | Low | | Context lines | -A, -B, -C | Fetch surrounding lines in JS | Medium | | Only matching | -o, --only-matching | Extract with regex in JS | Low | | Quiet mode | -q, --quiet | Check if totalMatches > 0 | Low | | Binary files | --binary | Not applicable (text only) | N/A | | Multiline | -U, --multiline | Use \n in pattern | High |

🔧 Workarounds for Missing Features

Invert Match (show non-matching lines)

// grep -v "pattern" file.txt
// Show lines that DON'T contain pattern

const allLines = fileContent.split('\n');
const results = await ripgrep.search('pattern', files);
const matchingLines = new Set(results.matches.map(m => m.lineNumber));

const nonMatchingLines = allLines
  .map((line, index) => ({ line, lineNumber: index + 1 }))
  .filter(item => !matchingLines.has(item.lineNumber));

console.log('Lines without pattern:');
nonMatchingLines.forEach(item => {
  console.log(`${item.lineNumber}: ${item.line}`);
});

Max Count (limit number of matches)

// grep -m 5 "pattern" file.txt
// Show only first 5 matches

const results = await ripgrep.search('pattern', files);
const limitedMatches = results.matches.slice(0, 5);

console.log('First 5 matches:', limitedMatches);

Count Only (show match count)

// grep -c "pattern" file.txt
// Show count of matches per file

const results = await ripgrep.search('pattern', files);

const countsPerFile = results.matches.reduce((acc, match) => {
  acc[match.path] = (acc[match.path] || 0) + 1;
  return acc;
}, {} as Record<string, number>);

Object.entries(countsPerFile).forEach(([path, count]) => {
  console.log(`${path}: ${count}`);
});

Context Lines (show surrounding lines)

// grep -C 2 "pattern" file.txt
// Show 2 lines before and after each match

const fileLines = files[0].content.split('\n');
const results = await ripgrep.search('pattern', files);

results.matches.forEach(match => {
  const lineIndex = match.lineNumber - 1;
  const contextBefore = 2;
  const contextAfter = 2;
  
  const start = Math.max(0, lineIndex - contextBefore);
  const end = Math.min(fileLines.length, lineIndex + contextAfter + 1);
  
  console.log(`\n--- ${match.path}:${match.lineNumber} ---`);
  for (let i = start; i < end; i++) {
    const prefix = i === lineIndex ? '>' : ' ';
    console.log(`${prefix} ${i + 1}: ${fileLines[i]}`);
  }
});

Only Matching Part (extract matched text)

// grep -o "pattern" file.txt
// Show only the matched text, not the full line

const results = await ripgrep.search('\\b\\w+Error\\b', files);

results.matches.forEach(match => {
  // Extract the actual matched text using regex
  const regex = /\b\w+Error\b/g;
  const matches = match.line.match(regex);
  
  if (matches) {
    matches.forEach(text => {
      console.log(`${match.path}:${match.lineNumber}: ${text}`);
    });
  }
});

🎯 Common Use Cases

1. Find All TODOs

import { searchInDirectory } from 'ripgrep-wasm/node';

const results = await searchInDirectory(
  './src',
  'TODO|FIXME|XXX',  // Regex pattern
  {
    fileTypes: ['*.ts', '*.js'],
    ignorePatterns: ['*.test.ts']
  }
);

// Group by file
const byFile = new Map();
results.matches.forEach(match => {
  if (!byFile.has(match.path)) {
    byFile.set(match.path, []);
  }
  byFile.get(match.path).push(match);
});

byFile.forEach((matches, file) => {
  console.log(`\n${file} (${matches.length} items):`);
  matches.forEach(m => console.log(`  Line ${m.lineNumber}: ${m.line.trim()}`));
});

2. Search in Web-Based IDE

import { ripgrep } from 'grep-wasm';

async function searchInProject(pattern: string, options?: SearchOptions) {
  // Get files from virtual file system (WebContainer, browser File API, etc.)
  const files = await readFilesFromVirtualFS();
  
  // Filter files
  const filtered = await ripgrep.filterDirectoryFiles({
    rootPath: '/project',
    fileTypes: ['*.js', '*.ts'],
    ignorePatterns: ['node_modules', '.git']
  }, files.map(f => f.path));
  
  const filteredPaths = new Set(filtered.map(f => f.path));
  const filesToSearch = files.filter(f => filteredPaths.has(f.path));
  
  // Search
  return ripgrep.search(pattern, filesToSearch, options);
}

3. Extract File Paths from Results

// Get unique file paths containing matches
const results = await search('pattern', files);
const filePaths = [...new Set(results.matches.map(m => m.path))];

console.log(`Found matches in ${filePaths.length} files`);

🛡️ Error Handling

grep-wasm provides structured error handling with detailed error information.

import { ripgrep, RipgrepException } from 'grep-wasm';

try {
  const results = await ripgrep.search('[invalid(regex', files);
} catch (error) {
  if (error instanceof RipgrepException) {
    console.error('Error type:', error.error.type);
    console.error('Message:', error.message);
    
    // Check specific error types
    if (error.isPatternError()) {
      console.error('Invalid regex pattern');
    } else if (error.isParseError()) {
      console.error('Failed to parse input');
    } else if (error.isSearchError()) {
      console.error('Search operation failed');
    }
    
    // Get error details
    const details = error.getDetails();
    console.error('Details:', details);
  }
}

Error Types:

  • ParseError: Failed to parse JSON input
  • InvalidPattern: Invalid regex pattern
  • SearchError: Search operation failed
  • InvalidConfiguration: Invalid configuration
  • FileError: File operation error
  • MemoryError: Memory-related error
  • SerializationError: Failed to serialize results

⚙️ Building from Source

Prerequisites

  1. Install Rust and Cargo: https://rust-lang.org
  2. Install wasm-pack:
    cargo install wasm-pack

Build Commands

# Build WASM module
npm run build:wasm

# Build TypeScript wrapper
npm run build:ts

# Build everything
npm run build

# Clean build artifacts
npm run clean

📊 Performance Tips

1. Use File Filtering

Pre-filter files to reduce the search space:

const filtered = await ripgrep.filterDirectoryFiles(config, allPaths);
// Then search only filtered files

2. Batch Processing

For large projects, process files in batches:

async function searchLargeProject(pattern: string, allFiles: FileEntry[]) {
  const batchSize = 100;
  const allMatches = [];
  
  for (let i = 0; i < allFiles.length; i += batchSize) {
    const batch = allFiles.slice(i, i + batchSize);
    const results = await ripgrep.search(pattern, batch);
    allMatches.push(...results.matches);
  }
  
  return allMatches;
}

3. Optimize Search Options

  • Fixed strings are faster than regex:

    { fixedStrings: true }
  • Case-sensitive is faster than case-insensitive:

    { caseInsensitive: false }  // Default

4. Pre-initialize WASM

For better UX, initialize early:

await ripgrep.init();  // Do this once at app startup

🌐 Browser vs Node.js

Browser

  • WASM module loads asynchronously
  • No direct file system access - you must provide file contents
  • Works great in web workers for non-blocking search

Vite config:

// vite.config.js
export default {
  optimizeDeps: {
    exclude: ['grep-wasm']
  }
}

Node.js

  • Works out of the box with ESM (recommended) or CommonJS
  • Use grep-wasm/node for directory-based search
  • Direct file system access via helper functions

📂 Architecture

Why No Direct Directory Search in Core SDK?

The core SDK (grep-wasm) does not directly access the file system because:

  • 🌐 Browsers: WASM cannot access local file system (security)
  • 📦 WebContainer: Requires virtual FS APIs
  • 🟢 Node.js: Needs fs module integration

Layered Design

┌─────────────────────────────────────┐
│   Application Layer                 │
│   - Browser: File API               │
│   - Node.js: fs module              │
│   - WebContainer: Virtual FS        │
└─────────────────────────────────────┘
           ↓ FileEntry[]
┌─────────────────────────────────────┐
│   ripgrep-wasm Core SDK             │
│   - Pattern matching                │
│   - Search logic                    │
│   - Result processing               │
└─────────────────────────────────────┘

Node.js Integration

For Node.js, we provide grep-wasm/node which automatically handles file system operations:

// Node.js - Automatic file reading
import { searchInDirectory } from 'grep-wasm/node';
const results = await searchInDirectory('./src', 'pattern');

// Browser/WebContainer - Manual file reading
import { search } from 'grep-wasm';
const files = await readFilesManually();  // Your implementation
const results = await search('pattern', files);

🔧 TypeScript Support

Full TypeScript support with comprehensive type definitions.

import { ripgrep, SearchResult, FileEntry, SearchOptions } from 'grep-wasm';

const files: FileEntry[] = [
  { path: 'test.js', content: 'function test() {}' }
];

const options: SearchOptions = {
  caseInsensitive: true,
  wordBoundary: false
};

const results: SearchResult = await ripgrep.search('test', files, options);

// Type-safe access
results.matches.forEach(match => {
  console.log(match.lineNumber);  // number
  console.log(match.line);        // string
});

❓ FAQ

Q: How large is the WASM module?

The compressed WASM module is approximately 500KB gzipped. Actual size depends on build optimizations.

Q: Can I use this in a web worker?

Yes! The module works perfectly in web workers for non-blocking search:

// In worker.js
import { ripgrep } from 'grep-wasm';

self.onmessage = async (e) => {
  const { pattern, files } = e.data;
  const results = await ripgrep.search(pattern, files);
  self.postMessage(results);
};

Q: Does it support streaming large files?

Currently, files must be loaded into memory. For very large files, consider splitting them or processing in chunks.

Q: How does it compare to native ripgrep?

This WASM version provides a subset of ripgrep's functionality optimized for browser/JS environments. It's faster than pure JavaScript implementations but slower than native ripgrep.

Q: Can I use it without TypeScript?

Yes! The package works with plain JavaScript:

const { ripgrep } = require('grep-wasm');

const results = await ripgrep.search('pattern', files);
console.log(results.totalMatches);

🎓 Examples

Basic Search

import { search } from 'grep-wasm';

// Simple search
const results = await search('hello', files);

// Case-insensitive
const results = await search('hello', files, { caseInsensitive: true });

// Whole word
const results = await search('main', files, { wordBoundary: true });

Advanced Filtering

import { filterDirectoryFiles } from 'grep-wasm';

const filtered = await filterDirectoryFiles({
  rootPath: '/project',
  fileTypes: ['*.js', '*.ts'],
  ignorePatterns: ['node_modules', 'dist'],
  maxDepth: 5,
  gitignoreFiles: [
    { path: '/project', content: 'node_modules/\n*.log\n' }
  ]
}, allFilePaths);

Node.js Directory Search

import { searchInDirectory, readDirectoryFiles } from 'grep-wasm/node';

// Automatic search
const results = await searchInDirectory('./src', 'pattern', {
  fileTypes: ['*.js'],
  caseInsensitive: true
});

// Manual control
const files = await readDirectoryFiles('./src', {
  fileTypes: ['*.js'],
  maxDepth: 3
});
const results = await search('pattern', files);

🌟 Comparison

| Feature | grep-wasm | grep.js | Native ripgrep | |---------|--------------|---------|----------------| | Performance | ⚡ Fast | 🐌 Slow | 🚀 Fastest | | TypeScript | ✅ Native | ❌ None | ❌ N/A | | Browser Support | ✅ Yes | ✅ Yes | ❌ No | | .gitignore Support | ✅ Yes | ❌ No | ✅ Yes | | File Filtering | ✅ Advanced | ⚠️ Basic | ✅ Advanced | | Error Handling | ✅ Structured | ❌ Basic | ⚠️ CLI only | | Bundle Size | 📦 ~500KB | 📦 ~50KB | 📦 N/A |


📄 License

This project is dual-licensed under MIT OR Unlicense. Choose whichever you prefer.

💐 Credits

🔗 Related Projects


Made with ❤️ for the web development community