Skip to content

dominique-mueller/create-react-app-typescript-web-worker-setup

Repository files navigation

create-react-app-typescript-web-worker-setup

Using Web Workers in a TypeScript React project based on create-react-app.




How to use Web Workers in a TypeScript 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.


1. Install dependencies

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"
    }
  }

2. Customize the build process

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",
  }

3. Create and use a Web Worker

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:


A note on testing

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.



Commands

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