ファランクスブログ

© 2026 all rights reserved.
  1. blog
  2. electron-js
  • main
  • preload
  • renderer
  • window.apiの型
  • 引数を渡す
  • ローカルデータ
  • JSONファイル
  • SQLite
  • 他
  • alert() / confirm()後にfocusが機能しない
  • 日本語フォント
  • アプリのインストール
  • snapの公開

Electron:

2026年6月24日

インストール:electron-vite

pnpm create @quick-start/electron

tailwind:

  • https://tailwindcss.com/docs/installation/using-vite

Shadcn:マニュアルで行う

  • https://ui.shadcn.com/docs/installation/manual


UIのボタンを押すとサーバーサイドでlogを出す例:

main/index.ts

ipcMain.on("get-log", () => {
  console.log("log");
});

preload/index.ts

    contextBridge.exposeInMainWorld("api", {
      getLog: () => ipcRenderer.invoke("get-log")
    });

renderer/app.tsx

      <button className="cursor-pointer" onClick={() => window.api.getLog()}>Log</button>

インストール後にできる3つのフォルダ

main

  • Nextjsにおける /api/route.tsのような役割

  • サーバーサイドのロジックを実行できる。

  • 一番使うと思われる機能は2つ

    • アプリのウインドウを管理するnew BrowserWindow()と、rendererからの通信を受け取る ipcMain()

ipcMain:

ipcMain.on("get-log", () => {
  console.log("log");
});
ipcMain.on("get-log", () => {
  return `${new Date().toLocaleDateString()}`;
});
  • ipcMain.on は何もreturnしない・できないが、 ipcMain.handleはreturnできる。

  • handleで返すものはrendererでは非同期で取得する。

const handleClick = async () => {
  const result = await window.api.getLog(); 
  console.log(result); 
}

resources:

import icon2 from "../../resources/icon2.png?asset";

const myTray = new Tray(icon2);
  • サーバー側で使う画像はルートの/resourcesに入れる。

  • パスはimportし、末尾に?assetをつけないとエラーになる。

preload

  • NextjsにおけるserverActionのようなクライアント(renderer)からAPIを呼び出す中間

contextBridge:

    contextBridge.exposeInMainWorld("api", {
      getLog: () => ipcRenderer.invoke("get-log")
    });
  • exposeInMainWorld()をメインで使う。

  • ipcRendererは sendとinvokeメソッドを持ち、 ipcMainのonとhandleに対応。sendは一方向でonに対応、invokeが双方向でhandleに対応。

(method) Electron.ContextBridge.exposeInMainWorld(apiKey: string, api: any): void
  • 第一引数は"api"という名前にするのが一般的

electronAPI:

  const ipcHandle = (): void => window.electron.ipcRenderer.send("ping");
  • electron-viteのモックには上記のようなpreloadフォルダのファイルを経由せずに直接mainとやり取りをしているコードがある。

  • preloadに設定されている"electron"というcontextBridgeを経由することでUI側のファイルから直接mainのicpMainにアクセスできる。

// preload

import { electronAPI } from "@electron-toolkit/preload";

contextBridge.exposeInMainWorld("electron", electronAPI);
  • 基本的には window.api.getData()のようにpreload側で定義・経由した方がいい。

renderer

  • Nextjsにおける"use client"をつけたファイルのような役割。

window.apiの型

window.api.quitApp()

'window.api''は 'unknown' 型です

  • 上記のようなエラーが赤線で出るのは、デフォルトだとブラウザ標準のwindowしか認識されていないため

preload/index.d.ts:

import { ElectronAPI } from "@electron-toolkit/preload";

declare global {
  interface Window {
    electron: ElectronAPI;
    api: unknown;
  }
}
  • デフォルトで作成されたものにwindow.apiの型を追加していく。

import { ElectronAPI } from "@electron-toolkit/preload";

interface CustomAPI {
  getCount: (target: string) => Promise<number>;
  incrementCount: (target: string) => Promise<number>;
  quitApp: () => void;
}

declare global {
  interface Window {
    electron: ElectronAPI;
    api: CustomAPI;
  }
}

assets:

import icon from "./assets/icon.png";

<img className="ring size-40 ring-orange-500" src={icon} alt="" />
  • デフォルトで作成されるrenderer/src/assetsに画像を入れる。srcにパスを書くのではなくimportしないと表示されない。

引数を渡す

