Electronでデスクトップアプリケーションを開発する方法

November 13, 2020
執筆者
Fikayo Adepoju
寄稿者
Twilio の寄稿者によって表明された意見は彼ら自身のものです
レビュー担当者

Electronでデスクトップアプリケーションを開発する方法

この記事はFikayo Adepojuこちらで公開した記事(英語)を日本語化したものです。

チームコラボレーションソフトウェアの定番であるSlack、本稿執筆時点で市場で最も人気のあるコードエディタの一つであるVisual Studio CodeWhatsAppのデスクトップ版には共通点があります。それは、これらすべてがElectron.jsで構築されているということです。これらの企業が、ネイティブのデスクトップソフトウェア開発手法よりもElectron.jsを採用したことにより、Electron.jsはデスクトップアプリケーション開発のための信頼できるフレームワークとして確立されました。

本稿では、Electron.jsの概要と、そのユースケースをご紹介します。

このチュートリアルでは、以下のようなデスクトップアプリケーションを開発します。

  • Web技術で一から構築されている。
  • メインプロセスとレンダラープロセスの間で通信を行う。
  • Electron.jsのAPIを利用し、ブラウザAPIでは利用できない機能にアクセスすることができる。
  • デスクトップ通知を表示する。

Electron.jsの概要

Electron.jsを知る

Electron.jsは、HTML、CSS、JavaScriptなどのWeb技術を使ってデスクトップアプリケーションを構築するためのオープンソースフレームワークです。これにより、デスクトップアプリケーションの構築は、もはやC++、C#、Javaの開発者だけのものではなくなり、Web開発者はそのスキルを業界標準のデスクトップソフトウェアの開発に転用できるようになったのです。

Chromium(Google Chromeブラウザのオープンソース版)とNode.js JavaScriptランタイムを使うことにより、Web開発者は既存のWebアプリケーションとElectron.jsを組み合わせ、WindowsmacOSおよびLinuxプラットフォーム用のデスクトップアプリケーションとインストーラーを作成できるようになっています。

Electron.jsはGithubでメンテナンスされており、強力なエンジニアチームに支えられた信頼性の高いツールです。

Electron.jsを使用する理由

Electron.jsが登場する以前は、WindowsとMacなど複数の異なるOSにインストールする場合、WindowsではC#Visual Basic、MacではObjective-Cといったプラットフォーム互換の言語を使って、それぞれのプラットフォーム用にアプリケーションを開発しなければなりませんでした。さらには、もし開発者がJavaを使ってクロスプラットフォームのデスクトップ・ソフトウェアを開発することを選んだ場合、そのアプリケーションのユーザーは、アプリケーションを実行するために両方のプラットフォーム上にJavaランタイムをインストールする必要がありました。

しかし、Electron.jsは単一のコードベースにより、インストールに依存することなく全てのプラットフォーム用のインストーラを生成することが可能です。このため、たった1つの開発チームでも複数のプラットフォームに対応するアプリケーションを開発できます。また、Electron.jsでは、Web開発ができればデスクトップアプリケーションも構築できるため、既存のWeb開発者がデスクトップソフトウェアの開発者に容易に転身できることも大きなメリットです。

Electron.jsアプリケーションを構築するための前提条件

Electron.jsでアプリケーション開発を始めるために必要な項目は以下のとおりです。

  • HTML、CSS、JavaScriptの基礎知識。
  • Node.jsがインストールされていること。
  • Node.jsの基本知識。

Electron.jsアプリケーションの構造

アプリケーション構造

Electron.jsは構造上、大きく3つの部分から構成されています。

  • Chromium: Electron.jsの構造体の中で、Webページの作成と表示を担当する部分です。WebコンテンツはElectron.jsのレンダラープロセス(詳細は後述)で表示され、Chromium環境のため、一般的なGoogle Chromeブラウザで操作するのと同様に、すべてのブラウザAPIと開発ツールにアクセスできます。
  • Node.js: Electron.jsの構造体の中で、システム機能にアクセスするためのコンポーネントです。Electron.jsはNode.jsをメインプロセスで実行し、ファイルシステムやOSなどNode.jsが提供するすべての機能を利用できます。
  • Custom APIs: 開発者が一般的なデスクトップ体験を作成し、ネイティブの機能で簡単に作業できるように、Electron.jsでは、コンテキストメニューの作成と表示、デスクトップ通知の表示、キーボードショートカットの操作などのタスクを実行するための使いやすいライブラリのAPIが用意されています。

メインプロセスとレンダラープロセス

Electron.jsアプリは、メインプロセスと、レンダラープロセスの2種類のプロセスを保持しています。

Electron.jsアプリケーションのエントリーポイントはメインプロセスで、これは単にNode.jsの環境となります。ここでネイティブの機能とのやり取りが行われます。

