This guide is for engineers integrating Tesseract.js at scale, operators running it in production (especially long-lived Node.js services), and contributors who need to understand the codebase deeply. It covers architecture, configuration, security, operations, the build/release pipeline, a module-by-module internals reference, and improvement opportunities.
For end-user task instructions (how to recognize an image, output formats, recipes), see the User Guide.
Note
This guide documents Tesseract.js v7.0.0 (package
tesseract.js, Apache-2.0). It reflects the source at commita1ca80d.
Table of Contents
- Executive Summary
- Architecture
- Repository Structure
- System Requirements
- Configuration Reference
- Module Reference
- Recognition Pipeline Internals
- Caching Subsystem
- Security Analysis
- Operations
- Build & Release Pipeline
- Testing Strategy
- Code Quality & Conventions
- Version Migration
- Potential Improvements
- Quick Reference
Executive Summary
Tesseract.js is a wrapper library, not an OCR engine. It brings the C++ Tesseract OCR engine to JavaScript by orchestrating a WebAssembly build of that engine (tesseract.js-core). The library's own code is small (~72 KB of source across ~52 code files); its job is transport, environment abstraction, resource management, and a clean async API — not recognition logic.
Key architectural facts an operator should internalize:
- Two-thread model. Every "worker" runs the heavy engine off the main thread — a Web Worker in browsers, a
worker_threadsWorkerin Node.js. All communication is message-passing. - Runtime asset loading. The WASM core and language
.traineddataare downloaded from a CDN (jsDelivr by default) at runtime, then cached (IndexedDB in browsers, filesystem in Node.js). Nothing is bundled. - Build selection by capability. At load time the worker detects SIMD / relaxed-SIMD support and whether the Legacy model is needed, then loads the matching core build for best performance.
- Memory is the operational risk. WASM memory only grows; workers accumulate dictionary state. Long-running services must recycle workers.
| Property | Value |
|---|---|
| Package | tesseract.js |
| Version | 7.0.0 |
| License | Apache-2.0 |
| Module type | CommonJS ("type": "commonjs") |
| Main entry | src/index.js |
| Type definitions | src/index.d.ts |
| CDN entry (UMD) | dist/tesseract.min.js |
| Runtime dependencies | 8 (see Dependencies) |
| Engine | tesseract.js-core (separate WASM project) |
Architecture
Tesseract.js cleanly separates a main-thread API layer from a worker-thread execution layer, with environment adapters that swap browser vs. Node.js implementations behind identical interfaces.
┌──────────────────────────── MAIN THREAD ────────────────────────────┐
│ src/index.js (public exports) │
│ ├── createWorker ── src/createWorker.js │
│ │ └── builds worker handle, manages promises, paths, OEM │
│ ├── createScheduler ─ src/createScheduler.js │
│ │ └── worker pool + job queue (round-robin to idle workers) │
│ ├── Tesseract.recognize/detect (deprecated one-shots) │
│ ├── OEM / PSM / languages constants │
│ └── createJob, utils/{getId,log,resolvePaths,getEnvironment} │
│ │
│ src/worker/{browser,node}/ ── environment ADAPTER (main side) │
│ spawnWorker · terminateWorker · send · onMessage · loadImage │
└───────────────────────────────┬──────────────────────────────────┘
│ postMessage(packet) / onmessage
▼
┌────────────────────────── WORKER THREAD ────────────────────────────┐
│ src/worker-script/index.js (action dispatcher) │
│ handlers: load · FS · loadLanguage · initialize · setParameters │
│ · recognize · detect · terminate │
│ setAdapter({ getCore, gunzip, fetch, readCache/writeCache/... }) │
│ │
│ src/worker-script/{browser,node}/ ── environment ADAPTER (worker) │
│ getCore (SIMD-aware WASM loader) · gunzip · cache (IndexedDB|fs) │
│ src/worker-script/utils/ setImage · dump · arrayBufferToBase64 │
│ │ │
│ ▼ │
│ tesseract.js-core (WebAssembly Tesseract engine) │
└──────────────────────────────────────────────────────────────────┘
Design patterns
Adapter pattern (the central idea). The same worker-script core (src/worker-script/index.js) runs in both browser and Node.js. It calls setAdapter(...) to receive environment-specific implementations of getCore, gunzip, fetch, and the cache methods. The browser adapter (src/worker-script/browser/) uses importScripts, zlibjs, and idb-keyval; the Node adapter (src/worker-script/node/) uses require, zlib, and fs. The recognition logic is written once.
Symmetric main-side adapter. Likewise, src/worker/browser/ and src/worker/node/ expose identical spawnWorker, terminateWorker, send, onMessage, loadImage modules. createWorker imports from ./worker/node and relies on the browser field in package.json to swap to the browser build at bundle time.
Job/promise correlation. Each call from the main thread creates a job (createJob) with an id and an action. createWorker stores {resolve, reject} keyed by `${action}-${jobId}` and resolves it when a matching message returns. This lets multiple in-flight jobs coexist over a single message channel.
Command dispatch. The worker script maps each packet.action to a handler function in a lookup object ({ load, FS, loadLanguage, initialize, setParameters, recognize, detect, terminate }[packet.action]).
Note
The
browserfield inpackage.json("./src/worker/node/index.js": "./src/worker/browser/index.js") is what makes a singlerequire('./worker/node')resolve to the browser implementation when bundled for the web. This is the seam that makes the dual-environment design work without conditionalrequires in the hot path.
Repository Structure
tesseract.js/
├── src/ Core library (the documented surface)
│ ├── index.js Public exports
│ ├── index.d.ts TypeScript definitions (full type surface)
│ ├── Tesseract.js Deprecated one-shot recognize()/detect()
│ ├── createWorker.js Main-thread worker factory & job manager
│ ├── createScheduler.js Worker pool + job queue
│ ├── createJob.js Job object factory
│ ├── constants/ OEM, PSM, imageType, defaultOptions, languages
│ ├── utils/ getEnvironment, getId, log, resolvePaths
│ ├── worker/ Main-side adapters
│ │ ├── browser/ spawnWorker (Blob URL), loadImage (DOM/Blob), ...
│ │ └── node/ spawnWorker (worker_threads), loadImage (fs/fetch)
│ └── worker-script/ Worker-side execution
│ ├── index.js Action dispatcher + all OCR handlers
│ ├── constants/ defaultOutput
│ ├── utils/ setImage, dump, arrayBufferToBase64
│ ├── browser/ getCore (importScripts), gunzip (zlibjs), cache (idb-keyval)
│ └── node/ getCore (require), gunzip (zlib), cache (fs)
├── examples/ Official runnable examples (browser + node)
├── benchmarks/ Speed & memory benchmarks (browser + node)
├── docs/ Authoritative user docs (api, faq, performance, ...)
├── scripts/ Dev server, webpack & rollup build configs
├── tests/ Mocha + Karma test suites + assets
├── package.json Metadata, scripts, deps, browser field
├── babel.config.json @babel/preset-env, "last 2 versions"
└── karma.conf.js Browser test runner (Chrome + Firefox headless)
System Requirements
| Layer | Requirement |
|---|---|
| Node.js (v7) | v16+ (v6 needs v14+) |
| Browser | JavaScript and WebAssembly support |
| CPU features | SIMD / relaxed-SIMD are auto-detected and used when present; a non-SIMD core is the fallback |
| Network (default) | Outbound HTTPS to jsDelivr for the core and to the tessdata CDN for language files |
| Memory | High — each worker holds a full WASM engine instance; budget hundreds of MB per active worker for large images |
| Storage | Cache for .traineddata (IndexedDB in browser, filesystem in Node.js) |
Important
React Native is not supported because it lacks WebAssembly. Other constrained runtimes (some serverless "edge worker" platforms, certain browser-extension manifest setups) may require explicit
workerPath/corePathconfiguration or may not work at all.
Configuration Reference
All configuration is supplied programmatically — there are no config files, environment variables, or .env settings in Tesseract.js. Configuration flows through three channels.
1. createWorker arguments
createWorker(langs = 'eng', oem = OEM.LSTM_ONLY, options = {}, config = {})
2. WorkerOptions (third argument)
| Option | Type | Default | Notes |
|---|---|---|---|
corePath |
string | jsDelivr tesseract.js-core@v<coreVersion> |
Must be a directory holding all four core builds |
langPath |
string | jsDelivr @tesseract.js-data/<lang>/4.0.0 (or …/4.0.0_best_int for LSTM-only) |
No trailing / |
workerPath |
string | jsDelivr tesseract.js@v<version>/dist/worker.min.js (browser); local path (node) |
Override for bundler/framework issues |
dataPath |
string | . |
Path inside the WASM MEMFS for traineddata; rarely changed |
cachePath |
string | . |
Node: cache folder; browser: IndexedDB key prefix |
cacheMethod |
string | write |
write / readOnly / refresh / none |
legacyCore |
boolean | false |
Download code supporting the Legacy model |
legacyLang |
boolean | false |
Download data supporting the Legacy model |
workerBlobURL |
boolean | true |
Wrap the worker script in a Blob URL (browser) |
gzip |
boolean | true |
Whether remote traineddata is gzipped |
logger |
function | () => {} |
Progress callback |
errorHandler |
function | — | Worker error callback |
Note
defaultOptions(src/constants/defaultOptions.js) sets onlyworkerBlobURL: trueand a no-oplogger. Environment-specificdefaultOptionsadd the appropriateworkerPath(node: a filesystem path underworker-script/node/index.js; browser: the CDNworker.min.jsfor the current version).
cacheMethod semantics (from source):
| Value | Reads cache | Writes cache | Cache-delete on init failure |
|---|---|---|---|
write (default) |
Yes | Yes | Yes |
readOnly |
Yes | No | No |
refresh |
No | Yes | Yes |
none |
No | No | No |
The read path is skipped when cacheMethod ∈ {refresh, none}; the write path runs when cacheMethod ∈ {write, refresh, undefined}. On initialization failure, cached data is deleted only when cacheMethod ∈ {write, refresh, undefined} (i.e. when we have write permission).
3. Tesseract engine parameters
Two sub-channels:
- Runtime parameters — via
worker.setParameters({...})or per-callrecognizeoptions. Applied withapi.SetVariable(key, value). Keys starting withtessjs_are filtered out (reserved for Tesseract.js internals). - Init-only parameters — via the
configargument ofcreateWorker/reinitialize. These map to Tesseract's[type]_INIT_MEMBERsettings and cannot be changed afterInit. Examples:load_system_dawg,load_freq_dawg,load_unambig_dawg,load_punc_dawg,load_number_dawg,load_bigram_dawg,tessedit_ocr_engine_mode,tessedit_init_config_only,language_model_ngram_on,language_model_use_sigmoidal_certainty.
Warning
Calling
setParameterswith an init-only key has no effect and logsAttempted to set parameters that can only be set during initialization: <names>. The engine silently ignores them — there is no exception, so watch the console during integration.
The config argument may be either a config-file string or an object. If an object, it is serialized to Tesseract config-file text (JSON.stringify then ,→newline, :→space, quotes/braces stripped) and written to /config in the WASM FS before Init.
Module Reference
Main thread
| Module | Exports | Responsibility |
|---|---|---|
src/index.js |
createWorker, createScheduler, recognize, detect, setLogging, OEM, PSM, languages |
Public API surface |
src/createWorker.js |
factory (langs, oem, options, config) => Promise<Worker> |
Spawn worker, resolve paths, manage per-job promises, drive the load → loadLanguage → initialize boot sequence, expose worker methods |
src/createScheduler.js |
factory () => Scheduler |
Maintain workers, runningWorkers, jobQueue; dequeue assigns the next queued job to an idle worker |
src/createJob.js |
factory ({id, action, payload}) => job |
Generate job ids (getId('Job', n)) and normalize job objects |
src/Tesseract.js |
recognize, detect |
Deprecated one-shots: create a worker, run one job, terminate in a finally |
Constants
| File | Contents |
|---|---|
src/constants/OEM.js |
TESSERACT_ONLY:0, LSTM_ONLY:1, TESSERACT_LSTM_COMBINED:2, DEFAULT:3 |
src/constants/PSM.js |
14 page-segmentation modes ('0'–'13') |
src/constants/imageType.js |
COLOR:0, GREY:1, BINARY:2 |
src/constants/defaultOptions.js |
workerBlobURL:true, no-op logger |
src/constants/languages.js |
~90 symbolic language-code constants |
src/worker-script/constants/defaultOutput.js |
Default output flags — only text:true; all others false |
Utilities
| File | Purpose |
|---|---|
src/utils/getEnvironment.js |
Detect runtime: webworker / browser / node (by globals WorkerGlobalScope, document, process) |
src/utils/getId.js |
`${prefix}-${cnt}-${random}` id generator |
src/utils/log.js |
Global logging flag, setLogging, gated log() |
src/utils/resolvePaths.js |
Resolve corePath/workerPath/langPath to absolute URLs in the browser (new URL(s, location.href)); identity in Node.js |
Main-side adapters (src/worker/{browser,node}/)
| Module | Browser | Node.js |
|---|---|---|
spawnWorker |
new Worker(URL.createObjectURL(blob)) wrapping importScripts, or direct new Worker(workerPath) |
new Worker(workerPath) from worker_threads |
terminateWorker |
worker.terminate() |
worker.terminate() |
send |
worker.postMessage(packet) |
worker.postMessage(packet) |
onMessage |
worker.onmessage = … |
worker.on('message', …) |
loadImage |
DOM-aware: URL/base64, File/Blob, img/video/canvas, OffscreenCanvas → Uint8Array |
fs/fetch/base64 → Uint8Array |
defaultOptions |
adds CDN workerPath for current version |
adds filesystem workerPath |
Worker-side execution
| Module | Purpose |
|---|---|
src/worker-script/index.js |
The engine driver. Implements load, FS, loadLanguage, initialize, setParameters, recognize, detect, terminate; dispatchHandlers routes packets; setAdapter injects env code |
src/worker-script/utils/setImage.js |
Writes the image into the WASM FS; converts BMP via bmp-js (Leptonica BMP gaps); reads EXIF orientation; api.SetImageFile(exif, angle) (throws Error attempting to read image. on failure) |
src/worker-script/utils/dump.js |
Builds the result JSON: pulls each requested output (GetUTF8Text, GetHOCRText, GetTSVText, …), renders PDF via TessPDFRenderer, extracts pre-processed images, maps PSM/OEM enums back to strings |
src/worker-script/utils/arrayBufferToBase64.js |
Pure-JS base64 encoder for image output |
src/worker-script/{browser,node}/getCore.js |
SIMD-aware loader that selects the correct tesseract.js-core build |
src/worker-script/{browser,node}/gunzip.js |
zlibjs.gunzipSync (browser) / zlib.gunzipSync (node) |
src/worker-script/{browser,node}/cache.js |
idb-keyval (browser) / fs (node) cache CRUD |
Recognition Pipeline Internals
What actually happens during worker.recognize(image, options, output):
- Image load (main thread).
loadImagenormalizes the input (URL, base64, Buffer, DOM element, File/Blob, local path) into aUint8Array, then sends arecognizejob to the worker. - Option splitting. In the worker, keys that are neither Tesseract.js options (
rectangle,pdfTitle,pdfTextOnly,rotateAuto,rotateRadians) nortessjs_*are treated as engine parameters and applied withSetVariable(afterSaveParameters, restored withRestoreParametersafterward). - Output planning.
processOutputmerges requested formats overdefaultOutputand counts how many require OCR. OutputsimageColor,imageGrey,imageBinary,layoutBlocks,debugdo not require recognition; if only those are requested,skipRecognitionis true and full OCR is bypassed. - Image placement & rotation. With
rotateAuto, the engine temporarily switches to an auto PSM, finds lines, computes the skew angle (GetGradientor legacyGetAngle), and re-sets the image at that angle (angles < 0.005 rad are ignored). OtherwiserotateRadians(default 0) is used. - Region selection. If
rectangleis given,api.SetRectangle(left, top, width, height). - Recognition.
api.Recognize(null)runs unless skipped; for layout-only requests,api.AnalyseLayout()runs instead. - Result dump.
dumpreads every requested format into a JSON object, appendsconfidence,psm,oem,version, androtateRadians, then the result is posted back and the matching promise resolves.
Note
Progress is reported throughout via
res.progress(...). The engine's internalTesseractProgress(percent)callback is rescaled to(percent - 30) / 70so the user-visible "recognizing text" progress runs 0→1 over the OCR phase.
Caching Subsystem
Language data is the largest runtime download (typically ~2 MB; worst case, e.g. Legacy + Chinese Simplified, ~20 MB), so caching is central to performance.
Flow (loadLanguage in src/worker-script/index.js):
- For each language, attempt a cache read of
<cachePath>/<lang>.traineddata(skipped ifcacheMethod ∈ {refresh, none}). - On a miss, fetch from
langPath(or the default CDN). In Node.js,langPathmay be a URL or a local path —is-urlplus scheme checks (file://,moz-extension://,chrome-extension://) decide which. URLs are fetched (.gzappended whengzip); local paths are read from disk. - Detect gzip via magic bytes (
0x1F 0x8B) and gunzip if needed. - Write the data into the WASM MEMFS (
<dataPath>/<lang>.traineddata). - Write back to the cache when
cacheMethod ∈ {write, refresh, undefined}.
Self-healing on init failure. If api.Init returns -1 and data came from cache, the worker assumes the cached data may be bad or lack the requested OEM. It deletes the cache entry; if the failure mentions "components are not present", it re-runs loadLanguage (forcing a fresh download) and retries Init once.
| Environment | Cache backend |
|---|---|
| Browser | IndexedDB via idb-keyval (cachePath becomes a key prefix) |
| Node.js | Filesystem via fs (cachePath is a folder; default is the current working directory) |
Tip
In Node.js, set
cachePathto a stable, writable directory (e.g. a mounted volume) so cached traineddata survives container restarts and isn't written to the process CWD unpredictably.
Security Analysis
Tesseract.js is a client/embedded library, so its threat surface differs from a server application. Findings below are based on the source.
Network & supply chain
- Default remote loading. By default, the WASM core and language data are loaded from the jsDelivr CDN over HTTPS at runtime. This is a runtime dependency on a third-party CDN and an implicit trust of its contents.
Important
For high-assurance or air-gapped deployments, self-host the core (
corePath), worker script (workerPath), and language data (langPath) from infrastructure you control, and pin versions. This removes the runtime CDN dependency and lets you verify artifacts. See Operations.
importScriptsof a remote URL (browser).getCorecallsglobal.importScripts(corePathImportFile)against the (possibly CDN) core URL — remote code execution by design. Whoever controlscorePathcontrols executed code. TreatcorePath/workerPathas security-sensitive configuration; never set them from untrusted user input.- Blob-URL worker (browser).
spawnWorkerbuilds a worker fromnew Blob(['importScripts("${workerPath}");']). Sites with a strict Content-Security-Policy must allowworker-src blob:(and the CDN inscript-src) or setworkerBlobURL: falseand serve a same-originworker.min.js.
Input handling
- Image bytes flow into a native (WASM) engine. Untrusted images are parsed by Leptonica/Tesseract inside WebAssembly. WASM provides memory-sandboxing (no host memory access), which substantially contains parser bugs — but denial-of-service via pathological images (memory growth, long runtimes) is still possible. Bound input sizes and apply timeouts in server contexts.
- BMP special-case.
setImageruns BMP files throughbmp-jsbefore handing them to Leptonica (which supports only some BMPs). This is extra attack surface in pure JS, though again sandbox-contained. - No code-injection from parameters. Engine parameters go through
api.SetVariable; theconfigobject is serialized to a Tesseract config file, not evaluated. Keys are filtered (tessjs_*removed). There is noevalof user data.
Filesystem (Node.js)
- The Node cache adapter reads/writes paths derived from
cachePath/langPath. If those are ever set from untrusted input, path traversal is conceivable — keep them operator-controlled.deleteCacheswallows errors by design (cache cleanup is best-effort).
Secrets
- The library handles no credentials, tokens, or secrets. There is no authentication, no outbound telemetry, and no secret material in the codebase.
Caution
The
postinstallscript runsopencollective-postinstall || true(a funding banner). It is harmless and non-fatal, but some environments dislike postinstall scripts. Issue #1030 tracks a request to remove it for Deno compatibility. Install with--ignore-scriptsif your policy forbids postinstall hooks.
Operations
Running in long-lived Node.js services
Warning
Recycle workers periodically. WebAssembly memory only ever grows, so a single large image permanently raises a worker's footprint. Additionally, workers "learn" by adding encountered words to an internal dictionary; over thousands of unrelated documents this bloats with typos. Terminate and recreate workers/schedulers on a cadence — e.g. every ~500 jobs.
Recommended production pattern
const { createScheduler, createWorker } = require('tesseract.js');
const POOL_SIZE = 4;
const MAX_JOBS = 500;
let scheduler;
let jobsSinceReset = 0;
async function buildScheduler() {
const s = createScheduler();
await Promise.all(Array.from({ length: POOL_SIZE }, async () => {
const w = await createWorker('eng', 1, { cachePath: '/var/cache/tessdata' });
s.addWorker(w);
}));
return s;
}
async function recognize(image) {
if (!scheduler || jobsSinceReset >= MAX_JOBS) {
const old = scheduler;
scheduler = await buildScheduler();
jobsSinceReset = 0;
if (old) await old.terminate(); // drain/replace
}
jobsSinceReset += 1;
const { data: { text } } = await scheduler.addJob('recognize', image);
return text;
}
Tip
Size
POOL_SIZEto available memory, not CPU count — each worker is memory-heavy. Start small (2–4) and load-test.
Operational checklist
- [ ] Pin
tesseract.jsandtesseract.js-coreto exact versions. - [ ] Self-host
corePath,workerPath,langPath(or accept the CDN dependency knowingly). - [ ] Set a stable, writable
cachePath(Node.js) on a persistent volume. - [ ] Bound image size and add per-job timeouts.
- [ ] Recycle workers/schedulers on a job-count or time cadence.
- [ ] Set
oem/psmexplicitly if you need parity with the Tesseract CLI. - [ ] For browsers, configure CSP for
blob:workers and the CDN, or serve a same-origin worker.
Observability
Enable progress/diagnostic logging with a logger callback or globally with setLogging(true):
const { setLogging } = require('tesseract.js');
setLogging(true); // verbose internal logs to console
There is no built-in metrics export; instrument around addJob/recognize (latency, success/empty/error counts) yourself.
Build & Release Pipeline
The dist/ artifacts are not committed; they are produced by the build. Source is authored as CommonJS and transpiled with Babel (@babel/preset-env, target "last 2 versions").
npm scripts (package.json)
| Script | Command | Purpose |
|---|---|---|
start |
node scripts/server.js |
Dev server on port 3000 (webpack-dev-middleware, writes to disk) |
build |
rimraf dist && webpack --config scripts/webpack.config.prod.js && rollup -c scripts/rollup.esm.mjs |
Build UMD (webpack) + ESM (rollup) bundles into dist/ |
profile:tesseract |
webpack-bundle-analyzer dist/tesseract-stats.json |
Inspect main bundle size |
profile:worker |
webpack-bundle-analyzer dist/worker-stats.json |
Inspect worker bundle size |
prepublishOnly |
npm run build |
Ensure a fresh build before publish |
wait |
rimraf dist && wait-on http://localhost:3000/dist/tesseract.min.js |
Wait for dev build before tests |
test |
npm-run-all -p -r start test:all |
Start server and run the full suite |
test:all |
npm-run-all wait test:browser test:node:all |
Browser + Node tests |
test:browser |
karma start karma.conf.js |
Headless Chrome + Firefox |
test:node / test:node:all |
nyc mocha … |
Node tests with coverage |
lint / lint:fix |
eslint src |
Airbnb-base lint |
Dev server (scripts/server.js)
An Express server that serves the webpack-compiled bundles and the repo statically on port 3000. It sets Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp — these COOP/COEP headers enable performance.measureUserAgentSpecificMemory() used by the benchmarks (and are required for SharedArrayBuffer-style isolation generally). The default dev page is examples/browser/basic-efficient.html.
Note
The COOP/COEP headers are needed only for the benchmark memory measurements; the docs note they can be removed if problematic in other contexts.
Contributor workflow
git clone https://github.com/naptha/tesseract.js.git
cd tesseract.js
npm install
npm start # dev server with live rebuild of tesseract.min.js / worker.min.js
Before opening a PR:
npm run lint
npm run test
Important
Always confirm the automated tests pass before submitting a pull request. The dev server rebuilds
tesseract.min.jsandworker.min.jsautomatically when files insrc/change.
Testing Strategy
| Layer | Tooling | Location |
|---|---|---|
| Browser | Karma + Mocha, headless Chrome & Firefox | karma.conf.js, tests/*.test.mjs |
| Node.js | Mocha + nyc (coverage), expect.js |
tests/*.test.mjs |
| Assertions | expect.js |
— |
Test suites cover the FS interface, OSD detection, recognition, and the scheduler (tests/FS.test.mjs, detect.test.mjs, recognize.test.mjs, scheduler.test.mjs). Test assets include sample images in many formats and .traineddata for eng, chi_tra, and osd. The Karma config serves the four SIMD/relaxed-SIMD core builds and proxies them so browser tests exercise the real WASM core.
Tip
Because browser tests need the built
dist/bundles, thetestscript orchestrates the dev server (start), waits for the bundle (wait), then runs browser and node suites in parallel vianpm-run-all.
Code Quality & Conventions
- Style: ESLint with
eslint-config-airbnb-base. Lint runs onsrconly. - Module format: CommonJS throughout
src/('use strict'headers,module.exports). Migration to ESM is a tracked aspiration (issue #890) but not done. - Environment abstraction: Strong, consistent adapter boundaries (
worker/andworker-script/each have parallelbrowser//node/trees with identical interfaces). This is the codebase's defining quality and makes it easy to reason about. - Small, single-purpose modules: Most files are tiny (the largest single source file is the worker-script dispatcher at ~19 KB). Utilities are one function each.
- Type coverage: A complete hand-written
index.d.tsdescribes the full public surface, including the deepPage/Block/Paragraph/Line/Word/Symbolresult tree. - Backward-compatibility care: The code carries explicit compatibility shims (e.g.
GetGradientvs. legacyGetAngle;TesseractCorevs. olderTesseractCoreWASM; toleratingcorePathpointing at a single.jsfile). Comments explain why, which aids maintenance.
Note
A reader auditing this codebase should start at
src/worker-script/index.js(all OCR behavior) andsrc/createWorker.js(all main-thread orchestration). Everything else is adapters, constants, and small utilities.
Version Migration
Highlights of recent breaking changes (full lists are in the linked upstream issues).
v6 (from v5)
- All output formats except
textare disabled by default. Re-enable explicitly, e.g.worker.recognize(image, {}, { hocr: true }). - Minor changes to the structure of the
blocksoutput. - Memory leak fixed; overall runtime and memory reductions.
- Tracking: issue #993.
v5 (from v2–v4)
createWorkerarguments changed — language and OEM are now set increateWorker(e.g.createWorker('chi_sim', 1)).worker.initializeandworker.loadLanguagewere removed — delete them from your code (workers come pre-loaded).- ~50%+ smaller default downloads and lower memory use.
- Tracking: issue #820; v2→v5 guide: issue #771.
v4
- Added rotation preprocessing (including auto-rotate); processed images retrievable; better scheduler/parallel support.
createWorkerbecame async;getPDFreplaced by thepdfrecognize option.
Important
The single most common migration break is the v6 output-format change. If a previously-working integration suddenly returns
nullforhocr/tsv/blocks, enable those formats in the third argument ofrecognize.
Potential Improvements
Observations from the source and the issue tracker, framed as actionable opportunities for maintainers/integrators.
- ESM migration (issue #890). The library is CommonJS with a separate rollup ESM bundle step. A native-ESM source tree would simplify the build and improve tree-shaking and modern-runtime support (Deno, Bun).
- Remove the
opencollective-postinstallhook (issue #1030). It complicates Deno installs and triggers postinstall-policy warnings; the funding banner could move elsewhere. - Non-standard runtime support. Tracked gaps include Bun + Next.js API routes (#1064), Cloudflare Workers (#950), Electron node-mode (#1043), and browser-extension MV3 (#961). Most stem from build systems relocating the worker script — a more robust worker-path resolution strategy would reduce the recurring "Cannot find module" class of issues (#868-style).
worker.spawnWorkererror propagation (issue #712). Errors thrown during worker spawn are hard to catch from the caller; surfacing them through the returned promise would improve DX.- Custom traineddata via non-URL paths / blob URLs (issues #998, #965). Loading custom language data from arbitrary locations has rough edges worth smoothing.
languagesexport on the default export (issue #887). A small consistency fix to the public surface.- Documented "fast" language data tradeoffs. The performance docs invite users to try
4.0.0_fastdata but note it is unbenchmarked; publishing quality/speed numbers would help operators make the call.
Note
None of these are correctness bugs in the wrapper's core path — recognition itself is delegated to the upstream engine. They are integration-surface and build-system refinements.
Quick Reference
Public API
createWorker(langs='eng', oem=1, options={}, config={}) -> Promise<Worker>
createScheduler() -> Scheduler
recognize(image, langs?, options?) // deprecated
detect(image, options?) // deprecated
setLogging(boolean)
OEM, PSM, languages
Worker
recognize(image, options?, output?, jobId?)
detect(image, jobId?) // needs legacyCore+legacyLang
setParameters(params, jobId?)
reinitialize(langs?, oem?, config?, jobId?)
writeText / readText / removeFile / FS
terminate(jobId?)
Scheduler
addWorker(worker) -> id
addJob('recognize'|'detect', ...payload) -> Promise
getQueueLen() -> number
getNumWorkers() -> number
terminate() -> Promise
Defaults
| Setting | Default |
|---|---|
langs |
'eng' |
oem |
1 (LSTM_ONLY) |
psm |
6 (SINGLE_BLOCK) |
| output | text only |
cacheMethod |
write |
gzip |
true |
workerBlobURL |
true |
Runtime dependencies
| Package | Role |
|---|---|
tesseract.js-core |
The WebAssembly Tesseract engine |
bmp-js |
Decode BMP images Leptonica can't |
idb-keyval |
Browser IndexedDB cache |
is-url |
Distinguish URL vs. local path (Node langPath) |
node-fetch |
fetch fallback on older Node |
wasm-feature-detect |
Detect SIMD / relaxed-SIMD for core selection |
zlibjs |
Browser gunzip of traineddata |
opencollective-postinstall |
Funding banner (postinstall) |
Does NOT exist / NOT supported
- ❌ PDF input (output PDF is supported)
- ❌ Handwriting recognition
- ❌ React Native (no WebAssembly)
- ❌ Config files / environment variables (config is programmatic only)
- ❌ Modifying the recognition model or engine accuracy (delegated to upstream Tesseract)
- ❌ Authentication, secrets, telemetry