spellchecker-wasm
v0.4.0
Published
An extremely fast multi-thread capable Spellchecker written in Rust compiled to WebAssembly for Node.
Maintainers
Readme
Spellchecker + WebAssembly
When you absolutely, positively have to have the fastest spellchecker in the room, accept no substitutes.
- Fast - Based on SymSpell v6.7.3 with bigram support.
- Plug and play - Ready to go out of the box (batteries included).
Spellcheck-wasm is an extremely fast spellchecker for WebAssembly complete with tooling for leveraging Worker threads to guarantee lightning fast processing of a single word or very large documents without the use of native Node plugins. Sub-millisecond benchmarks bring near native speeds to spellchecking in Node.
Spellcheck-wasm uses a zero dependency Rust port of the extremely popular SymSpell engine with several optimizations for WebAssembly.
| | | |---------------|-----------:| | Electron | ✓ | | Node | ✓ | | Browsers | ✓ | | Workers | ✓ | | Cli | ✓ |
Installation
npm i -s spellchecker-wasmAs an interactive CLI
npm i -g spellchecker-wasmThen use spellcheck to enter interactive mode. For supported arguments, run spellcheck --help.
Usage in Electron
// Within the preload script of your BrowserWindow instance
const { webFrame } = require('electron');
const { dirname, join } = require('node:path');
(async () => {
const { SpellcheckerWasm } = await import('spellchecker-wasm');
const libDir = join(dirname(require.resolve('spellchecker-wasm')), '..');
const spellchecker = new SpellcheckerWasm();
await spellchecker.prepareSpellchecker(
join(libDir, 'spellchecker-wasm.wasm'),
join(libDir, 'frequency_dictionary_en_82_765.txt')
);
webFrame.setSpellCheckProvider('en-US', {
spellCheck(words, callback) {
const misspelledWords = words.filter(word => spellchecker.suggest(word).length > 0);
callback(misspelledWords);
}
});
})();Multilingual dictionaries
Spellcheck-wasm ships with frequency dictionaries for six languages:
| Language | Dictionary file | Entries |
|----------|-----------------|---------|
| German | frequency_dictionary_de.txt | 100,000 |
| French | frequency_dictionary_fr.txt | 100,000 |
| Italian | frequency_dictionary_it.txt | 100,000 |
| Spanish | frequency_dictionary_es.txt | 100,000 |
| Hebrew | frequency_dictionary_he.txt | 100,000 |
| Russian | frequency_dictionary_ru.txt | 100,000 |
All dictionaries are UTF-8 encoded and sourced from the SymSpell Frequency Dictionary project (MIT licensed, master branch as of Oct 2026). They are loaded the same way as the English dictionaries:
const deDictionary = join(libDir, 'frequency_dictionary_de.txt');
await spellchecker.prepareSpellchecker(wasmPath, deDictionary);
const suggestions = spellchecker.suggest('Spellepruefung'); // German spelling correctionUsage in Node
import { SpellcheckerWasm } from 'spellchecker-wasm';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
const libDir = join(dirname(fileURLToPath(import.meta.resolve('spellchecker-wasm'))), '..');
const wasmPath = join(libDir, 'spellchecker-wasm.wasm');
const dictionaryLocation = join(libDir, 'frequency_dictionary_en_82_765.txt');
// Optional bigram support for compound lookups - add only when needed
const bigramLocation = join(libDir, 'frequency_bigramdictionary_en_243_342.txt');
const spellchecker = new SpellcheckerWasm();
await spellchecker.prepareSpellchecker(wasmPath, dictionaryLocation, bigramLocation);
for (const word of ['tiss', 'gves', 'practiclly']) {
console.log(word, spellchecker.suggest(word).map(item => item.term));
}
console.log(spellchecker.suggestCompound('tiss cheks th entir sentance'));Preparation is asynchronous. Once it completes, suggest and suggestCompound
return results synchronously for the supplied input. The existing
checkSpelling and checkSpellingCompound methods still call an optional
resultHandler synchronously and return void.
Usage as a Node Worker
// spellchecker-worker.mjs
import { parentPort } from 'node:worker_threads';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { SpellcheckerWasm } from 'spellchecker-wasm';
const libDir = join(dirname(fileURLToPath(import.meta.resolve('spellchecker-wasm'))), '..');
const spellchecker = new SpellcheckerWasm();
await spellchecker.prepareSpellchecker(
join(libDir, 'spellchecker-wasm.wasm'),
join(libDir, 'frequency_dictionary_en_82_765.txt')
);
parentPort.on('message', word => {
parentPort.postMessage(spellchecker.suggest(word).map(item => item.toJSON()));
});Usage in the Browser
// Serve the package's lib directory at /spellchecker-wasm/lib/.
import { SpellcheckerWasm } from '/spellchecker-wasm/lib/esm/index.js';
async function initializeSpellchecker() {
const wasm = await fetch('/spellchecker-wasm/lib/spellchecker-wasm.wasm');
const dictionary = await fetch('/spellchecker-wasm/lib/frequency_dictionary_en_82_765.txt');
const bigramLocation = await fetch('/spellchecker-wasm/lib/frequency_bigramdictionary_en_243_342.txt'); // Optional
const spellchecker = new SpellcheckerWasm();
await spellchecker.prepareSpellchecker(wasm, dictionary, bigramLocation);
return spellchecker;
}
initializeSpellchecker().then(spellchecker => {
for (const word of ['tiss', 'gves', 'practiclly']) {
console.log(word, spellchecker.suggest(word).map(item => item.term));
}
console.log(spellchecker.suggestCompound('tiss cheks th entir sentance'));
});Common use cases
Differentiating between a correct word and a word with no suggestions
By default, the spellchecker will return no results for both 'there' and 'thereeeee'. The former is correct and so will not produce suggestions. The latter word is obviously a mistake, but its distance from any word in the dictionary is greater than the maxEditDistance.
To distinguish between the two, one can use the includeUnknown option :
let lastResults;
const resultsHandler = results => {
lastResults = results;
};
spellchecker.checkSpelling('there');
// lastResults.length === 0
spellchecker.checkSpelling('thereeeee');
// lastResults.length === 0
spellchecker.checkSpelling('thereeeee', {
includeUnknown: true,
maxEditDistance: 2,
verbosity: 2,
includeSelf: false
});
// lastResults.length === 1includeUnknown returns the input with count 0 and distance maxEditDistance + 1
only when the input is absent from the dictionary and no correction is found.
If the input is known, includeSelf: false suppresses that exact match even when
includeUnknown: true. With verbosity: 2 (All), other dictionary matches
within the edit distance may still be returned.
Allowing for deeper word searches
Given that the default maxEditDistance, which controls up to which edit distance words from the dictionary should be treated as suggestions, is 2, words such as cofvvvfee will not return suggestions.
This can be remedied as follows:
let lastResults;
const resultsHandler = results => {
lastResults = results;
};
spellchecker.checkSpelling('cofvvvfee');
// lastResults.length === 0
spellchecker.checkSpelling('cofvvvfee', {
includeUnknown: false,
maxEditDistance: 4,
verbosity: 1,
includeSelf: false
});
// lastResults.length === 1, lastResults[0] --> 'coffee'Caveat : the maxEditDistance parameter that is passed to checkSpelling must be less-than-or-equal to the dictionaryEditDistance parameter of prepareSpellchecker. E.g. :
Dictionary frequencies above 4,294,967,295 are capped at that value in results because counts use a 32-bit wire format.
// Default dictionaryEditDistance is 2.
let lastResults;
const spellchecker = new SpellcheckerWasm(results => { lastResults = results; });
await spellchecker.prepareSpellchecker(wasmPath, dictionaryLocation);
spellchecker.checkSpelling('cofvvvfee', {
includeUnknown: false,
maxEditDistance: 4,
verbosity: 1,
includeSelf: false
});
// Throws RangeError: maxEditDistance (4) must be less than or equal to dictionaryEditDistance (2)// Configure the dictionary to support a distance of 4.
let lastResults;
const spellchecker = new SpellcheckerWasm(results => { lastResults = results; });
await spellchecker.prepareSpellchecker(wasmPath, dictionaryLocation, undefined, {countThreshold: 2, dictionaryEditDistance: 4});
spellchecker.checkSpelling('cofvvvfee', {
includeUnknown: false,
maxEditDistance: 4,
verbosity: 1,
includeSelf: false
});
// lastResults.length === 1Controlling the amount and ordering of returned suggestions
The verbosity parameter to checkSpelling can be used to tweak the amount of suggestions returned. Its supported values are :
verbosity:
0: (top) returns only the suggestion with the highest term frequency of the suggestions of smallest edit distance found,
1: (closest) returns all suggestions of smallest edit distance found, suggestions ordered by term frequency,
2: (all) returns all suggestions within maxEditDistance, suggestions ordered by edit distance, then by term frequency,
Building from source
Prerequisites
This project requires rust v1.30+ since it contains the wasm32-unknown-unknown target out of the box.
Install rust:
curl https://sh.rustup.rs -sSf | shInstall the stable compiler and switch to it.
rustup install stable
rustup default stableInstall the wasm32-unknown-unknown target.
rustup target add wasm32-unknown-unknown --toolchain stableInstall node with npm then run the following command from the project root.
npm installInstall the wasm-bindgen-cli tool
cargo install wasm-bindgen-cliThe project can now be built using:
npm run buildThe artifacts from the build will be located in the /libs directory.
