Using Web Workers in a TypeScript React project based on create-react-app.
This project is an example React application that uses Web Workers. You can clone it and play around with it (see Commands).
The following sub-chapters explain how to setup Web Worker support in a create-react-app
project, and how to use Web Workers in your app -
both vanilla and using Comlink.
First of all, we need a few new dependencies. In particular:
- We need react-app-rewired to hook into the Webpack configuration that
react-scripts
uses under the hood (without having to eject the config) - We use the Webpack worker-loader to integrate Web Workers into our build process
Add both dependencies to your package.json
file and install them. For example:
{
"devDependencies": {
+ "react-app-rewired": "2.1.x",
+ "worker-loader": "3.0.x"
}
}
First, replace all mentions of react-scripts
within the scripts
area of your package.json
file by react-app-rewired
. This enables us
to tap into the build process in the next step. For example:
{
"scripts": {
- "start": "react-scripts start",
+ "start": "react-app-rewired start",
- "build": "react-scripts build",
+ "build": "react-app-rewired build",
- "test": "react-scripts test",
+ "test": "react-app-rewired test",
}
}
Then, create a file named react-app-rewired.config.js
(or whatever name you prefer) within the root folder of your project. This file will
be used by react-app-rewired
when the build process runs, and allows us to customize the underlying Webpack configuration before the build
runs. Fill it with the following content:
/**
* React App Rewired Config
*/
module.exports = {
// Update webpack config to use custom loader for worker files
webpack: (config) => {
// Note: It's important that the "worker-loader" gets defined BEFORE the TypeScript loader!
config.module.rules.unshift({
test: /\.worker\.ts$/,
use: {
loader: 'worker-loader',
options: {
// Use directory structure & typical names of chunks produces by "react-scripts"
filename: 'static/js/[name].[contenthash:8].js',
},
},
});
return config;
},
};
Finally, reference the react-app-rewired.config.js
file in your package.json
file by adding the following line:
{
+ "config-overrides-path": "react-app-rewired.config.js",
}
Now you can start using Web Workers! Two things are important here: Files that contain a Web Worker must end with *.worker.ts
, and they
must start with the following two lines of code in order to work nicely together with TypeScript:
declare const self: DedicatedWorkerGlobalScope;
export default {} as typeof Worker & { new (): Worker };
// Your code ...
In your application, you can import your Web Workers like a normal module, and instantiate them as a class. For example:
import MyWorker from './MyWorker.worker';
const myWorkerInstance: Worker = new MyWorker();
Implementation pointers:
Bonus: Using Comlink
Using Web Workers as is comes with the additional overhead of messaging between the main thread and the worker thread. Comlink is a library by Googlers that simplifies the usage of Web Workers by turning the message-based communication into a "remote function execution"-like system.
Within your React app, you can use Comlink just like you would expect. For example, expose your API within your worker:
import { expose } from 'comlink';
export default {} as typeof Worker & { new (): Worker };
// Define API
export const api = {
createMessage: (name: string): string => {
return `Hello ${name}!`;
},
};
// Expose API
expose(api);
Then, from within you main thread, wrap the instantiated worker and use the worker API (asynchronously):
import { wrap } from 'comlink';
import MyComlinkWorker, { api } from './MyComlinkWorker.worker';
// Instantiate worker
const myComlinkWorkerInstance: Worker = new MyComlinkWorker();
const myComlinkWorkerApi = wrap<typeof api>(myComlinkWorkerInstance);
// Call function in worker
myComlinkWorkerApi.createMessage('John Doe').then((message: string) => {
console.log(message);
});
Implementation pointers:
Soooo, Jest does not support Web Workers (see jest/#3449). Plus, Jest does not use our customized Webpack configuration anyways. Thus - using one of the many ways you can mock stuff in Jest - mock away Web Workers when testing code that instantes them / works with them.
The following commands are available:
Command | Description | CI |
---|---|---|
npm start |
Creates a development build, running in watch mode | |
npm run build |
Creates a production build | ✔️ |
npm run start:build |
Runs the production build | |
npm run test |
Executes all unit tests | ✔️ |
npm run test:watch |
Executes all unit tests, running in watch mode |