一昔前は、何も考えずに ipcMain と ipcRenderer でやり取りを行なっていたのですが、最近は contextBridge を挟むのがセオリーとなってます。
レンダラープロセス → メインプロセス
例として、レンダラープロセスからアプリの終了を行なってみます。
ソースコード
preload.mjs
const { ipcRenderer, contextBridge } = require('electron'); contextBridge.exposeInMainWorld('api', { quit: () => ipcRenderer.send('quit') });
app.mjs
import { app, BrowserWindow, ipcMain } from 'electron'; const __dirname = import.meta.dirname; app.once('ready', async () => { const mainWindow = new BrowserWindow({ webPreferences: { preload: `${__dirname}/preload.mjs`, } }); ipcMain.on('quit', () => { app.quit(); }); mainWindow.loadFile(`${__dirname}/index.html`); }); app.once('window-all-closed', () => { app.quit(); });
index.html
<html> <head> <meta charset="UTF-8" /> <style> body { display: flex; align-items: center; justify-content: center; position: fixed; inset: 0; } </style> </head> <body> <button>quit</button> <script> document.querySelector('button').addEventListener('click', () => window.api.quit()); </script> </body> </html>
これで、レンダラープロセスのquitボタンを押した際にアプリが終了します。
メインプロセス → レンダラープロセス
ありえない例ですが、メインプロセスからウィンドウを閉じてみます。
ウィンドウが閉じると結果的にアプリも終了します。
ソースコード
preload.mjs
const { ipcRenderer, contextBridge } = require('electron'); contextBridge.exposeInMainWorld('api', { close: (callback) => ipcRenderer.on('close', () => callback()) });
app.mjs
import { app, BrowserWindow, ipcMain } from 'electron'; const __dirname = import.meta.dirname; app.once('ready', async () => { const mainWindow = new BrowserWindow({ webPreferences: { preload: `${__dirname}/preload.mjs`, } }); ipcMain.on('quit', () => { app.quit(); }); mainWindow.loadFile(`${__dirname}/index.html`); setTimeout(() => { mainWindow.webContents.send('close'); }, 5000); }); app.once('window-all-closed', () => { app.quit(); });
index.html
<html> <head> <meta charset="UTF-8" /> </head> <body> <script> window.api.close(() => { window.close(); }); </script> </body> </html>
これで、5秒後にウィンドウが閉じて、結果的にアプリが終了します。
レンダラープロセス → メインプロセス → レンダラープロセス
こちらもありえない例ですが、上記2つの例を組み合わせてみます。
ソースコード
preload.mjs
const { ipcRenderer, contextBridge } = require('electron'); contextBridge.exposeInMainWorld('api', { quit: () => ipcRenderer.invoke('quit'), });
app.mjs
import { app, BrowserWindow, ipcMain } from 'electron'; const __dirname = import.meta.dirname; app.once('ready', async () => { const mainWindow = new BrowserWindow({ webPreferences: { preload: `${__dirname}/preload.mjs`, } }); ipcMain.on('quit', () => { app.quit(); }); mainWindow.loadFile(`${__dirname}/index.html`); ipcMain.handle('quit', () => { return new Promise((resolve) => { setTimeout(() => { resolve('success'); }, 5000); }); }); }); app.once('window-all-closed', () => { app.quit(); });
index.html
<html> <head> <meta charset="UTF-8" /> <style> body { display: flex; align-items: center; justify-content: center; position: fixed; inset: 0; } </style> </head> <body> <button>quit</button> </body> <script> document.querySelector('button').addEventListener('click', async () => { const result = await window.api.quit(); if (result === 'success') { window.close(); } }); </script> </html>
これで、レンダラープロセスのquitボタンを押した5秒後にウィンドウが閉じ、結果的にアプリが終了します。