useEffect(() => {
  const showAlert = async () => {
    const message = await window.api.alertOnce("hello");    
    alert(message);
  };
  showAlert();
}, []);

preload:

    contextBridge.exposeInMainWorld("api", {
      alertOnce: async (message: string) => {
        return await ipcRenderer.invoke("alert-once", message);
      }
    });

main:

ipcMain.handle("alert-once", async (_e, message) => {
  console.log(message);
  return message;
});
  • 第一引数はeventで固定、第二引数以降は自由に渡せる。

  ipcMain.handle("set-limit", async (_e, target: string, limit: number) => {
  });

ローカルデータ

const dataPath = join(app.getPath("userData"), "log.json");
  • ファイルの保存場所は /app.getPath("userData")/package.jsonのname/

    • Ubuntuの場合/home/ユーザー/.config/

  • OS毎にパスを設定する必要は無い。

JSONファイル

// READ
  const raw = fs.readFileSync(dataPath, "utf-8");

---

// WRITE
  fs.writeFileSync(dataPath, JSON.stringify(data, null, 2), "utf-8");
  • fs.writeFileSyncはファイルが存在しない場合に指定したパスへファイルを作成する。

SQLite

npm install better-sqlite3

Ubuntuでエラーが出る場合

sudo apt install -y build-essential python3-dev libsqlite3-dev pkg-config

他

alert() / confirm()後にfocusが機能しない

問題:

  • UI側でalert()、confirm()が表示された後にInputタグにfocusが当たらなくなる。Electronの仕様

対策:

  • dialog.showMessageBox()を使用する。

alert:

  ipcMain.handle("show-message-box", async (_e, message: string) => {
    const win = BrowserWindow.getFocusedWindow();
    await dialog.showMessageBox(win!, {
      type: "info",
      buttons: ["OK"],
      message: message,
      detail: ""
    });
  });
  • アイコンのタイプ(info, error, question, warning)

confirm:

  ipcMain.handle("show-confirm-box", async (_e, message: string) => {
    const win = BrowserWindow.getFocusedWindow();
    const { response } = await dialog.showMessageBox(win!, {
      type: "question",
      buttons: ["はい", "いいえ"],
      defaultId: 1,
      cancelId: 1,
      message: message
      detail: ""
    });
    return response === 0;
  });

---

// UI
const confirmed: boolean = await window.api.showConfirmBox(`削除?`);

日本語フォント

問題:

  • dialog.showMessageBox()とshowConfirmBox()は「Linux x Snap」だとデプロイ後に日本語フォントがすべて□になる。他の組み合わせだと起きない模様。

    • UI側でDialogを作成したり、toastを利用してUI側にメッセージを出す。

アプリのインストール

ビルド:

    "build:win": "npm run build && electron-builder --win",
    "build:mac": "electron-vite build && electron-builder --mac",
    "build:linux": "electron-vite build && electron-builder --linux"
  • linux用のコマンドを実行するとルートにdistフォルダが作成され、その中に .debや.snapが作成される。

  • package.jsonのversionを上げた場合はdistを削除してからコマンドを実行する。

インストール:

 sudo snap install --dangerous dist/xxx.snap
  • Snap-storeへ登録していないので--dangerousフラグが必要

    • WindowsやMacも登録・認証しないとインストール時に警告が出る。

snapの公開

electron-builder.ymlのモック値を変える必要がある箇所はAI等を参照

  1. アカウント作成:https://snapcraft.io/

  2. snapcraft CLI インストール&ログイン

sudo snap install snapcraft --classic
snapcraft login
  1. Snap名を登録

snapcraft register test-counter-app
  1. Snap-storeにupload

snapcraft upload dist/test-counter-app-*.snap --release=stable
  • ここまで行うとアプリストアを検索するとアプリが見つかる。

  • stableとあるが、アプリには複数の段階がある。

    • edge = 開発段階 、stable = 安定版

    • edgeでuploadしたらhttps://snapcraft.io/ or ターミナルからstableに昇格させる。

更新:

pnpm run build:linux

snapcraft upload dist/test-counter-app-*.snap --release=stable
  • package.jsonのversionを上げるのを忘れない事

react
/
other
  • main
  • preload
  • renderer
  • window.apiの型
  • 引数を渡す
  • ローカルデータ
  • JSONファイル
  • SQLite
  • 他
  • alert() / confirm()後にfocusが機能しない
  • 日本語フォント
  • アプリのインストール
  • snapの公開