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 engineering intern—automating the grunt work, learning your standards, explaining its decisions, and checking in for feedback at key moments.
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

Git for Hardware: Version Control for PCB Design Teams

Git for Hardware: Version Control for PCB Design Teams

How hardware teams apply Git-style version control to PCB design — tracking revisions, collaborating safely, and maintaining a complete design history.

Profile avatar of Gabriel Hacohen
Gabriel Hacohen
|June 25, 2026
PCB Design Documentation: What Every Manufacturing Package Should Include

PCB Design Documentation: What Every Manufacturing Package Should Include

A checklist for PCB design documentation -- covering the files, drawings, and release data every manufacturing package should include.

Profile avatar of Gabriel Hacohen
Gabriel Hacohen
|June 25, 2026
PCB Library Management: How to Standardize Components Across Teams

PCB Library Management: How to Standardize Components Across Teams

A practical guide to PCB library management -- how hardware teams standardize components, centralize libraries, and scale collaborative design workflows.

Profile avatar of Gabriel Hacohen
Gabriel Hacohen
|June 25, 2026
PCB Netlists Explained: How Schematics Connect to Layouts

PCB Netlists Explained: How Schematics Connect to Layouts

An engineer-friendly explainer of PCB netlists -- what they contain, how they are generated, and how they connect schematic capture to PCB layout.

Profile avatar of Gabriel Hacohen
Gabriel Hacohen
|June 25, 2026
How to Use Via Stitching in PCB Design

How to Use Via Stitching in PCB Design

A practical guide to via stitching in PCB design -- what it is, why it improves EMI, signal integrity, and thermal performance, and where to place stitching vias. Includes spacing rules (like lambda/20), design guidelines, common mistakes to avoid, and how Flux can automate stitching via placement.

Profile avatar of Gabriel Hacohen
Gabriel Hacohen
|June 24, 2026
Component Selection in PCB Design: How Engineers Choose the Right Parts

Component Selection in PCB Design: How Engineers Choose the Right Parts

A guide to PCB component selection, covering electrical specs, footprints, thermal performance, sourcing, and best practices for picking parts that ship reliably.

Profile avatar of Gabriel Hacohen
Gabriel Hacohen
|June 15, 2026
PCB Design Reviews: Best Practices for Catching Errors Early

PCB Design Reviews: Best Practices for Catching Errors Early

A guide to PCB design reviews, covering schematic, layout, and DFM checks engineers use to catch errors early and ship more reliable boards.

Profile avatar of Gabriel Hacohen
Gabriel Hacohen
|June 15, 2026
How to Create and Manage PCB Footprint Libraries

How to Create and Manage PCB Footprint Libraries

A guide to creating and managing PCB footprint libraries, covering IPC standards, pad sizing, validation workflows, and best practices for reliable land patterns.

Profile avatar of Gabriel Hacohen
Gabriel Hacohen
|June 15, 2026