Skip to content

Commit

Permalink
docs: new main -> renderers messageChannel example (#35133)
Browse files Browse the repository at this point in the history
* docs: new main -> renderers messageChannel example

* consistent use of your

* fix a typo

* linting

* markdown linting

* Update docs/tutorial/message-ports.md

Co-authored-by: Erick Zhao <erick@hotmail.ca>

* update code example headings, reference contextIsolation example

* remove nodeIntegration: false from browserWindows

* rename "messagePort" to "electronMessagePort" for compatibility

Co-authored-by: Kilian Valkhof <kilian@kilianvalkhof.com>
Co-authored-by: Erick Zhao <erick@hotmail.ca>
  • Loading branch information
3 people authored Jul 29, 2022
1 parent 348e76b commit 890adef
Showing 1 changed file with 81 additions and 22 deletions.
103 changes: 81 additions & 22 deletions docs/tutorial/message-ports.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ your app.

Here is a very brief example of what a MessagePort is and how it works:

```js
// renderer.js ///////////////////////////////////////////////////////////////
```js title='renderer.js (Renderer Process)'
// MessagePorts are created in pairs. A connected pair of message ports is
// called a channel.
const channel = new MessageChannel()
Expand All @@ -28,8 +27,7 @@ port2.postMessage({ answer: 42 })
ipcRenderer.postMessage('port', null, [port1])
```

```js
// main.js ///////////////////////////////////////////////////////////////////
```js title='main.js (Main Process)'
// In the main process, we receive the port.
ipcMain.on('port', (event) => {
// When we receive a MessagePort in the main process, it becomes a
Expand Down Expand Up @@ -84,14 +82,84 @@ process, you can listen for the `close` event by calling `port.on('close',

## Example use cases

### Setting up a MessageChannel between two renderers

In this example, the main process sets up a MessageChannel, then sends each port
to a different renderer. This allows renderers to send messages to each other
without needing to use the main process as an in-between.

```js title='main.js (Main Process)'
const { BrowserWindow, app, MessageChannelMain } = require('electron')

app.whenReady().then(async () => {
// create the windows.
const mainWindow = new BrowserWindow({
show: false,
webPreferences: {
contextIsolation: false,
preload: 'preloadMain.js'
}
})

const secondaryWindow = BrowserWindow({
show: false,
webPreferences: {
contextIsolation: false,
preload: 'preloadSecondary.js'
}
})

// set up the channel.
const { port1, port2 } = new MessageChannelMain()

// once the webContents are ready, send a port to each webContents with postMessage.
mainWindow.once('ready-to-show', () => {
mainWindow.webContents.postMessage('port', null, [port1])
})

secondaryWindow.once('ready-to-show', () => {
secondaryWindow.webContents.postMessage('port', null, [port2])
})
})
```

Then, in your preload scripts you receive the port through IPC and set up the
listeners.

```js title='preloadMain.js and preloadSecondary.js (Preload scripts)'
const { ipcRenderer } = require('electron')

ipcRenderer.on('port', e => {
// port received, make it globally available.
window.electronMessagePort = e.ports[0]

window.electronMessagePort.onmessage = messageEvent => {
// handle message
}
})
```

In this example messagePort is bound to the `window` object directly. It is better
to use `contextIsolation` and set up specific contextBridge calls for each of your
expected messages, but for the simplicity of this example we don't. You can find an
example of context isolation further down this page at [Communicating directly between the main process and the main world of a context-isolated page](#communicating-directly-between-the-main-process-and-the-main-world-of-a-context-isolated-page)

That means window.messagePort is globally available and you can call
`postMessage` on it from anywhere in your app to send a message to the other
renderer.

```js title='renderer.js (Renderer Process)'
// elsewhere in your code to send a message to the other renderers message handler
window.electronMessagePort.postmessage('ping')
```

### Worker process

In this example, your app has a worker process implemented as a hidden window.
You want the app page to be able to communicate directly with the worker
process, without the performance overhead of relaying via the main process.

```js
// main.js ///////////////////////////////////////////////////////////////////
```js title='main.js (Main Process)'
const { BrowserWindow, app, ipcMain, MessageChannelMain } = require('electron')

app.whenReady().then(async () => {
Expand Down Expand Up @@ -129,8 +197,7 @@ app.whenReady().then(async () => {
})
```

```html
<!-- worker.html ------------------------------------------------------------>
```html title='worker.html'
<script>
const { ipcRenderer } = require('electron')
Expand All @@ -153,8 +220,7 @@ ipcRenderer.on('new-client', (event) => {
</script>
```

```html
<!-- app.html --------------------------------------------------------------->
```html title='app.html'
<script>
const { ipcRenderer } = require('electron')
Expand Down Expand Up @@ -182,9 +248,7 @@ Electron's built-in IPC methods only support two modes: fire-and-forget
can implement a "response stream", where a single request responds with a
stream of data.

```js
// renderer.js ///////////////////////////////////////////////////////////////

```js title='renderer.js (Renderer Process)'
const makeStreamingRequest = (element, callback) => {
// MessageChannels are lightweight--it's cheap to create a new one for each
// request.
Expand Down Expand Up @@ -213,9 +277,7 @@ makeStreamingRequest(42, (data) => {
// We will see "got response data: 42" 10 times.
```

```js
// main.js ///////////////////////////////////////////////////////////////////

```js title='main.js (Main Process)'
ipcMain.on('give-me-a-stream', (event, msg) => {
// The renderer has sent us a MessagePort that it wants us to send our
// response over.
Expand All @@ -242,8 +304,7 @@ the renderer are delivered to the isolated world, rather than to the main
world. Sometimes you want to deliver messages to the main world directly,
without having to step through the isolated world.

```js
// main.js ///////////////////////////////////////////////////////////////////
```js title='main.js (Main Process)'
const { BrowserWindow, app, MessageChannelMain } = require('electron')
const path = require('path')

Expand Down Expand Up @@ -278,8 +339,7 @@ app.whenReady().then(async () => {
})
```

```js
// preload.js ////////////////////////////////////////////////////////////////
```js title='preload.js (Preload Script)'
const { ipcRenderer } = require('electron')

// We need to wait until the main world is ready to receive the message before
Expand All @@ -297,8 +357,7 @@ ipcRenderer.on('main-world-port', async (event) => {
})
```

```html
<!-- index.html ------------------------------------------------------------->
```html title='index.html'
<script>
window.onmessage = (event) => {
// event.source === window means the message is coming from the preload
Expand Down

0 comments on commit 890adef

Please sign in to comment.