最近の業務中にデスクトップアプリが欲しいという要望がありましたので、初めてElcetronを使ってアプリを作成してみた時に学んだことをメモしました。
環境
- OS: MacOS Catalina v10.15.5
- Node.js: v12.16.3
- NPM: v6.14.4
- Electron: v9.1.0
- electron-builder: 22.7.0
Node.js & NPM
Node.jsのインストールについて、こちらを参考してください。
https://nodejs.org/ja/
NPMはNode.jsの中に同梱されていますので、Node.jsをインストールすればNPMもインストールされます。
Electron
Build cross-platform desktop apps with JavaScript, HTML, and CSS
公式サイトには、このような説明を書いています。
開発会社はGithub社でもあり、これを使って作った有名なソフトウェアも沢山あります。
(VS Code, Slack, VS Code, Slack, Microsoft teams...etc)
インストール方法は、プロジェクトの配下に下記のコマンドを実行する。
npm install --save-dev electron
Main Process & Renderer Processの概要
Main process
package.jsonのmainに設定しているscriptはElectronのMain processになります。
ElectonアプリにMain processは一つだけでして、アプリの画面作成、起動、終了の制御はできます。
Main processにBrowserWindowで画面を作成し、画面を描画するprocessはrenderer processと呼ばれます。
一つの画面は一つのrenderer processで制御されます。
Main processでは、Node.jsのAPIを利用できますので、OS機能レベルのやりとりは可能です。
Renderer Process
Main processには、BrowserWindowインスタンスでRenderer processを作成し、Chromiumで画面を描画します。
複数Renderer processは可能です。各Renderer processは独立しています。
基本的には、ネイティブGUIのAPIを操作することを禁止されますので、必要があればMain process経由((IPC通信))で行う方法はあります。
なので、Renderer processにはブラウザで使えるJavaScriptを動きます。
main.js (Main process)
アプリのMain processになります。
アプリの制御と画面を作成にはElectron APIを使用するため、electronをrequireします。
const { app, BrowserWindow } = require('electron')
画面作成
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: false,
contextIsolation: false,
preload: __dirname + "/preload.js"
}
})
win.openDevTools();
win.loadFile(__dirname + "/index.html")
BrowserWindowインスタンスを作成し、画面のサイズを設定できます。
nodeIntegration, contextIsolation, preloadの設定は、Renderer processでNPMのモジュール、Node.js APIを使えるため、直接にAPIを使うことではなく、preload.js経由で使うようにすることです。
ここのnodeIntegrationはfalseにしています。trueにしてしまうとRenderer processでは普通にNode.js APIを使えるようになりますが、かなりの操作権限をRenderer processに与えていますので、想定外の挙動やセキュリティー周りの影響も発生しやすいです。例えば、ElectronアプリでPC内のファイルを全部削除することも可能です。ようこそ!Electron入門
loadFile関数で表示する画面の指定ができます。
また、openDevTools関数でChromeのデベロッパー ツールをデフォルトで表示します。
アプリのイベント検知
参考:app
app.whenReady().then(...)
app.on('window-all-closed', () => {})
app.on('activate', () => {})
...
よく使うのはready、window-all-closed、activateです。
イベントが発火する際に特定処理を行うことはできます。
main.jsの例:
const { app, BrowserWindow } = require('electron')
const path = require("path")
function createWindow () {
// Create the browser window.
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: false,
contextIsolation: false,
preload: path.join(__dirname + "/preload.js")
}
})
// and load the index.html of the app.
win.loadFile(path.join(__dirname + "/index.html"))
}
app.whenReady().then(createWindow)
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', () => {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) {
createWindow()
}
})
index.html (Renderer process)
こちらのindex.htmlはmain.jsには、BrowserWindowのloadFile関数に指定されてるHTMLファイルです。
普通のウェブページを作成する方法と同じくHTML、CSS、Javascriptの記述はできます。
index.htmlの例
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<h2 id=counter value=0>0</h2>
<button onclick="counter()">Add</button>
</body>
<script>
function counter() {
var counter = document.getElementById('counter')
var count = parseInt(counter.textContent)
count++
counter.textContent = count
}
</script>
<style>
</style>
</html>
preload.js
Renderer processで使うNode.js、npmnのモジュール、Electron APIを提供する。
例えば、下記のように記述したら、electron, fsの変数をRenderer processに渡すようになります。
const electron = require('electron');
const fs = require('fs')
process.once('loaded', () => {
global.electron = electron
global.fs = fs
});
index.htmlでは、下記のように記述すればelectron, fsのAPIを使えるようになります。
const electron = window.electron;
const csvFileUtil = window.csvFileUtil
ビルド
こちらではelectron-builderを使って、Mac PCとWindows PCも実行できるアプリをビルドしました。
electron-builderのインストール
npm install -D electron-builder
package.jsonの編集
package.jsonにビルドの設定を追加します。
例
{
"name": "app name",
"version": "1.0.0",
"description": "electron application",
"main": "src/main.js",
"build": {
"appId": "com.example.my-app",
"directories": {
"output": "dist"
},
"files": [
"assets",
"src",
"package.json",
"package-lock.json"
],
"mac": {
"icon": "assets/icon/app_icon.png",
"target": [
"dmg"
]
},
"win": {
"icon": "assets/icon/app_icon.png",
"target": "nsis"
},
"nsis": {
"oneClick": false,
"allowToChangeInstallationDirectory": true
}
},
"devDependencies": {
"electron-builder": "^22.7.0",
"electron": "^9.1.0"
},
"scripts": {
"start": "electron --inspect=5858 .",
"build-win": "node_modules/.bin/electron-builder --win --x64",
"build-mac": "node_modules/.bin/electron-builder --mac --x64",
"pack": "electron-builder --dir",
"dist": "electron-builder",
"postinstall": "electron-builder install-app-deps"
}
}
buildの部分はビルトの設定になります。
今回アプリのプロジェクトの構成はこのようになっています。
myapp
-- assets
-- css
-- style.css
-- icon
-- app_icon.png
-- node_modules
-- src
-- main.js
-- index.html
-- preload.js
-- package.json
ソースコードは一つフォルダー(src)の配下に入れています。
(よくわからないですが、最初にpreload.jsをmyapp配下に別のフォルダに保存していましたが、ビルドしてみたら、preload.jsが一緒にビルドされないことが発生しましたので、srcに入れたらうまくビルドされるようになりました。)
参考:configuration
ビルドの実行について
先ほどのpackage.jsonの例のscriptの部分に記載している通り、scriptを実行するとアプリのビルドが行われます。
Windows用のアプリを作成したい場合、npm run build-winを実行したら、distの配下にwin-unpackedフォルダとインストール用の.exeファイルが作成されます。
win-unpackedフォルダの中にWindows環境で実行できるファイルがあります。
Mac PC用のアプリを作成したい場合、npm run build-macを実行したら、distの配下にmacフォルダとインストール用の.dmgファイルが作成されます。
macフォルダの中にMac環境で実行できるファイルがあります。
注意点
ファイルパスの記述には、Macだと/を使いますが、Windowsだと\を使います。このような違いがあると処理する時に気をつける必要があります。Node.jsのpathAPIを使ったらその辺をうまく処理してくれますので、パスの部分をpathAPIで処理しましょう。
参考:path