easy-web-worker 7.0.5 → 8.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/EasyWebWorker.cjs +1 -0
  2. package/EasyWebWorker.d.ts +2 -2
  3. package/EasyWebWorker.js +1 -1
  4. package/EasyWebWorker.mjs +1 -0
  5. package/EasyWebWorkerMessage.cjs +1 -0
  6. package/EasyWebWorkerMessage.d.ts +2 -2
  7. package/EasyWebWorkerMessage.js +1 -1
  8. package/EasyWebWorkerMessage.mjs +1 -0
  9. package/README.md +891 -339
  10. package/StaticEasyWebWorker.cjs +1 -0
  11. package/StaticEasyWebWorker.d.ts +1 -1
  12. package/StaticEasyWebWorker.js +1 -1
  13. package/StaticEasyWebWorker.mjs +1 -0
  14. package/buildWorker.cjs +1 -0
  15. package/buildWorker.d.ts +7 -0
  16. package/buildWorker.js +1 -0
  17. package/buildWorker.mjs +1 -0
  18. package/bundle.cjs +1 -0
  19. package/bundle.js +1 -1
  20. package/bundle.mjs +1 -0
  21. package/createBlobWorker.cjs +2 -0
  22. package/createBlobWorker.d.ts +1 -0
  23. package/createBlobWorker.js +2 -1
  24. package/createBlobWorker.mjs +2 -0
  25. package/createEasyWebWorker.cjs +1 -0
  26. package/createEasyWebWorker.js +1 -1
  27. package/createEasyWebWorker.mjs +1 -0
  28. package/createStaticEasyWebWorker.cjs +1 -0
  29. package/createStaticEasyWebWorker.js +1 -1
  30. package/createStaticEasyWebWorker.mjs +1 -0
  31. package/createWorker.cjs +4 -0
  32. package/createWorker.d.ts +53 -0
  33. package/createWorker.js +4 -0
  34. package/createWorker.mjs +4 -0
  35. package/defineWorker.cjs +1 -0
  36. package/defineWorker.d.ts +31 -0
  37. package/defineWorker.js +1 -0
  38. package/defineWorker.mjs +1 -0
  39. package/getDefineWorkerTemplate.cjs +1 -0
  40. package/getDefineWorkerTemplate.d.ts +7 -0
  41. package/getDefineWorkerTemplate.js +1 -0
  42. package/getDefineWorkerTemplate.mjs +1 -0
  43. package/getWorkerTemplate.cjs +1 -0
  44. package/getWorkerTemplate.d.ts +3 -7
  45. package/getWorkerTemplate.js +1 -1
  46. package/getWorkerTemplate.mjs +1 -0
  47. package/index.d.ts +3 -0
  48. package/package.json +61 -68
  49. package/types.cjs +1 -0
  50. package/types.d.ts +115 -0
  51. package/types.js +1 -1
  52. package/types.mjs +0 -0
  53. package/uniqueId.cjs +1 -0
  54. package/uniqueId.js +1 -1
  55. package/uniqueId.mjs +1 -0
  56. package/unwrap.cjs +1 -0
  57. package/unwrap.d.ts +16 -0
  58. package/unwrap.js +1 -0
  59. package/unwrap.mjs +1 -0
  60. package/webpack.config.js +0 -86
package/README.md CHANGED
@@ -1,539 +1,1091 @@
1
1
  # easy-web-worker 🌟
2
2
 
