MessagePort ist die moderne IPC-Variante in Electron — bidirektional, mit Zero-Copy-Transfer für ArrayBuffer, und mit der Möglichkeit, direkt zwischen zwei Renderern zu kommunizieren ohne jeden Call durch den Main zu schleifen. Aufwendiger im Setup als invoke/handle, aber für High-Throughput-Cases unschlagbar.

Wann MessagePort statt invoke/handle?

SzenarioEmpfehlung
Renderer fragt Main, will Antwortinvoke/handle
Main pusht Events an RendererwebContents.send
Echtzeit-Stream (Audio, Video, Sensor-Daten)MessagePort
Renderer-zu-Renderer-KommunikationMessagePort
Große Binary-Daten transferierenMessagePort (Zero-Copy)
Bidirektionale, lange laufende KanäleMessagePort

Für 90 % aller Use-Cases reicht invoke/handle. MessagePort ist Spezial-Werkzeug.

Web-Standard

MessageChannel und MessagePort sind Web-Standards — funktionieren in jedem Browser und in Electron-Renderer-Code direkt:

JavaScript renderer.js — innerhalb eines Renderers
const channel = new MessageChannel();
const { port1, port2 } = channel;

port1.onmessage = (event) => {
    console.log('A bekommt:', event.data);
};

port2.postMessage('Hallo von B');
// → A: 'Hallo von B'

Das ist Web-Standard, läuft in einem einzelnen Renderer. Mit Electron kommt dazu: das Übertragen eines Ports an den Main oder einen anderen Renderer.

Renderer ↔ Main mit MessagePort

JavaScript preload.js / renderer
// Im Preload
contextBridge.exposeInMainWorld('api', {
    connectStream: () => {
        const channel = new MessageChannel();
        ipcRenderer.postMessage('stream:connect', null, [channel.port2]);
        return channel.port1;
    }
});

// Im Renderer
const port = window.api.connectStream();
port.onmessage = (event) => {
    console.log('Daten:', event.data);
};
port.postMessage('start');

ipcRenderer.postMessage(channel, data, [transferables]) ist die spezielle Variante, die einen MessagePort als Transferable mitschicken kann.

JavaScript main.js
ipcMain.on('stream:connect', (event) => {
    const [port] = event.ports;

    port.on('message', (msg) => {
        console.log('Renderer sagt:', msg.data);
        port.postMessage('Antwort vom Main');
    });

    port.start();   // Pflicht im Main, aber automatisch im Renderer
});

Im Main musst du port.start() explizit aufrufen — anders als im Renderer, wo es automatisch passiert.

Renderer ↔ Renderer

Direkter Kanal zwischen zwei Fenstern, ohne den Main als Hub:

JavaScript main.js — Vermittler
// Beide Renderer melden sich
const renderers = [];

ipcMain.on('renderer:ready', (event) => {
    renderers.push(event.sender);
    if (renderers.length === 2) {
        // Channel erzeugen, je einen Port an jeden Renderer
        const channel = new MessageChannelMain();

        renderers[0].postMessage('peer:port', null, [channel.port1]);
        renderers[1].postMessage('peer:port', null, [channel.port2]);
    }
});

MessageChannelMain ist die Main-Prozess-Variante (im Renderer wäre es das normale Web-MessageChannel).

JavaScript renderer.js (in beiden Fenstern)
ipcRenderer.on('peer:port', (event) => {
    const [port] = event.ports;
    port.onmessage = (e) => console.log('Peer sagt:', e.data);
    port.postMessage('Hallo Peer');
});
ipcRenderer.send('renderer:ready');

Der Main hat den Channel nur erzeugt und vermittelt — die Nachrichten danach gehen direkt zwischen den Renderern, ohne durch den Main zu fließen.

Zero-Copy für ArrayBuffer

JavaScript
// Großes ArrayBuffer (z. B. 100 MB Daten)
const big = new Uint8Array(100 * 1024 * 1024).buffer;

// Klassisch (invoke): wird kopiert — langsam und Memory-Spike
await window.api.processData(big);

// MessagePort mit Transfer: Zero-Copy
port.postMessage(big, [big]);  // big wird transferiert, nicht kopiert
// big ist jetzt detached, kann nicht mehr genutzt werden

Beim Transfer wechselt die Owner-ship — der Sender verliert das Objekt, der Empfänger kriegt es. Sehr effizient für große Buffers.

Transferable-Typen:

  • ArrayBuffer
  • MessagePort
  • ImageBitmap
  • OffscreenCanvas

Besonderheiten

MessagePort ist Web-Standard, Electron erweitert es nur.

MessageChannel und postMessage mit Transferables funktionieren in jedem Browser. Electron fügt nur die Möglichkeit hinzu, Ports zwischen Renderern und Main zu transferieren. Wer die Web-Variante kennt, ist hier zu Hause.

Im Main: port.start() ist Pflicht.

Im Renderer startet der Port automatisch beim ersten onmessage-Set. Im Main nicht — explizit port.start() rufen, sonst sammeln sich Messages in einem internen Buffer und werden nicht ausgeliefert.

Zero-Copy nur mit Transferable-Liste.

port.postMessage(buffer) ohne Transferables kopiert. port.postMessage(buffer, [buffer]) transferiert. Ohne den zweiten Parameter ist der Buffer beim Sender weiterhin nutzbar — kostet aber RAM. Transfer macht ihn beim Sender unbrauchbar, dafür ohne Kopier-Cost.

Direkte Renderer-zu-Renderer ohne Main-Roundtrip.

Klassisch: A → IPC → Main → IPC → B. Mit MessagePort einmal vom Main vermittelt, dann A ↔ B direkt. Bei häufiger Kommunikation deutlich performanter.

Setup ist umständlicher als invoke.

Drei Schritte (Channel erzeugen, Port transferieren, beidseitig verbinden) statt einem invoke. Pragmatisch: nur nehmen, wenn die Use-Cases (Streaming, Bidirektional, Zero-Copy) das wirklich brauchen.

MessageChannelMain für Main-Prozess.

Im Main benutzt du nicht das Web-MessageChannel, sondern MessageChannelMain aus dem electron-Modul. Funktioniert ähnlich, ist aber Main-Prozess-spezifisch.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu IPC

Zur Übersicht