メインプロセスはウェブページを作成する役割を担っています。これは、Electron.jsのBrowserWindowオブジェクトの新しいインスタンスを作成することで行われます。これによって、独自のレンダラープロセスで動作する新しいウェブページが作成されます。メインプロセスは複数のウェブページを作成でき、それぞれが独自のレンダラープロセスで実行されます。

通常、Electron.jsのアプリケーションはデフォルトのWebページを起動画面とします。アプリケーションで必要であれば、さらに画面を作成できます。

各レンダラープロセスは、それぞれのWebページを管理し、他のレンダラープロセスやメインプロセス自体から完全に分離されています。このため、一つのレンダラープロセスが終了しても、他のレンダラープロセスには影響しません。レンダラープロセスは、メインプロセスからそのインスタンスを破棄することによっても終了できます。

レンダラープロセスがアクセスできるのは、ブラウザのAPIであるwindowや documentオブジェクトなどだけです。これは、レンダラープロセスが単に実行中の Chromiumブラウザインスタンスであるためです。ただし、Node.jsのAPIである processrequireにアクセスできるように設定することは可能です。

プロセス説明

メインプロセスとレンダラープロセスのコミュニケーション

Electron.jsアプリケーションでは、ユーザーがボタンをクリックするなどのイベントに応じてネイティブ機能を使いたいことがよくあります。しかし、レンダラープロセスとメインプロセスは完全に分離されているため、Webページからネイティブ機能に直接アクセスできません。

この問題を解決するために、Electron.jsではIPC(Inter-Process Communication)というチャネルを用意し、レンダラープロセスとメインプロセス、またはその逆の通信を可能にしています。

メインプロセスとレンダラープロセスにそれぞれ対応したモジュールであるipcMainipcRendererを使用すると、一方のプロセスからイベントを発信し、もう一方のプロセスのイベントをリッスンできます。また、あるプロセスから別のプロセスへデータを渡すこともできます。本稿の後半では、これらのモジュールを使用して、レンダラープロセスとメインプロセスの間で通信を行う予定です。

Electron.jsの簡単なプロジェクトの構築

それでは、Electron.jsを実際に使ってコーディングしてみましょう。本稿では、タスクリストに項目を追加する簡単なデスクトップアプリケーションを作成します。目標は、ゼロからデスクトップアプリケーションを作成し、正常に実行することです。

アプリケーションの雛型を作成する

まず、任意のディレクトリから以下のコマンドを実行して、プロジェクト用のフォルダを作成し、新しいフォルダにディレクトリを変更します。

mkdir my-electron-app
cd my-electron-app

Electron.jsのアプリケーションは、基本的にWebページを実行するNode.jsアプリケーションなので、以下のコマンドを実行してアプリを初期化し、package.jsonファイルを作成します。

npm init -y

次に、プロジェクトフォルダのルートにindex.htmlファイルを作成し、次のコードを追加して、アプリケーションのホームページを作成します。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>My Electron App</title>
</head>

<body>
    <h1>Welcome to My Electron App</h1>
</body>

</html>

上記のHTMLコードでは、タイトルに「My Electron App」、「Welcome to My Electron App」というh1タグを含むウェブサイトが作成されます。

これで、Node.jsアプリケーションが作成できました。次のステップでは、Electron.jsを使用して、デスクトップアプリケーションに変換します。

まず、Electron.js ライブラリをインストールします。コマンドプロンプトに戻り、プロジェクトのルートディレクトリで、以下のコマンドを実行します。

npm install --save-dev electron

インストールが完了したら、main.jsという名前のファイルを新規作成します。これは、アプリケーションへの入り口となるもので、メイン処理のスクリプトです。このスクリプトは、次のような処理を行います。

  • アプリケーションのホーム画面用のウェブページを作成する。
  • Electron.jsアプリの起動時にアプリのホーム画面をロードする。
  • アプリケーションのウィンドウを閉じていても、アプリケーションが起動していれば、アイコンをクリックしたときにホーム画面を読み込む。

main.jsでは、まず必要なパッケージをインポートし、アプリケーションのホーム画面に新しいウェブページを作成するための関数を作成します。

const { app, BrowserWindow } = require("electron");
const path = require("path");

const loadMainWindow = () => {
    const mainWindow = new BrowserWindow({
        width : 1200,
        height: 800,
        webPreferences: {
            nodeIntegration: true
        }
    });

    mainWindow.loadFile(path.join(__dirname, "index.html"));
}

上のコードブロックでは、Electron.jsパッケージからapp (Electron.jsアプリケーションオブジェクト) とBrowserWindow(Webページの作成と読み込みを行う Electron.jsモジュール)をインポートしています。また、プロジェクトディレクトリを操作できるようにするためのモジュールであるpathもインポートされています。

