概要
いまさらかもしれませんが、ElectronはNode.js環境でブラウザベースのデスクトップアプリケーションを作成できるフレームワークです。前回は基本的なアプリ作成方法について、ご説明しました。
今回は、もう一歩踏み込んで、Node.jsのモジュールを活用する方法を学びます。サンプルとしファイル操作を行うfsモジュールを利用したElectronアプリを紹介します。
htmlファイルにJavascriptでコーディングしても、ブラウザからファイル操作は基本的にできません。Electronはブラウザベースのアプリですが、nodeベースのアプリでもあります。nodeのfsモジュールはファイル操作がでます。とういうことは、ブラウザベースのアプリもElectronなら、nodeの力をかりてファイル操作が、できるはずではありませんか!
そうなんだ、じゃあ簡単に出来るんだ!と思っていたら、自分のようなJavascript知識レベルが浅はかな人間では、実行できるまで、まぁまぁ時間がかかってしまいました。自分のやろうとしていることの解説サイトも少ないようでした(検索力がないだけかも)。
というわけで、ElectronがNode.jsのモジュールを活用するためのサンプルコードをできるだけ、簡略した形で書いております。今回も公式サイトを存分に活用しております。
準備1
ファイルの書き込みを行う、Electronアプリを作成します。以下の環境にて動作を確認しております。
Windows11
node.js v18.18.1
electron v27.0.0
Promise型について理解があった方がよさげです。わかりやすい解説ありがとうございます。
準備2
まずは、前回と同様にElectronアプリの作成を行います。
この先、
・preload.js
・main.js
・index.html
を修正していきます。
preload.js
ざっくり言えば、以下の通りです。
・main.js メインプロセス(Node.js)
・index.html レンダラープロセス(ブラウザ表示)
・preload.js メインとレンダラーの橋渡し
セキュリティ面からレンダラープロセスとメインプロセスを分離する必要があるので、その仲介役としてpreload.jsが存在しています。以下の例の場合、レンダラープロセスからwindow.myObj.appendTxt()にてメインプロセスにアプローチします。メインプロセスではチャンネル'appendTxt'を拾って、処理を実行します。
// preload.jsはレンダラーからNode.jsにアクセスするためのファイルです
const { contextBridge, ipcRenderer } = require('electron');
// メインプロセスから分離した処理をレンダラーに'myObj'として公開します
// preload.jsのinvokeとmain.jsのhandleがセットになっており
// invokeされたをチャンネル'appendTxt'をメインプロセスのhandleで拾う
contextBridge.exposeInMainWorld('myObj', {
appendTxt: async (myText) => ipcRenderer.invoke('appendTxt', myText),
});
main.js
app.whenReady内のipcMain.handleにてチャンネル'appendTxt'を拾って処理をします。第2引数がPromise型のコールバック関数として、処理を記述しています。プロジェクト内に「text.txt」ファイルをなければ作成し、fsモジュールを使って文字列を追記しています。コールバック関数はPromise型でなくてもよいようですが、asyncを外しても、なぜかPromise型で返るのだけど、、、理由はわかりません。サンプルコードもPromise型になってるので、第2引数はPromise型と思いこんどきます。
// main.jsはNode.jsにて動作する requireにてelectronモジュールを読み込んでいる
const { app, BrowserWindow, ipcMain } = require('electron');
const path = require('path');
//fsモジュールの読み込み
const fs = require('fs');
// ウィンドウの作成準備
const createWindow = () => {
const win = new BrowserWindow({
width: 800, //ウィンドウサイズ
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'), //プレローダーの指定
}
});
// 読み込むファイル
win.loadFile('index.html');
// デベロッパーツールを開く 不要ならコメントアウト
win.webContents.openDevTools();
}
//ウィンドウの初期化時
app.whenReady().then(() => {
// preload.jsにてinvokeされたチャンネル'appendTxt'を拾い呼び出される
// 第2引数がPromise型のコールバック関数になっています。
ipcMain.handle('appendTxt', async (eve, myText) => {
const writeString = new Date().toLocaleString() + ':' + myText + '\n';
fs.appendFileSync('./text.txt', writeString, 'utf-8', (err) => {
if (err) throw err;
});
return "書き込み文字列:" + writeString;
});
// ウィンドウの表示
createWindow();
// macOS用
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
})
// ウィンドウのクローズ
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit();
})
index.html
window.myObj.appendTxt()を呼び出し、文字列をファイルに書き込みます。プロジェクトフォルダ内に「text.txt」が作成され、ボタンを押すたびに文字列が追記されます。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Electron基本2</title>
</head>
<body>
<h1>Electron基本2</h1>
<input type="text" id="text">
<input type="button" id="button" value="書込">
<script>
const write = document.getElementById("button");
const text = document.getElementById("text");
write.onclick = () => {
// preload.js経由にてメインプロセスの処理を呼び出します。
window.myObj.appendTxt(text.value)
.then((value) => { console.log(value) })
.catch((reason) => { console.log(reason) });
}
</script>
</body>
</html>
できる限り、簡素なコーディングにしたつもりですが、いかがでしょうか。Electronでnodeの機能がフルに使えるとなると、やれることも広がるのではないでしょうか。つたない説明でしたが、読んでもらって感謝です。ありがとうございました。