electron-vite で Electron アプリ開発の生産性を上げる
electron-vite は Electron アプリ開発用に構成された次世代の開発環境を謳っています。名前の通り JavaScript のデファクトビルドツールになりつつある Vite を採用しています。
概要
#electron-vite の Git リポジトリです。記事執筆時点のバージョンは v1.0.22、スター数は922でした。個人開発のプロジェクトです。
公式サイトからその特徴を列挙してみます。
Next Generation Electron Build Tooling | electron-vite
Vite Powered
: Vite の利点を継承し、Vite での開発方法が利用できるPre-configured
: Electron 向けに設定されているOptimize Asset Handling
: Electron の main process 向けに最適化されたアセットの取り扱いFast HMR
: renderer process で HMR が利用可能Hot Reloading
: main process と preload scripts もホットリローディングが可能Easy to Debug
: IDE でのデバッグが容易TypeScript Decorators
: emit に TypeScript の Decorators によるメタデータをサポートSource Code Protection
: ソースコード保護のため V8 bytecode へのコンパイルOut-of-the-box
: 追加設定なしで、TypeScript / Vue/ React / Svelte/ SolidJS などをサポート
素の Electron アプリ開発では renderer process で使用するアセット開発は特にサポートされておらず、自前でビルド環境を作る必要があります。ホットリローディングもなくアプリのデバッグメニューからリロードします。main process や preload script の変更はアプリを再起動しないと反映されません。electron-vite はこのあたりをかなり改善・省力化してくれます。
main process / renderer process / preload といった Electron のプログラミング要素については以下の記事で解説しています。
elecrtron-vite の公式ドキュメントからもう少し説明を見てみましょう。
コミュニティリソースに多くある Vite ベースの Electron 開発テンプレートは、複雑で補助スクリプトが必要だったり、ソースコードの保護ができないなどの課題がある。electron-vite はこれら課題を解決し Electron のための俊敏で無駄のない開発体験を提供することを目的としているとのことです。
electron-vite では main process / preload スクリプトは CommonJS でバンドルされ、renderer process では Vite の開発サーバで ESM としてビルドされ HMR が使用されます。これによりビルドや動作確認の手順が大幅に削減されます。実際、electron-vite で作業をしていると Vue などで SPA を開発しているのに近い開発体験が得られました。
さらに electron-vite は Electron の推奨構成(nodeIntegration 無効化、contextIsolation 有効化など)が設計に反映されており、ベストプラクティスを利用できます。生成されたプロジェクト構成を見ると近年の Electron の推奨構成にほぼ準拠したものでした。
electron-vite では sandbox:false
で renderer process のサンドボックスを無効化しています。これは CommonJS module を複数ファイルに分割するために必要な措置とのことです。
Limitations of Sandboxing | Development | electron-vite
renderer プロセスのサンドボックス化については以下の記事で紹介しています。
Electron v20 で有効化された Renderer プロセスサンドボックス化に対応する
electron-vite を利用した開発では preload スクリプト内でサードパーティの Node モジュールを利用しないよう注意が必要です。
プロジェクトの作成
#以下のコマンドで electron-vite を使ったプロジェクトを作成します。
npm create @quick-start/electron
フレームワークに Vue を選択して作成しました。
Need to install the following packages:
@quick-start/create-electron@1.0.12
Ok to proceed? (y) y
✔ Project name: … hello-evite
✔ Select a framework: › vue
✔ Add TypeScript? … No / Yes
✔ Add Electron updater plugin? … No / Yes
✔ Enable Electron download mirror proxy? … No / Yes
Scaffolding project in /Users/kondoh/dev/electron-study/hello-evite...
Done. Now run:
cd hello-evite
npm install
npm run dev
プロジェクトのディレクトリ構成は、以下のようになりました。src 配下に main process、preload スクリプト、renderer process のディレクトリが掘られます。Vue を選択したので renderer 配下は通常の Vue3 のプロジェクト構成になっています。
.
├── dev-app-update.yml
├── electron-builder.yml
├── electron.vite.config.js
├── package.json
├── resources
│ └── icon.png
└── src
├── main
│ └── index.js
├── preload
│ └── index.js
└── renderer
├── index.html
└── src
├── App.vue
├── assets
│ ├── css
│ │ └── styles.less
│ └── icons.svg
├── components
│ └── Versions.vue
└── main.js
開発・デバッグ
#以下のコマンドで Electron アプリのビルドと実行が可能です。
npm run dev
プロジェクトを作成した直後は以下のようなアプリが起動してきます。
package.json の npm script では、"dev": "electron-vite dev",
が定義されています。
HMR による renderer process のホットリローディングが可能になっていますので、src/renderer 配下のファイルを編集したら即座にアプリ画面に反映されます。main process や preload スクリプトを書き換えた際もリロードさせるには electron-vite dev
に --watch
フラグをつけます。
npm script で常時 --watch
を指定してしまうと、再起動のたびに編集中の IDE からアプリ側にフォーカスを持っていかれますので、必要時だけ指定するのが無難です。main process や preload スクリプトを微修正する場合に使うのがよいでしょう。以下のように npm script の引数でフラグを指定することも可能です。
npm run dev -- --watch
Electron アプリでは、HTML や JavaScript のアセットは通常ローカルファイルシステムから読み込みます。electron-vite では HMR を利用するため、開発時は環境変数で指定されたローカル URL から読み込むようになっています(環境変数は electron-vite が自動設定します)。
- src/main/index.js (ボイラープレートコードの抜粋)
function createWindow() {
// Create the browser window
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, '../preload/index.js')
}
})
// Load the local URL for development or the local
// html file for production
if (!app.isPackaged && process.env['ELECTRON_RENDERER_URL']) {
mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'])
} else {
mainWindow.loadFile(path.join(__dirname, '../renderer/index.html'))
}
}
簡単なアプリの開発
#簡単なアプリを electron-vite で開発して使用感を見ていきます。今回作成したアプリのコードは以下にあります。
electron-study/hello-evite at master · kondoumh/electron-study
「Vue 3 と D3.js で作る可視化アプリ」の記事で作成した Vue 3 のアプリのコードを利用します。この SPA アプリのソースコードを electron-vite で生成した src/renderer 配下に配置するだけでほぼ手直しなく[1]アプリが動作しました。
今回は、アプリのヘッダーに export to svg
ボタンをつけて、D3.js の可視化結果を SVG としてファイルにエクスポートできるように機能を追加します。
Header.vue にボタンとクリック時の emit 処理を追加します。
- src/renderer/src/components/Header.vue
<template>
<header>
<h2>Scrapbox graph</h2>
<select v-model="project" @change="onChange">
<option>help-jp</option>
<option>comic-forum</option>
<option>icons</option>
</select>
<button @click="onExportToSvg">export to svg</button> <!-- 追加 -->
</header>
</template>
<script setup>
import { ref } from 'vue';
const project = ref('help-jp');
const emit = defineEmits(['project-changed', 'export-svg']); // emit 定義を追加
const onChange = () => {
emit('project-changed', project.value);
}
const onExportToSvg = () => {
emit('export-svg'); // イベントを発行
}
</script>
親の App.vue で Header コンポーネントのクリックをハンドリングして Graph コンポーネントのメソッドを呼び出します。表示中の Scrapbox プロジェクト名を引数に渡しています。
- src/renderer/src/App.vue
<template>
<div>
<Header @project-changed="switchProject" @export-svg="exportToSvg" /> <!-- イベントを追加 -->
<Graph ref="graph" v-bind:project="project" /> <!-- ref を追加 -->
</div>
</template>
<script setup>
import { ref } from 'vue';
import Header from './components/Header.vue';
import Graph from './components/Graph.vue';
const project = ref('help-jp');
const switchProject = value => {
project.value = value;
}
const graph = ref(null); // Graph コンポーネントの ref
const exportToSvg = () => {
graph.value.exportToSvg(project.value); // Graph コンポーネントのメソッド呼び出し
}
</script>
main process に対して SVG ファイルエクスポートのメッセージを送信するメソッドを preload スクリプトに追加します。
- src/preload/index.js (抜粋)
import { contextBridge } from 'electron'
contextBridge.exposeInMainWorld(
'mainApi', {
exportToSvg: async (project, data) => await ipcRenderer.invoke('export-to-svg', project, data),
}
);
Graph.vue で d3.select して SVG の表示に必要な属性情報を追加し、上記の preload で追加したメソッドを呼び出します。
- src/renderer/src/components/Graph.vue (抜粋)
const exportToSvg = async project => {
const svgContainer = d3.select('svg')
.attr('xmlns','http://www.w3.org/2000/svg')
.attr("version",'1.1');
window.mainApi.exportToSvg(project, svgContainer.node().outerHTML);
}
main process ではファイル保存用ダイアログを起動して保存先が確定したら SVG ファイルを書き出します。
- src/main/index.js (抜粋)
import * as fs from 'node:fs/promises'
ipcMain.handle('export-to-svg', async (event, project, data) => {
const win = BrowserWindow.getFocusedWindow();
const result = await dialog.showSaveDialog(
win, {
title: 'Export to SVG',
defaultPath: `${project}.svg`,
filters: [ { name: 'SVG', extensions: ['svg'] }, ],
},
);
if (!result.canceled) {
await fs.writeFile(result.filePath, data);
}
});
以上のコード追加で export to svg
ボタンクリック時に保存ダイアログが出て SVG ファイルを保存することが可能になりました。
開発体験
#上記の開発作業においてホットリローディングの効果はやはり大きく、Vite の HMR で画面が一瞬で書き換わります。これまで手動でちまちまリロードしていたのが嘘のような快適さでした。main process や preload スクリプトを書き換えた時のリロードも可能なので、ちょっとしたコードの調整時に重宝します。
Electron アプリ開発を始める上でのツール設定など、オールインワンで揃っていて開発着手が早まります。
electron-builder も内包しており、バイナリやインストーラーも用意された npm script を実行するだけで作成可能です[2]。
VS Code など IDE でのデバッグもサポートされています。
Elctron に内蔵された Chrome DevTools の方が慣れているので今回は使いませんでした。
最後に
#以上、electron-vite の使用レポートでした。Electron をよく理解して設計されており、ドキュメントも整備されていて、現時点での完成度は非常に高いものがあります。Electron のベストプラクティスに準拠した構成になっており Electron に慣れていない開発者でもさほどの苦労なく入っていけそうです。
後発の Tauri では最初からこのレベルの環境が提供されていますが、Electron でもここまでのサポートがあれば随分と効率が上がりそうです。
Electron と Web のビルドツールを組み合わせるボイラープレートプロジェクトはこれまでもありましたが、あまり普及しているとは言い難い状況です[3]。Electron の開発スピードや Web 開発の流行にも追従する必要があり、コミュニティベースでのサポートは難しい面があるのでしょう。electron-vite も個人プロジェクトのため継続性については懸念があります。
Web アプリのアセットを保有しており、クイックに Electron アプリ化をしたい時などに採用を検討するのがよいでしょう。
Netlify に配置した JSON ファイルを取得するために src/renderer/index.html の meta タグを少し修正した程度です。 ↩︎
GitHub Actions によるアプリ配布のワークフローサンプルもドキュメントにあります。https://evite.netlify.app/guide/distribution.html#github-action-ci-cd ↩︎
VS Code や Slack などの大きな Electron プロジェクトでは自前で用意しているのでしょう。 ↩︎