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.
- package/EasyWebWorker.cjs +1 -0
- package/EasyWebWorker.d.ts +2 -2
- package/EasyWebWorker.js +1 -1
- package/EasyWebWorker.mjs +1 -0
- package/EasyWebWorkerMessage.cjs +1 -0
- package/EasyWebWorkerMessage.d.ts +2 -2
- package/EasyWebWorkerMessage.js +1 -1
- package/EasyWebWorkerMessage.mjs +1 -0
- package/README.md +891 -339
- package/StaticEasyWebWorker.cjs +1 -0
- package/StaticEasyWebWorker.d.ts +1 -1
- package/StaticEasyWebWorker.js +1 -1
- package/StaticEasyWebWorker.mjs +1 -0
- package/buildWorker.cjs +1 -0
- package/buildWorker.d.ts +7 -0
- package/buildWorker.js +1 -0
- package/buildWorker.mjs +1 -0
- package/bundle.cjs +1 -0
- package/bundle.js +1 -1
- package/bundle.mjs +1 -0
- package/createBlobWorker.cjs +2 -0
- package/createBlobWorker.d.ts +1 -0
- package/createBlobWorker.js +2 -1
- package/createBlobWorker.mjs +2 -0
- package/createEasyWebWorker.cjs +1 -0
- package/createEasyWebWorker.js +1 -1
- package/createEasyWebWorker.mjs +1 -0
- package/createStaticEasyWebWorker.cjs +1 -0
- package/createStaticEasyWebWorker.js +1 -1
- package/createStaticEasyWebWorker.mjs +1 -0
- package/createWorker.cjs +4 -0
- package/createWorker.d.ts +53 -0
- package/createWorker.js +4 -0
- package/createWorker.mjs +4 -0
- package/defineWorker.cjs +1 -0
- package/defineWorker.d.ts +31 -0
- package/defineWorker.js +1 -0
- package/defineWorker.mjs +1 -0
- package/getDefineWorkerTemplate.cjs +1 -0
- package/getDefineWorkerTemplate.d.ts +7 -0
- package/getDefineWorkerTemplate.js +1 -0
- package/getDefineWorkerTemplate.mjs +1 -0
- package/getWorkerTemplate.cjs +1 -0
- package/getWorkerTemplate.d.ts +3 -7
- package/getWorkerTemplate.js +1 -1
- package/getWorkerTemplate.mjs +1 -0
- package/index.d.ts +3 -0
- package/package.json +61 -68
- package/types.cjs +1 -0
- package/types.d.ts +115 -0
- package/types.js +1 -1
- package/types.mjs +0 -0
- package/uniqueId.cjs +1 -0
- package/uniqueId.js +1 -1
- package/uniqueId.mjs +1 -0
- package/unwrap.cjs +1 -0
- package/unwrap.d.ts +16 -0
- package/unwrap.js +1 -0
- package/unwrap.mjs +1 -0
- package/webpack.config.js +0 -86
package/README.md
CHANGED
|
@@ -1,539 +1,1091 @@
|
|
|
1
1
|
# easy-web-worker 🌟
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<div align="center">
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+