インポートの下に、loadMainWindow()関数があります。この関数は、プロジェクトのルートからindex.htmlファイルをロードして、1200px × 800pxの新しいブラウザウィンドウを作成するためにBrowserWindowオブジェクトを使用します。

次に、main.jsで先程追加したコードの下に、アプリが起動した直後に関数が呼び出されるように、 loadMainWindow()関数の呼び出しを追加します。

app.on("ready", loadMainWindow);

アプリケーションにreadyイベントが発生したときだけ、loadMainWindow()が呼び出されます。一部のAPIはこのイベントが発生した後にしか使用できないため、Webページはこのイベントを待つ必要があります。

次のステップは、一部のOSで、すべてのウィンドウを閉じた後もアプリケーションがアクティブなままになっている問題への対処です。これは、MacOS以外のプラットフォームでよく発生します。この問題を解決するには、main.jsの既存のコードの下に、次のコードを追加します。

app.on("window-all-closed", () => {
  if (process.platform !== "darwin") {
    app.quit();
  }
});

このコードは、メインのプロセスによって作成されたすべてのウィンドウが閉じられたときに発生するイベントであるwindow-all-closedをリッスンするよう指示します。次にプラットフォームがMacOSであるかどうかを確認し、MacOSでない場合は明示的にアプリケーションを終了してメインのプロセスを終了させ、アプリケーションを終了します。

このファイルの最後のステップは、ウィンドウが開いていないときに、オペレーティングシステムのDockでアイコンをクリックすると、アプリケーションが起動するようにすることです。main.jsファイルの最後に以下のコードを追加します。

app.on("activate", () => {
    if (BrowserWindow.getAllWindows().length === 0) {
        loadMainWindow();
    }
});

このコードでは、アプリケーションのactivateイベントをリッスンします。このイベントが発生すると、現在開いているアプリケーションのウィンドウがあるかどうか確認します。開いていない場合は、ホーム画面をロードするためにloadMainWindow()を呼び出します。

これでmain.jsファイルの作業は以上です。

アプリケーションの設定

アプリケーションがElectrion.jsで正しく動作するよう設定するために、package.jsonファイルを変更する必要があります。

package.json* ファイルを開きます。mainキーの値を以下のように“main.js”に変更します。

"main": "main.js",

次に、以下のように、scriptsセクションにstartスクリプトを追加してください。

"scripts": {
    "start" : "electron .",
    "test": "echo \"Error: no test specified\" && exit 1"
  }

ファイルを保存して閉じます。この時点で、以下のコマンドで新しいElectron.jsアプリケーションを実行できます。

npm start

これにより、アプリケーションが起動し、ホーム画面がロードされます。

アプリ画面

シンプルなタスクリストシステムを作る

Electrion.jsの他の機能を学ぶために、シンプルなタスクリストシステムを作成します。

まず始めに、アプリケーションのホーム画面に基本的なコンテンツを追加します。

index.htmlファイルを開き、以下のようにBootstrapライブラリをheadセクションのmetaタグの下に追加してください。

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css" integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" crossorigin="anonymous">
    <title>My Electron App</title>
</head>

次に、body要素内のh1タグの下に、2カラムのレイアウトを作成するために、以下のハイライトされた行を追加します。1カラム目にはタスクリストが入ります。

<body>
    <h1>Welcome to My Electron App</h1>
    <div class="container">
        <div class="row">
            <div class="col-md-6">
                <ul id="list" class="list-group">
                  <li class="list-group-item">Buy Groceries</li>
                  <li class="list-group-item">Cook Meal</li>
                </ul>
            </div>

            <div class="col-md-6">
            </div>
        </div>
    </div>
</body>

現在アプリケーションが起動している場合は、コマンドプロンプトのCtrl+Cキーを押してアプリを終了させた後、npm startを実行してアプリを再起動させます。以下の画面が表示されます。

アプリ画面

Electron.jsのAPIを利用したデスクトップ通知の表示

このセクションでは、タスクリストに新しい項目を追加し、項目が追加されたときに通知を表示する機能を実装します。このセクションの目的は、レンダラープロセスとメインプロセスの間の通信をデモンストレーションすることです。

タスクリストへの新規項目の追加

index.htmlファイルに、form inputとbutton要素を追加します。ユーザーはこれらの要素を操作して、タスクリストに新しい項目を追加します。これらの要素を追加するには、以下のハイライトされた行をコピーして、2カラムグリッドの2つ目のカラムに貼り付けます。

<body>
    <h1>Welcome to My Electron App</h1>
    <div class="container">
        <div class="row">
            <div class="col-md-6">
                <ul id="list" class="list-group">
                  <li class="list-group-item">Buy Groceries</li>
                  <li class="list-group-item">Cook Meal</li>
                </ul>
            </div>
            
            <div class="col-md-6">
                <input class="form-control" id="newTask" placeholder="Enter New Task" />
                <br />
                <button type="button" class="btn btn-primary" id="addTask">
                    Add Task
                </button>
            </div>
        </div>
    </div>
