【環境構築】electron-vue と node_addon_api を連携し、C++とVue.jsを使ってelectronアプリを開発する

## この記事の用途
- electron + vue.jsでモダンなデスクトップアプリ開発を行う
- C++プログラムと連携する（活用例: 機械学習アプリなど、高速な計算が必要となる場面）
    - この記事では、環境構築に重点を置いて説明しています。

## 登場人物
- electron: Webブラウザベースで動くデスクトップアプリ開発フレームワーク
- Vue.js: フロントエンド開発フレームワーク
- node_addon_api: javaScriptからC++資産を利用するためのフレームワーク
    - node_addon_apiを使ったことがない方は、[こちらの記事](https://qiita.com/NA_simple/items/dc31f9ae9519602f9f50) もご覧ください！

## バニラelectron と electron-vue の違い
- はじめに、バニラelectronとの違いを列挙する
    - ※ バニラelectronとは、特に拡張機能をインストールしておらず、画面を html, css, js で作成したものを指す
    - [バニラってどういう意味？](https://ja.wikipedia.org/wiki/バニラ_(ソフトウェア))

| | バニラelectron | electron-vue |
|--|--|--|
| メインプロセスファイル | main.js | src/background.js |
| レンダラープロセスファイル | renderer.js | src/main.js |
| electronBuilderの設定| package.jsonに記述 | vue.config.jsに記述|
| 出力先フォルダ | dist | dist_electron |
| .nodeファイルの読み込み | [bindings](https://www.npmjs.com/package/bindings) | [node-loader](https://www.npmjs.com/package/node-loader) |

## 使用するテンプレート
- [GitHub: electron-vue](https://github.com/SimulatedGREG/electron-vue)

### electron-vueのインストール
- こちらの記事を参考にインストールする
    - [vue + electron で windowsデスクトップアプリを作成する](https://qiita.com/quzq/items/608fa811f7ff2c1ae7f3)

## electron-vueで C++ライブラリを使用するための準備
### node-addon-apiをインストールする
- C++ソースのビルド環境として、`node_addon_api`を利用する。
    - node_addon_apiの使用方法は [こちらの記事](https://qiita.com/NA_simple/items/dc31f9ae9519602f9f50) で紹介しています。

- node_addon_apiを使って、C++ファイルをコンパイルする
    - コンパイルに成功すると、プロジェクトのルートディレクトリ内に `build/Release`フォルダが生成され、そこに.nodeファイルが生成される
    - ※ もし、コンパイルしたC++ファイルがOpenCVなどのDLLに依存している場合、`build/Release`内に .dllファイルを設置しないといけません。

- 以降、node-addon-apiでビルドしたC++ライブラリを `.nodeモジュール`と呼ぶことにします。
    - 以下に .nodeモジュールを使用するための手順を示します。

### vue.config.jsの作成
- はじめに、ルートディレクトリ(package.jsonと同階層)に `vue.config.js` を手動で作成する
    - [なぜ vue.config.jsが必要？](https://stackoverflow.com/questions/62777834/how-fix-dirname-not-defined-when-using-electron-events-with-vue)

#### nodeIntegrationの有効化
- vue.config.js内の `pluginOptinons`セクション内に下記の記述を追加する

    ```js:vue.config.js

    module.exports = {
        pluginOptions: {
            electronBuilder: {
                pluginOptions: {
                    nodeIntegration: true
                }
            }
        }
    }
    ```
    - 注) nodeIntegration: true はブラウザ側でのネイティブアプリの実行が可能となるが、セキュリティリスクが高まる 
        - https://nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration

#### builderOptionsセクションの作成
- 作成したvue.config.js内に__"builderOptions"__セクションを追加する。
    - package.jsonの __"build"__ セクション内の項目を、builderOptionsセクションへ移動する
    - 注意) package.json 内に "build" セクションがあると、下記のようなエラーが発生する

        ``` bash:error
        InvalidConfigurationError: 'build' in the application package.json (\package.json) is not supported since 3.0 anymore. Please move 'build' into the development package.json
        ```

-  "builderOptions"内に、"extraResources"セクションを設けて、node_addon_apiでビルドした.dllと.node拡張子を登録する
    - これにより、`dist-electron/win-unpacked/resources` フォルダ内(※ windowsの場合)にビルドしたファイルが転送され、electron側で読み込めるようになる

        ```js:vue.config.js
        "extraResources": [
            "./build/Release/*.node",
            "./build/Release/*.dll"
        ],
        ```

#### （補足）　builderOptionsセクションの設定項目一覧
- vue.config.js内の __builderOptions__ 内で設定できるセクション名は下記を参照のこと
    - [electron-builder: Common-configuration](https://www.electron.build/configuration/configuration)

### node-loaderをインストールする
- node-addon-apiでビルドした.nodeファイルを読み込むために使用する[（node-loader）](https://www.npmjs.com/package/node-loader)
    - 初めに npm installを行う
        - `npm install node-loader`

- vue.config.js内の `pluginOptinons`セクション内に下記の記述を追加する

    ```js:vue.config.js
        pluginOptions: {
            // ---- ここから↓ ---- //
            target: 'node',
            node: {
                __dirname: false,
            },
            module: {
                rules: [
                    {
                        test: /\.node$/,
                        loader: 'node-loader',
                    },
                ],
            },
            // ---- ↑ここまで ---- //
            electronBuilder: {
                ...中略
            }
        }
    ```

- ここまでの準備で、electron側から.nodeアプリを呼び出す準備ができた。

## メインプロセスで .nodeモジュールを使用する
- src/background.jsで以下のように読み込む。
    - `__non_webpack_require__` 関数を使用し、.nodeファイルのパスを直接指定する
    - ※ バニラelectron で使用していた `require("bindings")("addon")`コマンドは使用できない

        ```js:background.js
            const path = require('path'); // pathモジュールインポート
            console.log("__dirname",__dirname); // Debug: ルートパスの表示
            // .nodeファイルのパスを直接指定
            var nodePath = path.dirname(__dirname) + '/build/Release/○○○.node';
            var addon = __non_webpack_require__(nodePath);
            var obj = new addon.○○○(); // nodeモジュールのインスタンス化
        ```

    - ※ ここでモジュールを読み込むことができるのは、vue.config.js内の __"extraResourcesFiles"__ セクションで .nodeファイルと、.dllファイルを登録しているから。

## レンダラープロセスで .nodeモジュールを使用する
- `preload.js`を経由して .nodeファイルを読み込む必要がある

### preload.jsの作成
- src/preload.jsを手動で作成する
- vue.config.jsに以下の項目を追加する

```js:vue.config.js
    pluginOptions: {
        nodeIntegration: true,
        preload: 'src/preload.js' // 追加
    }
```

- background.jsのcreateWindow()関数内で下記を追加する

```js:src/background.js
function createWindow() {
  // Create the browser window.
  const win = new BrowserWindow({
      webPreferences: {
          nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION,
          preload: path.join(__dirname, 'preload.js'),　// 追加
      }
  })
```


### preload.js内で .nodeモジュールを呼び出す
- 呼び出し方は、 background.jsとほぼ同じ
    - 相違点は最後の1行。`window.変数名` として登録することで、レンダラープロセスから利用できる

    ```js:preload.js
        const path = require('path'); // pathモジュールインポート
        console.log("__dirname", __dirname); // Debug: ルートパス
        var nodePath = path.dirname(__dirname) + '/build/Release/○○○.node';
        var addon = __non_webpack_require__(nodePath);
        var obj = new addon.○○○();
        window.obj = obj; // !重要: windowオブジェクトに紐付けることで、app.Vueから呼び出せる
    ```

- App.vueから、preload.jsで呼び出したモジュールを利用する
    - windowオブジェクトから変数を参照する

    ```js:App.Vue
      var obj = window.obj; // windowオブジェクトから参照する 
      obj.funcA(); 
    ```

- [参考にしたStackOverflow](https://stackoverflow.com/questions/57807459/how-to-use-preload-js-properly-in-electron)
   - ※ ゆくゆくは[context-bridge](https://www.electronjs.org/docs/api/context-bridge)を使った方がセキュアだと思います。

## その他Tips
#### よくあるミス
- <u>関連するDLLを build/Releaseフォルダに置いていない
  - " __build/Release/○○.node__ が見つからない" とのエラーが出たときは、DLLも確認すること</u>

#### electron + vue で複数ページのアプリを作る方法
- https://github.com/nklayman/electron-multipage-example
  
#### 静的ファイル(assets, images)をコピーする方法
- vue.config.js の builderOptions内に、`extraFiles`セクションを追加する

    ```js:vue.config.js
        "extraFiles": [
                    "images/*.png",
                    "config/**/.txt"
        ]
    ```

#### electron-vueで sass, scss を使う方法
- `npm install --save-dev sass-loader node-sass`
    - https://github.com/SimulatedGREG/electron-vue/blob/master/docs/en/using_pre-processors.md
- npm install 時にエラーが出たら... 古いバージョンを指定して再度インストールする
    - 例:  `npm install --save-dev sass-loader@10.1.1`
    - こちらが参考になる -> [StackOverflow](https://stackoverflow.com/questions/66082397/typeerror-this-getoptions-is-not-a-function)

#### レンダラープロセスから、dialogを利用する方法
- background.js で画面を生成する際に、`enableRemoteModule: true`オプションを追加する

    ```js:background.js
    new BrowserWindow({
        webPreferences: {
            enableRemoteModule: true, // 追加
         }
     });
    ```

## リンク集
- [__ElectronでcontextBridgeによる安全なIPC通信__](https://qiita.com/pochman/items/64b34e9827866664d436)
- [__ non_webpack_require__ は、レンダラプロセスで使用できない](https://stackoverflow.com/questions/46185302/non-webpack-require-is-not-defined)
- electron + Reactでの開発も可能なようだ
    - [GitHub: secure-electron-template](https://github.com/reZach/secure-electron-template)