Electronアプリを作成した時に学んだこと

最近の業務中にデスクトップアプリが欲しいという要望がありましたので、初めて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

[公式サイト](https://www.electronjs.org/)には、このような説明を書いています。
開発会社は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します。

```js
const { app, BrowserWindow } = require('electron')
```

画面作成

```js
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入門](https://qiita.com/umamichi/items/6ce4f46c1458e89c4cfc)

`loadFile`関数で表示する画面の指定ができます。
また、`openDevTools`関数でChromeのデベロッパー ツールをデフォルトで表示します。

アプリのイベント検知
参考：[app](https://www.electronjs.org/docs/api/app)

```js
app.whenReady().then(...)
app.on('window-all-closed', () => {})
app.on('activate', () => {})
...
```

よく使うのは`ready`、`window-all-closed`、`activate`です。
イベントが発火する際に特定処理を行うことはできます。


**main.jsの例：**

```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の例**

```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に渡すようになります。

```preload.js
const electron = require('electron');
const fs = require('fs')

process.once('loaded', () => {
    global.electron = electron
    global.fs = fs
});
```

index.htmlでは、下記のように記述すれば`electron`, `fs`のAPIを使えるようになります。 

```index.html
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にビルドの設定を追加します。

**例**

```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](https://www.electron.build/configuration/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の`path`APIを使ったらその辺をうまく処理してくれますので、パスの部分を`path`APIで処理しましょう。
参考：[path](https://nodejs.org/api/path.html)