Introduction

Web Workers are an established browser technology for running Javascript tasks in a background thread. They're the gold standard for executing long-running, CPU-intensive tasks in the browser. At Flux.ai, we successfully harnessed Web Workers, paired with ImmerJS patches, to minimize data transfer and deliver an ultra-fast user experience. This post will take you through our journey of using Web Workers and ImmerJS for data replication in our web-based EDA tool.

The Problem

Flux.ai, an innovative web-based EDA tool, needs to compute the layout of thousands of electronic components simultaneously for its unique PCB layouting system. This process must adhere to user-defined rules. Our initial prototype revealed that layouting could take several seconds, leading us to explore the capabilities of Web Workers to parallelize this process and unblock the UI.

At bottom, the web worker API is extremely simple. A single method, postMessage, sends data to a web woker, and the same postMessage method is used to send data back to the main thread. We use a popular abstraction layer on top of postMessage, Comlink, developed several years ago by Google, that makes it possible to call one of your functions in a web worker as if it existed in the main thread. Newer, better or similar abstractions may exist. We did learn in using Comlink that it can easily blow up your JavaScript bundle size.

The trouble with using a web worker in a pure RPC style is that you most likely have a lot of data to pass through postMessage which is as slow as JSON.stringify, as a rule of thumb. This was definitely true in our case. We calculated that it would take 100ms at our desired level of scale just to transfer the layouting data each way, eating into the benefit of a parallel web worker.

Exploring SharedArrayBuffer for Data Transfer

A potential solution to the data transfer problem could be using SharedArrayBuffer, recommended for use with web workers. However, SharedArrayBuffer "represents a generic raw binary data buffer" meaning that a) it is of fixed size and b) it does not accept JS objects, strings, or other typical application data. Our investigations led us to conclude that the performance benefits were offset by the encoding and decoding costs in SharedArrayBuffer. One hope for the future is a Stage 3 ECMAScript proposal for growable ArrayBuffers.

The Solution

We decided instead to populate our web worker with all the data on initial load of a Flux document (while the user is already waiting) and update it with changes as they happened. An added benefit of this approach is that the functions designed to run inside the web worker can also be run in the main thread with the flip of a global variable. You might want to do this for Jest tests, for example, which do not support web workers by default.

We got our changes in document data from ImmerJS, something we were already using as part of Redux Toolkit. Immer is an extremely popular library that enables copy-on-write for built-in data types via a Proxy. A lesser-known feature of Immer is Patches. The function produceWithPatches will return a sequence of patches that represent the changes to the original input.

We made a function that wraps produceWithPatches and assigns the patches back into the document for use downstream.

//
// in helpers.ts
//
export function withPatches(
  document: IDocumentState,
  mutationFn: Parameters[1]
): IDocumentState {
  const [newDocument, forward] = produceWithPatches(document, mutationFn);
  if (forward.length === 0) {
    return newDocument;
  } else {
    return produce(newDocument, (draftDoc) => {
      draftDoc.latestPatchSeq = forward;
    });
  }
}

