業務ツールを作成する際、試しにElectronで作ってみました。 諸々の理由で結局採用しなかったんですが、せっかくなのでやったことの記録をしておきたいと思います。
実際、公式のドキュメントがわかりやすいのでそれで事足りちゃいます。
この辺までは触ったな・・という自分用のメモですのでしっかり学びたい方は公式のクイック スタート | Electronがおすすめですのよ。
Electronとは
ElectronはGitHubが開発したソフトウェアフレームワークです。
トップページでは
JavaScript, HTML, CSS でクロスプラットフォームなデスクトップアプリ開発
さらに、はじめに | Electronでは
Electron は Chromium と Node.js をバイナリに組み込むことで、単一の JavaScript コードベースを維持しつつ、ネイテイブ開発経験無しでも Windows、macOS、Linux で動作するクロスプラットフォームアプリを作成できます。
とあります。
ブラウザなら各プラットフォームにポーティングされてるし、その上で動くJavascriptでコード書けばマルチプラットフォームできるじゃんという発想ですね。 考えた人すごいなぁと素直に感動した覚えがあります。
今回作るアプリ
業務ツールで必要だった要素は以下の通りでした。
今回は
みたいな役に立たないアプリケーションを作ってみます。
プロジェクト作成
クイック スタートを参考にプロジェクトを作成します。
mkdir electron-tray-sample && cd electron-tray-sample yarn init
entry pointだけ main.js
にしておきました。ついでに空っぽのファイルを用意しておきましょう。
touch main.js
続いてdevDependencies に Electronパッケージをインストールします。
devDependenciesはアプリケーションじゃなくて開発に必要なパッケージで、---dev
オプションをつけてインストールできます。
yarn add --dev electron
最後にstart
で実行できるようにpackage.jsonを書き換えます。
{ "name": "electron-tray-sample", "version": "1.0.0", "main": "index.js", "license": "MIT", "devDependencies": { "electron": "^19.0.4" }, "scripts": { "start": "electron ." } }
この状態で実行してみましょう。
yarn start
ElectronのアイコンがDockに出てきました。成功です。
トレイ
トレイへアイコンを追加してみます。
アイコン画像を準備しなきゃなので適当に作ります。tray.pngを16x16、tray@2x.pngを32x32で作成しました。
app.whenReady()
でアプリケーションの初期化完了時のPromiseを取得してトレイを生成します。
トレイのモジュールはTray
です。わかりやすい!
const { app, Tray, Menu, nativeImage } = require('electron') app.whenReady().then(() => { const img = nativeImage.createFromPath(__dirname + "/assets/tray.png") let tray = new Tray(img) tray.setToolTip('Tray app') tray.setContextMenu(Menu.buildFromTemplate([ { label: 'Quit', role: 'quit' } ])) })
実行してみるとトレイに先程のアイコンが出ました。
右クリックでメニューも出ます。
macOSだとDockにアイコンが表示されてしまうので、常駐アプリっぽくないです。
app.dock.hide()
で消せますので消しちゃいましょう。
if (process.platform === 'darwin') { app.dock.hide() }
電源状態とログ
電源状態のモニタをするためpowerMonitor
というモジュールが用意されています。
さまざまな電源がらみのイベントが用意されていますが、suspend
、resume
、shutdown
を受けてコンソールへメッセージを出力してみます。
const { app, powerMonitor } = require('electron') app.whenReady().then(() => { powerMonitor.on('suspend', () =>{ console.log('suspend') }) powerMonitor.on('resume', () =>{ console.log('resume') }) powerMonitor.on('shutdown', () =>{ console.log('shutdown') }) })
実行後スリープ→復帰させてみた様子です。う、うん・・としか言いようのない画像ですね。
シャットダウンはコンソールだと見逃すのでログに残しましょう。
electron-logというモジュールを利用します。
yarn add electron-log
const { app, powerMonitor } = require('electron') const log = require('electron-log'); app.whenReady().then(() => { log.info('begin') powerMonitor.on('suspend', () =>{ log.info('suspend') }) powerMonitor.on('resume', () =>{ log.info('resume') }) powerMonitor.on('shutdown', () =>{ log.info('shutdown') }) })
ログの出力先はこんなルールです。
- macOS: ~/Library/Logs/{app name}/{process type}.log
- Windows: %USERPROFILE%\AppData\Roaming{app name}\logs{process type}.log
- Linux: ~/.config/{app name}/logs/{process type}.log
メインプロセスから出力しているので、macOSで動かしたらここに書き込まれています。
~/Library/Logs/electron-tray-sample/main.log
WiFiスキャナ
node-wifi-scannerというモジュールをインストールします。
yarn add node-wifi-scanner
Readmeの例を参考に、スキャンできたSSIDの数を表示してみます。
よく考えてみるとSSIDじゃなくてMACアドレスの方が良かったですかね?
const scanner = require('node-wifi-scanner'); scanner.scan((err, networks) => { if (err) { console.error(err); return; } const ssids = networks.map(n => n['ssid']) if (ssids.length > 0) { console.log(`Found ${ssids.length} access point`) } });
この処理を定期的に呼び出し、結果をコンソールじゃなくて通知をするように変更します。
const { app, Notification, powerMonitor } = require('electron') const scanner = require('node-wifi-scanner'); const findap = () => { scanner.scan((err, networks) => { if (err) { console.error(err) return } const ssids = networks.map(n => n['ssid']) if (ssids.length > 0) { new Notification({ title: 'WiFi AP', body: `Found ${ssids.length} access point`, silent: true, }).show() } }) } app.whenReady().then(() => { setInterval(() => findap(), 30 * 1000) })
ちゃんと通知が来ました。それにしても意味がない通知ですね。
Package
なんとなく完成した気がするので配布用パッケージを作ります。
クイックスタートに倣って Electron Forge を利用したいと思います。
yarn add --dev @electron-forge/cli
npx electron-forge import
これで yarn run make
でパッケージが出来上がりますが、アイコンがElectronのままなのでせっかくだから変更しましょう。
各プラットフォーム用にアイコン作るのは面倒ですが、electron-icon-builderを利用すると簡単にできます。
1024x1024のpng画像から作成したアイコンファイルをassetsディレクトリに配置しました。
アイコンの指定はpackagerConfig
のicon
に指定します。
"config": { "forge": { "packagerConfig": { "icon": "assets/icon" }, "makers": [ : :
これでyarn run make
すると、out
ディレクトリにパッケージが作成されます。
ちゃんとアプリケーションアイコンも指定した通りになっていますね。
作成したアプリをmacOSとWindows10で動作させた様子です。
どちらもちゃんと動いてますね。
ElectronでNotificationのテスト pic.twitter.com/Nu0fA3heIq
— イナバ (@hollyhockberry) 2022年6月15日
Windowsはこんな感じなんですね。
— イナバ (@hollyhockberry) 2022年6月15日
アプリ名?がちょい違うね。どうやって変えるのかな pic.twitter.com/3L8FZJ8L2d
終わりに
バイナリサイズが大きいなどいくつか不利な点はありますが、簡単にマルチなプラットフォームのアプリケーションができてしまうのは便利だな〜と感じます。
ちなみに採用しなかった理由ですが、Windows環境でShutdownのイベントが発火しなかったからです。
何度もシャットダウンしてデバッグしちゃいましたが、ちゃんとAPIのドキュメントにWindows対応してないこと書いてありますね。
教訓;ドキュメントはちゃんと読もう
プロジェクトはGitHubにあげてますのでよければどうぞ