🏒

Electronのメインプロセスとレンダラープロセス

に公開

📣 概要

Electronを初めて書いたので、メインプロセスとレンダラープロセスの超ざっくり説明と書き方の覚書

📛 Electronとは

Electron は、JavaScript、HTML、CSS によるデスクトップアプリケーションを構築するフレームワークです。

つまりWebサイトを作る技術(JS,HTML,CSS)でデスクトップアプリケーションを作れるようにするもの
https://www.electronjs.org/ja/docs/latest/

🤖 Electronを起動してみる

index.js
const { app, BrowserWindow } = require("electron");
function createWindow() {
  let window = new BrowserWindow({
    width: 1400,
    height: 1000,
    webPreferences: { nodeIntegration: true },
  });
  window.loadFile("index.html");
}

app.whenReady().then(createWindow);
index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>ここテスト</title>
  </head>
  <body>
    <h1>ここがテストのページ</h1>
  </body>
</html>

こんな感じでした後

npx electron .

したら表示される

BrowserWindowオブジェクトを用いてウィンドウが生成される

📚 メインプロセスとレンダラープロセス

先ほど書いた index.js の処理は メインプロセス と呼ばれる。
.htmlに処理を書いた場合、それは表示されたウィンドウで実行されるので レンダラープロセス と呼ばれる。

index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Button Counter</title>
    <style>
      body {
        padding: 20px;
      }
      button {
        padding: 10px 20px;
        font-size: 16px;
      }
      #counter {
        margin-top: 20px;
        font-size: 24px;
      }
    </style>
  </head>
  <body>
    <button id="clickButton">クリック</button>
    <div id="counter">クリック回数: 0</div>

    <script>
      let count = 0;
      const button = document.getElementById("clickButton");
      const counter = document.getElementById("counter");

      button.addEventListener("click", () => {
        count++;
        counter.textContent = `クリック回数: ${count}`;
      });
    </script>
  </body>
</html>

↔️ メインプロセス↔︎レンダラープロセス

レンダラープロセスからメインプロセスの処理を動かしたい時は以下のように書く

index.html(レンダラープロセス)
// メインプロセスのbutton-clickを発動させる
ipcRenderer.send("button-click", count);

// メインプロセスから発動されたclick-processedを実行する
ipcRenderer.on("click-processed", (event, data) => {
  counter.textContent = `クリック回数: ${data.count}`;
  messageDiv.textContent = data.message;
});
index.js(メインプロセス)
// レンダラープロセスからbutton-clickが指定されたら実行する処理
ipcMain.on("button-click", (event, count) => {
  // ここで行う処理
  console.log(`メインプロセス: ${count}回目のクリックを検知`);

  // レンダラープロセスに結果を返す
  event.reply("click-processed", {
    count: count,
    message: `${count}回目のクリックを記録しました!`,
  });
});

この時に.htmlでrequireを使うと Uncaught ReferenceError: require is not defined というエラーがコンソールに出るのでウィンドウの作成でちょっと変更を入れておく

let window = new BrowserWindow({
  width: 1400,
  height: 1000,
  webPreferences: {
    nodeIntegration: true,
    contextIsolation: false, // ここを追加
  },
});
window.loadFile("index.html");

コードの全体感はこんな感じ

index.js
const { app, BrowserWindow, ipcMain } = require("electron");

function createWindow() {
  let window = new BrowserWindow({
    width: 1400,
    height: 1000,
    webPreferences: {
      nodeIntegration: true,
      contextIsolation: false,
    },
  });
  window.loadFile("index.html");
}

// アプリケーション起動時の処理
app.whenReady().then(createWindow);

// レンダラープロセスから送られる処理の定義
ipcMain.on("button-click", (event, count) => {
  // ここで行う処理
  console.log(`メインプロセス: ${count}回目のクリックを検知`);

  // レンダラープロセスに結果を返す
  event.reply("click-processed", {
    count: count,
    message: `${count}回目のクリックを記録しました!`,
  });
});

// Macでもウィンドウが閉じたらアプリを終了するように設定
app.on("window-all-closed", () => {
  if (process.platform !== "darwin") {
    app.quit();
  }
});
index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Button Counter</title>
    <style>
      body {
        padding: 20px;
      }
      button {
        padding: 10px 20px;
        font-size: 16px;
      }
      #counter {
        margin-top: 20px;
        font-size: 24px;
      }
      #message {
        margin-top: 10px;
        color: #666;
      }
    </style>
  </head>
  <body>
    <button id="clickButton">クリック</button>
    <div id="counter">クリック回数: 0</div>
    <div id="message"></div>

    <script type="text/javascript">
      const { ipcRenderer } = require("electron");

      let count = 0;
      const button = document.getElementById("clickButton");
      const counter = document.getElementById("counter");
      const messageDiv = document.getElementById("message");

      button.addEventListener("click", () => {
        count++;
        // メインプロセスにメッセージを送信
        ipcRenderer.send("button-click", count);
      });

      // メインプロセスからの応答を受信
      ipcRenderer.on("click-processed", (event, data) => {
        counter.textContent = `クリック回数: ${data.count}`;
        messageDiv.textContent = data.message;
      });
    </script>
  </body>
</html>

ちなみに、レンダラープロセスではNodeのAPIなどは使用できないためメインプロセスに送って実行する必要がある。
そのためにipcRendererを使用している。

✍︎ ちょっと書き方変更

ipcRenderer.send() ipcMain.on() の書き方ではなく、 invoke handle を用いて表現することもできる

index.js
const { app, BrowserWindow, ipcMain } = require("electron");

function createWindow() {
  let window = new BrowserWindow({
    width: 1400,
    height: 1000,
    webPreferences: {
      nodeIntegration: true,
      contextIsolation: false,
    },
  });
  window.loadFile("index.html");
}

// アプリケーション起動時の処理
app.whenReady().then(createWindow);

// レンダラープロセスから送られる処理の定義
ipcMain.handle("button-click", (_, count) => {
  // ここで行う処理
  console.log(`メインプロセス: ${count}回目のクリックを検知`);

  // レンダラープロセスに結果を返す
  return {
    count: count,
    message: `${count}回目のクリックを記録しました!`,
  };
});

// Macでもウィンドウが閉じたらアプリを終了するように設定
app.on("window-all-closed", () => {
  if (process.platform !== "darwin") {
    app.quit();
  }
});
index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Button Counter</title>
    <style>
      body {
        padding: 20px;
      }
      button {
        padding: 10px 20px;
        font-size: 16px;
      }
      #counter {
        margin-top: 20px;
        font-size: 24px;
      }
      #message {
        margin-top: 10px;
        color: #666;
      }
    </style>
  </head>
  <body>
    <button id="clickButton">クリック</button>
    <div id="counter">クリック回数: 0</div>
    <div id="message"></div>

    <script type="text/javascript">
      const { ipcRenderer } = require("electron");

      let count = 0;
      const button = document.getElementById("clickButton");
      const counter = document.getElementById("counter");
      const messageDiv = document.getElementById("message");

      button.addEventListener("click", async () => {
        count++;
        // メインプロセスにメッセージを送信して結果を受け取る
        const result = await ipcRenderer.invoke("button-click", count);
        counter.textContent = `クリック回数: ${result.count}`;
        messageDiv.textContent = result.message;
      });
    </script>
  </body>
</html>

Discussion