3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめてのアドベントカレンダーAdvent Calendar 2024

Day 15

ElectronのDBにelectron-storeを使う。

Last updated at Posted at 2024-12-14

はじめに

ElectronでToDoアプリを作る際にデータを保存するためelectron-storeを使いました。
今後も使うかもしれないので得た知識をここに置いておきます。
electron-storeの機能紹介ではなく、完成したソースコードと関連するElectronの仕組みの説明です。

今回はelectron-viteからnpm create @quick-start/electron my-appで作成を開始しています。ファイル名など他の記事と違うところがあるかと思いますがご了承ください。

electron-storeとは

https://github.com/sindresorhus/electron-store
electronでデータを保存するためのモジュールです。

Simple data persistence for your Electron app or module - Save and load user settings, app state, cache, etc

Electron doesn't have a built-in way to persist user settings and other data. This module handles that for you, so you can focus on building your app. The data is saved in a JSON file named config.json in app.getPath('userData').

保存形式はjsonになります。
デフォルトだとC:\Users\[user name]\AppData\Roaming\[app name]\config.jsonにデータが保存されます(Windowsでの話です)

インストールは以下のコマンドを実行するだけ。

npm install electron-store

実装

とりあえず今回書いたソースコードは以下の通り。
この後解説します。

preload/index.ts
import { contextBridge, ipcRenderer } from 'electron'
import { electronAPI } from '@electron-toolkit/preload'

// 保存するデータの型定義
interface Task {
  task: string
  createdTime: Date
  status: number
}

// データの取得と保存の処理を定義
const api = {
  getList: (): Promise<Task[]> => ipcRenderer.invoke('getList'),
  setList: (data): Promise<void> => ipcRenderer.invoke('setList', data)
}

if (process.contextIsolated) {
  try {
    contextBridge.exposeInMainWorld('electron', electronAPI)
    // windowにAPIを入れる
    // window.api.getListとwindow.api.setList
    contextBridge.exposeInMainWorld('api', api)
  } catch (error) {
    console.error(error)
  }
} else {
  // @ts-ignore (define in dts)
  window.electron = electronAPI
  // @ts-ignore (define in dts)
  window.api = api
}
main/index.ts
import { app, shell, BrowserWindow, ipcMain } from 'electron'
import { join } from 'path'
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
import icon from '../../resources/icon.png?asset'
import Store from 'electron-store'

// storeの読み込み
const store = new Store({ taskList: [] })

function createWindow(): void {
  // webPreferences.preloadでpreloadを使用することを明示する
  const mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    show: false,
    autoHideMenuBar: true,
    ...(process.platform === 'linux' ? { icon } : {}),
    webPreferences: {
      preload: join(__dirname, '../preload/index.mjs'),
      sandbox: false
    }
  })

}

...

// getListの処理を追加
ipcMain.handle('getList', async (event, data) => {
  return store.get('taskList', [])
})

// setListの処理を追加
ipcMain.handle('setList', async (event, data) => {
  store.set('taskList', data)
})

これでウェブページからデータを読み書きする処理getList, setListを使用できます。

// データの取得
const taskList = await window.api.getList();

// データの保存
await window.api.setList(taskList);

ちょっと解説

勉強中の身なので大雑把な理解しかできていませんが、Electronの理解の助けになればと思います。
※間違いがあればすみません。

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

ElectronにはNodeモジュールを扱うなどするメインプロセスとウェブページを実行するレンダラープロセスという2つのプロセスが存在します。ちなみにNode.jsを実行するのはメインプロセスで、レンダラープロセスはセキュリティの関係でNode.jsを実行しません。
この2つのプロセスをつないでくれるのがプリロードと呼ばれるスクリプトです。
今回でいうpreload/index.tsがこれにあたります。

プリロードスクリプトはウェブページを読み込む前に実行されます。
ウェブページからメインプロセスの処理を実行できるようにするにはcontextBridge APIを使用して処理内容を定義する必要があります。

pleload/index.ts
contextBridge.exposeInMainWorld('api', api)

このスクリプトをレンダラープロセスにアタッチするためにメインプロセスのBrowserWindowwebPreferences.preloadcontextBridgeを記載したファイルのパスを追加します。

main/index.ts
function createWindow(): void {
  // webPreferences.preloadでpreloadを使用することを明示する
  const mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    show: false,
    autoHideMenuBar: true,
    ...(process.platform === 'linux' ? { icon } : {}),
    webPreferences: {
      preload: join(__dirname, '../preload/index.mjs'),
      sandbox: false
    }
  })
  ...
}

これでレンダラープロセスがapiという処理にアクセスできるようになりました。
ウェブページからはwindow.apiでアクセスできます。

プロセス間通信

メインプロセスとレンダラープロセスは互換性がないためメインプロセスはウェブページに、レンダラープロセスはNode.jsにアクセスすることができません。
この問題を解決してウェブページからjsonファイルのデータを取得する処理を行えるようにipcMainipcRendererモジュールを使用します。
ウェブページからメインプロセスに通信するにはipcMain.handleでメインプロセスでの処理を設定し、ipcRenderer.invokeでプリロードからメインプロセスの処理を公開します。

preload/index.ts
// データの取得と保存の処理を定義
const api = {
  getList: (): Promise<Task[]> => ipcRenderer.invoke('getList'),
  setList: (data): Promise<void> => ipcRenderer.invoke('setList', data)
}
main/index.ts
// getListの処理を追加
ipcMain.handle('getList', async (event, data) => {
  return store.get('taskList', [])
})

// setListの処理を追加
ipcMain.handle('setList', async (event, data) => {
  store.set('taskList', data)
})

これにより、ipcRenderer.invokeで定義したgetList setListというチャネルを通じてレンダラーからメインプロセスへ処理の実行をお願いすることができます。
公式が言うにはipcRendererを直接contextBridgeで公開するのはセキュリティ面(?)からよくないそうです。

おわりに

わかりにくい箇所や間違い等ありましたら修正しますので、お声がけいただけると嬉しいです!

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?