Межпроцессное взаимодействие | Electron
https://www.electronjs.org/ru/docs/latest/tutorial/ipc • 270 KB fetched
Open original page
Межпроцессное взаимодействие | Electron
Перейти к основному содержанию
Electron Документация API Блог Инструменты
* Electron Forge
* Electron Fiddle
Сообщество
* Управление
* Примеры
* Ресурсы
Релизы Русский
* English
* Deutsch
* Español
* Français
* 日本語
* Português
* Русский
* 中文
Поиск
* Начать
* Процессы в Electron
* Моделирование процесса
* Context Isolation
* Межпроцессное взаимодействие
* Песочница процессов
* MessagePorts in Electron
* Рекомендации
* Примеры
* Разработка
* Native Node Modules
* Распространение
* Тестирование и отладка
* Ссылки
* Вклад
*
* Процессы в Electron
* Межпроцессное взаимодействие На этой странице
Межпроцессное взаимодействие
Inter-process communication (IPC) is a key part of building feature-rich desktop applications in Electron. Because the main and renderer processes have different responsibilities in Electron's process model, IPC is the only way to perform many common tasks, such as calling a native API from your UI or triggering changes in your web contents from native menus.
IPC channels
In Electron, processes communicate by passing messages through developer-defined "channels" with the ipcMain and ipcRenderer modules. These channels are arbitrary (you can name them anything you want) and bidirectional (you can use the same channel name for both modules).
In this guide, we'll be going over some fundamental IPC patterns with concrete examples that you can use as a reference for your app code.
Understanding context-isolated processes
Before proceeding to implementation details, you should be familiar with the idea of using a preload script to import Node.js and Electron modules in a context-isolated renderer process.
* For a full overview of Electron's process model, you can read the process model docs .
* For a primer into exposing APIs from your preload script using the contextBridge module, check out the context isolation tutorial .
Pattern 1: Renderer to main (one-way)
To fire a one-way IPC message from a renderer process to the main process, you can use the ipcRenderer.send API to send a message that is then received by the ipcMain.on API.
You usually use this pattern to call a main process API from your web contents. We'll demonstrate this pattern by creating a simple app that can programmatically change its window title.
For this demo, you'll need to add code to your main process, your renderer process, and a preload script. The full code is below, but we'll be explaining each file individually in the following sections.
docs/fiddles/ipc/pattern-1 ( 43.4.0 ) Open in Fiddle
* main.js
* preload.js
* index.html
* renderer.js const { app , BrowserWindow , ipcMain } = require ( 'electron/main' )
const path = require ( 'node:path' )
function handleSetTitle ( event , title ) {
const webContents = event . sender
const win = BrowserWindow . fromWebContents ( webContents )
win . setTitle ( title )
}
function createWindow ( ) {
const mainWindow = new BrowserWindow ( {
webPreferences : {
preload : path . join ( __dirname , 'preload.js' )
}
} )
mainWindow . loadFile ( 'index.html' )
}
app . whenReady ( ) . then ( ( ) => {
ipcMain . on ( 'set-title' , handleSetTitle )
createWindow ( )
app . on ( 'activate' , function ( ) {
if ( BrowserWindow . getAllWindows ( ) . length === 0 ) createWindow ( )
} )
} )
app . on ( 'window-all-closed' , function ( ) {
if ( process . platform !== 'darwin' ) app . quit ( )
} )
const { contextBridge , ipcRenderer } = require ( 'electron/renderer' )
contextBridge . exposeInMainWorld ( 'electronAPI' , {
setTitle : ( title ) => ipcRenderer . send ( 'set-title' , title )
} )
<! DOCTYPE html >
< html >
< head >
< meta charset = " UTF-8 " >
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
< meta http-equiv = " Content-Security-Policy " content = " default-src 'self'; script-src 'self' " >
< title > Hello World! </ title >
</ head >
< body >
Title: < input id = " title " />
< button id = " btn " type = " button " > Set </ button >
< script src = " ./renderer.js " > </ script >
</ body >
</ html >
const setButton = document . getElementById ( 'btn' )
const titleInput = document . getElementById ( 'title' )
setButton . addEventListener ( 'click' , ( ) => {
const title = titleInput . value
window . electronAPI . setTitle ( title )
} )
1. Listen for events with ipcMain.on
In the main process, set an IPC listener on the set-title channel with the ipcMain.on API:
main.js (Main Process)
const { app , BrowserWindow , ipcMain } = require ( 'electron' )
const path = require ( 'node:path' )
// ...
function handleSetTitle ( event , title ) {
const webContents = event . sender
const win = BrowserWindow . fromWebContents ( webContents )
win . setTitle ( title )
}
function createWindow ( ) {
const mainWindow = new BrowserWindow ( {
webPreferences : {
preload : path . join ( __dirname , 'preload.js' )
}
} )
mainWindow . loadFile ( 'index.html' )
}
app . whenReady ( ) . then ( ( ) => {
ipcMain . on ( 'set-title' , handleSetTitle )
createWindow ( )
} )
// ...
The above handleSetTitle callback has two parameters: an IpcMainEvent structure and a title string. Whenever a message comes through the set-title channel, this function will find the BrowserWindow instance attached to the message sender and use the win.setTitle API on it.
информация
Make sure you're loading the index.html and preload.js entry points for the following steps!
2. Expose ipcRenderer.send via preload
To send messages to the listener created above, you can use the ipcRenderer.send API. By default, the renderer process has no Node.js or Electron module access. As an app developer, you need to choose which APIs to expose from your preload script using the contextBridge API.
In your preload script, add the following code, which will expose a global window.electronAPI variable to your renderer process.
preload.js (Preload Script)
const { contextBridge , ipcRenderer } = require ( 'electron' )
contextBridge . exposeInMainWorld ( 'electronAPI' , {
setTitle : ( title ) => ipcRenderer . send ( 'set-title' , title )
} )
At this point, you'll be able to use the window.electronAPI.setTitle() function in the renderer process.
Security warning
We don't directly expose the whole ipcRenderer.send API for security reasons . Make sure to limit the renderer's access to Electron APIs as much as possible.
3. Build the renderer process UI
In our BrowserWindow's loaded HTML file, add a basic user interface consisting of a text input and a button:
index.html
<! DOCTYPE html >
< html >
< head >
< meta charset = " UTF-8 " >
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
< meta http-equiv = " Content-Security-Policy " content = " default-src 'self'; script-src 'self' " >
< title > Hello World! </ title >
</ head >
< body >
Title: < input id = " title " />
< button id = " btn " type = " button " > Set </ button >
< script src = " ./renderer.js " > </ script >
</ body >
</ html >
To make these elements interactive, we'll be adding a few lines of code in the imported renderer.js file that leverages the window.electronAPI functionality exposed from the preload script:
renderer.js (Renderer Process)
const setButton = document . getElementById ( 'btn' )
const titleInput = document . getElementById ( 'title' )
setButton . addEventListener ( 'click' , ( ) => {
const title = titleInput . value
window . electronAPI . setTitle ( title )
} )
At this point, your demo should be fully functional. Try using the input field and see what happens to your BrowserWindow title!
Pattern 2: Renderer to main (two-way)
A common application for two-way IPC is calling a main process module from your renderer process code and waiting for a result. This can be done by using ipcRenderer.invoke paired with ipcMain.handle .
In the following example, we'll be opening a native file dialog from the renderer process and returning the selected file's path.
For this demo, you'll need to add code to your main process, your renderer process, and a preload script. The full code is below, but we'll be explaining each file individually in the following sections.
docs/fiddles/ipc/pattern-2 ( 43.4.0 ) Open in Fiddle
* main.js
* preload.js
* index.html
* renderer.js const { app , BrowserWindow , ipcMain , dialog } = require ( 'electron/main' )
const path = require ( 'node:path' )
async function handleFileOpen ( ) {
const { canceled , filePaths } = await dialog . showOpenDialog ( )
if ( ! canceled ) {
return filePaths [ 0 ]
}
}
function createWindow ( ) {
const mainWindow = new BrowserWindow ( {
webPreferences : {
preload : path . join ( __dirname , 'preload.js' )
}
} )
mainWindow . loadFile ( 'index.html' )
}
app . whenReady ( ) . then ( ( ) => {
ipcMain . handle ( 'dialog:openFile' , handleFileOpen )
createWindow ( )
app . on ( 'activate' , function ( ) {
if ( BrowserWindow . getAllWindows ( ) . length === 0 ) createWindow ( )
} )
} )
app . on ( 'window-all-closed' , function ( ) {
if ( process . platform !== 'darwin' ) app . quit ( )
} )
const { contextBridge , ipcRenderer } = require ( 'electron/renderer' )
contextBridge . exposeInMainWorld ( 'electronAPI' , {
openFile : ( ) => ipcRenderer . invoke ( 'dialog:openFile' )
} )
<! DOCTYPE html >
< html >
< head >
< meta charset = " UTF-8 " >
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
< meta http-equiv = " Content-Security-Policy " content = " default-src 'self'; script-src 'self' " >
< title > Dialog </ title >
</ head >
< body >
< button type = " button " id = " btn " > Open a File </ button >
File path: < strong id = " filePath " > </ strong >
< script src = ' ./renderer.js ' > </ script >
</ body >
</ html >
const btn = document . getElementById ( 'btn' )
const filePathElement = document . getElementById ( 'filePath' )
btn . addEventListener ( 'click' , async ( ) => {
const filePath = await window . electronAPI . openFile ( )
filePathElement . innerText = filePath
} )
1. Listen for events with ipcMain.handle
In the main process, we'll be creating a handleFileOpen() function that calls
dialog.showOpenDialog and returns the value of the file path selected by the user. This function
is used as a callback whenever an ipcRenderer.invoke message is sent through the dialog:openFile
channel from the renderer process. The return value is then returned as a Promise to the original
invoke call.
A word on error handling
Errors thrown through handle in the main process are not transparent as they
are serialized and only the message property from the original error is
provided to the renderer process. Please refer to
#24427 for details.
main.js (Main Process)
const { app , BrowserWindow , dialog , ipcMain } = require ( 'electron' )
const path = require ( 'node:path' )
// ...
async function handleFileOpen ( ) {
const { canceled , filePaths } = await dialog . showOpenDialog ( { } )
if ( ! canceled ) {
return filePaths [ 0 ]
}
}
function createWindow ( ) {
const mainWindow = new BrowserWindow ( {
webPreferences : {
preload : path . join ( __dirname , 'preload.js' )
}
} )
mainWindow . loadFile ( 'index.html' )
}
app . whenReady ( ) . then ( ( ) => {
ipcMain . handle ( 'dialog:openFile' , handleFileOpen )
createWindow ( )
} )
// ...
on channel names
The dialog: prefix on the IPC channel name has no effect on the code. It only serves as a namespace that helps with code readability.
информация
Make sure you're loading the index.html and preload.js entry points for the following steps!
2. Expose ipcRenderer.invoke via preload
In the preload script, we expose a one-line openFile function that calls and returns the value of ipcRenderer.invoke('dialog:openFile') . We'll be using this API in the next step to call the native dialog from our renderer's user interface.
preload.js (Preload Script)
const { contextBridge , ipcRenderer } = require ( 'electron' )
contextBridge . exposeInMainWorld ( 'electronAPI' , {
openFile : ( ) => ipcRenderer . invoke ( 'dialog:openFile' )
} )
Security warning
We don't directly expose the whole ipcRenderer.invoke API for security reasons . Make sure to limit the renderer's access to Electron APIs as much as possible.
3. Build the renderer process UI
Finally, let's build the HTML file that we load into our BrowserWindow.
index.html
<! DOCTYPE html >
< html >
< head >
< meta charset = " UTF-8 " >
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
< meta http-equiv = " Content-Security-Policy " content = " default-src 'self'; script-src 'self' " >
< title > Dialog </ title >
</ head >
< body >
< button type = " button " id = " btn " > Open a File </ button >
File path: < strong id = " filePath " > </ strong >
< script src = ' ./renderer.js ' > </ script >
</ body >
</ html >
The UI consists of a single #btn button element that will be used to trigger our preload API, and a #filePath element that will be used to display the path of the selected file. Making these pieces work will take a few lines of code in the renderer process script:
renderer.js (Renderer Process)
const btn = document . getElementById ( 'btn' )
const filePathElement = document . getElementById ( 'filePath' )
btn . addEventListener ( 'click' , async ( ) => {
const filePath = await window . electronAPI . openFile ( )
filePathElement . innerText = filePath
} )
In the above snippet, we listen for clicks on the #btn button, and call our window.electronAPI.openFile() API to activate the native Open File dialog. We then display the selected file path in the #filePath element.
Note: legacy approaches
The ipcRenderer.invoke API was added in Electron 7 as a developer-friendly way to tackle two-way IPC from the renderer process. However, a couple of alternative approaches to this IPC pattern exist.
Avoid legacy approaches if possible
We recommend using ipcRenderer.invoke whenever possible. The following two-way renderer-to-main patterns are documented for historical purposes.
информация
For the following examples, we're calling ipcRenderer directly from the preload script to keep the code samples small.
Using ipcRenderer.send
The ipcRenderer.send API that we used for single-way communication can also be leveraged to perform two-way communication. This was the recommended way for asynchronous two-way communication via IPC prior to Electron 7.
preload.js (Preload Script)
// You can also put expose this code to the renderer
// process with the `contextBridge` API
const { ipcRenderer } = require ( 'electron' )
ipcRenderer . on ( 'asynchronous-reply' , ( _event , arg ) => {
console . log ( arg ) // prints "pong" in the DevTools console
} )
ipcRenderer . send ( 'asynchronous-message' , 'ping' )
main.js (Main Process)
ipcMain . on ( 'asynchronous-message' , ( event , arg ) => {
console . log ( arg ) // prints "ping" in the Node console
// works like `send`, but returning a message back
// to the renderer that sent the original message
event . reply ( 'asynchronous-reply' , 'pong' )
} )
There are a couple downsides to this approach:
* You need to set up a second ipcRenderer.on listener to handle the response in the renderer process. With invoke , you get the response value returned as a Promise to the original API call.
* There's no obvious way to pair the asynchronous-reply message to the original asynchronous-message one. If you have very frequent messages going back and forth through these channels, you would need to add additional app code to track each call and response individually.
Using ipcRenderer.sendSync
The ipcRenderer.sendSync API sends a message to the main process and waits synchronously for a response.
main.js (Main Process)
const { ipcMain } = require ( 'electron' )
ipcMain . on ( 'synchronous-message' , ( event , arg ) => {
console . log ( arg ) // prints "ping" in the Node console
event . returnValue = 'pong'
} )
preload.js (Preload Script)
// You can also put expose this code to the renderer
// process with the `contextBridge` API
const { ipcRenderer } = require ( 'electron' )
const result = ipcRenderer . sendSync ( 'synchronous-message' , 'ping' )
console . log ( result ) // prints "pong" in the DevTools console
The structure of this code is very similar to the invoke model, but we recommend avoiding this API for performance reasons. Its synchronous nature means that it'll block the renderer process until a reply is received.
Pattern 3: Main to renderer
When sending a message from the main process to a renderer process, you need to specify which renderer is receiving the message. Messages need to be sent to a renderer process via its WebContents instance. This WebContents instance contains a send method that can be used in the same way as ipcRenderer.send .
To demonstrate this pattern, we'll be building a number counter controlled by the native operating system menu.
For this demo, you'll need to add code to your main process, your renderer process, and a preload script. The full code is below, but we'll be explaining each file individually in the following sections.
docs/fiddles/ipc/pattern-3 ( 43.4.0 ) Open in Fiddle
* main.js
* preload.js
* index.html
* renderer.js const { app , BrowserWindow , Menu , ipcMain } = require ( 'electron/main' )
const path = require ( 'node:path' )
function createWindow ( ) {
const mainWindow = new BrowserWindow ( {
webPreferences : {
preload : path . join ( __dirname , 'preload.js' )
}
} )
const menu = Menu . buildFromTemplate ( [
{
label : app . name ,
submenu : [
{
click : ( ) => mainWindow . webContents . send ( 'update-counter' , 1 ) ,
label : 'Increment'
} ,
{
click : ( ) => mainWindow . webContents . send ( 'update-counter' , - 1 ) ,
label : 'Decrement'
}
]
}
] )
Menu . setApplicationMenu ( menu )
mainWindow . loadFile ( 'index.html' )
// Open the DevTools.
mainWindow .
Links found on this page
- Перейти к основному содержанию [direct]
- Electron [direct]
- Документация [direct]
- API [direct]
- Блог [direct]
- Electron Forge [direct]
- Electron Fiddle [direct]
- Управление [direct]
- Примеры [direct]
- Ресурсы [direct]
- Релизы [direct]
- English [direct]
- Deutsch [direct]
- Español [direct]
- Français [direct]
- 日本語 [direct]
- Português [direct]
- 中文 [direct]
- Процессы в Electron [direct]
- Context Isolation [direct]
- Песочница процессов [direct]
- MessagePorts in Electron [direct]
- Рекомендации [direct]
- Примеры [direct]
- Разработка [direct]
- Native Node Modules [direct]
- Распространение [direct]
- Тестирование и отладка [direct]
- Ссылки [direct]
- Вклад [direct]
- ipcMain [direct]
- ipcRenderer [direct]
- docs/fiddles/ipc/pattern-1 ( 43.4.0 ) [direct]
- Open in Fiddle [direct]
- IpcMainEvent [direct]
- docs/fiddles/ipc/pattern-2 ( 43.4.0 ) [direct]
- Open in Fiddle [direct]
- #24427 [direct]
- WebContents [direct]
- docs/fiddles/ipc/pattern-3 ( 43.4.0 ) [direct]
- Open in Fiddle [direct]
- Structured Clone Algorithm [direct]
- Редактировать эту страницу [direct]
- Безопасность [direct]
- Discord [direct]
- Bluesky [direct]
- X [direct]
- Mastodon [direct]
- Stack Overflow [direct]
- GitHub [direct]
- Open Collective [direct]
- Панель управления инфраструктурой [direct]
- OpenJS Foundation [direct]
- Политике в отношении товарных знаков [direct]
- Списке товарных знаков [direct]
- Условия использования [direct]
- Политика конфиденциальности [direct]
- Устав [direct]
- Кодекс поведения [direct]
- Политика использования файлов cookie [direct]