Web Workers are incredibly useful if you need to do some heavy processing without hanging the UI.
This tutorial is aimed at implementing web workers in a create-react-app
project without ejecting and avoiding TypeScript errors. If you want to know how to set up a React project you should check out my previous post
Web Workers
In a normal web app, all the JS is running in the main thread, so this means if you execute a very intensive function you could potentially hang the UI until the function has finished, which is not a good user experience. This can be avoided by using web workers because they run scripts in background threads.
Web Workers execute a script from a static file that is separate from your main JS file. Previously, this meant that you would need to eject from create-react-app
in order to modify the webpack config and export a separate file. However, with recent developments you can now use web workers in CRA without ejecting!
comlink-loader
There's a great package called comlink-loader that allows you to call functions from a web worker like a class method, and skip the postMessage
and onmessage
exchange described in the docs.
Install the package:
npm install -D comlink-loader
or
yarn add comlink-loader
Create the worker
Create a directory called worker
and it will contain three files:
custom.d.ts
index.ts
worker.ts
worker.ts
is where you will keep the functions that you want executed in a background thread:
/* ./worker/worker.ts */
export function processData(data: string): string {
// Process the data without stalling the UI
return data;
}
custom.d.ts
is a TypeScript declaration file to avoid compilation errors.
For type safety, add your functions from worker.ts
to the class as a method and the return type should be wrapped in a promise.
/* ./worker/custom.d.ts */
declare module 'comlink-loader!*' {
class WebpackWorker extends Worker {
constructor();
// Add any custom functions to this class.
// Make note that the return type needs to be wrapped in a promise.
processData(data: string): Promise<string>;
}
export = WebpackWorker;
}
index.ts
contains the inline loader so you don't have to eject and modify the webpack config
/* ./worker/index.ts */
// eslint-disable-next-line
import Worker from 'comlink-loader!./worker'; // inline loader
export default Worker;
Depending on your linter rules, you may get a build error:
Unexpected '!' in 'comlink-loader!./worker'. Do not use import syntax to configure webpack loader
This is not a problem and the rule can be disabled for the line (see lines 3 and 4 above).
Import into React app
Now the fun part, simply import the worker into your React app and create a new instance to start using it:
/* App.tsx */
import Worker from './worker'
// Create new instance
const instance = new Worker();
const onClick = () => {
const data = 'Some data';
return new Promise(async resolve => {
// Use a web worker to process the data
const processed = await instance.processData(data);
resolve(processed);
});
};
Conclusion
It is quite simple to implement a web worker into your React app, and it will be a major improvement to the user experience if your app does a lot of heavy processing.
If you have any suggestions or questions feel free to leave a comment.
Top comments (7)
Hi, thanks for this effort.
Can you start an expressjs server in a web worker in this infrastructure?
A demo would be of great appreciation :-)
Just send a PR :-)
github.com/cchanxzy/tutorial-creat...
Hi, thanks for this effort.
Can you start an expressjs server in a web worker in this infrastructure?
A demo would be of great appreciation :-)
Hi, thanks for this effort.
Can you start an expressjs server in a web worker in this infrastructure?
A demo would be of great appreciation :-)
Just send a PR :-)
Flawless ๐
But I wish there were a way to proxy DOM nodes to perform calculations
This is great, do you have an example repo for this? :D
Hi, sorry I only saw this comment yesterday.
Hereโs a repo I created as a demo:
github.com/cchanxzy/tutorial-creat...
Hope you and others find it useful.
seems to fail when running in the production build ! did you encounter such an issue?
Error : Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'apply')