その他

【Electron】Electron入門!

その他
この記事は約14分で読めます。

はじめに

みなさんこんにちは。インプルの岩崎です。

最近ではAI使ったり、裏側のシステムをつくったりしていましたが、今日はアプリを作ってみるお話です。

Electronとはなに?

今回はElectronというフレームワークを使ってみようと思います。

Build cross-platform desktop apps with JavaScript, HTML, and CSS | Electron
Build cross-platform desktop apps with JavaScript, HTML, and CSS

Electron(エレクトロン)は JavaScript / HTML / CSS を使って デスクトップアプリケーション を開発できるフレームワークです。

利用技術として、次の2つがあります。

  • Chromium(ブラウザエンジン)
  • Node.js(サーバーサイドJavaScriptランタイム)

この2つを組み合わせることで、ウェブ技術で書かれたアプリを クロスプラットフォームのデスクトップアプリ として配布可能になります。

最小構成で起動させる

早速使っていきましょう!

インストール

まずは下記のコマンドでインストールをします。インストールには、Node.jsが必要です!

# 新しいプロジェクトを作成
mkdir my-electron-app
cd my-electron-app

# npm 初期化
npm init -y

# Electron をインストール
npm install electron --save-dev

最小構成ファイル

インストールが終わったら、下記のような構成にしましょう。これがとりあえずアプリを起動できる最小構成です。最小構成で必要な3つのファイルについての詳細は、この後説明します。

my-electron-app/
├── node_modules/       ← npm install で自動生成(編集しない)
├── package.json
├── package-lock.json
├── main.js             ← 自分で作る(Mainプロセス)
└── index.html          ← 自分で作る(Rendererプロセス)

package.json

1つ目に package.json ファイルです。Electronでは通常のNode.jsアプリと同じく package.json がアプリ全体の設定を担います。インストールする時のオプションによって、ファイルの中の構成は若干変わってしまいますが、とりあえず下記の要素が詰まっていればOKです。

### package.json

{
  "name": "my-electron-app",
  "version": "1.0.0",
  "main": "main.js",      # アプリのエントリーポイント
  "scripts": {
    "start": "electron ."   # "npm start" だけでアプリを立ち上げられるようにする
  }
}

main.js

2つ目に main.js です。
Electronアプリはブラウザのように見えて、裏では Mainプロセス が全体を管理しており、司令塔的な役割です。ここでウィンドウを作ったり、index.htmlを読み込んだりします。

### main.js

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

function createWindow() {
  // ウィンドウを作成
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: true // Node.js API を使えるようにする
    }
  })

  // index.html をロード
  win.loadFile('index.html')
}

// Electron が起動したらウィンドウ生成
app.whenReady().then(createWindow)

// macOS用の終了処理
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') app.quit()
})

index.html

3つ目に index.html(Rendererプロセス)です。
普通のHTMLファイルと一緒の構成ですが、nodeIntegrationを有効にすることで、Node.js APIが使用できるようになっています。ここではwebページを表示し、ユーザーとやり取りをするUI担当をしています。

### index.html(Rendererプロセス)

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Hello Electron</title>
  </head>
  <body>
    <h1>Hello Electron!</h1>
    <p>これはElectronで作った最初のアプリです 🎉</p>
  </body>
</html>

Electronを起動させてみよう

先ほどまでの設定をすることで、とりあえずアプリを起動させることができます。早速下記のコマンドを実施してみてみましょう。

npm start

すると、画面のポップアップとともにhtmlで入力した内容が無事に表示されました。無事に動作できているようです。

アレンジを加える

それでは、上記ファイルを利用してそれぞれ練習していきましょう。
ファイルは新たに作ってもいいですし、index.htmlに追記していく形でも構いません。お好きなやり方で進めていってください。新たにファイルを作る場合、 win.loadFile('index.html') の部分でファイルを名前を変えましょう!

テキスト入力とボタン選択

まずは、基本的な部分を習得していきましょう。テキスト入力とボタンの選択処理です。Renderer内だけで処理ができるようにしていきます。先ほどのhtmlファイル内に、下記の処理を追加します。

<h2>1. テキスト入力の練習</h2>
    <input type="text" id="txt_ls_textbox" placeholder="ここにテキストを入力">
    <button id="submit_textbox">送信</button>
    <p id="result_textbox"></p>

    <h2>2. ボタン入力の練習</h2>
    <button id="btn_ls_sea">海派</button>
    <button id="btn_ls_mountain">山派</button>
    <p id="result_btn"></p>

    <script>
        // 1. テキスト入力の練習
        const txt_ls_textbox = document.getElementById("txt_ls_textbox");   // テキスト入力の要素を取得
        const submit_textbox = document.getElementById("submit_textbox");   // 送信ボタンの要素を取得
        const result_textbox = document.getElementById("result_textbox");   // 結果の要素を取得

        submit_textbox.addEventListener("click", () => {
            const input_text = txt_ls_textbox.value;                          // 入力されたテキストを取得
            result_textbox.textContent = `入力されたテキストは${input_text}です`; // <p>に文字を表示
        });

        // 2. ボタン入力の練習
        const btn_ls_sea = document.getElementById("btn_ls_sea");   // 海派ボタンの要素を取得
        const btn_ls_mountain = document.getElementById("btn_ls_mountain");   // 山派ボタンの要素を取得
        const result_btn = document.getElementById("result_btn");   // 結果の要素を取得

        btn_ls_sea.addEventListener("click", () => {
            result_btn.textContent = "あなたは海派ですね!";
        });

        btn_ls_mountain.addEventListener("click", () => {
            result_btn.textContent = "あなたは山派ですね!";
        });
    </script>

