'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