Skip to content

Commit

Permalink
feat: add createWorker function for non-React usage and TanStack Quer…
Browse files Browse the repository at this point in the history
…y support
  • Loading branch information
ethanpv34 committed Jan 23, 2025
1 parent 641e0d6 commit 0c224f3
Show file tree
Hide file tree
Showing 7 changed files with 138 additions and 12 deletions.
52 changes: 52 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,58 @@ function MyComponent() {
}
```

## Non-React Usage

You can also use the worker functionality without React using the `createWorker` function:

```typescript
import { createWorker } from 'ez-web-worker';

// Create a worker instance
const worker = createWorker((input: number) => {
// This code runs in a Web Worker
let total = 0;
for (let i = 0; i < 1e8; i++) {
total += (i * input) % 1234567;
}
return total;
});

// Run the worker
worker.run(42)
.then(result => console.log('Result:', result))
.catch(error => console.error('Error:', error));

// Clean up when done
worker.terminate();
```

### Usage with TanStack Query

```typescript
import { createWorker } from 'ez-web-worker';
import { useQuery } from '@tanstack/react-query';

const computationWorker = createWorker((input: number) => {
// Your heavy computation here
return input * 2;
});

function MyComponent() {
const { data, isLoading } = useQuery({
queryKey: ['computation', 42],
queryFn: () => computationWorker.run(42)
});

// Don't forget to clean up
useEffect(() => {
return () => computationWorker.terminate();
}, []);

return isLoading ? <div>Loading...</div> : <div>Result: {data}</div>;
}
```
## API Reference
```typescript
Expand Down
9 changes: 6 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ez-web-worker",
"version": "1.1.0",
"version": "1.2.0",
"description": "React hook for easy Web Worker integration",
"main": "dist/index.js",
"module": "dist/index.esm.js",
Expand All @@ -9,8 +9,11 @@
"dist"
],
"scripts": {
"build": "rollup -c",
"dev": "rollup -c -w",
"build": "npm run build:clean && npm run build:esm && npm run build:cjs",
"build:clean": "rimraf dist",
"build:esm": "tsc -p tsconfig.json --outDir dist/esm --module ES2015",
"build:cjs": "tsc -p tsconfig.json --outDir dist/cjs --module CommonJS",
"dev": "tsc -p tsconfig.json --watch",
"test": "vitest",
"clean": "rimraf dist",
"prepare": "npm run clean && npm run build"
Expand Down
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
export { useWorker } from './useWorker';
export { createWorker } from './worker';
export type { WorkerFunction } from './worker';
export type {
WorkerStatus,
WorkerState,
WorkerOptions,
WorkerFunction,
UseWorkerReturn,
} from './types';
13 changes: 9 additions & 4 deletions src/useWorker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,32 +20,36 @@ export function useWorker<TArgs, TResult>(

const brokerRef = useRef<WorkerBroker<TArgs, TResult> | null>(null);

// Initialize the worker broker
useEffect(() => {
console.log('Creating new worker broker');
brokerRef.current = new WorkerBroker<TArgs, TResult>(workerFn);

return () => {
console.log('Cleaning up worker broker');
brokerRef.current?.terminate();
};
}, [workerFn]);

const run = useCallback((args: TArgs) => {
console.log('Running worker with args:', args);
if (!brokerRef.current) return;

setState(prev => ({ ...prev, status: 'running', error: null }));

const worker = brokerRef.current.createWorker();
const worker = brokerRef.current.getWorker();

worker.onmessage = (e: MessageEvent<WorkerMessage<TResult>>) => {
console.log('Received message from worker:', e.data);
const { type, data, error } = e.data;

if (type === 'result') {
console.log('Setting done state with result:', data);
setState({
status: 'done',
result: data as TResult,
error: null,
});
} else if (type === 'error') {
console.log('Setting error state:', error);
setState({
status: 'error',
result: null,
Expand All @@ -55,6 +59,7 @@ export function useWorker<TArgs, TResult>(
};

worker.onerror = (error) => {
console.log('Worker error:', error);
setState({
status: 'error',
result: null,
Expand All @@ -77,7 +82,7 @@ export function useWorker<TArgs, TResult>(
// Handle Suspense integration
if (options.suspense && state.status === 'running') {
throw new Promise((resolve) => {
const worker = brokerRef.current?.createWorker();
const worker = brokerRef.current?.getWorker();
if (worker) {
worker.onmessage = resolve;
}
Expand Down
50 changes: 50 additions & 0 deletions src/worker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
export type WorkerFunction<TArgs, TResult> = (args: TArgs) => TResult | Promise<TResult>;

export function createWorker<TArgs, TResult>(
fn: WorkerFunction<TArgs, TResult>,
options: { transferable?: boolean } = {}
) {
const fnString = fn.toString();
const workerCode = `
self.onmessage = async (e) => {
try {
const fn = ${fnString};
const result = await fn(e.data);
self.postMessage({ type: 'result', data: result });
} catch (error) {
self.postMessage({ type: 'error', error: error.message });
}
};
`;

const blob = new Blob([workerCode], { type: 'application/javascript' });
const url = URL.createObjectURL(blob);
const worker = new Worker(url);
URL.revokeObjectURL(url);

return {
run: (args: TArgs, transferables?: Transferable[]): Promise<TResult> => {
return new Promise((resolve, reject) => {
worker.onmessage = (e) => {
const { type, data, error } = e.data;
if (type === 'result') {
resolve(data as TResult);
} else if (type === 'error') {
reject(new Error(error));
}
};

worker.onerror = (error) => {
reject(new Error(error.message));
};

if (options.transferable && transferables) {
worker.postMessage(args, transferables);
} else {
worker.postMessage(args);
}
});
},
terminate: () => worker.terminate()
};
}
20 changes: 17 additions & 3 deletions src/workerBroker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,42 @@ export class WorkerBroker<TArgs, TResult> {
private workerCode: string;

constructor(fn: WorkerFunction<TArgs, TResult>) {
// Convert the function to a string while preserving its name
const fnString = fn.toString();
console.log('Creating worker with function:', fnString);

this.workerCode = `
self.onmessage = async (e) => {
try {
console.log('Worker received message:', e.data);
const fn = ${fnString};
const result = await fn(e.data);
console.log('Worker computed result:', result);
self.postMessage({ type: 'result', data: result });
} catch (error) {
console.log('Worker error:', error);
self.postMessage({ type: 'error', error: error.message });
}
};
`;
}

createWorker(): Worker {
console.log('Creating new worker');
const blob = new Blob([this.workerCode], { type: 'application/javascript' });
const url = URL.createObjectURL(blob);
this.worker = new Worker(url);
URL.revokeObjectURL(url); // Clean up the URL
URL.revokeObjectURL(url);
return this.worker;
}

run(args: TArgs, transferables?: Transferable[]): void {
console.log('Running worker with args:', args);
if (!this.worker) {
this.worker = this.createWorker();
}

if (transferables) {
if (transferables && transferables.length > 0) {
console.log('Transferring with transferables:', transferables);
this.worker.postMessage(args, transferables);
} else {
this.worker.postMessage(args);
Expand All @@ -43,8 +49,16 @@ export class WorkerBroker<TArgs, TResult> {

terminate(): void {
if (this.worker) {
console.log('Terminating worker');
this.worker.terminate();
this.worker = null;
}
}

getWorker(): Worker {
if (!this.worker) {
this.worker = this.createWorker();
}
return this.worker;
}
}
3 changes: 2 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
"forceConsistentCasingInFileNames": true,
"declarationMap": true
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
Expand Down

0 comments on commit 0c224f3

Please sign in to comment.