js-search
Lightweight, dependency-free client-side search for JavaScript and JSON objects
Repository Health
Technical Analysis
js-search is a small JavaScript library that lets you search arbitrary JavaScript and JSON objects entirely in the browser, without a server round-trip. It began as a leaner, faster reimplementation of Lunr.js and has grown to support configurable tokenization, stemming, stop-word filtering, and TF-IDF relevance ranking, all while staying ES5-compatible and free of runtime dependencies like jQuery.
You configure a Search instance with the fields to index (including nested paths), add your documents, and query with plain text; results come back ranked by TF-IDF by default, or unranked via a lighter index if you don’t need scoring. Index strategy (prefix, all-substrings, or exact-word matching), tokenization, sanitization, and the search index implementation are all swappable, making it a flexible base for embedding search into static sites, admin dashboards, or any app that indexes a modest in-memory dataset of JSON records.
What You Get
- A
Searchclass you instantiate with a UID field, then populate viaaddIndex()(including nested field paths like['author', 'name']) andaddDocuments() - Three index strategies —
PrefixIndexStrategy(default),AllSubstringsIndexStrategy, andExactWordIndexStrategy— selectable viasearch.indexStrategy - Two search index implementations — the ranked
TfIdfSearchIndex(default) and the lighter, unrankedUnorderedSearchIndex— swappable viasearch.searchIndex - A tokenizer pipeline (
SimpleTokenizer,StemmingTokenizer,StopWordsTokenizer) plus sanitizers (LowerCaseSanitizer,CaseSensitiveSanitizer) and an editableStopWordsMap - Prebuilt UMD and ESM bundles under
dist/, plus a generated Flow type declaration, for zero-config consumption via npm or Bower
Common Use Cases
- Adding instant client-side search to a small-to-medium JSON dataset (product catalogs, book lists) with no backend search service
- Powering live-filtering search boxes over arrays of objects in dashboards or admin tools
- Indexing bundled JSON content in offline or static apps (static sites, Electron apps) with no search API to call
- Prototyping configurable search behavior (custom tokenizers, stemming, index strategy) before committing to a heavier engine
Under The Hood
Architecture
Search.js is a small façade over four pluggable interfaces — IIndexStrategy, ISanitizer, ISearchIndex, and ITokenizer — each swappable through setters that throw once the instance has begun indexing (an _initialized guard). addDocuments/addIndex funnel through a private indexDocuments_ that walks each document’s searchable fields (resolving nested paths via getNestedFieldValue), tokenizes and sanitizes each field’s text, expands each token per the configured index strategy (e.g. PrefixIndexStrategy emits incremental prefixes of a token), and forwards every expanded token to searchIndex.indexDocument(). TfIdfSearchIndex maintains a nested token map (token to document-occurrence counts and a per-uid map) with an IDF cache invalidated on every write, then search() intersects per-token uid sets across the query tokens and sorts the survivors by a closure-computed TF-IDF score. It’s a compact, cleanly separated strategy-pattern design for its size, though every strategy/tokenizer/index implementation has to match the façade’s exact hook signatures if that contract ever changes.
Tech Stack
Source is Flow-typed ES2015+ JavaScript (// @flow annotations throughout source/), transpiled with Babel (@babel/preset-env, @babel/preset-flow) and bundled by Rollup into both UMD (dist/umd) and ESM (dist/esm) outputs, plus a hand-generated Flow declaration file for the UMD build. There are no runtime dependencies at all — only devDependencies (Babel, Rollup plus rollup-plugin-terser, Jest with babel-jest, Flow, Prettier, rimraf). Tests run via flow check && jest, and the package is published to npm and Bower for import/require consumption.
Code Quality
Twelve *.test.js files pair roughly one-to-one with the library’s index-strategy, sanitizer, search-index, tokenizer, Search, and TokenHighlighter implementations, using descriptive Jest describe/it blocks with fixture documents and explicit expected-result assertions, including nested-field and array-field indexing edge cases. Flow supplies static type checking as part of the test script, but runtime error handling is minimal — getNestedFieldValue silently returns null on a missing path rather than raising, favoring graceful degradation over explicit exceptions. No CI workflow file is present in the repo, so test runs depend on local yarn test invocations rather than an automated gate. Naming is consistent (PascalCase classes/files, camelCase methods) throughout.
What Makes It Unique By its own README, js-search’s core pitch is being a leaner, faster-at-runtime reimplementation of Lunr.js, quantified against Lunr with published JSPerf benchmarks for index-build and search-execution time. It doesn’t introduce a novel ranking algorithm — TF-IDF plus prefix/substring token expansion are standard techniques — but the degree of pluggability (swappable index strategy, search index implementation, tokenizer, and sanitizer, each independently overridable before first indexing) is more configurable than most comparably small client-side search libraries offer.