3
- ![Image John Avatar](https://raw.githubusercontent.com/johnny-quesada-developer/global-hooks-example/main/public/avatar2.jpeg)
3
+ <div align="center">
4
4
 
5
- Hello and welcome to **easy-web-worker** with [easy-cancelable-promise](https://www.npmjs.com/package/easy-cancelable-promise) – your go-to solution for seamless **Web Workers** integration, now enhanced with cancelable promises! 🚀
5
+ ![Johnny Quesada](https://raw.githubusercontent.com/johnny-quesada-developer/global-hooks-example/main/public/avatar2.jpeg)
6
6
 
7
- [Web Workers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers) are a native tool provided by **JavaScript**, allowing you to use them within your favorite framework like **React**, **Angular**, **Vue**, and others, or with pure **JavaScript** and **TypeScript**.
7
+ </div>
8
8
 
9
- Check out the running example with **React** and **TypeScript** at [CODEPEN](https://codepen.io/johnnynabetes/full/wvOvygW); let's explore the capabilities of JavaScript's concurrent processing with Web Workers!"
9
+ <div align="center">
10
10
 
11
- [Important!] Starting from version 4.0.0, EasyWebWorker supports concurrency mode. This means you can now configure whether a single **EasyWebWorker** should use multiple Web Worker instances. This feature is extremely powerful for code that requires not only heavy computations occasionally but also robust concurrent processing. For more detailed information, please see the section below. [concurrency mode](#concurrency-mode)
11
+ ## **Web Workers are powerful. Now they're also easy.**
12
12
 
13
- #### IMPORTANT!
13
+ Let `easy-web-worker` handle everything between the threads.
14
14
 
15
- If you were previously using **easy-web-worker** with **cancelable-promise-jq**, please note that the **cancelable-promise-jq** package has been renamed/deprecated. To continue using the latest version of **easy-web-worker**, simply uninstall **cancelable-promise-jq** and replace all imports with **easy-cancelable-promise**.
15
+ You keep the power of native Web Workers without dealing with all the complexity that usually comes with them.
16
16
 
17
- I sincerely apologize for any inconvenience this may cause.
17
+ `easy-web-worker` handles the plumbing between both sides: message routing, request matching, promises, errors, cancellation, progress, transferable objects, lifecycle management, and worker pools.
18
18
 
19
- ### Creating a web worker never was easier!
19
+ [![npm version](https://img.shields.io/npm/v/easy-web-worker.svg)](https://www.npmjs.com/package/easy-web-worker)
20
+ [![Downloads](https://img.shields.io/npm/dm/easy-web-worker.svg)](https://www.npmjs.com/package/easy-web-worker)
21
+ [![License](https://img.shields.io/github/license/johnny-quesada-developer/easy-web-worker)](https://github.com/johnny-quesada-developer/easy-web-worker/blob/main/LICENSE)
20
22
 
21
- ```ts
22
- import createEasyWebWorker from 'easy-web-worker/createEasyWebWorker';
23
+ [**Website**](https://johnny-quesada-developer.github.io/easy-web-worker/) · [**Documentation**](https://johnny-quesada-developer.github.io/easy-web-worker/docs/) · [**Examples**](https://johnny-quesada-developer.github.io/easy-web-worker/examples/) · [**API reference**](https://johnny-quesada-developer.github.io/easy-web-worker/docs/api-reference/) · [**Video Tutorial**](https://www.youtube.com/watch?v=CK-Uri9lDOE)
23
24
 
24
- /**
25
- * The callback parameter will be the body of the worker
26
- */
27
- const worker = createEasyWebWorker(({ onMessage }) => {
28
- const fibonacci = (n) => {
29
- if (n <= 1) return n;
30
- return fibonacci(n - 1) + fibonacci(n - 2);
31
- };
25
+ Created by [Johnny Quesada](https://github.com/johnny-quesada-developer).
32
26
 
33
- /**
34
- * Inside the worker we have to define an action when onMessage
35
- */
36
- onMessage((message) => {
37
- /**
38
- * The payload includes whatever parameters are sent from the main thread
39
- */
40
- const { payload } = message;
41
- const result = fibonacci(payload.base);
42
-
43
- message.resolve(result);
44
- //message.reject(); // or reject
45
- });
46
- });
27
+ </div>
28
+
29
+ ---
30
+
31
+ ## Start with a single worker
32
+
33
+ **Imagine defining your worker like this:**
34
+
35
+ ```ts
36
+ export const worker = defineWorker(() => ({
37
+ fibonacci,
38
+ }));
39
+
40
+ function fibonacci(n: number): number {
41
+ return n <= 1 ? n : fibonacci(n - 1) + fibonacci(n - 2);
42
+ }
47
43
  ```
48
44
 
49
- Then, for sending a message to the worker:
45
+ **And calling it like this:**
50
46
 
51
47
  ```ts
48
+ import type { worker as MathWorker } from './math.worker';
49
+ import mathWorkerUrl from './math.worker?worker&url';
50
+
52
51
  /**
53
- * This returns a CancelablePromise
52
+ * The type of the worker is inferred from it's declaration,
53
+ * so everything is typed on the main thread.
54
54
  */
55
- await worker.send(40);
55
+ const mathWorker = createWorker<typeof MathWorker>(mathWorkerUrl);
56
+
57
+ const result = await mathWorker.fibonacci(40); // 102334155
56
58
  ```
57
59
 
58
- And that's it! You now have a worker running heavy computation in a real separate thread, with real asynchronous programming in JavaScript.
60
+ The computation runs inside a real native Web Worker. The page keeps scrolling, typing and animating while it runs.
61
+
62
+ No manual `postMessage`.
63
+ No message IDs.
64
+ No response matching.
65
+ No hand-written promise bridge.
66
+
67
+ Just functions.
68
+
69
+ > **[See it live →](https://johnny-quesada-developer.github.io/easy-web-worker/#live)** Same task, two ways to run it. On the main thread the page freezes. In a Worker it never stops. It runs in your browser, with real Workers.
70
+
71
+ ---
59
72
 
60
- You can also create an easy web worker from a static file, or from a native worker instance, here various examples:
73
+ ## Built for work that should not block the page
74
+
75
+ ### **Functions, not messages**
76
+
77
+ Every key you return from `defineWorker` becomes a typed method on the main thread.
61
78
 
62
79
  ```ts
63
- // on vite and typescript
64
- import workerUrl from './worker?worker&url';
80
+ // The worker does the heavy lifting
81
+ const worker = defineWorker(() => ({
82
+ countWords: (text: string) => text.trim().split(/\s+/).length,
83
+ }));
84
+ ```
65
85
 
66
- const isProduction = import.meta.env.MODE === 'production';
86
+ ```ts
87
+ // The main thread remains 100% responsive
88
+ await text.countWords(article); // CancelablePromise<number>
89
+ ```
67
90
 
68
- const workerSource = isProduction
69
- ? workerUrl
70
- : new URL('./worker.ts', import.meta.url);
91
+ [**How typed workers work →**](https://johnny-quesada-developer.github.io/easy-web-worker/docs/typed-workers/)
71
92
 
72
- const worker = new EasyWebWorker(workerSource, {
73
- workerOptions: {
74
- type: 'module',
75
- },
76
- });
93
+ ### **Return a value, get a promise**
94
+
95
+ A method resolves with what it returns and rejects with what it throws. `async` works the same way.
96
+
97
+ ```ts
98
+ const worker = defineWorker(() => ({
99
+ parseReport: async (url: string) => {
100
+ const response = await fetch(url);
101
+
102
+ if (!response.ok) throw new Error('Report not available');
77
103
 
78
- // or
79
- const worker = createEasyWebWorker(workerSource, {
80
- workerOptions: {
81
- type: 'module',
104
+ return buildReport(await response.text());
82
105
  },
83
- });
106
+ }));
107
+ ```
84
108
 
85
- // if you are using ESM - ECMAScript Modules
86
- const worker = createEasyWebWorker([
87
- new Worker(new URL('./worker.js', import.meta.url)),
88
- ]);
109
+ [**Return values and errors →**](https://johnny-quesada-developer.github.io/easy-web-worker/docs/typed-workers/#return-values-and-errors)
89
110
 
90
- // other ways with vanilla js
91
- const worker = createEasyWebWorker('./worker.js');
92
- const worker = createEasyWebWorker(new Worker('./worker.js')); // ƒ Worker() { [native code] }
111
+ ### **Cancellation that reaches the Worker**
93
112
 
94
- const worker = new EasyWebWorker('./worker.js');
95
- const worker = new EasyWebWorker(new Worker('./worker.js')); // ƒ Worker() { [native code] }
113
+ Every call returns a `CancelablePromise`. Canceling it tells the Worker to stop.
114
+
115
+ ```ts
116
+ const task = math.findPrimes(50_000_000);
117
+
118
+ task.cancel('Canceled by user');
96
119
  ```
97
120
 
98
- When working with **static files**, which can offer substantial benefits with web workers, you simply need to create an instance of **StaticEasyWebWorker**.
121
+ [**Run it live →**](https://johnny-quesada-developer.github.io/easy-web-worker/examples/progress-and-cancellation/) · [Read the guide](https://johnny-quesada-developer.github.io/easy-web-worker/docs/cancellation-and-progress/#cancellation-reaches-the-worker)
99
122
 
100
- The **StaticEasyWebWorker** provides an interface to continue working with [easy-cancelable-promise](https://www.npmjs.com/package/easy-cancelable-promise) and build more complex APIs within your worker.
123
+ ### **Progress on the same call**
101
124
 
102
- From inside your worker, the message callbacks receive a message that includes multiple methods and functions. You can use these to communicate back with the main thread, or to subscribe to and react to the lifecycle of a worker.
125
+ Long-running methods report progress through the promise they already returned.
103
126
 
104
127
  ```ts
105
- const { onMessage } = new StaticEasyWebWorker();
128
+ await math.findPrimes(50_000_000).onProgress((percentage) => {
129
+ progressBar.value = percentage;
130
+ });
131
+ ```
106
132
 
107
- /**
108
- * For adding a default onMessage
109
- */
110
- onMessage((message) => {
111
- /** Your message receives a payload,
112
- * which is any information sent from the main thread.*/
113
- const { payload } = message;
133
+ [**Run it live →**](https://johnny-quesada-developer.github.io/easy-web-worker/examples/progress-and-cancellation/) · [Read the guide](https://johnny-quesada-developer.github.io/easy-web-worker/docs/cancellation-and-progress/#progress-on-the-same-call)
114
134
 
115
- const bigArrayBuffer = new new ArrayBuffer(1000000)();
135
+ ### **Every core, one option**
116
136
 
117
- /** You can resolve the message and respond to the main thread's promise that is listening.
118
- * This promise could also send data back to the main thread and transfer data if needed */
119
- message.resolve({ bigArrayBuffer }, [bigArrayBuffer]);
137
+ Set `maxWorkers` and the same object spreads calls across several native Workers.
120
138
 
121
- /** You can reject the message and send back a reason,
122
- * if no transfer is necessary just avoid the second parameter */
123
- message.reject(new Error('something happened'));
139
+ ```ts
140
+ const math = createWorker<MathWorker>(source, { maxWorkers: 4 });
141
+
142
+ // three calls, three threads, at the same time
143
+ await Promise.all([math.fibonacci(40), math.fibonacci(41), math.fibonacci(42)]);
144
+ ```
124
145
 
125
- const metadata = { message: 'progress from inside the worker' };
146
+ [**Run it live →**](https://johnny-quesada-developer.github.io/easy-web-worker/examples/worker-pool/) · [Read the guide](https://johnny-quesada-developer.github.io/easy-web-worker/docs/worker-pools/)
126
147
 
127
- /** You can report progress */
128
- message.reportProgress(10, metadata, []); // all the methods allows you to send Transferable[]
148
+ ### **Move large buffers instead of copying them**
129
149
 
130
- /** You can cancel an operation from within the worker */
131
- message.cancel('the operating was canceled from the worker');
150
+ Pass transferable objects as the second argument. Ownership moves and nothing is cloned.
132
151
 
133
- /** You can subscribe to the cancellation event of the message,
134
- * regardless of whether this cancellation is internal or external to the worker.*/
135
- const unsubscribeCancel = message.onCancel((reason) => {});
152
+ ```ts
153
+ const blurred = await images.blur(buffer, [buffer]);
154
+ ```
136
155
 
137
- // you can unsubscribe from the cancel event as well
138
- unsubscribeCancel();
156
+ [**Run it live →**](https://johnny-quesada-developer.github.io/easy-web-worker/examples/transferable-buffers/) · [Read the guide](https://johnny-quesada-developer.github.io/easy-web-worker/docs/transferable-objects/)
139
157
 
140
- /**
141
- * And there is extra listeners for the other events
142
- * This events represent the lifecycle of the message
143
- */
144
- const unsubscribeResolve = message.onResolve((data) => {});
145
- const unsubscribeReject = message.onReject((data) => {});
146
- const unsubscribeProgress = message.onProgress((data) => {});
147
- const unsubscribeFinalize = message.onFinalize((data) => {});
158
+ ### **Any way you create a Worker**
148
159
 
149
- /** You can also review the status of the message at any time*/
150
- const status = message.getStatus(); // pending | resolved | rejected | canceled
151
- });
160
+ A Worker file, a native `Worker`, your own pool, or just a function. The API on top stays the same.
152
161
 
153
- /**
154
- * For adding specific actions
155
- */
156
- onMessage('readCSV', (message) => {
157
- // do something
162
+ ```ts
163
+ createWorker<MathWorker>(new URL('./math.worker.ts', import.meta.url)); // Worker file
164
+ createWorker<MathWorker>(workerUrl); // URL generated by your bundler
165
+ createWorker<MathWorker>(new Worker(...)); // existing Worker
166
+ createWorker<MathWorker>([worker1, worker2]); // existing pool
167
+ createWorker(() => ({ fibonacci })); // a function, no file at all
168
+ ```
169
+
170
+ [**Every source, with the setup for each bundler →**](https://johnny-quesada-developer.github.io/easy-web-worker/docs/creating-workers/)
171
+
172
+ ### **No Worker file required**
173
+
174
+ Pass a function instead of a file. Same methods, same inferred types, nothing to configure.
175
+
176
+ ```ts
177
+ const math = createWorker(() => {
178
+ const fibonacci = (n: number): number =>
179
+ n <= 1 ? n : fibonacci(n - 1) + fibonacci(n - 2);
180
+
181
+ return { fibonacci };
158
182
  });
183
+
184
+ await math.fibonacci(40); // 102334155, typed as number
159
185
  ```
160
186
 
161
- It's important to mention that the **cancel** method is the only one that provides two-way binding. It can travel all the way from the main thread to the worker, cancel something inside the worker, and notify the main thread upon completion.
187
+ [**Run it live →**](https://johnny-quesada-developer.github.io/easy-web-worker/examples/worker-without-a-file/) · [Read the guide](https://johnny-quesada-developer.github.io/easy-web-worker/docs/runtime-workers/)
162
188
 
163
- **easy-web-worker** is designed to enhance the capabilities of the **Worker** class by integrating a pattern of cancelable promises from the [easy-cancelable-promise](https://www.npmjs.com/package/easy-cancelable-promise) library. For straightforward tasks, it simplifies the process by eliminating the need to configure webpack or other bundlers. And for more complex requirements, the **StaticEasyWebWorker** class allows the integration of easy worker and cancelable promises capabilities into your static workers.
189
+ ---
164
190
 
165
- Start enhancing your applications with robust, cancelable promises and easy web worker integration today! 🌐
191
+ ## Installation
166
192
 
167
- Experience it in action with a [Live Example featuring text-diff](https://johnny-quesada-developer.github.io/easy-web-workers-example/) 📘.
193
+ ```bash
194
+ npm install easy-web-worker
195
+ ```
168
196
 
169
- For a comprehensive understanding, watch our informative [introduction video](https://www.youtube.com/watch?v=CK-Uri9lDOE) 🎥. You can also dive deeper into the code and explore on [easy-web-workers-examples](https://github.com/johnny-quesada-developer/easy-web-workers-example) 🧩.
197
+ - **Any framework**: built on the native Worker API of the browser, with no dependency on React, Vue, Angular, Svelte or any other UI library.
198
+ - **Promises**: calls return a `CancelablePromise` from [`easy-cancelable-promise`](https://www.npmjs.com/package/easy-cancelable-promise), installed with the package.
170
199
 
171
- ## Creating a simple Web Worker
200
+ ---
172
201
 
173
- Creating a new worker is as simple as
202
+ ## Quick Start
174
203
 
175
- ```TS
176
- const backgroundWorker = createEasyWebWorker<string, string>(({ onMessage }) => {
177
- onMessage((message) => {
178
- const { payload } = message;
204
+ ### Your first typed worker
179
205
 
180
- message.resolve(`this is a message from the worker: ${payload}`);
181
- });
206
+ ```ts
207
+ // math.worker.ts
208
+ import { defineWorker } from 'easy-web-worker/defineWorker';
182
209
 
183
- // you could also define and send specific methods which allow you to create a better structured API
184
- onMessage<number, number>('doSomething', (message) => {
185
- const { payload } = message;
210
+ const fibonacci = (n: number): number =>
211
+ n <= 1 ? n : fibonacci(n - 1) + fibonacci(n - 2);
186
212
 
187
- message.resolve(payload + 2);
188
- });
213
+ // 1. Define it (in the Worker file)
214
+ const worker = defineWorker(() => ({ fibonacci }));
215
+
216
+ export type MathWorker = typeof worker;
217
+ ```
218
+
219
+ ```ts
220
+ // main.ts
221
+ import { createWorker } from 'easy-web-worker/createWorker';
222
+ import type { MathWorker } from './math.worker';
223
+
224
+ // 2. Create it (on the main thread)
225
+ const math = createWorker<MathWorker>(
226
+ new URL('./math.worker.ts', import.meta.url),
227
+ {
228
+ workerOptions: { type: 'module' },
229
+ }
230
+ );
231
+
232
+ // 3. Call it
233
+ const result = await math.fibonacci(40); // 102334155
234
+ ```
235
+
236
+ The main thread imports only the **type** of the Worker. TypeScript infers both sides: `math.fibonacci` takes a `number` and returns a `CancelablePromise<number>`. Change the Worker, and the main thread stops compiling until it matches.
237
+
238
+ > Shipping with a bundler? See [Every way to create a worker](#every-way-to-create-a-worker) for the production setup.
239
+ >
240
+ > Prefer a step-by-step page? [Getting started](https://johnny-quesada-developer.github.io/easy-web-worker/docs/getting-started/) covers the same code, and [Keep the page responsive](https://johnny-quesada-developer.github.io/easy-web-worker/examples/keep-the-page-responsive/) runs it in your browser.
241
+
242
+ ### Progress and cancellation
243
+
244
+ Wrap a method with `onMessage` to receive the message of the call.
245
+
246
+ ```ts
247
+ // math.worker.ts
248
+ const worker = defineWorker(({ onMessage }) => ({
249
+ findPrimes: onMessage(async (limit: number, message) => {
250
+ const primes: number[] = [];
251
+
252
+ for (let candidate = 2; candidate <= limit; candidate++) {
253
+ if (isPrime(candidate)) primes.push(candidate);
254
+
255
+ if (candidate % 100_000 !== 0) continue;
256
+
257
+ // give the Worker a moment to receive a cancellation
258
+ await new Promise((resolve) => setTimeout(resolve));
259
+
260
+ if (!message.isPending()) break;
261
+
262
+ message.reportProgress((candidate / limit) * 100);
263
+ }
264
+
265
+ return primes;
266
+ }),
267
+ }));
268
+ ```
269
+
270
+ ```ts
271
+ // main.ts
272
+ const task = math.findPrimes(50_000_000).onProgress((percentage) => {
273
+ progressBar.value = percentage;
189
274
  });
190
275
 
191
- // outside your worker
192
- const messageResult = await backgroundWorker.send('hello!');
276
+ cancelButton.onclick = () => task.cancel('Canceled by user');
193
277
 
194
- // for specific methods us the sendToMethod function
195
- const messageResult2 = await backgroundWorker.sendToMethod('doSomething', 2);
278
+ const primes = await task;
196
279
  ```
197
280
 
198
- ### Important notes:
281
+ Progress and cancellation belong to the call itself, not to a second channel.
199
282
 
200
- EasyWebWorker<IPayload, IResult> has two generic parameters... They will affect the typing of the send() and response() methods.
283
+ > This exact pattern runs live in [Progress and cancellation](https://johnny-quesada-developer.github.io/easy-web-worker/examples/progress-and-cancellation/): start a long search, watch it advance, and cancel it.
201
284
 
202
- - If IResult is null, the _resolve_ method will not require parameters
203
- - If IPayload is null, the _send_ method will not require parameters
285
+ ---
204
286
 
205
- Take into consideration that the _workerBody_ is a template to create a worker in run time, so you'll not be able to use anything outside of the Worker-Scope.
287
+ ## Core Features Deep Dive
206
288
 
207
- ```TS
208
- const message = 'Hello';
289
+ ### Typed workers with `defineWorker` and `createWorker`
209
290
 
210
- await createEasyWebWorker<null, string>(({ onMessage }) => {
211
- onMessage((message) => {
291
+ Write the Worker as a set of methods and call them from the main thread like local async functions.
292
+
293
+ #### The Basics
294
+
295
+ ```ts
296
+ // text.worker.ts
297
+ const worker = defineWorker(() => ({
298
+ countWords: (text: string) => text.trim().split(/\s+/).length,
299
+
300
+ findDuplicates: (lines: string[]) =>
301
+ lines.filter((line, index) => lines.indexOf(line) !== index),
302
+ }));
303
+
304
+ export type TextWorker = typeof worker;
305
+ ```
306
+
307
+ ```ts
308
+ // main.ts
309
+ const text = createWorker<TextWorker>(source);
310
+
311
+ await text.countWords(article); // number
312
+ await text.findDuplicates(lines); // string[]
313
+ ```
314
+
315
+ The contract between the threads lives next to the code that implements it. There is no second file of message types to keep in sync.
316
+
317
+ How the types cross the threads:
318
+
319
+ | In the Worker | On the main thread |
320
+ | --------------------------------- | ------------------------------------------------------------- |
321
+ | `() => string` | `() => CancelablePromise<string>` |
322
+ | `(value: number) => number` | `(payload: number, transfer?) => CancelablePromise<number>` |
323
+ | `async (ids: string[]) => User[]` | `(payload: string[], transfer?) => CancelablePromise<User[]>` |
324
+ | `(value?: number) => number` | `(payload?: number, transfer?) => CancelablePromise<number>` |
325
+
326
+ A method receives **one payload**. Send several values as an object or a tuple. Required payloads are enforced when TypeScript runs with `strict` or `strictNullChecks`.
327
+
328
+ #### What it replaces
329
+
330
+ `fibonacci(40)` on the main thread freezes the page for about a second. A Web Worker fixes that, but the native API gives you messages, not functions. This is the least you write by hand:
331
+
332
+ ```ts
333
+ // fibonacci.worker.js
334
+ self.onmessage = ({ data: { id, n } }) => {
335
+ self.postMessage({ id, result: fibonacci(n) });
336
+ };
337
+ ```
338
+
339
+ ```ts
340
+ // main.js
341
+ const worker = new Worker(new URL('./fibonacci.worker.js', import.meta.url));
342
+ const pending = new Map();
343
+ let nextId = 0;
344
+
345
+ worker.onmessage = ({ data: { id, result } }) => {
346
+ pending.get(id)(result);
347
+ pending.delete(id);
348
+ };
349
+
350
+ const fibonacci = (n) =>
351
+ new Promise((resolve) => {
352
+ const id = nextId++;
212
353
 
213
- message.resolve(message); // THIS WILL PRODUCE AND ERROR!! the variable *message* will not exist in Worker-Scope.
354
+ pending.set(id, resolve);
355
+ worker.postMessage({ id, n });
214
356
  });
215
- }).send('hello!');
216
357
  ```
217
358
 
218
- If you need to pass a primitive parameter to the body of the worker, you can use the **primitiveParameters** configuration. This is an array of values that will be serialized and embedded into the worker's body.
359
+ And that is before errors, cancellation, progress, types, or a second method.
360
+
361
+ #### Return values and errors
219
362
 
220
363
  ```ts
221
- const message = 'Hello';
364
+ // report.worker.ts
365
+ const worker = defineWorker(() => ({
366
+ parseReport: async (url: string) => {
367
+ const response = await fetch(url);
222
368
 
223
- await createEasyWebWorker<null, string>(
224
- ({ onMessage }, context) => {
225
- const [message] = context.primitiveParameters;
369
+ if (!response.ok) {
370
+ throw new Error(`Report not available: ${response.status}`);
371
+ }
226
372
 
227
- console.log(message); // "hello!" // 👍 it works!
373
+ return buildReport(await response.text());
228
374
  },
229
- {
230
- primitiveParameters: [message],
231
- }
232
- ).send('hello!');
375
+ }));
233
376
  ```
234
377
 
235
- Take a look at Workers API if you don't know yet how they work: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API,
236
- If you need t to send data to the worker, please define IPayload while creating a worker. _new EasyWebWorker<IPayload>(_
237
- You are just allowed to send information to Workers by messages, and vice versa
378
+ ```ts
379
+ // main.ts
380
+ try {
381
+ const report = await reports.parseReport('/reports/2026.csv');
382
+ } catch (error) {
383
+ console.log((error as Error).message); // 'Report not available: 404'
384
+ }
385
+ ```
238
386
 
239
- ## IEasyWebWorkerMessage<IPayload = null, IResult = void>
387
+ An error rejects that one call. The Worker keeps serving the next one.
240
388
 
241
- When you defined an onMessage callback in your **Worker**, this will receive all messages from the **send** method:
389
+ #### Three ways to write a method
242
390
 
243
- ```TS
244
- easyWorker.onMessage((message) => {
245
- // the *message* will be strongly typed with TS
391
+ ```ts
392
+ const worker = defineWorker(({ onMessage }) => ({
393
+ // 1. a plain function
394
+ double: (value: number) => value * 2,
395
+
396
+ // 2. onMessage: same thing, with the message and the event typed for you
397
+ count: onMessage(async (to: number, message, event) => {
398
+ message.reportProgress(50);
399
+
400
+ return to;
401
+ }),
402
+
403
+ // 3. onMessage().handle: only the message, you complete it
404
+ wait: onMessage<number, string>().handle((message, event) => {
405
+ const timeoutId = setTimeout(
406
+ () => message.resolve('done'),
407
+ message.payload
408
+ );
409
+
410
+ message.onCancel(() => clearTimeout(timeoutId));
411
+ }),
412
+ }));
413
+ ```
246
414
 
247
- // the message could resolve the *send* promise.
248
- message.resolve();
415
+ | Style | Completes the call | Types |
416
+ | --------------------------------------------- | ----------------------- | --------------------------------- |
417
+ | Plain function | With the returned value | Inferred |
418
+ | `onMessage((payload, message, event) => ...)` | With the returned value | Inferred, `message` typed |
419
+ | `onMessage<P, R>().handle((message) => ...)` | You do | Explicit, `message.resolve` typed |
420
+
421
+ All three are called the same way from the main thread. Use the third for timers, streams, event-based APIs, or to transfer a result.
422
+
423
+ If a method completes the message itself with `resolve`, `reject` or `cancel`, its return value is ignored.
424
+
425
+ #### The message
426
+
427
+ Available in `onMessage`, in `.handle(...)`, and in the message API.
428
+
429
+ | Member | Purpose |
430
+ | ------------------------------------------------- | --------------------------------------------- |
431
+ | `payload` | Value sent from the main thread |
432
+ | `resolve(result?, transfer?)` | Resolves the call |
433
+ | `reject(reason?, transfer?)` | Rejects the call |
434
+ | `cancel(reason?, transfer?)` | Cancels the call from inside the Worker |
435
+ | `reportProgress(percentage, payload?, transfer?)` | Reports progress and keeps the call pending |
436
+ | `onCancel`, `onResolve`, `onReject`, `onProgress` | Subscribe to each outcome; return unsubscribe |
437
+ | `onFinalize` | Runs once the call ends, whatever the outcome |
438
+ | `isPending()`, `getStatus()` | Current state of the call |
249
439
 
250
- // the message could be rejected from the worker
251
- message.reject(new Error());
440
+ #### Cancellation
252
441
 
253
- // this message could be cancelled from inside the worker
254
- message.cancel();
442
+ The cancellation reaches the Worker, so the work stops there instead of finishing for nobody. React to it with `message.onCancel(...)` or check `message.isPending()`.
255
443
 
256
- // the message is also able to listen to cancelation evens
257
- message.onCancel(() => {
258
- // release resources
259
- })
444
+ Canceling twice, or after the call finished, does nothing.
445
+
446
+ A Worker that never pauses cannot receive a cancellation. See [Stop work that never pauses](#stop-work-that-never-pauses).
447
+
448
+ #### Transferable objects
449
+
450
+ ```ts
451
+ // main.ts
452
+ const buffer = await file.arrayBuffer();
453
+
454
+ const blurred = await images.blur(buffer, [buffer]);
455
+ ```
456
+
457
+ Returned values are cloned. To transfer a result back, complete the call yourself:
458
+
459
+ ```ts
460
+ // images.worker.ts
461
+ const worker = defineWorker(({ onMessage }) => ({
462
+ blur: onMessage<ArrayBuffer, ArrayBuffer>().handle((message) => {
463
+ const result = applyBlur(message.payload);
464
+
465
+ message.resolve(result, [result]);
466
+ }),
467
+ }));
468
+ ```
260
469
 
261
- // you could also report progress to the principal thread if you configured a onProgress callback
262
- message.reportProgress(25);
470
+ #### `unwrap`: the controls behind the methods
471
+
472
+ The object returned by `createWorker` holds only your methods, so names like `send`, `dispose` or `reboot` stay free for your own API. `unwrap` reaches the `EasyWebWorker` instance behind it.
473
+
474
+ ```ts
475
+ import { createWorker, unwrap } from 'easy-web-worker/createWorker';
476
+
477
+ const math = createWorker<MathWorker>(source);
478
+
479
+ await unwrap(math).cancelAll('Leaving the page');
480
+ await unwrap(math).reboot();
481
+ await unwrap(math).dispose();
482
+ ```
483
+
484
+ | `EasyWebWorker` method | Purpose |
485
+ | -------------------------------------------------------------- | --------------------------------------------------------------- |
486
+ | `send(payload?, transfer?)` | Sends a message to the default handler |
487
+ | `sendToMethod<TResult, TPayload>(method, payload?, transfer?)` | Sends a message to a named method |
488
+ | `cancelAll(reason?, { force? })` | Cancels every pending call; `force` restarts instead of waiting |
489
+ | `override(payload?, reason?, { force? })` | Cancels the pending calls and sends a new message |
490
+ | `overrideAfterCurrent(payload?, reason?)` | Keeps the current call, cancels the rest, sends a new message |
491
+ | `reboot(reason?)` | Terminates the Workers, rejects pending calls, starts again |
492
+ | `dispose()` | Cancels pending calls and releases the Workers |
493
+
494
+ The same helper works inside the Worker file, where it returns the Worker-side controls:
495
+
496
+ ```ts
497
+ import { defineWorker, unwrap } from 'easy-web-worker/defineWorker';
498
+
499
+ const worker = defineWorker(() => ({
500
+ shutdown: () => unwrap(worker).close(),
501
+ }));
502
+
503
+ unwrap(worker).importScripts('https://example.com/library.js');
504
+
505
+ // handles messages sent without a method: send, override, overrideAfterCurrent
506
+ unwrap(worker).onMessage((message) => {
507
+ message.resolve();
263
508
  });
264
509
  ```
265
510
 
266
- ## onProgress
511
+ One name is not available for a method: `then`. The worker object is not a promise, so `await math` never sends a message.
512
+
513
+ #### Entry points
514
+
515
+ Import each side from its own entry, so the Worker bundle carries no main-thread code:
516
+
517
+ | Entry | Use it in | Exports |
518
+ | ------------------------------ | --------------- | ------------------------------- |
519
+ | `easy-web-worker/defineWorker` | The Worker file | `defineWorker`, `unwrap`, types |
520
+ | `easy-web-worker/createWorker` | The main thread | `createWorker`, `unwrap`, types |
521
+
522
+ Everything is also exported from the root `easy-web-worker` entry.
523
+
524
+ > **On the website:** [Typed workers](https://johnny-quesada-developer.github.io/easy-web-worker/docs/typed-workers/) · [Methods and the message](https://johnny-quesada-developer.github.io/easy-web-worker/docs/methods-and-the-message/) · [TypeScript](https://johnny-quesada-developer.github.io/easy-web-worker/docs/typescript/) · [API reference](https://johnny-quesada-developer.github.io/easy-web-worker/docs/api-reference/)
267
525
 
268
- Let say you are performing some heavy process in your worker, but you still wanted to implement some kind of progress bar in the main thread... you could add an onProgress callback.
526
+ ---
269
527
 
270
- ```TS
271
- await worker.send().onProgress((progress: number) => {
272
- // change some progress bar percentage
273
- }).then(doSomething);
528
+ ### Every way to create a worker
529
+
530
+ Pick the source that fits how your project builds and serves its Workers. `createWorker`, `createEasyWebWorker` and `new EasyWebWorker(...)` accept all of them. A function means methods for `createWorker` and messages for the other two.
531
+
532
+ | Source | Best for | Pool with `maxWorkers` | `reboot` / `force` |
533
+ | ---------------------- | ----------------------------------------------- | ---------------------- | ------------------ |
534
+ | `URL` of a Worker file | Worker files in a bundled project | ✅ | ✅ |
535
+ | `string` path | A Worker file you serve yourself | ✅ | ✅ |
536
+ | Existing `Worker` | Bundlers that detect `new Worker(new URL(...))` | One Worker | ❌ |
537
+ | Existing `Worker[]` | Bringing your own pool | The array is the pool | ❌ |
538
+ | Function | Workers with no file at all | ✅ | ✅ |
539
+ | Function array | Workers composed from reusable pieces | ✅ | ✅ |
540
+
541
+ #### A Worker file in development
542
+
543
+ ```ts
544
+ const math = createWorker<MathWorker>(
545
+ new URL('./math.worker.ts', import.meta.url),
546
+ {
547
+ workerOptions: { type: 'module' },
548
+ }
549
+ );
274
550
  ```
275
551
 
276
- onProgress Is gonna be executed every time you call _message.reportProgress_ inside the worker... the cool part here is that the _reportProgress_ is not gonna finish the main promise returned by the _send_ method.
552
+ `type: 'module'` is required when the Worker file uses `import` or `export`, which every `defineWorker` file does.
277
553
 
278
- ## Having multiple Worker-Templates
554
+ #### Vite: development and production
279
555
 
280
- As _WorkerBody_ are just templates, you could reuse them on other _Workers_, or use them as plugins for your _Workers_. Let's see:
556
+ In development, Vite serves the TypeScript file. A production build needs the Worker URL that Vite generates:
281
557
 
282
- ```TS
283
- const reusableWorkerSegment: EasyWebWorkerBody = ({ onMessage, close, importScripts }, context) => {
284
- context.doSomething = () => Promise.resolve('This is a plugin example');
285
- };
558
+ ```ts
559
+ import { createWorker } from 'easy-web-worker/createWorker';
560
+ import workerUrl from './math.worker?worker&url';
561
+ import type { MathWorker } from './math.worker';
562
+
563
+ // the Worker URL in production, the TypeScript file in development
564
+ const isProduction = import.meta.env.MODE === 'production';
565
+
566
+ const math = createWorker<MathWorker>(
567
+ isProduction ? workerUrl : new URL('./math.worker.ts', import.meta.url),
568
+ {
569
+ workerOptions: { type: 'module' },
570
+ }
571
+ );
572
+ ```
573
+
574
+ The same source works with the message API:
575
+
576
+ ```ts
577
+ import { EasyWebWorker } from 'easy-web-worker';
578
+
579
+ const worker = new EasyWebWorker(
580
+ isProduction ? workerUrl : new URL('./TextDiff.worker.ts', import.meta.url),
581
+ {
582
+ workerOptions: { type: 'module' },
583
+ }
584
+ );
585
+ ```
286
586
 
287
- const reusableWorkerSegment = await createEasyWebWorker([
288
- reusableWorkerSegment,
289
- ({ onMessage }, context) => onMessage(async (message) => {
290
- // context will have all stuff we added on other plugins
291
- const result = await context.doSomething();
587
+ #### A native Worker you create
292
588
 
293
- message.resolve(result);
294
- })]).send();
589
+ webpack 5, Parcel and Vite all detect `new Worker(new URL(...))` and bundle the file for you. Create the Worker that way and hand it over:
295
590
 
591
+ ```ts
592
+ const math = createWorker<MathWorker>(
593
+ new Worker(new URL('./math.worker.ts', import.meta.url), { type: 'module' })
594
+ );
296
595
  ```
297
596
 
298
- In this way, you could avoid having to create more than once the same template for your worker.
597
+ The library cannot create that Worker again, so `reboot` and `force` are not available. Leave `keepAlive` at its default so the Worker is not terminated when idle.
299
598
 
300
- ## Importing scripts into your _Workers_
599
+ #### Your own pool
301
600
 
302
- Web Workers has this amazing method called importScripts, are you passed an array of strings in the EasyWorker extra configuration, all those files are gonna be imported into your worker.
601
+ Pass an array of native Workers and calls are distributed across them:
303
602
 
304
- // test.js
603
+ ```ts
604
+ const createMathWorker = () =>
605
+ new Worker(new URL('./math.worker.ts', import.meta.url), { type: 'module' });
305
606
 
306
- ```TS
307
- self.message = 'Hello coders!';
308
- self.doSomething = () => console.log(self.message);
607
+ const math = createWorker<MathWorker>([
608
+ createMathWorker(),
609
+ createMathWorker(),
610
+ createMathWorker(),
611
+ ]);
309
612
  ```
310
613
 
311
- ```TS
312
- await createEasyWebWorker(({ onMessage }, context) => {
313
- onMessage((message) => context.doSomething());
314
- }, {
315
- scripts: ['http://localhost:3000/test.js'],
316
- }).send();
614
+ #### A file you serve yourself
317
615
 
616
+ A Worker script that is already built and public only needs its path:
617
+
618
+ ```ts
619
+ const math = createWorker<MathWorker>('/workers/math.worker.js');
318
620
  ```
319
621
 
320
- This is a very simple example, but you could import a whole library into your worker, as _JQUERY_, _Bluebird_ for example
622
+ #### A function, with no file at all
623
+
624
+ ```ts
625
+ const math = createWorker(() => ({
626
+ double: (value: number) => value * 2,
627
+ }));
628
+
629
+ await math.double(21); // 42
630
+ ```
321
631
 
322
- ## StaticEasyWebWorker
632
+ The function returns the methods, exactly like the builder of `defineWorker`. See [Runtime Workers](#runtime-workers-a-function-as-the-worker) for scope rules, parameters and external scripts.
323
633
 
324
- If you want to create a _Worker_ with a static .js file and don't want to lose the structure of messages and promises and the onProgress callback from the library... you could use StaticEasyWebWorker<IPayload = null, IResult = void>\_ directly in your Worker.
634
+ > **On the website:** [Creating workers](https://johnny-quesada-developer.github.io/easy-web-worker/docs/creating-workers/) · [Troubleshooting](https://johnny-quesada-developer.github.io/easy-web-worker/docs/troubleshooting/)
325
635
 
326
- let's see how to use it:
636
+ ---
327
637
 
328
- // worker.js
329
- // This is gonna be the content of your worker
330
- // onMessage Callback is gonna receive all _send_ method calls.
638
+ ### Worker pools
331
639
 
332
- ```TS
333
- // imports only the static web worker
334
- import createStaticEasyWebWorker from 'easy-web-worker/createStaticEasyWebWorker';
640
+ One Worker runs one task at a time. A pool runs several at once, behind the same object.
335
641
 
336
- // this is gonna create the same message structure the runtime Workers
337
- const { onMessage } = createStaticEasyWebWorker();
642
+ #### Scale on demand
338
643
 
339
- onMessage((message) => {
340
- setTimeout(() => {
341
- message.resolve(200);
342
- }, 5000);
644
+ ```ts
645
+ const math = createWorker<MathWorker>(source, {
646
+ maxWorkers: 4,
343
647
  });
344
648
 
345
- onMessage('action', (message) => {
346
- setTimeout(() => {
347
- message.resolve(200);
348
- }, 5000);
649
+ // three calls, three threads, at the same time
650
+ const results = await Promise.all([
651
+ math.fibonacci(40),
652
+ math.fibonacci(41),
653
+ math.fibonacci(42),
654
+ ]);
655
+ ```
656
+
657
+ The calling code does not change. It does not know whether one Worker or four are behind it.
658
+
659
+ Workers are created as concurrent calls arrive, so an idle application pays for none of them.
660
+
661
+ #### Warm up or release the pool
662
+
663
+ ```ts
664
+ createWorker<MathWorker>(source, {
665
+ maxWorkers: 4,
666
+ warmUpWorkers: true, // create the whole pool up front, for the fastest first call
667
+ });
668
+
669
+ createWorker<MathWorker>(source, {
670
+ maxWorkers: 4,
671
+ keepAlive: false, // terminate idle Workers...
672
+ terminationDelay: 5_000, // ...after five seconds without work
349
673
  });
350
674
  ```
351
675
 
352
- By the way, if you're in need of a super simple static worker, just know that the first parameter of createStaticEasyWebWorker is a function which will be used as the default onmessage callback.
676
+ #### Configuration
677
+
678
+ | Option | Default | Purpose |
679
+ | --------------------- | ----------------------- | ---------------------------------------------------- |
680
+ | `maxWorkers` | `1` | Maximum number of native Workers |
681
+ | `warmUpWorkers` | `true` for one Worker | Creates the pool during initialization |
682
+ | `keepAlive` | Same as `warmUpWorkers` | Keeps idle Workers alive |
683
+ | `terminationDelay` | `1000` ms | Wait before terminating idle Workers |
684
+ | `workerOptions` | `{}` | Native `WorkerOptions`, such as `{ type: 'module' }` |
685
+ | `onWorkerError` | Rethrows | Receives the uncaught errors of the Worker |
686
+ | `scripts` | `[]` | Scripts imported into a runtime Worker |
687
+ | `primitiveParameters` | `[]` | Static values for a runtime Worker, in `context` |
688
+
689
+ The same options apply to `createWorker`, `createEasyWebWorker` and `new EasyWebWorker(...)`.
690
+
691
+ > **On the website:** [Worker pools](https://johnny-quesada-developer.github.io/easy-web-worker/docs/worker-pools/) · [Live example: one worker or three](https://johnny-quesada-developer.github.io/easy-web-worker/examples/worker-pool/)
692
+
693
+ ---
694
+
695
+ ### Runtime Workers: a function as the Worker
696
+
697
+ Give `createWorker` a function instead of a file. No extra file, no bundler configuration.
698
+
699
+ #### The Basics
353
700
 
354
701
  ```ts
355
- createStaticEasyWebWorker((message) => {
356
- // this is the default onMessage
702
+ import { createWorker } from 'easy-web-worker/createWorker';
703
+
704
+ const math = createWorker(({ onMessage }) => {
705
+ const fibonacci = (n: number): number =>
706
+ n <= 1 ? n : fibonacci(n - 1) + fibonacci(n - 2);
707
+
708
+ return {
709
+ fibonacci,
710
+
711
+ countTo: onMessage(async (limit: number, message) => {
712
+ for (let step = 1; step <= limit; step++) {
713
+ message.reportProgress((step * 100) / limit);
714
+ }
715
+
716
+ return limit;
717
+ }),
718
+ };
357
719
  });
720
+
721
+ const result = await math.fibonacci(40); // 102334155
358
722
  ```
359
723
 
360
- and in your main thread:
724
+ The function is the builder of [`defineWorker`](#three-ways-to-write-a-method): it receives `onMessage`, returns the methods, and their types are inferred. There is no type to export or import, because both sides are in the same file.
725
+
726
+ Everything else works the same: cancellation, progress, transferable objects, `unwrap`, and pools with `maxWorkers`.
727
+
728
+ #### Worker scope
361
729
 
362
- ```TS
363
- const worker = createEasyWebWorker<null,number>('./worker.js');
730
+ The function becomes the source of a real Worker, so it **cannot use variables from outside**.
731
+
732
+ ```ts
733
+ const greeting = 'Hello';
364
734
 
365
- await worker.send();
735
+ createWorker(() => ({
736
+ // `greeting` does not exist inside the Worker
737
+ greet: (name: string) => `${greeting} ${name}`,
738
+ }));
366
739
  ```
367
740
 
368
- Super easy right?
741
+ Everything the Worker needs must be defined inside the function, imported as a script, sent as a payload, or passed through `primitiveParameters`.
369
742
 
370
- ## Concurrency mode
743
+ #### Parameters and the scope of the Worker
371
744
 
372
- With EasyWebWorker, you can create operations that require heavy concurrency and delegate them to a web workers queue, or create workers on demand depending on the traffic and specific tasks. Let's take a look:
745
+ The second parameter is the global scope of the Worker. Static values arrive in `context.primitiveParameters`:
373
746
 
374
- ```TS
375
- /**
376
- * Notice that the structure of the worker remains the same;
377
- * the only changes are in the configuration parameters of the worker.
378
- * Take a look below.*/
379
- const worker = createEasyWebWorker(({ onMessage }) => {
380
- onMessage((message) => {
381
- const { payload } = message;
747
+ ```ts
748
+ const worker = createWorker(
749
+ (_helpers, context) => {
750
+ const [prefix] = context.primitiveParameters;
751
+
752
+ return {
753
+ label: (text: string) => `${prefix} ${text}`,
754
+ };
755
+ },
756
+ {
757
+ primitiveParameters: ['Result:'] as [string],
758
+ }
759
+ );
760
+
761
+ await worker.label('complete'); // 'Result: complete'
762
+ ```
763
+
764
+ #### The message API inside the function
382
765
 
383
- // heavy computation like fibonacci
766
+ The function also receives `easyWorker`, the worker instance behind the methods. Everything the message API offers stays available, so existing runtime Workers can move over at their own pace.
384
767
 
385
- message.resolve(result);
768
+ ```ts
769
+ const worker = createWorker<{
770
+ double: (value: number) => number;
771
+ triple: (value: number) => number;
772
+ }>(({ easyWorker }) => {
773
+ // named handler, message style
774
+ easyWorker.onMessage<number, number>('double', (message) => {
775
+ message.resolve(message.payload * 2);
776
+ });
777
+
778
+ // handler for messages sent without a method
779
+ easyWorker.onMessage((message) => {
780
+ message.resolve();
386
781
  });
387
- }, {
388
- // We will now scale up to four workers if necessary.
389
- maxWorkers: 4
782
+
783
+ return {
784
+ triple: (value: number) => value * 3,
785
+ shutdown: () => easyWorker.close(),
786
+ };
390
787
  });
788
+
789
+ await worker.double(21); // 42
790
+ await worker.triple(21); // 63
391
791
  ```
392
792
 
393
- By default, creating an **EasyWebWorker** also creates a single native JavaScript worker. Without any added configuration, this Worker instance will remain active unless it is programmatically disposed. However, by modifying the **maxWorkers** parameter, you can control the number of native workers used, allowing the **EasyWebWorker** to execute multiple messages across multiple threads.
793
+ Only the returned methods are inferred. Handlers registered through `easyWorker` are described in the generic, as above, or called with `unwrap(worker).sendToMethod(...)`.
394
794
 
395
- You can also control whether these additional workers should be created on demand when messages are sent to the **EasyWebWorker**. This can be done along with setting the **terminationDelay**, which indicates how long to wait before disposing of a **Worker** to avoid unnecessary resource consumption. Alternatively, you can choose to **warmUp** and keep the Workers alive from the moment the **EasyWebWorker** is created.
795
+ #### Import scripts
396
796
 
397
- From the main thread, the consumption of the worker remains the same. Depending on the configuration, the workers will be created either statically or on-demand as needed. The EasyWebWorker will be responsible for selecting them from a pool. Additionally, the EasyWebWorker will manage the distribution of messages among the available workers.
797
+ External libraries load with the `scripts` option and appear in the scope of the Worker:
398
798
 
399
799
  ```ts
400
- // Since maxWorkers is configured to 4, a total of 3 native workers, will be created.
401
- const results = await Promise.all([
402
- worker.send(payload),
403
- worker.send(payload1),
404
- worker.send(payload2),
405
- ]);
800
+ const text = createWorker(
801
+ (_helpers, context) => {
802
+ // the script adds `Diff` to the scope of the Worker
803
+ const { diffWords } = context.Diff as {
804
+ diffWords: (before: string, after: string) => unknown[];
805
+ };
806
+
807
+ return {
808
+ compare: ({ before, after }: { before: string; after: string }) =>
809
+ diffWords(before, after),
810
+ };
811
+ },
812
+ {
813
+ scripts: ['https://cdn.jsdelivr.net/npm/diff@9.0.0/dist/diff.min.js'],
814
+ }
815
+ );
816
+
817
+ const changes = await text.compare({ before, after });
406
818
  ```
407
819
 
408
- In the previous example, since the **warmUp** parameter wasn’t included, only 3 Native workers will be created to handle the 3 messages, even though the maximum available is 4. Additionally, since the **keepAlive** parameter was not included, if a total of 1 second passes without new messages, the Native workers will be disposed of to save resources.
820
+ No Worker file is required, and the script does not become part of your application bundle.
409
821
 
410
- ## Want to see more?
822
+ #### Compose a Worker from several functions
411
823
 
412
- Here is an example of how you could easily create data filter into a Worker, to avoid performing loops process into the main thread that could end affecting user experience.
824
+ Pass an array and every function runs in the same Worker. They share its scope, and the methods they return are merged.
413
825
 
414
- ```TS
415
- interface FilterSource {
416
- filter: string,
417
- collection: any[],
418
- reportProgress: boolean,
419
- }
826
+ ```ts
827
+ const worker = createWorker([
828
+ // reusable piece: leaves a helper in the scope of the Worker
829
+ (_helpers, context) => {
830
+ context.round = (value: number) => Math.round(value * 100) / 100;
831
+ },
420
832
 
421
- const worker = createEasyWebWorker<FilterSource, any[]>(({ onMessage }) => {
422
- const containsValue = (item: any, filter: string): boolean => {
423
- const itemKeys = Object.keys(item);
833
+ () => ({
834
+ double: (value: number) => value * 2,
835
+ describe: () => 'first',
836
+ }),
424
837
 
425
- return itemKeys.some((key) => {
426
- const prop = item[key] || null;
838
+ (_helpers, context) => {
839
+ const round = context.round as (value: number) => number;
427
840
 
428
- if (typeof prop !== 'string' && Object.keys(prop).length) return containsValue(prop, filter);
429
- if (prop.toString().replace(/(\r\n|\n|\r)/gm, '').trim().toLowerCase()
430
- .indexOf(filter) !== -1) return true;
841
+ return {
842
+ average: (values: number[]) =>
843
+ round(values.reduce((total, value) => total + value, 0) / values.length),
431
844
 
432
- return false;
433
- });
434
- };
845
+ // repeated method: the last function wins
846
+ describe: () => 'last',
847
+ };
848
+ },
849
+ ]);
435
850
 
436
- onMessage((message: IEasyWebWorkerMessage<FilterSource, any[]>) => {
437
- const { payload } = message;
438
- const { collection, filter = '', reportProgress: countProgress } = payload;
439
- const { length: collectionLength } = collection;
440
- const result = filter === '' ? collection : [];
441
- const progressPerItem = collectionLength ? 100 / collectionLength : 0;
851
+ await worker.double(21); // 42
852
+ await worker.average([1, 2, 2]); // 1.67
853
+ await worker.describe(); // 'last'
854
+ ```
442
855
 
443
- let currentProgress = 0;
856
+ Types are merged the same way: `worker.describe` takes the signature of the last function. Write the array inline so each function keeps its own type.
444
857
 
445
- if (filter) {
446
- for (let index = 0; index < collectionLength; index += 1) {
447
- if (countProgress) {
448
- currentProgress += progressPerItem;
449
- message.reportProgress(currentProgress);
450
- }
858
+ #### Runtime Workers with the message API
451
859
 
452
- const item = collection[index];
860
+ `createEasyWebWorker` builds a Worker from a function too, with messages instead of methods:
453
861
 
454
- if (containsValue(item, filter)) result.push(item);
455
- }
456
- }
862
+ ```ts
863
+ import { createEasyWebWorker } from 'easy-web-worker';
457
864
 
458
- message.resolve(result);
865
+ const worker = createEasyWebWorker<number, number>(({ onMessage }) => {
866
+ onMessage((message) => {
867
+ message.resolve(message.payload * 2);
459
868
  });
460
869
  });
870
+
871
+ await worker.send(21); // 42
872
+ ```
873
+
874
+ The first generic is the payload of `send()`. The second is the value passed to `message.resolve()`. It also accepts an array of functions that share the scope of the Worker, to compose a Worker from reusable pieces.
875
+
876
+ > **On the website:** [Runtime workers](https://johnny-quesada-developer.github.io/easy-web-worker/docs/runtime-workers/) · [Live example: a worker without a file](https://johnny-quesada-developer.github.io/easy-web-worker/examples/worker-without-a-file/)
877
+
878
+ ---
879
+
880
+ ### The message API with `StaticEasyWebWorker`
881
+
882
+ `defineWorker` is built on a message API that you can use directly, in Worker files and in runtime Workers.
883
+
884
+ #### The Basics
885
+
886
+ ```ts
887
+ // worker.ts
888
+ import { createStaticEasyWebWorker } from 'easy-web-worker/createStaticEasyWebWorker';
889
+
890
+ const { onMessage } = createStaticEasyWebWorker<number, number>((message) => {
891
+ message.resolve(message.payload * 2);
892
+ });
893
+
894
+ onMessage<string, string>('uppercase', (message) => {
895
+ message.resolve(message.payload.toUpperCase());
896
+ });
461
897
  ```
462
898
 
463
- And how to use this?
899
+ ```ts
900
+ // main.ts
901
+ import { createEasyWebWorker } from 'easy-web-worker';
902
+
903
+ const worker = createEasyWebWorker<number, number>(source);
464
904
 
465
- ```TS
466
- worker.send({
467
- collection: [{ name: 'julio perez' }, { name: 'carol starling' }, { name: 'goku' }, { name: { firstname: 'johnny' } }],
468
- filter: 'johnny',
469
- reportProgress: true,
470
- }).onProgress((progressPercentage) => console.log(progressPercentage))
471
- .then((filtered: any[]) => console.log(filtered));
905
+ await worker.send(21); // default handler
906
+ await worker.sendToMethod<string, string>('uppercase', 'hello'); // result type first
472
907
  ```
473
908
 
474
- the output should be:
475
- => 25
476
- => 50
477
- => 75
478
- => 100
479
- => [{ name: { firstname: 'johnny' } }]
909
+ #### Typed methods for an existing Worker
480
910
 
481
- Of course this is a very tiny array, but is just to give you and idea, actually you also could make fetch requests into workers... give it a try.
911
+ A Worker written with the message API can still be called with typed methods. Describe them:
482
912
 
483
- # Methods
913
+ ```ts
914
+ const worker = createWorker<{ uppercase: (text: string) => string }>(source);
484
915
 
485
- ### `EasyWebWorker.reboot(reason?: unknown): CancelableCancelablePromise<void>[]`
916
+ await worker.uppercase('hello');
917
+ ```
486
918
 
487
- This method will reboot the worker and cancel all the messages in the queue.
919
+ > **On the website:** [Message API](https://johnny-quesada-developer.github.io/easy-web-worker/docs/message-api/) · [API reference](https://johnny-quesada-developer.github.io/easy-web-worker/docs/api-reference/)
488
920
 
489
- - `reason` - (optional) reason why the worker will be restarted.
921
+ ---
490
922
 
491
- Returns an array of promises that are resolved with the rejection reason provided when the messages are canceled.
923
+ ## Advanced Patterns
492
924
 
493
- Example usage:
925
+ ### Latest request wins
494
926
 
495
- ```typescript
496
- const worker = createEasyWebWorker<string, string>(({ onMessage }) => {
497
- onMessage((message) => {
498
- message.resolve(`Received message: ${message.payload}`);
499
- });
500
- });
927
+ For search, previews and autocomplete, an older request is worthless once a newer one exists.
501
928
 
502
- const messagePromise = worker.send('Hello!');
929
+ ```ts
930
+ const search = createEasyWebWorker<string, SearchResult[]>(searchBody);
503
931
 
504
- worker.reboot('Worker was restarted');
932
+ input.oninput = async () => {
933
+ // cancels what is pending, then sends the new message
934
+ const results = await search.override(input.value, 'Superseded');
505
935
 
506
- // The message promise will be rejected with the reason 'Worker was restarted'
936
+ render(results);
937
+ };
507
938
  ```
508
939
 
509
- ### override(payload?, reason?, config?): CancelablePromise
940
+ `overrideAfterCurrent` lets the call in progress finish and replaces only what is waiting behind it.
510
941
 
511
- Cancel all current messages and send a new one.
942
+ ### Stop work that never pauses
512
943
 
513
- ### cancelAll(reason?: unknown): CancelablePromise<void>[]
944
+ A Worker stuck in a synchronous loop cannot receive a cancellation. Restart it instead:
514
945
 
515
- Cancels all messages that are currently waiting to be processed by the worker.
946
+ ```ts
947
+ import { unwrap } from 'easy-web-worker/createWorker';
516
948
 
517
- - `reason` - (optional) The reason for the cancellation.
949
+ // terminates the Workers, rejects the pending calls, starts fresh ones
950
+ await unwrap(math).cancelAll('Taking too long', { force: true });
951
+ ```
518
952
 
519
- Returns an array of promises that are resolved with the rejection reason provided when the messages are canceled.
953
+ ### Clean up when the owner goes away
520
954
 
521
- ### overrideAfterCurrent(payload?, reason?, config?): CancelablePromise
955
+ ```ts
956
+ await unwrap(math).dispose(); // typed workers
957
+ await worker.dispose(); // EasyWebWorker
958
+ ```
959
+
960
+ `dispose` cancels the pending calls, revokes the generated Worker URL when there is one, and terminates the Workers.
961
+
962
+ > **On the website:** [Cancellation and progress](https://johnny-quesada-developer.github.io/easy-web-worker/docs/cancellation-and-progress/) · [Testing](https://johnny-quesada-developer.github.io/easy-web-worker/docs/testing/) · [Troubleshooting](https://johnny-quesada-developer.github.io/easy-web-worker/docs/troubleshooting/)
963
+
964
+ ---
965
+
966
+ ## Documentation and examples
967
+
968
+ Everything in this README runs for real on the [easy-web-worker website](https://johnny-quesada-developer.github.io/easy-web-worker/): real Web Workers, in your browser, against the same files that are published to npm.
969
+
970
+ ### See it before you install it
971
+
972
+ | Live example | What you will see |
973
+ | --- | --- |
974
+ | [Keep the page responsive](https://johnny-quesada-developer.github.io/easy-web-worker/examples/keep-the-page-responsive/) | The same computation on the main thread and in a Worker. One freezes the page, the other does not. |
975
+ | [Progress and cancellation](https://johnny-quesada-developer.github.io/easy-web-worker/examples/progress-and-cancellation/) | A long search that reports how far it is, and stops inside the Worker when you cancel it. |
976
+ | [Worker pool](https://johnny-quesada-developer.github.io/easy-web-worker/examples/worker-pool/) | Three searches with one Worker and then with three. Same answers, sooner. |
977
+ | [Transferable buffers](https://johnny-quesada-developer.github.io/easy-web-worker/examples/transferable-buffers/) | A large buffer moved to the Worker instead of copied. |
978
+ | [Worker without a file](https://johnny-quesada-developer.github.io/easy-web-worker/examples/worker-without-a-file/) | A Worker created from a function, with no extra file and no bundler setup. |
979
+
980
+ Every example shows its full source next to the running result.
981
+
982
+ ### Then go deeper
522
983
 
523
- Cancel all the messages but the current execution and add a new message
984
+ | Guide | What you will find |
985
+ | --- | --- |
986
+ | [Getting started](https://johnny-quesada-developer.github.io/easy-web-worker/docs/getting-started/) | Define your first worker and call it from the main thread. |
987
+ | [Guides](https://johnny-quesada-developer.github.io/easy-web-worker/docs/) | Typed workers, cancellation, progress, transferables, pools and bundler setup. |
988
+ | [API reference](https://johnny-quesada-developer.github.io/easy-web-worker/docs/api-reference/) | `defineWorker`, `createWorker`, `unwrap`, the message and the configuration. |
989
+ | [Testing](https://johnny-quesada-developer.github.io/easy-web-worker/docs/testing/) | Test worker logic as plain functions, and the calling code without a Worker. |
990
+ | [Troubleshooting](https://johnny-quesada-developer.github.io/easy-web-worker/docs/troubleshooting/) | Symptoms, causes and fixes. |
991
+ | [Platform and versions](https://johnny-quesada-developer.github.io/easy-web-worker/docs/platform-and-versions/) | Browser support and what changes when upgrading. |
524
992
 
525
- ### send(payload?, reason?, config?): CancelablePromise
993
+ ---
526
994
 
527
- Sends a message to the worker
995
+ ## Built to grow with your application
996
+
997
+ | Without `easy-web-worker` | With `easy-web-worker` |
998
+ | ------------------------------------ | ---------------------------------------- |
999
+ | `postMessage()` protocols | Functions |
1000
+ | Manual request IDs | Automatic routing |
1001
+ | Response matching | Promises |
1002
+ | Custom error transport | Throw / catch |
1003
+ | Custom cancellation protocol | `task.cancel()` |
1004
+ | Custom progress messages | `.onProgress()` |
1005
+ | Hand-maintained TypeScript contracts | Inferred types |
1006
+ | Manual Worker orchestration | Built-in pools |
1007
+ | Manual lifecycle handling | Warm-up, keep-alive and idle termination |
1008
+
1009
+ The Worker is still a real Web Worker, and the browser still provides the isolation and the extra thread. `easy-web-worker` removes what you would otherwise build around it.
1010
+
1011
+ > **Write Worker code like an API, call it like normal asynchronous JavaScript, and scale it without rebuilding the communication layer.**
1012
+
1013
+ ---
1014
+
1015
+ ## Get Started Now
1016
+
1017
+ ```bash
1018
+ npm install easy-web-worker
1019
+ ```
1020
+
1021
+ Then in your Worker file:
1022
+
1023
+ ```ts
1024
+ import { defineWorker } from 'easy-web-worker/defineWorker';
528
1025
 
529
- - `payload` - (optional) The message payload.
530
- - `options` - (optional) Additional send options.
1026
+ const fibonacci = (n: number): number =>
1027
+ n <= 1 ? n : fibonacci(n - 1) + fibonacci(n - 2);
531
1028
 
532
- **_Thanks for reading, hope this help someone_**
1029
+ const worker = defineWorker(() => ({ fibonacci }));
1030
+
1031
+ export type MathWorker = typeof worker;
1032
+ ```
1033
+
1034
+ And in your app:
1035
+
1036
+ ```ts
1037
+ import { createWorker } from 'easy-web-worker/createWorker';
1038
+ import type { MathWorker } from './math.worker';
1039
+
1040
+ const math = createWorker<MathWorker>(source);
1041
+
1042
+ console.log(await math.fibonacci(40)); // 102334155
1043
+ ```
1044
+
1045
+ Continue with the [guides and interactive examples](https://johnny-quesada-developer.github.io/easy-web-worker/) to build your next worker.
1046
+
1047
+ ---
533
1048
 
534
1049
  ## Collaborators
535
1050
 
536
- [![Image Johnny Quesada](https://avatars.githubusercontent.com/u/62082152?v=4&s=150)](https://github.com/johnny-quesada-developer)
537
- &nbsp;&nbsp;&nbsp;&nbsp;[![Image Gabriele Cirulli](https://avatars.githubusercontent.com/u/886011?v=4&s=150)](https://github.com/gabrielecirulli)
1051
+ <div align="center">
1052
+
1053
+ <table>
1054
+ <tr>
1055
+ <td align="center">
1056
+ <a href="https://github.com/johnny-quesada-developer">
1057
+ <img src="https://avatars.githubusercontent.com/u/62082152?v=4&s=150" width="100" alt="Johnny Quesada" />
1058
+ <br />
1059
+ <sub><b>Johnny Quesada</b></sub>
1060
+ </a>
1061
+ </td>
1062
+ <td align="center">
1063
+ <a href="https://github.com/gabrielecirulli">
1064
+ <img src="https://avatars.githubusercontent.com/u/886011?v=4&s=150" width="100" alt="Gabriele Cirulli" />
1065
+ <br />
1066
+ <sub><b>Gabriele Cirulli</b></sub>
1067
+ </a>
1068
+ </td>
1069
+ </tr>
1070
+ </table>
1071
+
1072
+ </div>
1073
+
1074
+ ---
1075
+
1076
+ ## Built by Johnny Quesada
1077
+
1078
+ `easy-web-worker` exists because using another thread should not require building a messaging framework first.
1079
+
1080
+ If it makes your codebase simpler, consider starring the project. It helps other developers discover it.
1081
+
1082
+ [**Star on GitHub**](https://github.com/johnny-quesada-developer/easy-web-worker) · [**Read the docs**](https://johnny-quesada-developer.github.io/easy-web-worker/docs/) · [**Run the examples**](https://johnny-quesada-developer.github.io/easy-web-worker/examples/) · [**Report an issue**](https://github.com/johnny-quesada-developer/easy-web-worker/issues)
1083
+
1084
+ ## Earlier resources
1085
+
1086
+ Demos and walkthroughs from previous versions, kept for reference. For the current API, start with the
1087
+ [documentation website](https://johnny-quesada-developer.github.io/easy-web-worker/).
538
1088
 
539
- [Johnny Quesada](https://github.com/johnny-quesada-developer) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[Gabriele Cirulli](https://github.com/gabrielecirulli)
1089
+ - [Original text diff demo](https://johnny-quesada-developer.github.io/easy-web-workers-example/) and its [source](https://github.com/johnny-quesada-developer/easy-web-workers-example)
1090
+ - [Original video walkthrough](https://www.youtube.com/watch?v=CK-Uri9lDOE)
1091
+ - [Original CodePen example](https://codepen.io/johnnynabetes/full/wvOvygW)