//
// in reducers.ts
//
const documentReducer = (
    state: IDocumentState | null = documentInitialState,
    action: IDocumentReduxAction,
): IDocumentState | null => {
    if (!state) {
        // note, we don't create patches on the first load of the document
        if (action.type === Actions.loadDocumentActions.successType) {
            return action.response
        }
        return state;
    } else {
        return withPatches(
            state,
            (draftState) => {
                if (isAnyOf(Actions.setSubjectProperties)(action)) {
                // ... do mutations
            }
        )
    }
}

With the patches in hand, we could then complete our data flow from main thread to web worker and back again. The main thread calls the worker functions from middleware after every global state change. In Flux, we use redux-observable middleware.

More Code Samples

In the code, the relevant functions look like this, assuming you are using Comlink.

//
// in LayoutEngineInMain.ts
//
import Comlink from "comlink-loader!./LayoutEngineInWorker";
import { Patch } from "immer";

const comlink = new Comlink();

export async function setInitialDocumentState(
  documentState: DocumentState
): void {
  comlink.setInitialDocumentState(documentState);
}

export function applyDocumentPatches(patches: Patch[]): Patch[] {
  const layoutPatches = comlink.applyDocumentPatches(patches);
  // apply these patches to the global state in middleware
  return layoutPatches;
}

//
// in LayoutEngineInWorker.ts
//
import { Patch, applyPatches } from "immer";
import { LayoutEngine, DocumentState } from "./LayoutEngine";

let documentState: DocumentState | undefined;

export function setInitialDocumentState(state: DocumentState): void {
  documentState = state;
}

export function applyDocumentPatches(patches: Patch[]): Patch[] {
  if (documentState === undefined) {
    throw new Error("First call setInitialDocumentState");
  }
  documentState = applyPatches(documentState, patches);
  return new LayoutEngine(documentState).recomputeLayout();
}

Results: Speedy Data Replication with Web Workers and ImmerJS

The result of our use of Web Workers and ImmerJS patches was a significant reduction in workload on every document change and the ability for users to continue interacting with the application during a large re-layout - a priceless benefit in our web-based EDA tool.

Extra Credit: Boosting Speed with ImmerJS

For extra speed in our web worker, we forked the Immer applyPatches function. The original version was too slow for our needs. So, we adapted applyPatches to skip the draft step and mutate the target object in-place, resulting in a 10X speedup.

In conclusion, Web Workers and ImmerJS have proven to be powerful tools for efficient data replication in Javascript, particularly in the context of our web-based EDA tool, Flux.ai. They offer a potent combination for handling complex, CPU-intensive tasks, and improving user experience through faster data transfer and processing.

Profile avatar of the blog author

Greg Dingle

Building the future with friends

Go 10x faster from idea to PCB
Work with Flux like an AI hardware engineer—handling complex tasks, learning your standards, explaining its decisions, and collaborating with you at every step.
Illustration of sub-layout. Several groups of parts and traces hover above a layout.
Design PCBs with AI
Introducing a new way to work: Give Flux a job and it plans, explains, and executes workflows inside a full browser-based eCAD you can edit anytime.
Screenshot of the Flux app showing a PCB in 3D mode with collaborative cursors, a comment thread pinned on the canvas, and live pricing and availability for a part on the board.
Design PCBs with AI
Introducing a new way to work: Give Flux a job and it plans, explains, and executes workflows inside a full browser-based eCAD you can edit anytime.
Screenshot of the Flux app showing a PCB in 3D mode with collaborative cursors, a comment thread pinned on the canvas, and live pricing and availability for a part on the board.
Design PCBs with AI
Introducing a new way to work: Give Flux a job and it plans, explains, and executes workflows inside a full browser-based eCAD you can edit anytime.
Screenshot of the Flux app showing a PCB in 3D mode with collaborative cursors, a comment thread pinned on the canvas, and live pricing and availability for a part on the board.

Related Content

RC Low-Pass Filter: Formula, Cutoff Frequency & Design

RC Low-Pass Filter: Formula, Cutoff Frequency & Design

Explains how RC low-pass filters attenuate high-frequency signals using a resistor and capacitor, with the cutoff frequency formula, component selection trade-offs, and PCB layout best practices.

Profile avatar of Gabriel Hacohen
Gabriel Hacohen
|July 29, 2026
What Is a Reflow Oven? PCB Reflow Soldering Explained

What Is a Reflow Oven? PCB Reflow Soldering Explained

Covers how reflow ovens solder SMT components through preheat, soak, reflow, and cooling stages, with common defects and how PCB layout decisions impact manufacturing yield.

Profile avatar of Gabriel Hacohen
Gabriel Hacohen
|July 29, 2026
Gerber Files Explained: PCB Layers, Formats & Export

Gerber Files Explained: PCB Layers, Formats & Export

Gerber files are the universal 2D vector format that bridges PCB design and fabrication. This guide covers what each layer file contains, how to export and verify a complete manufacturing package, and the common mistakes that cause production delays.

Profile avatar of Gabriel Hacohen
Gabriel Hacohen
|July 19, 2026
Pi Filters Explained: Design, Components & Applications

Pi Filters Explained: Design, Components & Applications

A pi filter is a three-element passive CLC network that provides stronger ripple and EMI suppression than a single capacitor. This guide covers topology selection, component derating, resonance damping, and PCB layout best practices.

Profile avatar of Gabriel Hacohen
Gabriel Hacohen
|July 19, 2026
Tantalum Capacitors Guide: Polarity, Derating & Risks

Tantalum Capacitors Guide: Polarity, Derating & Risks

Tantalum capacitors offer high capacitance density in a tiny footprint but demand strict polarity and voltage derating. This guide explains how they compare to ceramics and aluminum electrolytics, common failure risks, and how to select and place them safely.

Profile avatar of Gabriel Hacohen
Gabriel Hacohen
|July 19, 2026
What Is PCBA? PCB vs PCBA and Assembly Explained

What Is PCBA? PCB vs PCBA and Assembly Explained

PCBA turns a bare PCB into a functional circuit by mounting and soldering components. This guide breaks down the assembly workflow, SMT vs through-hole methods, required manufacturing files, and common defects to avoid.

Profile avatar of Gabriel Hacohen
Gabriel Hacohen
|July 19, 2026
Electrical Rule Check (ERC) vs DRC in PCB Design

Electrical Rule Check (ERC) vs DRC in PCB Design

ERC checks schematic-level electrical issues while DRC checks PCB layout rules -- engineers run ERC before layout and DRC during or after routing.

Profile avatar of Gabriel Hacohen
Gabriel Hacohen
|July 7, 2026
Hardware Design Workflow: From Schematic Capture to Manufacturing

Hardware Design Workflow: From Schematic Capture to Manufacturing

The complete hardware design workflow covers requirements, schematic capture, PCB layout, validation, prototyping, and manufacturing.

Profile avatar of Gabriel Hacohen
Gabriel Hacohen
|July 7, 2026