書籍【Electronではじめるデスクトップアプリケーション開発】のipcRendererで詰まった人へ

#書籍のサンプルコードはバージョンが古くてそのまま写経しても動かない
書籍で使用しているElectronのバージョンはおそらく10.1.1(p.027)ですが、Electronはバージョン12で大きな改変があったらしく、しかも2021/12/24現在のバージョンは既に16ですので、ソースコードを写経してもエラーが出て動きません。p.152から始まるプロセス間通信の章では、ipcRenderer.sendは動くのにipcRenderer.onが動かないという事態になります。

#いろいろググって解決法が（一部だけ）わかった
とりあえずipcRenderer.onを動かす方法がわかったので共有します。ポイントは
_レンダラープロセスで使いたいものはpreload.jsに書かないといけない_
_remoteはElectron14で削除された_
ということです。

#免責事項
JavaScriptとElectronを書き始めて3日の素人です。間違ってたら教えてください。

#以下、ソースコード

##index.js（webPreferences内に修正あり）
~~~JavaScript
const { app, Menu, BrowserWindow } = require("electron");
const path = require("path");
const { ipcMain } = require("electron");
const win_name = ["banana", "orange", "apple"];
ipcMain.on("hello", (event) => {
   const result = createWindow();
   event.reply("hello-result", win_name[result % 3] + "-" + result);
});
function createWindow() {
   let win = new BrowserWindow({
      width: 1000,
      height: 800,
      webPreferences: {
         // remoteは使えないので削除します
         // enableRemoteModule: true, 
         //以下二つはデフォルト設定の通りなので書かなくても大丈夫です
         nodeIntegration: false,
         contextIsolation: true,
         preload: path.join(__dirname, "preload.js"),
      },
   });
   win.loadFile("index.html");
   return win.id;
}
function createMenu() {
   let menu_temp = [
      {
         label: "File",
         submenu: [
            {
               label: "New",
               click: () => {
                  console.log("New menu.");
                  createWindow();
               },
            },
            {
               label: "File",
               click: () => {
                  console.log("File menu.");
                  createWindow();
               },
            },
            {
               role: "close",
            },
            { type: "separator" },
            {
               role: "quit",
            },
         ],
      },
      { role: "editMenu" },
      { role: "viewMenu" },
      { role: "windowMenu" },
      {
         label: "Help",
         submenu: [
            { role: "about" },
            { type: "separator" },
            { role: "reload" },
            { role: "zoomIn" },
            { role: "zoomOut" },
         ],
      },
   ];
   let menu = Menu.buildFromTemplate(menu_temp);
   Menu.setApplicationMenu(menu);
}
createMenu();
app.whenReady().then(createWindow);
app.on("window-all-closed", () => {
   if (process.platform !== "darwin") {
      app.quit();
   }
});
~~~

##preload.js
~~~JavaScript
// remoteモジュールはElectron14で削除されました
// https://www.electronjs.org/ja/blog/electron-14-0#%E5%89%8A%E9%99%A4-remote-%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB
// const { remote } = require("electron");
const { contextBridge, ipcRenderer } = require("electron");

// window.remote = remote; //使えない
// window.ipcRenderer = ipcRenderer; //使えない
contextBridge.exposeInMainWorld("requires", { // <-- ここでつけた名前でひもづく。ここでは"window.requires"
   ipcRenderer: ipcRenderer, //ipcRendererも渡せるのでやり取りできる
   //ipcRenderer.onはpreload.jsでしか使えないので、ここに書いておく
   on: (channel, callback) =>
      ipcRenderer.on(channel, (event, argv) => callback(event, argv)),
});
~~~

##index.html（書籍から使えない部分をコメントアウト）
~~~html
<!DOCTYPE html>
<html lang="ja">
   <head>
      <meta charset="UTF-8" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <link
         rel="stylesheet"
         href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css"
      />
      <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js"></script>
      <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js"></script>
      <title>Sample App</title>
   </head>
   <body>
      <nav class="navbar bg-primary mb-4">
         <h1 class="display-4 text-light">Hello world</h1>
      </nav>
      <div class="container">
         <p id="msg">Please click button.</p>
         <button class="btn btn-primary" onclick="doit()">Click</button>
      </div>
      <script>
         // remoteは使えません
         // const BrowserWindow = window.requires.remote.BrowserWindow;
         function doit() {
            // remoteは使えません
            //let w = window.requires.remote.getCurrentWindow();
            window.requires.ipcRenderer.send("hello");

            window.requires.on("hello-result", (result, arg) => {
               document.querySelector("#msg").textContent =
                  "Create window " + arg;
            });
         }
      </script>
   </body>
</html>
~~~