|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
</div>
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
<div align="center">
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
## **Web Workers are powerful. Now they're also easy.**
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
Let `easy-web-worker` handle everything between the threads.
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
You keep the power of native Web Workers without dealing with all the complexity that usually comes with them.
|
|
16
16
|
|
|
17
|
-
|
|
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
|
-
|
|
19
|
+
[](https://www.npmjs.com/package/easy-web-worker)
|
|
20
|
+
[](https://www.npmjs.com/package/easy-web-worker)
|
|
21
|
+
[](https://github.com/johnny-quesada-developer/easy-web-worker/blob/main/LICENSE)
|
|
20
22
|
|
|
21
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
52
|
+
* The type of the worker is inferred from it's declaration,
|
|
53
|
+
* so everything is typed on the main thread.
|
|
54
54
|
*/
|
|
55
|
-
|
|
55
|
+
const mathWorker = createWorker<typeof MathWorker>(mathWorkerUrl);
|
|
56
|
+
|
|
57
|
+
const result = await mathWorker.fibonacci(40); // 102334155
|
|
56
58
|
```
|
|
57
59
|
|
|
58
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
64
|
-
|
|
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
|
-
|
|
86
|
+
```ts
|
|
87
|
+
// The main thread remains 100% responsive
|
|
88
|
+
await text.countWords(article); // CancelablePromise<number>
|
|
89
|
+
```
|
|
67
90
|
|
|
68
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
79
|
-
const worker = createEasyWebWorker(workerSource, {
|
|
80
|
-
workerOptions: {
|
|
81
|
-
type: 'module',
|
|
104
|
+
return buildReport(await response.text());
|
|
82
105
|
},
|
|
83
|
-
});
|
|
106
|
+
}));
|
|
107
|
+
```
|
|
84
108
|
|
|
85
|
-
|
|
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
|
-
|
|
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
|
-
|
|
95
|
-
|
|
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
|
-
|
|
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
|
-
|
|
123
|
+
### **Progress on the same call**
|
|
101
124
|
|
|
102
|
-
|
|
125
|
+
Long-running methods report progress through the promise they already returned.
|
|
103
126
|
|
|
104
127
|
```ts
|
|
105
|
-
|
|
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
|
-
|
|
135
|
+
### **Every core, one option**
|
|
116
136
|
|
|
117
|
-
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
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
|
-
|
|
128
|
-
message.reportProgress(10, metadata, []); // all the methods allows you to send Transferable[]
|
|
148
|
+
### **Move large buffers instead of copying them**
|
|
129
149
|
|
|
130
|
-
|
|
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
|
-
|
|
134
|
-
|
|
135
|
-
|
|
152
|
+
```ts
|
|
153
|
+
const blurred = await images.blur(buffer, [buffer]);
|
|
154
|
+
```
|
|
136
155
|
|
|
137
|
-
|
|
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
|
-
|
|
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
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
-
|
|
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
|
-
|
|
189
|
+
---
|
|
164
190
|
|
|
165
|
-
|
|
191
|
+
## Installation
|
|
166
192
|
|
|
167
|
-
|
|
193
|
+
```bash
|
|
194
|
+
npm install easy-web-worker
|
|
195
|
+
```
|
|
168
196
|
|
|
169
|
-
|
|
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
|
-
|
|
200
|
+
---
|
|
172
201
|
|
|
173
|
-
|
|
202
|
+
## Quick Start
|
|
174
203
|
|
|
175
|
-
|
|
176
|
-
const backgroundWorker = createEasyWebWorker<string, string>(({ onMessage }) => {
|
|
177
|
-
onMessage((message) => {
|
|
178
|
-
const { payload } = message;
|
|
204
|
+
### Your first typed worker
|
|
179
205
|
|
|
180
|
-
|
|
181
|
-
|
|
206
|
+
```ts
|
|
207
|
+
// math.worker.ts
|
|
208
|
+
import { defineWorker } from 'easy-web-worker/defineWorker';
|
|
182
209
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
const { payload } = message;
|
|
210
|
+
const fibonacci = (n: number): number =>
|
|
211
|
+
n <= 1 ? n : fibonacci(n - 1) + fibonacci(n - 2);
|
|
186
212
|
|
|
187
|
-
|
|
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
|
-
|
|
192
|
-
const messageResult = await backgroundWorker.send('hello!');
|
|
276
|
+
cancelButton.onclick = () => task.cancel('Canceled by user');
|
|
193
277
|
|
|
194
|
-
|
|
195
|
-
const messageResult2 = await backgroundWorker.sendToMethod('doSomething', 2);
|
|
278
|
+
const primes = await task;
|
|
196
279
|
```
|
|
197
280
|
|
|
198
|
-
|
|
281
|
+
Progress and cancellation belong to the call itself, not to a second channel.
|
|
199
282
|
|
|
200
|
-
|
|
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
|
-
|
|
203
|
-
- If IPayload is null, the _send_ method will not require parameters
|
|
285
|
+
---
|
|
204
286
|
|
|
205
|
-
|
|
287
|
+
## Core Features Deep Dive
|
|
206
288
|
|
|
207
|
-
|
|
208
|
-
const message = 'Hello';
|
|
289
|
+
### Typed workers with `defineWorker` and `createWorker`
|
|
209
290
|
|
|
210
|
-
|
|
211
|
-
|
|
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
|
-
|
|
354
|
+
pending.set(id, resolve);
|
|
355
|
+
worker.postMessage({ id, n });
|
|
214
356
|
});
|
|
215
|
-
}).send('hello!');
|
|
216
357
|
```
|
|
217
358
|
|
|
218
|
-
|
|
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
|
-
|
|
364
|
+
// report.worker.ts
|
|
365
|
+
const worker = defineWorker(() => ({
|
|
366
|
+
parseReport: async (url: string) => {
|
|
367
|
+
const response = await fetch(url);
|
|
222
368
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
369
|
+
if (!response.ok) {
|
|
370
|
+
throw new Error(`Report not available: ${response.status}`);
|
|
371
|
+
}
|
|
226
372
|
|
|
227
|
-
|
|
373
|
+
return buildReport(await response.text());
|
|
228
374
|
},
|
|
229
|
-
|
|
230
|
-
primitiveParameters: [message],
|
|
231
|
-
}
|
|
232
|
-
).send('hello!');
|
|
375
|
+
}));
|
|
233
376
|
```
|
|
234
377
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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
|
-
|
|
387
|
+
An error rejects that one call. The Worker keeps serving the next one.
|
|
240
388
|
|
|
241
|
-
|
|
389
|
+
#### Three ways to write a method
|
|
242
390
|
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
//
|
|
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
|
-
|
|
248
|
-
|
|
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
|
-
|
|
251
|
-
message.reject(new Error());
|
|
440
|
+
#### Cancellation
|
|
252
441
|
|
|
253
|
-
|
|
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
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
-
|
|
262
|
-
|
|
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
|
-
|
|
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
|
-
|
|
526
|
+
---
|
|
269
527
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
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
|
-
|
|
552
|
+
`type: 'module'` is required when the Worker file uses `import` or `export`, which every `defineWorker` file does.
|
|
277
553
|
|
|
278
|
-
|
|
554
|
+
#### Vite: development and production
|
|
279
555
|
|
|
280
|
-
|
|
556
|
+
In development, Vite serves the TypeScript file. A production build needs the Worker URL that Vite generates:
|
|
281
557
|
|
|
282
|
-
```
|
|
283
|
-
|
|
284
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
599
|
+
#### Your own pool
|
|
301
600
|
|
|
302
|
-
|
|
601
|
+
Pass an array of native Workers and calls are distributed across them:
|
|
303
602
|
|
|
304
|
-
|
|
603
|
+
```ts
|
|
604
|
+
const createMathWorker = () =>
|
|
605
|
+
new Worker(new URL('./math.worker.ts', import.meta.url), { type: 'module' });
|
|
305
606
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
607
|
+
const math = createWorker<MathWorker>([
|
|
608
|
+
createMathWorker(),
|
|
609
|
+
createMathWorker(),
|
|
610
|
+
createMathWorker(),
|
|
611
|
+
]);
|
|
309
612
|
```
|
|
310
613
|
|
|
311
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
636
|
+
---
|
|
327
637
|
|
|
328
|
-
|
|
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
|
-
|
|
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
|
-
|
|
337
|
-
const { onMessage } = createStaticEasyWebWorker();
|
|
642
|
+
#### Scale on demand
|
|
338
643
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
}, 5000);
|
|
644
|
+
```ts
|
|
645
|
+
const math = createWorker<MathWorker>(source, {
|
|
646
|
+
maxWorkers: 4,
|
|
343
647
|
});
|
|
344
648
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
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
|
-
|
|
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
|
-
|
|
356
|
-
|
|
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
|
|
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
|
-
|
|
363
|
-
|
|
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
|
-
|
|
735
|
+
createWorker(() => ({
|
|
736
|
+
// `greeting` does not exist inside the Worker
|
|
737
|
+
greet: (name: string) => `${greeting} ${name}`,
|
|
738
|
+
}));
|
|
366
739
|
```
|
|
367
740
|
|
|
368
|
-
|
|
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
|
-
|
|
743
|
+
#### Parameters and the scope of the Worker
|
|
371
744
|
|
|
372
|
-
|
|
745
|
+
The second parameter is the global scope of the Worker. Static values arrive in `context.primitiveParameters`:
|
|
373
746
|
|
|
374
|
-
```
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
389
|
-
|
|
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
|
-
|
|
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
|
-
|
|
795
|
+
#### Import scripts
|
|
396
796
|
|
|
397
|
-
|
|
797
|
+
External libraries load with the `scripts` option and appear in the scope of the Worker:
|
|
398
798
|
|
|
399
799
|
```ts
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
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
|
-
|
|
820
|
+
No Worker file is required, and the script does not become part of your application bundle.
|
|
409
821
|
|
|
410
|
-
|
|
822
|
+
#### Compose a Worker from several functions
|
|
411
823
|
|
|
412
|
-
|
|
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
|
-
```
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
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
|
-
|
|
422
|
-
|
|
423
|
-
|
|
833
|
+
() => ({
|
|
834
|
+
double: (value: number) => value * 2,
|
|
835
|
+
describe: () => 'first',
|
|
836
|
+
}),
|
|
424
837
|
|
|
425
|
-
|
|
426
|
-
|
|
838
|
+
(_helpers, context) => {
|
|
839
|
+
const round = context.round as (value: number) => number;
|
|
427
840
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
.
|
|
841
|
+
return {
|
|
842
|
+
average: (values: number[]) =>
|
|
843
|
+
round(values.reduce((total, value) => total + value, 0) / values.length),
|
|
431
844
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
845
|
+
// repeated method: the last function wins
|
|
846
|
+
describe: () => 'last',
|
|
847
|
+
};
|
|
848
|
+
},
|
|
849
|
+
]);
|
|
435
850
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
860
|
+
`createEasyWebWorker` builds a Worker from a function too, with messages instead of methods:
|
|
453
861
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
}
|
|
862
|
+
```ts
|
|
863
|
+
import { createEasyWebWorker } from 'easy-web-worker';
|
|
457
864
|
|
|
458
|
-
|
|
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
|
-
|
|
899
|
+
```ts
|
|
900
|
+
// main.ts
|
|
901
|
+
import { createEasyWebWorker } from 'easy-web-worker';
|
|
902
|
+
|
|
903
|
+
const worker = createEasyWebWorker<number, number>(source);
|
|
464
904
|
|
|
465
|
-
|
|
466
|
-
worker.
|
|
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
|
-
|
|
475
|
-
=> 25
|
|
476
|
-
=> 50
|
|
477
|
-
=> 75
|
|
478
|
-
=> 100
|
|
479
|
-
=> [{ name: { firstname: 'johnny' } }]
|
|
909
|
+
#### Typed methods for an existing Worker
|
|
480
910
|
|
|
481
|
-
|
|
911
|
+
A Worker written with the message API can still be called with typed methods. Describe them:
|
|
482
912
|
|
|
483
|
-
|
|
913
|
+
```ts
|
|
914
|
+
const worker = createWorker<{ uppercase: (text: string) => string }>(source);
|
|
484
915
|
|
|
485
|
-
|
|
916
|
+
await worker.uppercase('hello');
|
|
917
|
+
```
|
|
486
918
|
|
|
487
|
-
|
|
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
|
-
|
|
921
|
+
---
|
|
490
922
|
|
|
491
|
-
|
|
923
|
+
## Advanced Patterns
|
|
492
924
|
|
|
493
|
-
|
|
925
|
+
### Latest request wins
|
|
494
926
|
|
|
495
|
-
|
|
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
|
-
|
|
929
|
+
```ts
|
|
930
|
+
const search = createEasyWebWorker<string, SearchResult[]>(searchBody);
|
|
503
931
|
|
|
504
|
-
|
|
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
|
-
|
|
936
|
+
render(results);
|
|
937
|
+
};
|
|
507
938
|
```
|
|
508
939
|
|
|
509
|
-
|
|
940
|
+
`overrideAfterCurrent` lets the call in progress finish and replaces only what is waiting behind it.
|
|
510
941
|
|
|
511
|
-
|
|
942
|
+
### Stop work that never pauses
|
|
512
943
|
|
|
513
|
-
|
|
944
|
+
A Worker stuck in a synchronous loop cannot receive a cancellation. Restart it instead:
|
|
514
945
|
|
|
515
|
-
|
|
946
|
+
```ts
|
|
947
|
+
import { unwrap } from 'easy-web-worker/createWorker';
|
|
516
948
|
|
|
517
|
-
|
|
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
|
-
|
|
953
|
+
### Clean up when the owner goes away
|
|
520
954
|
|
|
521
|
-
|
|
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
|
-
|
|
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
|
-
|
|
993
|
+
---
|
|
526
994
|
|
|
527
|
-
|
|
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
|
-
|
|
530
|
-
|
|
1026
|
+
const fibonacci = (n: number): number =>
|
|
1027
|
+
n <= 1 ? n : fibonacci(n - 1) + fibonacci(n - 2);
|
|
531
1028
|
|
|
532
|
-
|
|
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
|
-
|
|
537
|
-
|
|
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
|
-
[
|
|
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)
|