$ npx create-react-app tutorial-webworker-with-react --typescript
$ cd tutorial-webworker-with-react/
$ npm install worker-loader --save-dev
export const heavyTask = (target: string) => {
console.log("hello,", target);
const startTime = Date.now();
while (Date.now() - startTime < 3000) {}
console.log("bye,", target);
return 42;
};
See full changes on commit: 774345f
const runOnUIThread = (e: any) => {
status = "running...";
heavyTask("UIThread");
status = "finished";
};
...
<button onClick={runOnUIThread}>on UI Thread</button>
import { heavyTask } from "./common";
onmessage = (e: any) => {
const result = heavyTask("webworker");
// @ts-ignore
postMessage(result);
};
Unless @ts-ignore, I saw following transpile error:
function postMessage(message: any, targetOrigin: string, transfer?: Transferable[] | undefined): void
Expected 2-3 arguments, but got 1.ts(2554)
lib.dom.d.ts(19636, 44): An argument for 'targetOrigin' was not provided.
However, adding second argument such as postMessage(result, "*")
causes following runtime error:
Uncaught TypeError: Failed to execute 'postMessage' on 'DedicatedWorkerGlobalScope':
No function was found that matched the signature provided.
If you hate @ts-ignore
, see another solution: "use cast instead of ts-ignore" 1d3c2a4
See full changes on commit: 0f5c964
// eslint-disable-next-line import/no-webpack-loader-syntax
import Worker from "worker-loader!./webworker";
export const runOnWebWorker = (e: any) => {
status = "running...";
const worker = new Worker();
worker.postMessage("heavyTask");
worker.onmessage = function(event: any) {
status = "finished";
};
};
...
<button onClick={runOnWebWorker}>on WebWorker</button>
NOTICE: You don't need to modifiy webpack.config.js if you use inlined webpack-loader-syntax
as above. I saw several people are confusing about that.
In development environment npm run start
, it works.
But you may see following error on IDE (for example VSCode): Cannot find module 'worker-loader!./webworker'.
The error also occurs in release environment npm run build
.
To fix the error, you need to add custom type definition.
See full changes on commit: a9c0c03
declare module "worker-loader!*" {
class WebpackWorker extends Worker {
constructor();
}
export default WebpackWorker;
}
If you didn't specify typeRoots
on tsconfig yet, you also need it. See the commit.
In this tutorial, I create new worker each call. If you push button before the previous task finishes, the new task runs concurrently.
export const runOnWebWorker = (e: any) => {
status = "running...";
const worker = new Worker();
worker.postMessage("heavyTask");
worker.onmessage = function(event: any) {
status = "finished";
};
};
If we re-use the worker object like below, the new message waits in queue until the previous task finishes.
const worker = new Worker();
worker.onmessage = function(event: any) {
status = "finished";
};
export const runOnWebWorker = (e: any) => {
status = "running...";
worker.postMessage("heavyTask");
};
Following code produce an error below.
const v = { inc: (x: number) => x + 1 };
worker.postMessage(v);
DataCloneError: Failed to execute 'postMessage' on 'Worker': x => x + 1 could not be cloned.
In case of a class instance with methods like below:
class Vec2D {
x: number;
y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
scale(s: number) {
this.x *= s;
this.y *= s;
return this;
}
}
the postMessage
success, but the methods are not passed to the worker.
// App.tsx
const v = new Vec2D(1, 2);
v.scale(2);
worker.postMessage(v); // OK
// webworker.ts
e.data.scale(3); // NG
// Uncaught TypeError: e.data.scale is not a function
// at onmessage (webworker.ts:4)
Commit: 588193c