'Using Web Workers with React and Webpack 5
I have been trying to use Web Workers with react for a few days and I ran into a few issues.
I started with a create-react-app using webpack 4. I could use a web worker using this tutorial: https://javascript.plainenglish.io/web-worker-in-react-9b2efafe309c which loads a WebWorker that way:
export default class WorkerBuilder extends Worker {
constructor(worker) {
const code = worker.toString();
const blob = new Blob([`(${code})()`]);
return new Worker(URL.createObjectURL(blob));
}
}
Unfortunately I need to use a lib from my worker (d3-delaunay) and webpack gave me an error when I tried to do so (I think it changed its path).
I heard about worker-loader and I learned that this had been deprecated since Web Worker importing is now built in webpack 5.
I updated my app to WebPack 5 (as explained in the create-react-app wiki), this was not easy since a lot of my dependencies broke.
But when I tried to load my WebWorker as explained here: https://webpack.js.org/guides/web-workers/ this didn't work at all (no errors).
That's my code:
console.log("hello");
const worker = new Worker(new URL('./world.worker.js', import.meta.url));
worker.postMessage({
question:
'The Answer to the Ultimate Question of Life, The Universe, and Everything.',
});
worker.onmessage = ({ data: { answer } }) => {
console.log(answer);
};
console.log("world");
And my world.worker.js:
import { Delaunay } from "d3-delaunay";
// eslint-disable-next-line import/no-anonymous-default-export
export default () => {
// eslint-disable-next-line no-restricted-globals
self.onmessage = (message) => {
console.log("yolo");
};
};
But my only output was:
hello
world
My worker didn't seem to work.
Solution 1:[1]
I finally figured this out, I had the same issue. The biggest issue is that you should not use export default in the web worker. Here is how I got it to work.
You need to instantiate the worker using React.useMemo. This is so only one worker is created.
const worker = React.useMemo(() => new Worker(new URL('./world.worker.js', import.meta.url)), []);
To get the response, you need to know when the worker object is updated. To do this, you can use React.useEffect.
React.useEffect(() => {
worker.onmessage = ({ data: { answer } }) => {
console.log(answer);
return () => worker.terminate();
}
}, [worker]);
Finally, your world.worker.js file has two issues. The first one is that it doesn't actually return anything. You will need to add a call to postMessage. The second issue which is the one I had trouble figuring out, is that you should not wrap the function in export default. I think this is because webpack will wrap it for you. See below for the correct world.worker.js code.
import { Delaunay } from "d3-delaunay";
self.onmessage = ({ data: { question } }) => {
postMessage({
answer: 42,
});
};
Solution 2:[2]
to use es6 modules in workers, you need to load it in script mode using this syntax :
new Worker('worker-path.js', {type: 'module'})
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | infamed |
Solution 2 | Huboh |