みかづきブログ・カスタム

基本的にはちょちょいのほいです。

Electronでメインプロセスとレンダラープロセスで通信を行う ⚡️

一昔前は、何も考えずに ipcMainipcRenderer でやり取りを行なっていたのですが、最近は contextBridge を挟むのがセオリーとなってます。

www.electronjs.org

レンダラープロセス → メインプロセス

例として、レンダラープロセスからアプリの終了を行なってみます。

ソースコード

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秒後にウィンドウが閉じ、結果的にアプリが終了します。