electron-forgeでpreloadスクリプトを設定する

typescript+webpackelectron-forgeで、preloadスクリプトを読み込もうと思った。

typescriptではあんまり意味はないです。私の設定が下手なだけかもしれませんが。なので結局、セキュリティを考えつつ、nodeIntegration: trueにすることにした。

一応、preloadの設定方法と使い方のメモを残しておく。

環境

  • @electron-forge/cli: 6.0.0-beta.51
  • Electron: 9.0.0
  • nodejs: v12.4.0
  • typescript: 3.9.2

すること

webpackの設定がelectron-forgeだと、webpack.*.config.jspackage.jsonconfigのところとに分けられている。

preloadの設定は、package.jsonの方で行う。こんなふうに:

{
  "config": {
    "forge": {
      "packagerConfig": {},
      "makers": [
        //.. electron-forgeのmakerの設定
      ],
      "plugins": [
        [
          "@electron-forge/plugin-webpack",
          {
            "mainConfig": "./webpack.main.config.js",
            "renderer": {
              "config": "./webpack.renderer.config.js",
              "entryPoints": [
                {
                  "html": "./src/renderer/index.html",
                  "js": "./src/renderer/renderer.tsx",
                  "name": "main_window",
                  // ここにpreloadを指定する
                  "preload": {
                    "js": "./src/preload.js"
                  }
                }
              ]
            }
          }
        ]
      ]
    }
  }
}

あとは、いつも通りBrowserWindowの生成時にpreloadスクリプトを指定しておく。

const mainWindow = new BrowserWindow({
  ...,
  webPreferences: {
    // ここで指定。パスは適切に指定する
    preload: `${__dirname}/preload.js`
  }
});

そもそもpreloadスクリプトとは

preloadの働きのイメージ

以下の項であまり使わない理由を述べるが、その前にpreloadスクリプトがどんなものか確認する。

Electronはプロセスがメインプロセスとレンダラプロセスの2つに分かれていて、最近のバージョンのデフォルトでは、Node APIの機能はメインプロセスでしか使えない。それを橋渡しで利用可能にするのがpreloadスクリプトだと考えておけばいい。橋渡しなので、必要な機能だけをレンダラプロセスに渡すことができる。

以前利用したときはこのように使った:

// preload.js
const _setImmediate = setImmediate;
const _clearImmediate = clearImmediate;
const React = require("react");
const ReactDom = require("react-dom");
const ReactTransitionGroup = require("react-transition-group");
const { ipcRenderer, remote } = require("electron");


process.once('loaded', () => {
    global.setImmediate = _setImmediate;
    global.clearImmediate = _clearImmediate;
    global.React = React;
    global.ReactDom = ReactDom;
    global.ReactTransitionGroup = ReactTransitionGroup;
    global.ipcRenderer = ipcRenderer;
    global.remote = remote;
});

console.log("preload.js was loaded.");

ReactipcRendererなどを渡しておけば、レンダラ側で利用できるという流れになる。

これにより、リモートコンテンツに悪意あるコードが含まれていたときにレンダラ側で実行されても、Node APIの機能は使えず、深刻なダメージは避けられる、といった具合になる。

あまり使わない

上のような設定でpreloadなスクリプトを読み込むことができるが、正直扱いがめんどう。特にtypescriptでは。

その理由は、preloadスクリプトは.jsファイルで渡す(おそらく)ので、型が指定できないこと。もう1つが、利用するときに、私が知る限りは、渡した機能(ipcRendererなど)はglobalのプロパティに設定しておくものだが、typescriptではコンパイル・トランスパイルした後に、globalundefinedになっているため、機能を取得できないことだ。スコープの問題のためだろう。

typescriptはあまり詳しくはないので、回避方法があるのかもしれないが、解決策は見つからなかった。

Lintの解決のために、必死にd.tsとかでNodeJS.Globalにプロパティ(ipcRenderer)を追加したりしたけど、無理だった。どうすればいいというのか…

という迷宮に陥ってしまったので、preloadスクリプトを使うのはやめた!

nodeIntegration: trueでいいや…

リモートコンテンツを読まなければ、nodeIntegration: trueオプションをBrowserWindowにつけておけばいいじゃんと開き直ることにした。

このオプションはNode APIの機能をpreloadで渡す必要がなくなるもの。 レンダラ側が簡単にNode APIにアクセスできるようになる。

nodeIntegration有効のイメージ

上記の理由により、このオプションを使うときは、セキュリティリスクをよく考える必要がある。今回は、ipcRendererを使うBrowserWindowではリモートコンテンツを読むことがないので、安全。…のはず。

const mainWindow = new BrowserWindow({
  ...,
  webPreferences: {
    // ここに指定する
    nodeIntegration: true
  }
});

参考

あとがき

electron-forgeは楽でいい。ReactVueなどフロントエンドのフレームワークのテンプレートも充実するともっと使いやすくなりそう。

Electron 10が8月ごろに公開されるようのでアップデート可能ならしていこう。 Electron 9はつい最近公開だった。四半期ごとのようだ。

以上です。

タイトルとURLをコピーしました