ドキュメントに騙されて苦労した。

ブツ

DEMOでは以下のようなエラーになった。どうやらHTTPS上ではエラーになるらしい。ローカルならちゃんと下の画像のように表示されたのだが。おそらくIPC通信の部分はローカルのプロセス間通信のしくみだからネットワーク上では動作しないということが原因なのだろう。

Uncaught (in promise) TypeError: Cannot read property 'setup' of undefined
    at renderer.js:4

インストール&実行

NAME='Electron.sql.js.Load.20220806150924'
git clone https://github.com/ytyaru/$NAME
cd $NAME
npm install
npm start

eyecatch.png

コード抜粋

package.json

{
  "main": "src/js/main.js",
  "scripts": {
    "start": "electron .",
  },
  "devDependencies": {
    "electron": "^20.0.1"
  },
  "dependencies": {
    "sql.js": "^1.7.0"
  }
}

前回はElectronのバージョンが19だったが、今回は20になっていた。そのせいかトップレベルでconst fs = require('fs')するとエラーになった。

プロジェクト作成するとき次のように作成した。するとその時点での最新版がインストールされる。どうやら前回と今回とではちょうどメジャーバージョンが更新されるタイミングだったらしい。メジャー版の更新はインタフェースに変更があるくらいの大きな変更があるときに行われる。今回fsのロードが失敗するようになったのも、Electronのメジャーバージョンアップのせいだろう。

NAME=electron-sqljs-not-load
mkdir $NAME
cd $NAME
npm init -y
npm i electron -D
npm i sql.js

sql.jsもNode.jsのパッケージとしてインストールしている。これはブラウザのときと違う。ブラウザのときはCDNにあったファイルを配置してロードしていた。

おそらくブラウザ版とNode.js版では異なる実装と思われる。

main.js

const { app, BrowserWindow, ipcMain, dialog } = require('electron')
const path = require('path')
const initSqlJs = require('sql.js');
...
// ここではdb.execを参照できるが、return後では参照できない謎
ipcMain.handle('loadDb', async (event, filePath) => {
    console.log('----- loadDb ----- ', filePath)
    const fs = require('fs')
    const SQL = await initSqlJs().catch(e=>console.error(e))
    const db = new SQL.Database(new Uint8Array(fs.readFileSync(filePath)))
    console.log(db)
    console.log(db.exec)
    const res = db.exec(`select * from comments;`)
    console.log(res)
    return db
})
// db.execの実行結果を返すならOK
ipcMain.handle('getComments', async (event, filePath) => {
    console.log('----- getComments ----- ', filePath)
    const fs = require('fs')
    const SQL = await initSqlJs().catch(e=>console.error(e))
    const db = new SQL.Database(new Uint8Array(fs.readFileSync(filePath)))
    console.log(db)
    const res = db.exec(`select * from comments;`)
    console.log(res)
    return res[0].values
})

initSqlJs()がブラウザ版ライブラリと異なる。ブラウザ版はこの引数のところで.wasmファイルパスを指定する必要があった。けれどnpmでインストールした今回のライブラリではそれが必要ないらしい。

本当はnew SQL.Databaseの戻り値であるdbオブジェクトを返したかった。そうすれば受け取る側でdb.exec(任意SQL文)とすることで任意SQL文を発行できた。だが、dbを返した先でexec関数がundefinedになってしまう。原因不明。

なので仕方なく結果だけを返すメソッドgetCommentsを作った。この方法だとユースケースの数だけライブラリのロードが必要になる。あまりに馬鹿げているので何とかしたい。fsさえもこのipcMain.handleの中でなければエラーになってしまうため、ライブラリのロードが頻発してしまう。実際にアプリを作るときはどうにかしてロードを工夫せねば実用的でない。

preload.js

contextBridge.exposeInMainWorld('myApi', {
    loadDb:async(filePath)=>await ipcRenderer.invoke('loadDb', filePath),
    getComments:async(filePath)=>await ipcRenderer.invoke('getComments', filePath),
})

単にIPCメッセージや引数を渡すだけ。

ちなみにこのファイルのトップレベルでもconst fs = require('fs')がエラーになった。おそらくElectron verson 20になったせいだと思われるが、原因不明。もはやただインタフェースを作るだけの面倒な作業でしかない。

renderer.js

window.addEventListener('DOMContentLoaded', async(event) => {
    console.log('DOMContentLoaded!!');
    console.log(window.myApi)
    window.myApi.setup();
    const db = await window.myApi.loadDb(`src/db/mylog.db`);
    console.log(db)
    console.log(db.exec) // main.jsでは関数なのにこちらではundefinedの謎
    //console.log(db.exec(`select * from comments;`)) // Uncaught (in promise) TypeError: db.exec is not a function
    const rows = await window.myApi.getComments(`src/db/mylog.db`)
    console.log(rows)
    const ps = []
    document.getElementById('show').innerHTML = rows.map(row=>`<p>${row[1]}</p>`).join('')
});

ここでloadDbを呼び出して取得できた戻り値にはexecメソッドがなくundefinedだった。原因不明。

getCommentsの戻り値はちゃんとSQL文の結果だったのでOK。

index.html

<!doctype html>
<meta charset="utf-8">
<div id="show"></div>
<script src="./src/js/renderer.js"></script>

所感

Electron学習は大変

一応使えたけど気になる点も多い。Node.jsやElectronに慣れていないせいもある。情報も生のJavaScriptより少ないため調べにくい。難易度があがっている。

sql.jsでは同じコードを使い、同じ結果を、ローカルとサーバで得られないようだ。それを期待してElectronを使っているのに残念。どうしたものか。とりあえずローカルで動けばいいと妥協しておこう。

サーバ上で動かすときは別途ブラウザ版ライブラリで実装することになるのかな。面倒そう。せっかく実行速度を犠牲にしてクロスプラットフォームにしたのにメリットが薄まる。今は勉強のためと割り切ろう。

きっとOSのAPIで実装するよりは遙かに簡単なはず。だからElectronを学習する意義は大きいはず。

もはや宗教

新しいことを学ぶとき、こんな感じでたくさんの不満が出てくる。エラー連発でかなりのフラストレーションが溜まり、心が折れて投げ出したくなる。答えはあるのか。対策はあるのか。わからないまま調査しつづけるときのあの苦痛や不安。ああ逃げたい。

なんとか言い訳をつけて「できるまでやる」ところまでもっていけば勝ち。痛い目をみて失敗して間違いまくってどうにか自己流自己欺瞞術をみにつけるしかない。

小さな失敗や成功それ自体をひとつの成果として記事にする。「ここまではできた」という証拠を作る。「ここから先はわからない」という資料を作る。再現できることが大事。それが次の成果にもつながると信じて。

こうした小細工で自分のモチベを保つ。結果的に小さなことからコツコツやって成すという一般論にいきつく。言うは易く行うは難し。その小さな成功に喜びを感じて楽しむのが一番大事。楽しくないと続かない。楽しくなれば勝手に手が動く。そこまでいけば勝ち。

プログラミングって精神の修行なんじゃないかと思うことすらある。どんなことでもきっとそうした面がある。やっているのが人間である以上、人間のことを理解してうまいこと操作してやるのが問題解決に必要なのだろう。人に向き合わねば何も成せない。

こんな小さなことでさえ考えざるを得ない。さっさと動くものを書ければそれでいいのに、心が言い訳という救いを求めてしまう。もどかしいが、現実を受け入れてやっていくしかない。