できた。どうやらIPC通信ではデフォルト引数を渡すとその値で固定されてしまうらしい。前回はそのせいで失敗していた模様。

ブツ

インストール&実行

NAME='Electron.fs.cp.Directory.20220829162638'
git clone https://github.com/ytyaru/$NAME
cd $NAME
npm install
npm start
  1. インストール&実行してアプリ終了する
  2. 以下ファイルが作成される(ファイルとディレクトリ両方コピー)
    • dst/src/lib/toastify/1.11.2/min.js
    • dst/src/lib/toastify/1.11.2/min.css
    • dst/memo/ディレクトリとその配下にあるファイル一式

前回

Node.jsのfs.cpでファイルのコピーを試みるのとき、ディレクトリがコピーできなかった。

今回

色々試した結果、どうやらIPC通信の引数にデフォルトオプションをつけていたせいらしい。それを必須にしたら成功した。何を言っているかわからないと思うので順に説明する。

バグ調査

main.jsの以下cpハンドルにconsole.logを仕込んだ。するとif側に入ってほしいのに、実際はelse側に入っていたことが判明した。

ipcMain.handle('cp', async(event, src, dst, options=null) => {
    if (options) { console.log('aaaaaaaaa', src, dst, options); fs.cp(src, dst, options, ()=>{}); }
    else { console.log('bbbbbbbbb', src, dst); fs.cp(src, dst, ()=>{}); }
})

ifの条件式をnull!=optionsに変えても同様だった。

つまり、optionsは必ずnullになっていたことになる。

だが、呼出元のsite-maker.jsではちゃんと以下のように{'recursive':true, 'preserveTimestamps':true}というoptionsを渡している。

await window.myApi.cp(path, `dst/${this.setting.github.repo}/${path}`, {'recursive':true, 'preserveTimestamps':true}) }

なのになぜnullになるのか? コードをみるかぎりmain.jsの以下しかない。メソッド定義の仮引数のところでoptions=nullにしている。これがそのまま入ってしまったのでは?

ipcMain.handle('cp', async(event, src, dst, options=null) => {

そんなバカな。JavaScriptはデフォルト引数にできるはず。MDNにも書いてある。

ああ、もしかしてこれもElectronのIPC通信における制約なのか? うわー、それっぽい予感。と思って確認するためにコードを書いてみた。すると案の定だった。

ディレクトリをコピーするコード

以下が成功したコード。思ったとおりデフォルト引数をやめたらディレクトリのコピーができた。

main.js

ipcMain.handle('cpdir', async(event, src, dst, options) => {
    if (null!=options) { console.log('aaaaaaaaa', src, dst, options); fs.cp(src, dst, options, ()=>{}); }
    else { console.log('bbbbbbbbb', src, dst); fs.cp(src, dst, ()=>{}); }
})

ようするに[fs.cp][]の引数optionsを必須にした。ただしcpdirという名前で新しくICPハンドルを作った。ファイルをコピーしたいなら先述のcpハンドルを使う。

preload.js

contextBridge.exposeInMainWorld('myApi', {
    cpdir:async(src, dst, options)=>await ipcRenderer.invoke('cpdir', src, dst, options),
})

renderer.js

class SiteMaker {
    constructor(setting) {
        this.setting = setting
    }
    async make() {
        await this.#cpdir(`memo/`)
    }
    async #cpdir(path) { await window.myApi.cpdir(path, `dst/${this.setting.github.repo}/${path}`, {'recursive':true, 'preserveTimestamps':true}) }
}

結論

IPC通信の制約によりIPCハンドラの引数にはデフォルト引数が使えない。仮に使ったとしたら、必ずそのデフォルト値がセットされてしまうらしい。

なんか謎のバグが起きるたびにElectronのIPC謎仕様のせいだと発覚しているなぁ。公式ドキュメントのどこかに書いてあるのかな? そのうち罠として情報をまとめておきたい。