これを実行することにより、テキストの入力とボタンに応じた反応をそれぞれ作ることができます。
テキスト入力に関しては、入力されたテキストを取得し、送信ボタンを押した時に “入力されたテキストは${input_text}です ” で表示させる処理をしています。
ボタン入力に関しては、2つのボタンを用意し、クリックされたボタンごとに “あなたは海派ですね!”“あなたは山派ですね!” でそれぞれ処理させています。

実行させると、上記の画像のような形でそれぞれ処理されます。問題なく動作していますね!

IPC通信にさせる

続けてIPC通信をさせていきましょう。いままではhtmlファイルだけで良かったのですが、これをすることにより、「Electronらしさ」が出てきます!

まず、基礎的な部分を押さえます。Electronでは Renderer(index.html)↔ Main(main.js) がやり取りするために ipcRendereripcMain を使います。また、今回はお試しではありますが、セキュリティ面を考慮した contextIsolation: true + preload.js の形にもしていきます。

  • Renderer(index.html内のJS) → ipcRenderer.send("チャンネル名", データ)
  • Main(main.js)        → ipcMain.on("チャンネル名", (event, データ) => { … })
  • Mainから返すとき        → event.reply("別のチャンネル名", 結果)
  • Rendererで受け取る       → ipcRenderer.on("別のチャンネル名", (event, 結果) => { … })

main.js には、IPC通信をしていくために、いくつかの記述を追加していきます。

const { app, BrowserWindow, ipcMain } = require('electron')
const path = require('path')

function createWindow() {
  // ウィンドウを作成
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: false,
      contextIsolation: true,
      preload: path.join(__dirname, 'preload.js')
    }
  })

  // index.html をロード
  win.loadFile('index.html')
}

// Electron が起動したらウィンドウ生成
app.whenReady().then(createWindow)

// Rendererから "greet" を受け取る
ipcMain.on("greet", (event, text) => {
    const message = `入力された文字は、${text}です! Mainで作成されました。`
    event.reply("greet-reply", message)
})

// macOS用の終了処理
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') app.quit()
})

preload.js は、新たにファイルを新規作成します。
これを追加することで、ElectronAPIを安全に呼び出せるようになります。

const { contextBridge, ipcRenderer } = require('electron')

// セキュアなAPIをレンダラープロセスに公開
contextBridge.exposeInMainWorld('electronAPI', {
  sendGreet: (text) => ipcRenderer.send('greet', text),
  onGreetReply: (callback) => ipcRenderer.on('greet-reply', (event, message) => callback(message))
})

index.html には、<script> の中身を変えていきます。

<h2>テキスト入力の練習 - IPC通信</h2>
    <input type="text" id="txt_ls_textbox" placeholder="ここにテキストを入力">
    <button id="submit_textbox">送信</button>
    <p id="result_textbox"></p>

<script>
        // 1. テキスト入力の練習
        const txt_ls_textbox = document.getElementById("txt_ls_textbox");   // テキスト入力の要素を取得
        const submit_textbox = document.getElementById("submit_textbox");   // 送信ボタンの要素を取得
        const result_textbox = document.getElementById("result_textbox");   // 結果の要素を取得

        // IPC通信 - ボタンを押したらMainに送信
        submit_textbox.addEventListener("click", () => {
            const text = txt_ls_textbox.value;
            window.electronAPI.sendGreet(text);
        });

        // IPC通信 - Mainからの応答を受け取る
        window.electronAPI.onGreetReply((message) => {
            result_textbox.textContent = message;
        });

 </script>

これらのプログラムを下記のような流れで処理をさせていきます。これで セキュアなIPC通信 が実現できます。

  1. Renderer(index.html)が window.electronAPI.greet(name) を呼ぶ
  2. preload.js 経由で ipcRenderer.send("greet", name) が実行される
  3. Main(main.js)が ipcMain.on("greet", …) で受け取る
  4. メッセージを作って event.reply("greet-response", …) で返す
  5. Rendererで window.electronAPI.onGreetResponse() が呼ばれてUIに反映

すると、上記の画像のように通信がうまくいった処理が表示されました。

そのほかの拡張性

先ほどまでやっていたことを広げていくことで、より実用性の高いアプリに仕上げることもできます。

Electronはhtmlで画面の表示を作っているので、cssを使ってUIを充実させることもできます。また、そのほかのweb技術であるTailwind CSSやBootstrap、Materializeも使用することができます。

また、OS連携をしてファイルを開いたり選んだりすることもできますし、ネットワークやAPI連携を使ってAPIキーを使用したアプリを作ることもできます。データの保存や読み書きをすることもできるので、入力関係も問題なしです!

ドキュメントにもさまざまなできることがまとめられていますので、ぜひご覧ください。

app | Electron
アプリケーションのイベントライフサイクルを制御します。

おわりに

いかがだったでしょうか?

今回作成したコードは、GitHubにもありますので、よろしければぜひご覧ください。

iwasakiterukazuimpl/electron-lesson
Contribute to iwasakiterukazuimpl/electron-lesson development by creating an account on GitHub.

ご覧いただきありがとうございました!