Electron Tips ~便利なモジュールや小技~


Electronを使用した開発を最近行ったので、個人的に便利だったモジュールや小技などをまとめていこうと思います。

使用した**Electronのバージョンは15系**です。

## Electronのメインプロセス側の基本コード
基本のメインプロセスの構成はだいたい誰が書いてもこのようになると思いますので、こちらをベースに紹介していこうと思います。

すべてのプラットフォームで動作する結合絶対パスを作成するためにnode.jsの標準モジュールである`path`を使用しています。

```jsx:main.js
//だいたいこんな感じ
import path from "path";

let mainWindow;

const createMainWindow = () => {
    mainWindow = new BrowserWindow({
        width: 1200,
        height: 800,
        webPreferences: {
            preload: path.resolve(__dirname, 'preload.js'),
        },
    });

    mainWindow.loadFile(path.resolve(__dirname, 'index.html'));
};

app.on('ready', () => {
    createMainWindow();
});

```

## アプリの二重起動を防止したい
開発するデスクトップアプリによっては、アプリの複数起動を避けたい場合もあると思います。

`requestSingleInstanceLock()`というメゾットでアプリケーションのインスタンスのロックが成功したかどうかが分かります。([ドキュメント](https://www.electronjs.org/docs/latest/api/app#apprequestsingleinstancelock))
ロックに失敗した場合(`false`の場合)、すでにこのアプリのインスタンスがロックされているということなので、既にアプリが起動しているかどうかを検知することができます。



```js:main.js
const createMainWindow = () => {
    // 省略
};

const gotTheLock = app.requestSingleInstanceLock();

// false(既に起動)の場合　即終了
if (!gotTheLock) {
  console.log("二重起動ストップ!! 即停止!!");
  app.quit();
}

// true(初回起動)の場合
app.on('ready', () => {
    createMainWindow();
});

```

## 開発時にホットリロードさせたい
開発時にソースコードを変更するたびに、アプリを落として再実行するのはかなり面倒くさいですよね。
`electron-reload`というライブラリを使えば簡単にホットリロード設定が可能です。

https://www.npmjs.com/package/electron-reload

```js:main.js
const  electronReload  =  require('electron-reload')
//環境変数で開発環境かどうかをチェック
const isDev = process.env.NODE_ENV === 'development';

// macとwinでElectron実行のPATHが違う
const execPath =
    process.platform === 'win32'
        ? '../node_modules/electron/dist/electron.exe'
        : '../node_modules/.bin/electron';

if(isDev) {
    electronReload(__dirname, {
        electron: path.resolve(__dirname, execPath)
    });
}

```

## OS起動時・ログイン時にアプリも自動起動させたい
常駐するアプリなどを実装する際，OSの起動やログイン時にアプリも一緒に起動させたい場合もあると思います。

そのような場合、`auto-launch`というモジュールを使うことによって、Windowsやmacの起動時に自動でアプリを起動するように「レジストリ」や「ログイン項目」に登録することができます。

https://www.npmjs.com/package/auto-launch



```bash
npm install --save auto-launch
```

```main.js
import AutoLaunch from "auto-launch";

//環境変数で開発環境かどうかをチェック
const isDev = process.env.NODE_ENV === 'development';

const launch = new AutoLaunch({ name: "your-app-name" });
// 即実行
(async () => {
　// 自動起動アプリとして登録されているかチェック(Promiseで返ってくる)
  const isEnabled = await launch.isEnabled();
// 本番環境かつ自動起動アプリとして登録されていない場合、レジストリ等に登録
  !isDev && !isEnabled && launch.enable();
})();

const createMainWindow = () => {
  // 略
}

// 略
```

:::note warn
開発環境で auto-launch を登録してしまうと空のelectronアプリが登録されてしまうので、環境変数などで本番環境でのみ動作するように気をつけましょう。
:::

## リンクをふんだらブラウザに飛ばしたい
Electron上でURLのリンクを踏むと、Electronで別ウィンドウが立ち上がり、そこでページが開かれてしまいます。

Electronがリンクを開くときに発生するイベントには2種類あり、リンクをクリックしたときに発生する`will-navigate`、**target="_blank"**などで新しいウィンドウを開こうとするときに発生する`new-window`の両方の処理にハンドラをかませます。

`shell.openExternal`はElectronの標準のモジュールで、URLを既定のブラウザで開くことができます。
(特定のページはElectronで開きたい！！みたいな場合はif文やswitch文で条件分岐させるとよいと思います)

```ts:main.js

const createMainWindow = () => {
    mainWindow = new BrowserWindow({
        width: 1200,
        height: 800,
        webPreferences: {
            preload: path.resolve(__dirname, 'preload.js'),
        },
    });

    // リンクをふんだらブラウザに飛ばす処理
    const urlOpenhandler = (event: Electron.Event, url: string) => {
        if (url.match(/^http/)) {
            // Electronデフォルトの処理を停止
            event.preventDefault();
            // 既定のブラウザでurlを開く
            shell.openExternal(url);
        }
    };
    
    // リンクをクリックしたときの処理
    mainWindow.webContents.on('will-navigate', urlOpenhandler);
    // 新しいウィンドウを開こうとしたときの処理
    mainWindow.webContents.on('new-window', urlOpenhandler);
    // ここまで

    mainWindow.loadFile(path.resolve(__dirname, 'index.html'));
};
```

## タスクトレイ登録

タスクトレイ登録というのは、Windowsでいう以下の画像に表示されているような場所にアイコンを表示させることです。

![image (23).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1645586/59b25c48-6452-bbfd-76e8-3c9da0775be2.png)

以下のサンプルコードを見れば使い方はなんとなくわかると思いますが、[ドキュメント](https://www.electronjs.org/ja/docs/latest/api/tray)に色んな設定メニュー・オプションがのっているのでタスクトレイに他の機能を付けた場合は参照してください。


```js:main.js
// グローバルスコープで定義
let tray = null;

app.on("ready", () => {
  createMainWindow();
　
　// タスクトレイ用のアイコンのパス定義
  const iconPath = path.resolve(
    __dirname,
    `iconName.${process.platform === "win32" ? "ico" : "png"}`
  );
　
　// trayのインスタンス化
  tray = new Tray(iconPath);
  // メニューの設定
  tray.setContextMenu(
    Menu.buildFromTemplate([
      {
        label: "リロード",
        click: () => {
          mainWindow.reload();
        },
      },
      {
        type: "separator",
      },
      {
        label: "Exit",
        role: "quit",
      },
    ])
  );
});
```

:::note warn
Windowsでは .ico ・Macでは.png 形式のアイコン画像を登録する必要があります。
:::

:::note warn
trayをグローバルスコープではなくローカルスコープ(readyの中など)で定義してしまうと、本番環境ではガベージコレクションによってタスクトレイから消されてしまうことがあるので注意が必要です。
:::

## グローバルショートカット登録

Electoronではアプリにフォーカスが当たっていない状態でもショートカットを登録することができます。

`globalShortcut.register(登録したいキー, () => 実行したいコード)`というメゾットで簡単に設定することが可能です。[ドキュメント](https://www.electronjs.org/ja/docs/latest/api/global-shortcut)

既にグローバルショートカットキーとして使用されているキーを登録はできません。
アプリを閉じる際はグローバルショートカットキーを解除してあげることが良心的だと思います。



以下、`Alt`と`Space`キーでアプリを出したり消したりするサンプルです。

```js:main.js
app.on("ready", () => {
    globalShortcut.register("Alt+Space", () => {
    // アプリのウィンドウがユーザーに見えているかチェック
    if (mainWindow.isVisible()) {
      mainWindow.hide();
    } else {
      mainWindow.show();
      mainWindow.focus();
    }
  });

  createMainWindow();
});

app.on('will-quit', () => {
  // ショートカットの登録解除
  globalShortcut.unregister('Alt+Space')
})
```
:::note warn
Ctrl+S などをグローバルショートカットキーとして登録してしまうと、すべてのアプリにおいてCtrl+S 保存できないみたいなことが起こりえるのであまり使わないキーで登録しましょう。
:::

## フォーカスはずれたときに閉じたい
ランチャーアプリのようなアプリを作る際は、アプリからフォーカスが外れるとアプリを閉じたり隠したりしたいという場面が発生すると思います。

`browser-window-blur`を使えば、アプリのウィンドウからフォーカスが外れたときにイベントを起こすことができます。([ドキュメント](https://www.electronjs.org/ja/docs/latest/api/app#%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88-browser-window-blur))



```js:main.js
app.on('ready', () => {
    createMainWindow();
});

app.on("browser-window-blur", () => {
  mainWindow.hide();
});

```

## 最後に

以上、僕がElectronでの開発時に使用したTipsでした!
どなたかの参考になれば幸いです。ありがとうございました:blush: