React アプリを Electron でデスクトップアプリ化する

heroImage

1. はじめに

React は,Facebook 社が主導で開発している Web アプリ開発フレームワークです。また,Electron は GitHub 社が主導で開発しているデスクトップアプリ開発フレームワークです。これらのフレームワークを組み合わせることによって,React で開発した Web アプリを Electron でデスクトップアプリ化することが出来ます。

インターネット上で類似の記事が複数公開されているので,本記事では Qiita に投稿されている記事と DEV に投稿されている記事,GitHub に投稿されているソースコードを参考に,ホットリロード + ビルドに対応した React + Electron の開発環境構築手順について記述します。

また,本記事内で行っている作業は,以下の環境下で実行したものです。以降,これらのツールはインストール済みの前提で記述していますが,インストール手順は割愛しているので,ご了承下さい。

2. React App の生成

Create React App は,Facebook 社が主導で開発している React アプリの生成ツールです。Create React App を用いることで React アプリのテンプレートを手軽に生成することが出来るため,まず初めに Create React App の README.md に従って,React アプリのテンプレートを任意のフォルダ内に生成します。tree コマンドを用いて確認すると,正常に React アプリのテンプレートが生成されていることが確認できます。

Terminal window
1
$ npx create-react-app my-app
2
$ ls
3
my-app
4
$ cd my-app
5
$ tree -L 2
6
.
7
├── README.md
8
├── node_modules
9
│   ├── @babel
10
│   ├── @bcoe
11
│   ├── @cnakazawa
12
│   ├── (割愛)
13
│   ├── yaml
14
│   ├── yargs
15
│   └── yargs-parser
16
├── package.json
17
├── public
18
│   ├── favicon.ico
19
│   ├── index.html
20
│   ├── logo192.png
21
│   ├── logo512.png
22
│   ├── manifest.json
23
│   └── robots.txt
24
├── src
25
│   ├── App.css
26
│   ├── App.js
27
│   ├── App.test.js
28
│   ├── index.css
29
│   ├── index.js
30
│   ├── logo.svg
31
│   ├── reportWebVitals.js
32
│   └── setupTests.js
33
└── yarn.lock

3. パッケージの追加

npm を用いて electron-is-dev を dependencies に,cross-envelectronelectron-buildernpm-run-allwait-on を devDependencies にインストールします。各パッケージの詳細に関して,ここでは割愛します。

Terminal window
1
$ ls
2
README.md node_modules package.json public src yarn.lock
3
$ npm i electron-is-dev
4
$ npm i cross-env electron electron-builder npm-run-all wait-on -D
5
$ ls
6
README.md node_modules package-lock.json package.json public src yarn.lock

4. JavaScript の追加

electron-quick-start の main.js を改変した以下のソースコードを,electron.js というファイル名で public フォルダ内に保存します。改変した箇所は,ハイライトしている 4 行目と 17 〜 21 行目になります。electronic-is-dev パッケージを用いることで開発環境と本番環境を区別し,開発環境の場合は localhost:3000 を,本番環境の場合は index.html を読み込むように設定します。

1
// Modules to control application life and create native browser window
2
const { app, BrowserWindow } = require('electron')
3
const path = require('path')
4
const isDev = require('electron-is-dev')
5
6
function createWindow() {
7
// Create the browser window.
8
const mainWindow = new BrowserWindow({
9
width: 800,
10
height: 600,
11
webPreferences: {
12
preload: path.join(__dirname, 'preload.js'),
13
},
14
})
15
16
// and load the index.html of the app.
17
mainWindow.loadURL(isDev ? 'http://localhost:3000' : `file://${path.join(__dirname, '../build/index.html')}`)
18
19
// Open the DevTools.
20
// mainWindow.webContents.openDevTools()
21
}
22
23
// This method will be called when Electron has finished
24
// initialization and is ready to create browser windows.
25
// Some APIs can only be used after this event occurs.
26
app.whenReady().then(() => {
27
createWindow()
28
29
app.on('activate', function () {
30
// On macOS it's common to re-create a window in the app when the
31
// dock icon is clicked and there are no other windows open.
32
if (BrowserWindow.getAllWindows().length === 0) createWindow()
33
})
34
})
35
36
// Quit when all windows are closed, except on macOS. There, it's common
37
// for applications and their menu bar to stay active until the user quits
38
// explicitly with Cmd + Q.
39
app.on('window-all-closed', function () {
40
if (process.platform !== 'darwin') app.quit()
41
})
42
43
// In this file you can include the rest of your app's specific main process
44
// code. You can also put them in separate files and require them here.
Terminal window
1
$ tree -L 2
2
.
3
├── README.md
4
├── node_modules
5
│   ├── 7zip-bin
6
│   ├── @babel
7
│   ├── @bcoe
8
│   ├── (割愛)
9
│   ├── yargs-parser
10
│   ├── yauzl
11
│   └── yocto-queue
12
├── package-lock.json
13
├── package.json
14
├── public
15
│   ├── electron.js 上記のソースコード
16
│   ├── favicon.ico
17
│   ├── index.html
18
│   ├── logo192.png
19
│   ├── logo512.png
20
│   ├── manifest.json
21
│   └── robots.txt
22
├── src
23
│   ├── App.css
24
│   ├── App.js
25
│   ├── App.test.js
26
│   ├── index.css
27
│   ├── index.js
28
│   ├── logo.svg
29
│   ├── reportWebVitals.js
30
│   └── setupTests.js
31
└── yarn.lock

5. package.json の編集

Create React App によって生成された package.json を改変します。マニュアルで改変した箇所はハイライトしている 5 〜 6 行目と 18 〜 25 行目になります。5 〜 6 行目の mainhomepage は,Electron の起動とビルドに必須のため,必ず追記します。18 〜 25 行目のキーは,任意で問題ありません。

18 〜 25 行目の値について詳しく記述します。18 行目の cross-env で環境変数 BROWSER に none を設定することで,React 起動時にブラウザが立ち上がるのを無効にしています。22 行目の wait-on で,React が起動してから Electron が起動するように設定しています。24 行目と 25 行目の npm-run-all (run-p と run-s) で,それぞれパラレル実行とシーケンシャル実行するように設定しています。

1
{
2
"name": "my-app",
3
"version": "0.1.0",
4
"private": true,
5
"main": "public/electron.js",
6
"homepage": "./",
7
"dependencies": {
8
"@testing-library/jest-dom": "^5.11.4",
9
"@testing-library/react": "^11.1.0",
10
"@testing-library/user-event": "^12.1.10",
11
"electron-is-dev": "^1.2.0",
12
"react": "^17.0.1",
13
"react-dom": "^17.0.1",
14
"react-scripts": "4.0.1",
15
"web-vitals": "^0.2.4"
16
},
17
"scripts": {
18
"react-start": "cross-env BROWSER=none react-scripts start",
19
"react-build": "react-scripts build",
20
"react-test": "react-scripts test",
21
"react-eject": "react-scripts eject",
22
"electron-start": "wait-on http://localhost:3000 && electron .",
23
"electron-build": "electron-builder",
24
"start": "run-p react-start electron-start",
25
"build": "run-s react-build electron-build"
26
},
27
"eslintConfig": {
28
"extends": ["react-app", "react-app/jest"]
29
},
30
"browserslist": {
31
"production": [">0.2%", "not dead", "not op_mini all"],
32
"development": ["last 1 chrome version", "last 1 firefox version", "last 1 safari version"]
33
},
34
"devDependencies": {
35
"cross-env": "^7.0.3",
36
"electron": "^11.1.0",
37
"electron-builder": "^22.9.1",
38
"npm-run-all": "^4.1.5",
39
"wait-on": "^5.2.0"
40
}
41
}

6. 動作確認

起動させる場合は,npm start を,ビルドする場合は npm run build をターミナルに入力します。正常に起動できた場合は,以下のようにデスクトップアプリが起動すると思います。また,正常にビルドできた場合は dist フォルダ内に AppImage ファイル名が生成されていると思います。生成された AppImage ファイルをダブルクリックすると起動時と同様に,デスクトップアプリが起動すると思います。

React + Electron

7. おわりに

ここまで,ホットリロード + ビルドに対応した React + Electron の開発環境構築手順について記述してきました。当初の予定通り,ホットリロード + ビルドには対応させることが出来ましたが,パッケージングに対応させることが出来ていないので,今後はパッケージングにも対応させていきたいと思います。