LEARNING OBJECTIVES ⌵
- Understand the architectural difference between Dedicated Workers (1:1 per tab) and Shared Workers (N:1 multi-tab).
- Instantiate shared workers with
new SharedWorker(scriptURL, name). - Handle client connections inside
SharedWorkerGlobalScopeusing theonconnectevent andMessagePortarray. - Master explicit
port.start()vs. implicitport.onmessageevent listener activation. - Implement a multi-tab broadcast bus and single-connection WebSocket proxy.
📖 The Mental Model & Story (Intuitive Foundation)
Imagine an apartment building where ten different tenants (ten open browser tabs) all want access to the current daily news bulletin.
APPROACH 1: DEDICATED WORKERS (10 PRIVATE COURIERS)
Tab 1 =======> [ Private Dedicated Worker 1 ] (10 separate OS threads!)
Tab 2 =======> [ Private Dedicated Worker 2 ] (10 separate WebSocket connections!)
Tab 3 =======> [ Private Dedicated Worker 3 ] (Zero cross-tab communication)
APPROACH 2: SHARED WORKER (1 CENTRAL BUILDING CONCIERGE)
Tab 1 (Lobby) =====\
Tab 2 (Kitchen) ======> [ MessagePort Connections ] ===> [ 1 Shared Worker Concierge ]
Tab 3 (Balcony) =====/ (Single shared OS thread)
(Single shared WebSocket)
(Broadcasts to all tabs!)
- Dedicated Workers: Every apartment tenant hires their own private butler. If 10 tabs are open, there are 10 separate worker threads, 10 separate WebSocket connections to the backend server, and no way for Tab 1 to know what Tab 2 is doing.
- Shared Worker: The building employs one central concierge in the lobby.
- When Tab 1 opens, it connects a direct telephone wire (
MessagePort) to the concierge. - When Tab 2 opens, it connects its own telephone wire to the same concierge.
- The concierge maintains a single WebSocket connection to the cloud server and broadcasts real-time notifications to all connected telephone lines simultaneously.
- When Tab 1 opens, it connects a direct telephone wire (
If the user closes Tab 1, the concierge stays alive for Tab 2 and Tab 3. The Shared Worker is only destroyed when every connected tab is closed.
Technical Deep Dive & Specifications
Dedicated vs. Shared Workers: Architectural Comparison
| Dimension | Dedicated Worker (Worker) |
Shared Worker (SharedWorker) |
|---|---|---|
| Scope Object | DedicatedWorkerGlobalScope |
SharedWorkerGlobalScope |
| Relationship | 1-to-1: Bound to a single page | N-to-1: Shared across tabs, windows, iframes |
| Connection Protocol | Direct worker.postMessage() |
Port-based worker.port.postMessage() |
| Lifecycle | Dies when the owner tab is closed | Persists until all connected tabs are closed |
| Global Connection Hook | Top-level script evaluation | self.onconnect = (e) => { ... } |
| Debugging | Chrome DevTools > Sources > Threads | chrome://inspect/#workers |
+---------------------------------------------------------------------------------------------------+
| SHARED WORKER ARCHITECTURE |
+---------------------------------------------------------------------------------------------------+
Browser Tab 1 (Origin A) Browser Tab 2 (Origin A) Browser Tab 3 (Origin A)
+------------------------+ +------------------------+ +------------------------+
| worker.port | | worker.port | | worker.port |
+------------------------+ +------------------------+ +------------------------+
\ | /
MessagePort 1 MessagePort 2 MessagePort 3
\ | /
v v v
+------------------------------------------------------------------------------------------------+
| SharedWorkerGlobalScope (Singleton) |
| self.onconnect = (e) => { const port = e.ports[0]; connections.push(port); }; |
| |
| - Central Shared State Store |
| - 1 Single WebSocket Connection to Backend Server |
| - Multi-Tab Broadcast Dispatcher |
+------------------------------------------------------------------------------------------------+
The MessagePort Connection Handshake
Unlike Dedicated Workers where communication is attached directly to the Worker instance, Shared Workers communicate through a MessagePort channel.
1. On the Main Thread (Tab):
// Instantiating the shared worker
const sharedWorker = new SharedWorker('./shared-hub.js', 'AppSharedHub');
// Option A: Explicit start (Required when using addEventListener)
sharedWorker.port.addEventListener('message', (event) => {
console.log('Received from SharedWorker:', event.data);
});
sharedWorker.port.start(); // MUST call start()!
// Option B: Implicit start (Automatically starts port)
sharedWorker.port.onmessage = (event) => {
console.log('Received:', event.data);
};
// Send message to shared worker
sharedWorker.port.postMessage({ action: 'SUBSCRIBE' });
2. Inside the Shared Worker (shared-hub.js):
// Set to hold all active tab ports
const ports = new Set();
self.onconnect = function(event) {
// Extract the newly connected tab's port
const port = event.ports[0];
ports.add(port);
port.onmessage = function(e) {
const { action, payload } = e.data;
if (action === 'BROADCAST_CHAT') {
// Broadcast message to ALL connected tabs
for (const p of ports) {
p.postMessage({ type: 'NEW_CHAT', text: payload });
}
}
};
port.start(); // Start listening on this port
};
[!IMPORTANT] When using
port.addEventListener('message', fn), you must callport.start(). If you use the property assignmentport.onmessage = fn, the browser callsstart()implicitly.
💻 Interactive Code Playground
Below is a complete, runnable Multi-Tab Synchronized Counter & Broadcast Hub. Open this page in two separate browser tabs or windows side-by-side to watch them update in real-time across tabs.
Starter Code
Line-by-Line Code Breakdown
- Line 66 (
const ports = new Set()): Inside the Shared Worker, aSettracks every connected tab'sMessagePort. - Line 74 (
self.onconnect = function(event)): Triggers every time a new browser tab instantiatesnew SharedWorker(). - Line 75 (
const port = event.ports[0]): Retrieves the distinct communication port for the incoming tab. - Lines 86–98: When a tab posts
{ action: 'CHANGE_COUNTER' }, the shared worker updatessharedState.counterand callsbroadcast(), sending the updated number to every open tab simultaneously. - Line 126 (
worker.port.start()): Opens the bidirectional port channel on the client tab.
Expected Browser Render Output
🌐 Multi-Tab Shared Worker Synchronizer
Instructions: Open this same HTML file in two separate browser tabs.
Connected Tabs Count: 2
Shared Global Counter:
[ 42 ]
[ Button: + Increment Shared Counter ] [ Button: - Decrement Shared Counter ] [ Button: Reset to Zero ]
Cross-Tab Activity Log:
[02:20:10] Connected to SharedWorker. Current counter: 0
[02:20:12] Active tabs connected: 2
[02:20:15] Counter modified by a connected tab -> New Value: 1
[02:20:16] Counter modified by a connected tab -> New Value: 2🏋️ Hands-On Exercise
🎯 The Challenge: Multi-Tab Broadcast Chat Bridge
Instructions:
- Build a Shared Worker that acts as a Local Cross-Tab Chat Room.
- When a tab posts
{ action: 'SEND_CHAT', username: 'Alice', text: 'Hello World' }, the shared worker timestamps the message and broadcasts it to all connected ports. - Keep an in-memory history of the last 10 chat messages inside the Shared Worker. When a 3rd tab opens, send it the existing 10 messages immediately in
INIT_CHAT.
🏁 Starter Code Sandbox
⚠️ Common Pitfalls
- Forgetting
port.start(): If you useworker.port.addEventListener('message', handler)instead ofworker.port.onmessage = handler, messages will be queued forever and never fire untilworker.port.start()is called. - Different Script URLs Creating Separate Workers:
new SharedWorker('a.js')andnew SharedWorker('a.js?v=2')are treated as two distinct shared workers. The script URL and name must match exactly. - Debugging Shared Workers: Shared workers do not show up in the standard page DevTools console. In Google Chrome, navigate to
chrome://inspect/#workersto open a dedicated DevTools window for shared workers.
💡 Pro Tips
- Single WebSocket Multiplexer: In large SaaS applications (e.g., Slack, Figma, Trading dashboards), open one single WebSocket connection inside a Shared Worker. All 30 open tabs can multiplex their subscriptions through this single socket, reducing backend server connection overhead by over 90%.
- Safari Compatibility Note: Shared Workers are supported in modern versions of Safari, Chrome, Edge, and Firefox. However, always feature-detect with
if ('SharedWorker' in window)and provide a fallback toBroadcastChannelorlocalStorageevents.
📌 Key Takeaways
SharedWorkercreates a single singleton worker shared across multiple tabs and windows of the same origin.- Connections are handled via the
onconnectevent, which receives aMessagePortfor each client tab. - Main threads access communication through
worker.port.postMessage()andworker.port.onmessage. - If using
addEventListener('message'), you must explicitly callport.start(). - Shared workers are ideal for centralized WebSocket connections, multi-tab state sync, and cross-window coordination.
- --