Usage with file
createWorkerFromFile gives you a full worker module. Import whatever you need, share helpers, and keep your main thread code lean while still talking to the worker through the same execute / terminate API.
Use this approach when you want module imports, external dependencies, or a more complex worker setup than a single inline function can provide.
Works in both TypeScript and JavaScript
The worker file and the main thread entry can be .ts, .tsx, .js, .jsx, or .mjs files. lite-worker remains ESM-only, so use native import syntax across both files and bundlers such as Vite or Webpack will handle the rest without extra configuration.
TypeScript example
import { expose } from 'lite-worker';
import { v4 } from 'uuid'; //external dependency
type Item = { name: string };
type ItemWithId = Item & { id: string };
const addIds = (items: Item[]): ItemWithId[] => {
return items.map((item) => ({ ...item, id: v4() }));
};
expose(addIds);
import { createWorkerFromFile } from 'lite-worker';
type Item = { name: string };
type ItemWithId = Item & { id: string };
const items: Item[] = [{ name: 'Alpha' }, { name: 'Beta' }, { name: 'Gamma' }];
const worker = createWorkerFromFile<ItemWithId[]>(
() => new Worker(new URL('./worker.ts', import.meta.url), { type: 'module' })
);
const result = await worker.execute(items);
console.log(result); // [{ name: 'Alpha', id: '...' }, ...]
worker.terminate(); // cleanup when you're done
JavaScript example
import { expose } from 'lite-worker';
import { v4 } from 'uuid'; //external dependency
const addIds = (items) => {
return items.map((item) => ({ ...item, id: v4() }));
};
expose(addIds);
import { createWorkerFromFile } from 'lite-worker';
const items = [{ name: 'Alpha' }, { name: 'Beta' }, { name: 'Gamma' }];
const worker = createWorkerFromFile(
() => new Worker(new URL('./worker.js', import.meta.url), { type: 'module' })
);
const result = await worker.execute(items);
console.log(result); // [{ name: 'Alpha', id: '...' }, ...]
Handling errors
If your worker throw an error or a rejects promise, it will bubbles back to the main thread.
import { expose } from 'lite-worker';
const fetchData = async () => {
const response = await fetch('https://invalidurl...'); // wrong url
const result = await response.json();
return result;
};
expose(fetchData);
import { createWorkerFromFile } from 'lite-worker';
const worker = createWorkerFromFile(
() => new Worker(new URL('./worker.ts', import.meta.url), { type: 'module' })
);
try {
const result = await worker.execute();
console.log(result);
} catch (e) {
console.log(e); // TypeError: Failed to fetch
}
Multiple calls
When you use Web Workers directly(worker.postMessage...) sometimes responses can come back out of order if asynchronous work finishes at different times.
lite-worker keeps each request paired with its result, so you can make concurrent calls and safely await them.
import { expose } from 'lite-worker';
const heavyTask = async (input: number) => {
await new Promise((resolve) => setTimeout(resolve, 500));
return input * input;
};
expose(heavyTask);
import { createWorkerFromFile } from 'lite-worker';
const worker = createWorkerFromFile(
() => new Worker(new URL('./worker.ts', import.meta.url), { type: 'module' })
);
const result1 = worker.execute(10);
const result2 = worker.execute(20);
const result3 = worker.execute(30);
const allResults = await Promise.all([result1, result2, result3]);
console.log(allResults); //[100, 400, 900]
Tips
- Keep everything the worker needs inside the worker module—imports, helper functions, and configuration.
- Use
exposeto register a single entry point that returns serializable data back to the main thread. - When the worker is finished, always call
terminate()to release browser resources. - Web Workers accept arguments that can be structured-cloned, so stick to plain objects, arrays, numbers, strings, and other serializable data.