Setup workers with Create React App and Typescript
February 14, 2020
Introduction
I was struggeling a lot setting up this, and to be honest, It’s a pain.
So here comes a brief summary of resources and steps to setup workers.
Necessary Packages and Tools
Worker Loader
It’s easy to setup a worker in Vanilla-JS and Create React App is prepared for this but if you want use your library code you will struggle on this issue. What you need is to install a worker-loader package, in my case I have used worker-loader.
$ yarn add worker-loader
To configure the loader you need to customize the webpack-configuration with the help of React App Rewired.
React App Rewired
You have to add a rule to the webpack.config.js!
For this reason you have to work with react-app-rewired or eject your configuration to change it.
$ yarn add react-app-rewired
To override the webpack-config you need to add a file ‘config-overrides.js’ to your root-folder of the project.
$ cat config-overrides.js
module.exports = function override(config, env) {
config.output.globalObject = 'this';
config.module.rules.push({
test: /\.worker\.js$/,
use: [
{loader: 'worker-loader'},
{loader: 'babel-loader'}
]
});
return config;
};
Also you should adapt your package.json’s script-scetion:
....
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"lint": "tslint --project . --fix",
"test": "react-app-rewired test",
"eject": "react-scripts eject"
},
....
Implementing the worker
The worker-code:
// /src/scenes/t03/components/content/observation/observation.worker.ts
// the filename must end on: .worker.ts or .worker.js
import calculateStatistics from '../../../../../services/statistics/calculateStatistics';
import {IObservationWorkerInput, IObservationWorkerResult} from './observation.worker.type';
export const CALCULATE_STATISTICS_INPUT = 'CALCULATE_STATISTICS_INPUT';
export const CALCULATE_STATISTICS_RESULT = 'CALCULATE_STATISTICS_RESULT';
const ctx: Worker = self as any;
// Respond to message from parent thread
ctx.addEventListener('message', (e) => {
if (!e) {
return;
}
const message: IObservationWorkerInput = e.data;
const {type, data} = message;
if (type === CALCULATE_STATISTICS_INPUT) {
const statistics = calculateStatistics(data.data, data.exclude);
postMessage({
type: CALCULATE_STATISTICS_RESULT,
data: statistics
});
}
});
The Type definitions:
import {IHobData, IStatistics} from './statistics';
export interface IObservationWorkerInput {
type: string;
data: IObservationInputData;
}
export interface IObservationWorkerResult {
type: string;
data: IStatistics;
}
export interface IObservationInputData {
data: IHobData;
exclude: string[];
}
The React-Component:
// /src/scenes/t03/components/content/observation/observation.ts
import React, {useEffect, useState} from 'react';
import {CALCULATE_STATISTICS_INPUT, CALCULATE_STATISTICS_RESULT} from './observation.worker';
....
....
let w: Worker | undefined;
// Wrapped function to avoid JEST-errors in test-environment
const loadWorker = () => {
let worker;
try {
// tslint:disable-next-line:no-var-requires
worker = require('worker-loader!./observation.worker');
} catch (e) {
if (process.env.NODE_ENV !== 'test') {
throw e;
}
}
return new worker() as Worker;
};
const observation = () => {
const [isCalculating, setIsCalculating] = useState<boolean>(false);
const [statistics, setStatistics] = useState<IStatistics | null>(null);
useEffect(() => {
// loads the worker when component mounts
w = loadWorker();
w.addEventListener('message', handleMessage);
// finishes on unmount
return () => {
if (w) {
// @ts-ignore
w.removeEventListener('message');
w.terminate();
}
};
}, []);
const calculate = () => {
if (w) {
setIsCalculating(true);
w.postMessage({
type: CALCULATE_STATISTICS_INPUT,
data: {
data: hobData,
exclude: excludedWells
}
});
}
}
const handleMessage = (m: any) => {
const message: IObservationWorkerResult = m.data;
if (message && message.type === CALCULATE_STATISTICS_RESULT) {
setIsCalculating(false);
setStatistics(message.data);
}
};
return (
// Rendering here...
)
....
}
DONE!!