Build Snappier Apps
with React and Web Workers
Mehdi Vasigh
twitter: @mehdi_vasigh




Who am I?
Engineer @ Mailchimp
♥️ creative coding
👩🍳 UI chef
I love meeting people!
Twitter: @mehdi_vasigh
What does it mean to have a high-performance web UI?
The frame budget
We want to paint 60 frames each second
Browser takes ~4-6 ms per frame to do work
Our JS for each frame should take
~10-12 ms or less
Browser pixel pipeline
JavaScript
Style
Layout
Paint
Composite
https://developers.google.com/web/fundamentals/performance/rendering
16 ms
JavaScript
Style
Layout
Paint
Composite
Browser can skip steps if nothing changes
16 ms
JavaScript
Style
Layout
Paint
Composite
16 ms
UI/Local Logic
JavaScript
Style
Layout
Paint
Composite
16 ms
CSS-in-JS Runtime
UI/Local Logic
JavaScript
Style
Layout
Paint
Composite
16 ms
CSS-in-JS Runtime
UI/Local Logic
Front-end Framework
JavaScript
Style
Layout
Paint
Composite
16 ms
CSS-in-JS Runtime
UI/Local Logic
Front-end Framework
State Management Logic
We've exceeded our frame budget and introduced jank
JavaScript
Style
Layout
Paint
Composite
16 ms
The more blocking JavaScript that we have, the more frames that we will drop
A silly example
Bouncy Ball Fibonacci: web-worker-example.netlify.app

JS is single-threaded*
- One single "execution context" where all code runs
- If a function takes long to execute, the thread is blocked

What does "blocking" mean?
- Code that occupies the thread
- Prevents or delays execution of code after it
while (true) {
console.log('To infinity and beyond!');
}
// Will never run!
extremelyImportantFunction();
JS is async by design
- Browser enables concurrency via the event loop
- Event loop allows us to write async, non-blocking code
-
This is why fetch or setTimeout don't block the render
Jake Archibald - "In the Loop"

Web Workers allow us to do work in parallel using separate threads
Web Workers
- Scripts run on a separate thread
- Have their own execution context
- Don't block the UI
- Data is copied, memory is not shared
How do Web Workers... work?
- Runs JS code in a completely separate execution context
- Can communicate with main script using postMessage
- Messages can contain all primitive JS types
- A few other types (i.e. ArrayBuffer, ImageData) can be transferred
- Data is copied, not shared (no shared memory)
- Workers can create new Workers
When to use Web Workers:
When to use
- CPU-intensive work
- Working with a lot of data
- Image processing
- Parallelizing tasks
- Frames are being dropped
- Games or lots of animations
- ... anywhere that you have isolated, hot path logic!
When NOT to use
- Work is I/O bound, not CPU bound
- i.e. if your constraint is network requests
- DOM manipulation is being performed
- localStorage (or some other unavailable API) is being used
- Prematurely
Let's look at some code!
Ecosystem & Tools: Bundlers
Webpack
worker-loader: github.com/NativeScript/worker-loader
worker-plugin: github.com/GoogleChromeLabs/worker-plugin
Rollup
rollup-plugin-off-main-thread: github.com/surma/rollup-plugin-off-main-thread
Parcel & Vite
(Web Worker support is baked in! Hooray!)
Ecosystem & Tools: Libraries
comlink: github.com/NativeScript/worker-loader
Allows you to create "local instances" of objects living in a Web Worker
and use them as if they're any other object.
workerize: github.com/GoogleChromeLabs/worker-plugin
Allows you to move a module into a Web Worker and exposes exported functions as methods
useWorker: github.com/surma/rollup-plugin-off-main-thread
Similar to workerize, but as a React Hook
Patterns: off-main-thread tasks
- Profile your application and identify blocking code
- Offload blocking code as one-off async tasks using a Web Worker
Depending on the nature of the code, you may not need to alter build system (i.e. using URL.createObjectURL and Blob)
Examples:
- Typeahead/autocomplete search
- Image processing or large file/dataset processing
Patterns: off-main-thread tasks
- If using "on the fly" Object URL method, Workers can't use variables outside of their function scope
- If wanting to use dependencies, must integrate into build system
- Clean up resources after use
workerize: github.com/developit/workerize
comlink: github.com/GoogleChromeLabs/comlink
- If using "on the fly" Object URL method, Workers can't use variables outside of their function scope
- If wanting to use dependencies, must integrate into build system
- Clean up resources after use
Patterns: services/Actor model
The Actor model: www.brianstorti.com/the-actor-model/
Treat different components of your UI system as "actors":
- Actors are isolated, maintain private internal state, no shared memory (ideal with Workers)
- Communicate with other actors via async messages
- Can spawn other actors and send them messages
Example
Additional Information
Mehdi Vasigh - "Getting Started with JavaScript Web Workers and Off-Main-Thread Tasks"
dev.to/mvasigh/getting-started-with-javascript-web-workers-and-off-main-thread-tasks-4029
Surma - "Use web workers to run JavaScript off the browser's main thread"
Surma - "The main thread is overworked and underpaid"
www.youtube.com/watch?v=7Rrv9qFMWNM
Jason Teplitz - "Using Web Workers for more responsive apps"
Thank you!
Questions? Ask me here or on Twitter!
Build Snappier Apps with React and Web Workers
By Mehdi Vasigh
Build Snappier Apps with React and Web Workers
- 256