Electron アプリの基本的な作り方
- カテゴリ:
- ソフトウェア/ツール/OSS
- コメント数:
- Comments: 0
◆ Node.js の代わりに Electron があるイメージ(Electron がランタイム)
◆ Node.js 自体は必須じゃない
◆ Electron にアプリフォルダを指定するか resources/app にアプリフォルダを配置して起動
◆ package.json にメインの JavaScript ファイルを指定
◆ メインプロセスでブラウザウィンドウを表示させて 表示する HTML ファイルを設定
◆ HTML ファイルでは画面の処理を行う
◆ DOM API と Node.js API の両方が使える
◆ Node.js 自体は必須じゃない
◆ Electron にアプリフォルダを指定するか resources/app にアプリフォルダを配置して起動
◆ package.json にメインの JavaScript ファイルを指定
◆ メインプロセスでブラウザウィンドウを表示させて 表示する HTML ファイルを設定
◆ HTML ファイルでは画面の処理を行う
◆ DOM API と Node.js API の両方が使える
昔 Electron の簡単な使い方の紹介をしましたが ほぼデフォルトを使いまわしで Node.js も使わず HTML を書くような前提で 単にサーバとの通信不要なウェブアプリをローカルアプリ化するようなものでした
あれから 3 年以上経ってますし いろいろ変わってるかもと思って もう少しちゃんとした使い方をまとめてみます
Electron は Node.js アプリともまたちょっと違うので 最初はなにすればいいのかもわかりづらくて 0 から作ろうとしても困ることが多いのですよね
Node.js で動かすタイプのアプリと違って Electron で動かすアプリになるので Node.js は必須ではありません
Node.js を入れるようなものと考えて Electron をインストールします
今の最新版だと
https://github.com/electron/electron/releases/tag/v4.1.4
から
electron-v4.1.4-win32-x64.zip
をダウンロードすれば良いです
ここでは Windows を前提にしてますが 環境に応じて置き換えてください
npm を使ってインストールもできます
通常はこんなデフォルト画面が出ます
昔はホーム画面にアプリをドロップして起動できたのですが今は無理のようです
なので electron でのアプリの実行方法はコマンドから electron の引数に Electron アプリのフォルダを指定して起動します
ファイルじゃなくてフォルダというところに注意です
windows だとフォルダをドラッグして electorn.exe にドロップしてもいいです
コマンドで引数に渡すのと同じことです
ただ コマンドからじゃないと console.log で出力したログが見れない不便なところもあるので 開発中はコマンドからが良さそうです
実行する Electron が Node.js や Python のようにアプリを動かすランタイムと考えるならこの方法がベストですが パッケージングした特定のアプリ専用と考えるなら electron フォルダ内にアプリを配置できます
Electron のフォルダ内の resources フォルダに app という名前のフォルダを作ります
app フォルダがあれば その Electron はコマンドラインの引数があったとしても無視して app フォルダを実行するアプリとして起動します
アプリ専用の Electron としてダウンロードしたならこっちの方法でもいいと思います
最低 main の設定だけあればおっけいです
main では最初に実行するエントリポイントの JavaScript を設定します
名前は何でもいいので main.js でも app.js でも [アプリ名].js でもおっけいです
自分で作らないとウィンドウは表示されないのでバックグラウンドで動作するアプリも作れます
最低限これだけであればウィンドウを出せます
ウィンドウを表示するだけでは何も表示されないので ファイルや URL をロードします
loadFile でローカルファイルをロードし loadURL で URL のページをロードします
通常のブラウザではローカルファイルにアクセスなどができないサンドボックス内での実行なのでセキュリティ的に守られていますが Electron の場合は普通にローカルファイルのアクセスもできるので外部の URL を開く場合は注意が必要です
ページ内の JavaScript で Node.js の機能を無効にする設定もあります
普通のウェブページを作るように HTML や JavaScript を書けます
index.html
index.js
と書けばこういう画面が出ます
ウィンドウサイズは少し縮めてます
レンダラープロセスでも Node.js の機能が使えます
ウェブページを操作することもできるので DOM の API と Node.js の API の両方が使えます
index.html
index.js
util や sub_process といった Node.js の機能を使って得られたデータを DOM に出力しています
Windows だとコマンドの結果が Shift_JIS になるので iconv-lite で変換しています
npm のライブラリを使うときは Node.js と同じようにインストールして require すれば使えます
通常は Electron アプリのフォルダの中に node_modules を置きますが親階層にあっても探してくれます
C:\dev3\playground\electron\a1
が Electron アプリのフォルダなら dev3 や playground に node_modules があっても大丈夫です
デバッグ用途では助かりそうです
ですが 今のところ Windows はサポート対象外らしく動きませんでした
ただ レンダラープロセスなら devtools 出せばいいし メインプロセスなら
で始めてるわけなので Node.js を REPL で使って上のコードを実行すればよさそうです
しかし やってみたら require("electron") の返り値が electron.exe ファイルのパスの文字列でした
electron パッケージの index.js はこうなっています
Electron のパスを返していますね
npm でインストールできるものの Node.js から実行することはサポートしていなくて Electron として実行しないと Electron の機能は使えなくなってるようです
重い Electron 部分は渡さなくて良くて アプリのソース部分だけなので楽ですね
ただ Electron 持ってる人って言うと開発者など限られた人になります
また Electron のバージョンで Node.js バージョンや Chromium バージョンが変わって動かないこともあります
なので この方法が使える場合は限られてきます
app フォルダに配置したら Electron のフォルダごと zip 化して相手に渡します
解凍して Electron を実行するだけで使えます
ただ zip ファイルのサイズが大きくなるのがデメリットです
app フォルダはただのフォルダですが 中身を見られたり変更されたくない場合には asar というアーカイブファイルにすることができます
app フォルダの代わりに app.asar を resources に配置しても同じように Electron アプリを実行できます
ただ 専用形式なだけで zip みたいなものなので 展開ツールさえあれば簡単に中身が見れます
Electron が使われてるアプリケーションで 一部動作を変えたくて unpack して修正して再 pack したことがありますが ファイルが壊れていると言われて使えないアプリがありました
単純に asar のハッシュ値をチェックしたりしてるだけかもですが 変更を許容しない方法はありそうです
https://www.electron.build/
使うには package.json に name と version プロパティが必須になります
main だけしか書いてないなら追加してから実行します
アプリのフォルダのパスを指定できないようなので カレントディレクトリを移動して実行します
--win を設定すると Windows 用にビルドされたファイルが生成されます
name を test1 にした場合の例です
アプリフォルダに dist フォルダができていて 中にはこういうファイルがあります
インストーラを用意してくれます
win-unpacked が Windows 用の Electron のフォルダになっていて その resources フォルダの app.asar にアプリが配置されています
自動で pack してくれてます
また electron.exe が name に応じた名前になります
この例だと test1.exe になってました
blockmap は差分更新用の gzipped json らしいです
https://github.com/electron-userland/electron-builder/issues/2851
https://electronjs.org/docs
英語しか無いと思ってたのですが 右上から言語を選べて日本語がありました
英語に比べるとちょっと古いようで 英語にある項目がなかったりもしますが 読みづらい機械翻訳というわけでもなさそうです
もっと早く気づいていれば……
キーに "" がなかったり最後に , があったり JSON として不正なものです
アプリを配置してるのになぜかデフォルト画面が出るということが起きたら package.json を確認してみるとよさそうです
main_window が GC で回収されて消えるとウィンドウが消えるそうです
なので main_window をトップレベルで参照を持つようにしているそうです
トップレベルと言ってもブラウザの実行環境じゃないので グローバル変数とは違いますし ready のリスナの中でもトップレベルでも変わらない気がします
それに 使われてる以上 GC されないように思います
信じられなかったので 試してみました
数十分で勝手に終了したという報告を見かけたので 数時間起動したままにしましたが 特に回収されることはなかったです
ただ まぁ公式ドキュメントがそうなってるわけですし とりあえずこうしておくのが安心かと思います
外部に参照を持ってるので closed イベントで null に出力しています
マルチウィンドウのアプリなら閉じたのは GC されるように参照を外すべきですが シングルウィンドウだと閉じれば勝手に消えるので closed イベントは無視でいいように思います
Windows で試した限りでは ウィンドウを閉じると自動でプロセスが終了していました
しかし Electron にフォルダ指定で動かしたのと app フォルダを用意したのでは動きが違うようで app フォルダのアプリを実行した場合のみ ウィンドウを閉じただけだと自動で終了してくれませんでした
他にどういう違いがあるかはわかりませんが app.quit は明示的にするべきみたいです
ここではシングルウィンドウなので main_window の closed イベントで app.quit していますが マルチウィンドウだと app の window-all-closed イベントで app.quit します
HTML に何も書いてないので HTML で表示する領域の外側だと思ってました
しかし 実はこれも HTML で管理する領域でした
イベントが起きたら画面を一新したくて document.body.innerHTML を置き換えると メニューバーも消えてしまいました
body の中に root になる div を用意してそこを書き換えると良さそうです
あれから 3 年以上経ってますし いろいろ変わってるかもと思って もう少しちゃんとした使い方をまとめてみます
Electron は Node.js アプリともまたちょっと違うので 最初はなにすればいいのかもわかりづらくて 0 から作ろうとしても困ることが多いのですよね
Install
まずはインストールですNode.js で動かすタイプのアプリと違って Electron で動かすアプリになるので Node.js は必須ではありません
Node.js を入れるようなものと考えて Electron をインストールします
今の最新版だと
https://github.com/electron/electron/releases/tag/v4.1.4
から
electron-v4.1.4-win32-x64.zip
をダウンロードすれば良いです
ここでは Windows を前提にしてますが 環境に応じて置き換えてください
npm を使ってインストールもできます
yarn global install electron
Electron
ダウンロードしたら electron.exe を起動します通常はこんなデフォルト画面が出ます
昔はホーム画面にアプリをドロップして起動できたのですが今は無理のようです
なので electron でのアプリの実行方法はコマンドから electron の引数に Electron アプリのフォルダを指定して起動します
electron app-folder
ファイルじゃなくてフォルダというところに注意です
windows だとフォルダをドラッグして electorn.exe にドロップしてもいいです
コマンドで引数に渡すのと同じことです
ただ コマンドからじゃないと console.log で出力したログが見れない不便なところもあるので 開発中はコマンドからが良さそうです
実行する Electron が Node.js や Python のようにアプリを動かすランタイムと考えるならこの方法がベストですが パッケージングした特定のアプリ専用と考えるなら electron フォルダ内にアプリを配置できます
Electron のフォルダ内の resources フォルダに app という名前のフォルダを作ります
app フォルダがあれば その Electron はコマンドラインの引数があったとしても無視して app フォルダを実行するアプリとして起動します
アプリ専用の Electron としてダウンロードしたならこっちの方法でもいいと思います
Electron Application
起動方法がわかったのでここからは Electron のアプリを作っていくことになりますpackage.json
Node.js のアプリと同じくフォルダの中に package.json が必要です最低 main の設定だけあればおっけいです
{
"main": "main.js"
}
main では最初に実行するエントリポイントの JavaScript を設定します
名前は何でもいいので main.js でも app.js でも [アプリ名].js でもおっけいです
Main Process
main で指定された JavaScript はメインプロセスと言われて ここでウィンドウを作ります自分で作らないとウィンドウは表示されないのでバックグラウンドで動作するアプリも作れます
最低限これだけであればウィンドウを出せます
const { app, BrowserWindow } = require("electron")
app.on("ready", () => {
new BrowserWindow()
})
ウィンドウを表示するだけでは何も表示されないので ファイルや URL をロードします
app.on("ready", () => {
const main_window = new BrowserWindow()
main_window.loadFile("index.html")
})
loadFile でローカルファイルをロードし loadURL で URL のページをロードします
通常のブラウザではローカルファイルにアクセスなどができないサンドボックス内での実行なのでセキュリティ的に守られていますが Electron の場合は普通にローカルファイルのアクセスもできるので外部の URL を開く場合は注意が必要です
ページ内の JavaScript で Node.js の機能を無効にする設定もあります
Renderer Process
ウィンドウにロードされた HTML の JavaScript はレンダラープロセスと言われます普通のウェブページを作るように HTML や JavaScript を書けます
index.html
<!doctype html>
<meta charset="utf-8">
<script defer src="index.js"></script>
<h1>Hello.</h1>
<p>Current time is <span id="now"></span></p>
index.js
const now_elem = document.getElementById("now")
setInterval(() => {
now_elem.textContent = new Date().toLocaleString()
}, 1000)
と書けばこういう画面が出ます
ウィンドウサイズは少し縮めてます
レンダラープロセスでも Node.js の機能が使えます
ウェブページを操作することもできるので DOM の API と Node.js の API の両方が使えます
index.html
<!doctype html>
<meta charset="utf-8">
<script defer src="index.js"></script>
index.js
const child_process = require("child_process")
const util = require("util")
const iconv = require("iconv-lite")
const exec = util.promisify(child_process.exec)
const ls = async () => {
if (process.platform === "win32") {
const { stdout } = await exec("dir", { encoding: "buffer" })
return iconv.decode(stdout, "shift_jis")
} else {
const { stdout } = await exec("ls -la")
return stdout
}
}
ls().then(text => {
const pre = document.createElement("pre")
pre.textContent = text
document.body.append(pre)
})
util や sub_process といった Node.js の機能を使って得られたデータを DOM に出力しています
Windows だとコマンドの結果が Shift_JIS になるので iconv-lite で変換しています
npm のライブラリを使うときは Node.js と同じようにインストールして require すれば使えます
yarn add iconv-lite
通常は Electron アプリのフォルダの中に node_modules を置きますが親階層にあっても探してくれます
C:\dev3\playground\electron\a1
が Electron アプリのフォルダなら dev3 や playground に node_modules があっても大丈夫です
REPL
Electron は --interactive オプションをつけると対話型で REPL 実行できるようですデバッグ用途では助かりそうです
ですが 今のところ Windows はサポート対象外らしく動きませんでした
ただ レンダラープロセスなら devtools 出せばいいし メインプロセスなら
const { app, BrowserWindow } = require("electron")
で始めてるわけなので Node.js を REPL で使って上のコードを実行すればよさそうです
しかし やってみたら require("electron") の返り値が electron.exe ファイルのパスの文字列でした
electron パッケージの index.js はこうなっています
var fs = require('fs')
var path = require('path')
var pathFile = path.join(__dirname, 'path.txt')
function getElectronPath () {
if (fs.existsSync(pathFile)) {
var executablePath = fs.readFileSync(pathFile, 'utf-8')
if (process.env.ELECTRON_OVERRIDE_DIST_PATH) {
return path.join(process.env.ELECTRON_OVERRIDE_DIST_PATH, executablePath)
}
return path.join(__dirname, 'dist', executablePath)
} else {
throw new Error('Electron failed to install correctly, please delete node_modules/electron and try installing again')
}
}
module.exports = getElectronPath()
Electron のパスを返していますね
npm でインストールできるものの Node.js から実行することはサポートしていなくて Electron として実行しないと Electron の機能は使えなくなってるようです
Release
自分で使う分にはこれまでの開発用の方法で問題ないですが Electron アプリを他の人に使ってもらう場合はいくつかの方法がありますElectron 持ってる場合
相手の人が Electron を持ってる人の場合は アプリケーションフォルダを zip 化してわたせばそれだけで使えます重い Electron 部分は渡さなくて良くて アプリのソース部分だけなので楽ですね
ただ Electron 持ってる人って言うと開発者など限られた人になります
また Electron のバージョンで Node.js バージョンや Chromium バージョンが変わって動かないこともあります
なので この方法が使える場合は限られてきます
zip でパッケージング
リリース用に Electron をダウンロードして app フォルダを作る方法を使いますapp フォルダに配置したら Electron のフォルダごと zip 化して相手に渡します
解凍して Electron を実行するだけで使えます
ただ zip ファイルのサイズが大きくなるのがデメリットです
app フォルダはただのフォルダですが 中身を見られたり変更されたくない場合には asar というアーカイブファイルにすることができます
app フォルダの代わりに app.asar を resources に配置しても同じように Electron アプリを実行できます
ただ 専用形式なだけで zip みたいなものなので 展開ツールさえあれば簡単に中身が見れます
Electron が使われてるアプリケーションで 一部動作を変えたくて unpack して修正して再 pack したことがありますが ファイルが壊れていると言われて使えないアプリがありました
単純に asar のハッシュ値をチェックしたりしてるだけかもですが 変更を許容しない方法はありそうです
electron-builder
自分で zip を作る方法でもいいですが ちゃんとしたビルドツールも存在しますhttps://www.electron.build/
使うには package.json に name と version プロパティが必須になります
main だけしか書いてないなら追加してから実行します
yarn global add electron-builder
cd a1
electron-builder build --win
アプリのフォルダのパスを指定できないようなので カレントディレクトリを移動して実行します
--win を設定すると Windows 用にビルドされたファイルが生成されます
name を test1 にした場合の例です
C:\dev3\playground\electron\a1>electron-builder --win
Configuring yargs through package.json is deprecated and will be removed in the next major release, please use the JS API instead.
Configuring yargs through package.json is deprecated and will be removed in the next major release, please use the JS API instead.
• electron-builder version=20.39.0
• description is missed in the package.json appPackageFile=C:\dev3\playground\electron\a1\package.json
• author is missed in the package.json appPackageFile=C:\dev3\playground\electron\a1\package.json
• writing effective config file=dist\builder-effective-config.yaml
• no native production dependencies
• packaging platform=win32 arch=x64 electron=4.1.4 appOutDir=dist\win-unpacked
• default Electron icon is used reason=application icon is not set
• downloading parts=1 size=5.6 MB url=https://github.com/electron-userland/electron-builder-binaries/releases/download/winCodeSign-2.4.0/winCodeSign-2.4.0.7z
• downloaded duration=7.472s url=https://github.com/electron-userland/electron-builder-binaries/releases/download/winCodeSign-2.4.0/winCodeSign-2.4.0.7z
• building target=nsis file=dist\test1 Setup 1.0.0.exe archs=x64 oneClick=true perMachine=false
• downloading parts=1 size=1.4 MB url=https://github.com/electron-userland/electron-builder-binaries/releases/download/nsis-3.0.3.2/nsis-3.0.3.2.7z
• downloaded duration=3.453s url=https://github.com/electron-userland/electron-builder-binaries/releases/download/nsis-3.0.3.2/nsis-3.0.3.2.7z
• downloading parts=1 size=1.0 MB url=https://github.com/electron-userland/electron-builder-binaries/releases/download/nsis-resources-3.3.0/nsis-resources-3.3.0.7z
• downloaded duration=3.664s url=https://github.com/electron-userland/electron-builder-binaries/releases/download/nsis-resources-3.3.0/nsis-resources-3.3.0.7z
• building block map blockMapFile=dist\test1 Setup 1.0.0.exe.blockmap
アプリフォルダに dist フォルダができていて 中にはこういうファイルがあります
a1\dist\win-unpacked
a1\dist\builder-effective-config.yaml
a1\dist\test1 Setup 1.0.0.exe
a1\dist\test1 Setup 1.0.0.exe.blockmap
インストーラを用意してくれます
win-unpacked が Windows 用の Electron のフォルダになっていて その resources フォルダの app.asar にアプリが配置されています
自動で pack してくれてます
また electron.exe が name に応じた名前になります
この例だと test1.exe になってました
blockmap は差分更新用の gzipped json らしいです
https://github.com/electron-userland/electron-builder/issues/2851
Document
Electron のドキュメントは公式にあって情報も多いですhttps://electronjs.org/docs
英語しか無いと思ってたのですが 右上から言語を選べて日本語がありました
英語に比べるとちょっと古いようで 英語にある項目がなかったりもしますが 読みづらい機械翻訳というわけでもなさそうです
もっと早く気づいていれば……
その他
package.json のエラー
package.json にエラーがあるとデフォルトアプリが表示されますキーに "" がなかったり最後に , があったり JSON として不正なものです
アプリを配置してるのになぜかデフォルト画面が出るということが起きたら package.json を確認してみるとよさそうです
メインウィンドウの参照
メインプロセスではこれだけでもいいのですが サンプルを見るともうちょっと複雑でしたconst { app, BrowserWindow } = require("electron")
app.on("ready", () => {
const main_window = new BrowserWindow()
main_window.loadFile("index.html")
})
const { app, BrowserWindow } = require("electron")
let main_window = null
app.on("ready", () => {
main_window = new BrowserWindow()
main_window.loadFile("index.html")
main_window.on("closed", () => {
main_window = null
app.quit()
})
})
- main_window をトップレベルの変数に格納
- main_window で参照を解除
- app.quit
main_window が GC で回収されて消えるとウィンドウが消えるそうです
なので main_window をトップレベルで参照を持つようにしているそうです
トップレベルと言ってもブラウザの実行環境じゃないので グローバル変数とは違いますし ready のリスナの中でもトップレベルでも変わらない気がします
それに 使われてる以上 GC されないように思います
信じられなかったので 試してみました
数十分で勝手に終了したという報告を見かけたので 数時間起動したままにしましたが 特に回収されることはなかったです
ただ まぁ公式ドキュメントがそうなってるわけですし とりあえずこうしておくのが安心かと思います
外部に参照を持ってるので closed イベントで null に出力しています
マルチウィンドウのアプリなら閉じたのは GC されるように参照を外すべきですが シングルウィンドウだと閉じれば勝手に消えるので closed イベントは無視でいいように思います
Windows で試した限りでは ウィンドウを閉じると自動でプロセスが終了していました
しかし Electron にフォルダ指定で動かしたのと app フォルダを用意したのでは動きが違うようで app フォルダのアプリを実行した場合のみ ウィンドウを閉じただけだと自動で終了してくれませんでした
他にどういう違いがあるかはわかりませんが app.quit は明示的にするべきみたいです
ここではシングルウィンドウなので main_window の closed イベントで app.quit していますが マルチウィンドウだと app の window-all-closed イベントで app.quit します
innerHTML
普通にウィンドウを開くと 自動でメニューバーが画面の上に存在しますHTML に何も書いてないので HTML で表示する領域の外側だと思ってました
しかし 実はこれも HTML で管理する領域でした
イベントが起きたら画面を一新したくて document.body.innerHTML を置き換えると メニューバーも消えてしまいました
body の中に root になる div を用意してそこを書き換えると良さそうです