Skip to main content

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

worker.ts
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);
main.ts
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

worker.js
import { expose } from 'lite-worker';
import { v4 } from 'uuid'; //external dependency

const addIds = (items) => {
return items.map((item) => ({ ...item, id: v4() }));
};

expose(addIds);
main.js
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.

worker.ts
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);
main.ts
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.

worker.ts
import { expose } from 'lite-worker';

const heavyTask = async (input: number) => {
await new Promise((resolve) => setTimeout(resolve, 500));
return input * input;
};

expose(heavyTask);

main.ts
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 expose to 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.