· 3 min read
Web Workers & CRA
Web Workers bring multi thread design patterns to the browser. This post describes how to bring the technology into Create React App without ejecting.
The Problem
Create React App (CRA from here on in) is a way of building React apps with no build configuration. It is the go-to community standard and provides many sensible defaults out the box. From Progressive Web App (PWA) best practices through to bundling and tree shaking, it is opinionated but flexible.
Web Workers (workers from here on in) are a technology that bring threading-like behaviour to the browser. Javascript is by design single thread, which means that instructions are executed in sequence and only one command can be processed at a time. This worked fine in the early days of the web when javascript was merely used to facilitate simple page interactions. However, modern web applications call for much more intensive operations to be carried out. This can easily start to overwhelm the main thread with application code and the browser's rendering and javascript engines competing for limited computation time and resources. Being able to transfer some of this work onto separate threads can help to keep computation-intensive web apps running smoothly.
There has been much debate on how to bring workers into CRA and maintain their philosophy of convention over configuration. So far there has been no consensus reached, so it is not currently an official part of CRA. However, there are currently a few methods to import web worker code. These include:
- Converting the worker scripts into a string and creating a Blob from the content which can be referenced via
URL.createObjectURL(). This can then be passed into thenew WebWorker(url)call; - Supplying the worker script as a resource in the
/publicfolder of the project; - Eject the project making the underlying configuration accessible.
The third solution is not ideal; the configurations are complex and you lose the ability to upgrade to newer version of CRA and benefit from improvements. The first two are acceptable solutions but both require additional processing for minification, dependencies and so forth. If you use Typescript in your CRA projects, this would also require separate compilation steps. Not ideal!
Solution
Searching the web for suitable alternatives, I stumbled across react-app-rewired. This package allows you to extend the CRA configuration without ejecting, so you can import the worker-loader package and link it up to the existing webpack configuration.
Firstly we need to install react-app-rewired and worker-loader:
npm install react-app-rewired worker-loader --save
Create a file at the root of the project named config-overrides.js. It should contain the following:
module.exports = function override(config, env) {
// make sure workers have the right global.
config.output.globalObject = 'this';
// compile workers.
config.module.rules.unshift({
test: /\.worker\.ts$/,
use: { loader: 'worker-loader' }
});
return config;
}
Next we create a web worker file with the name example.worker.ts this can be anywhere in the project. It can be named anything you want, just ensure that it has the .worker.ts extension.
// import dependencies as normal
import myDep from 'dependency';
// use the correct typings for the global self variable
// eslint must ignore this line else the build will fail to compile
const ctx = self as unknown as Worker; // eslint-disable-line
// use the worker APIs as normal
ctx.addEventListener('message', e => {
// do stuff here
});
We import the worker script from anywhere in our app and initiate the worker.
// import the worker using the worker-loader we have configured
import myWorker from './example.worker';
// initiate the worker and set the correct typings.
const worker = new myWorker() as Worker;
// use the worker APIs as normal
worker.postMessage(data, transferrables);