1523 words
8 minutes
React アプリを Electron でデスクトップアプリ化する
2020-12-13
2020-12-15

1. はじめに#

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

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

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

  • Create React App Ver.4.0.1
  • npm Ver.6.14.9
  • Zorin OS 15.2 Core (Ubuntu 18.04 LTS)

2. React App の生成#

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

Terminal window
$ npx create-react-app my-app
$ ls
my-app
$ cd my-app
$ tree -L 2
.
├── README.md
├── node_modules
│   ├── @babel
│   ├── @bcoe
│   ├── @cnakazawa
│   ├── (割愛)
│   ├── yaml
│   ├── yargs
│   └── yargs-parser
├── package.json
├── public
│   ├── favicon.ico
│   ├── index.html
│   ├── logo192.png
│   ├── logo512.png
│   ├── manifest.json
│   └── robots.txt
├── src
│   ├── App.css
│   ├── App.js
│   ├── App.test.js
│   ├── index.css
│   ├── index.js
│   ├── logo.svg
│   ├── reportWebVitals.js
│   └── setupTests.js
└── yarn.lock

3. パッケージの追加#

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

Terminal window
$ ls
README.md node_modules package.json public src yarn.lock
$ npm i electron-is-dev
$ npm i cross-env electron electron-builder npm-run-all wait-on -D
$ ls
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 を読み込むように設定します。

// Modules to control application life and create native browser window
const { app, BrowserWindow } = require('electron')
const path = require('path')
const isDev = require('electron-is-dev')
function createWindow() {
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
},
})
// and load the index.html of the app.
mainWindow.loadURL(isDev ? 'http://localhost:3000' : `file://${path.join(__dirname, '../build/index.html')}`)
// Open the DevTools.
// mainWindow.webContents.openDevTools()
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(() => {
createWindow()
app.on('activate', function () {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit()
})
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.
Terminal window
$ tree -L 2
.
├── README.md
├── node_modules
│   ├── 7zip-bin
│   ├── @babel
│   ├── @bcoe
│   ├── (割愛)
│   ├── yargs-parser
│   ├── yauzl
│   └── yocto-queue
├── package-lock.json
├── package.json
├── public
│   ├── electron.js 上記のソースコード
│   ├── favicon.ico
│   ├── index.html
│   ├── logo192.png
│   ├── logo512.png
│   ├── manifest.json
│   └── robots.txt
├── src
│   ├── App.css
│   ├── App.js
│   ├── App.test.js
│   ├── index.css
│   ├── index.js
│   ├── logo.svg
│   ├── reportWebVitals.js
│   └── setupTests.js
└── 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) で,それぞれパラレル実行とシーケンシャル実行するように設定しています。

{
"name": "my-app",
"version": "0.1.0",
"private": true,
"main": "public/electron.js",
"homepage": "./",
"dependencies": {
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",
"electron-is-dev": "^1.2.0",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-scripts": "4.0.1",
"web-vitals": "^0.2.4"
},
"scripts": {
"react-start": "cross-env BROWSER=none react-scripts start",
"react-build": "react-scripts build",
"react-test": "react-scripts test",
"react-eject": "react-scripts eject",
"electron-start": "wait-on http://localhost:3000 && electron .",
"electron-build": "electron-builder",
"start": "run-p react-start electron-start",
"build": "run-s react-build electron-build"
},
"eslintConfig": {
"extends": ["react-app", "react-app/jest"]
},
"browserslist": {
"production": [">0.2%", "not dead", "not op_mini all"],
"development": ["last 1 chrome version", "last 1 firefox version", "last 1 safari version"]
},
"devDependencies": {
"cross-env": "^7.0.3",
"electron": "^11.1.0",
"electron-builder": "^22.9.1",
"npm-run-all": "^4.1.5",
"wait-on": "^5.2.0"
}
}

6. 動作確認#

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

React + Electron

7. おわりに#

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