</body>

ここで、プロジェクトのルートにscript.jsという新しいJavaScriptファイルを作成し、以下のようにindex.htmlファイルにインポートしてください。

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css" integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" crossorigin="anonymous">
    <script src="script.js"></script>
    <title>My Electron App</title>
</head>

script.jsファイルに、以下のコードを追加します。

let list = document.getElementById("list");
let newTask = document.getElementById("newTask");

document.getElementById("addTask").addEventListener('click', () => {
    list.insertAdjacentHTML('beforeend', `<li class="list-group-item">${newTask.value}</li>`)
    newTask.value = '';
});

上記のコードでは、index.htmlで追加したbutton要素にclickイベントハンドラーを追加しています。ボタンがクリックされると、入力フィールドの値が新規の <li>要素に挿入され、タスクリストに追加されます。

ここで、アプリケーションを終了し、再起動します。入力フィールドに値を入力し、Add Taskボタンをクリックして、いくつか新しい項目を追加してみてください。

アプリ画面

新着アイテムのお知らせを表示する

アプリケーションに追加する最後の機能は、デスクトップ通知です。リストに新しい項目が追加されるたびに、通知が表示されます。Electron.jsはレンダラープロセスからHTML 5 Notifications APIを使って通知を作成できるにもかかわらず、メインプロセスでのみ利用できるElectron.js Notificationモジュールを使用することになります。したがって、レンダラープロセスは、通知を機能させるために、メインプロセスと通信する必要があります。

これを実現するために、今回はipcRendereripcMainモジュールを使用します。ここでは、メインプロセスにshow-notificationイベントを送信し、ペイロードとして taskを送信するためにipcRendererモジュールを使用します。script.jsを以下のように変更してください。

const { ipcRenderer } = require('electron');
  
let list = document.getElementById("list");
let newTask = document.getElementById("newTask");

document.getElementById("addTask").addEventListener('click', () => {
    list.insertAdjacentHTML('beforeend', \`<li class="list-group-item">${newTask.value}</li>\`);
    ipcRenderer.invoke('show-notification', newTask.value);
    newTask.value = '';
});

レンダラープロセスの中でrequireにアクセスできるのは、main.jsで nodeIntegration: trueを設定しているからです。これにより、Node.jsのAPIにアクセスできます。

Mainプロセスはこのイベントに対応し、新しいタスクの通知を表示する必要があります。

まず、main.jsの1行目を以下のように変更します。

const { app, BrowserWindow, ipcMain, Notification } = require("electron");

これは、Electron.jsパッケージからipcMainNotificationモジュールのインポートを追加するものです。次に、main.jsにある既存のコードの下に、以下を追加します。

ipcMain.handle('show-notification', (event, ...args) => {
    const notification = {
        title: 'New Task',
        body: `Added: ${args[0]}`
    }

    new Notification(notification).show()
});

上記のコードでは、レンダラープロセスから送信されるshow-notificationイベントをリッスンし、通知を作成して表示するために、ipcMainを使用しています。

アプリケーションをテストする

これまでに書いたコードをテストするために、アプリケーションを終了し、再起動します。アプリケーションが正常にロードされたら、新しいタスクを追加してください。画面右上に以下のような通知ポップアップが表示されます。

タスク追加

macOS(および他の一部のオペレーティングシステム)では、アプリケーションからの通知を表示するための許可を承認するよう求めるプロンプトが表示される場合があります。通知を表示するためには、この要求を承認してください。

最後に

Electron.jsは、Web開発者が既存のスキルを活かしてネイティブアプリケーション開発に参入するための大きな力となり、アプリケーション開発の世界に大きな変化をもたらしました。

本稿では、以下をご紹介しました。

  • Electron.jsとは何か、Electron.jsを使うべき理由
  • Electron.jsのプロジェクトの構造と動作について
  • Electron.jsプロジェクトをビルドするための前提条件とビルド方法
  • Electron.jsのプロジェクトでネイティブプラットフォームの機能を利用する方法

次のステップ

さらに、このアプリケーションを拡張するために、以下のいずれかの方法をとることができます。

  • アプリを認証して利用するための認証機能の追加(Twilio Verifyをご確認ください。)
  • デスクトップアプリに音声機能を追加する(Twilio Programmable Voiceをご確認ください。)

Fikayo Adepojuは、10年以上の経験を持つフルスタックのWebおよびモバイル開発者です。現在、フルタイムのテクニカルコンテンツクリエイターとして、さまざまなブログに技術記事を書いたり、ビデオコースを作ったりしていpます。現在は開発者と知識を共有することに注力しています。ご連絡はTwitterDMからどうぞ! @coderonfleek