React アプリを Electron でデスクトップアプリ化する
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 アプリのテンプレートが生成されていることが確認できます。
1$ npx create-react-app my-app2$ ls3my-app4$ cd my-app5$ tree -L 26.7├── README.md8├── node_modules9│ ├── @babel10│ ├── @bcoe11│ ├── @cnakazawa12│ ├── (割愛)13│ ├── yaml14│ ├── yargs15│ └── yargs-parser16├── package.json17├── public18│ ├── favicon.ico19│ ├── index.html20│ ├── logo192.png21│ ├── logo512.png22│ ├── manifest.json23│ └── robots.txt24├── src25│ ├── App.css26│ ├── App.js27│ ├── App.test.js28│ ├── index.css29│ ├── index.js30│ ├── logo.svg31│ ├── reportWebVitals.js32│ └── setupTests.js33└── yarn.lock
3. パッケージの追加
npm を用いて electron-is-dev を dependencies に,cross-env,electron,electron-builder,npm-run-all,wait-on を devDependencies にインストールします。各パッケージの詳細に関して,ここでは割愛します。
1$ ls2README.md node_modules package.json public src yarn.lock3$ npm i electron-is-dev4$ npm i cross-env electron electron-builder npm-run-all wait-on -D5$ ls6README.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 window2const { app, BrowserWindow } = require('electron')3const path = require('path')4const isDev = require('electron-is-dev')5
6function 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 finished24// initialization and is ready to create browser windows.25// Some APIs can only be used after this event occurs.26app.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 the31 // 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 common37// for applications and their menu bar to stay active until the user quits38// explicitly with Cmd + Q.39app.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 process44// code. You can also put them in separate files and require them here.
1$ tree -L 22.3├── README.md4├── node_modules5│ ├── 7zip-bin6│ ├── @babel7│ ├── @bcoe8│ ├── (割愛)9│ ├── yargs-parser10│ ├── yauzl11│ └── yocto-queue12├── package-lock.json13├── package.json14├── public15│ ├── electron.js ⇦ 上記のソースコード16│ ├── favicon.ico17│ ├── index.html18│ ├── logo192.png19│ ├── logo512.png20│ ├── manifest.json21│ └── robots.txt22├── src23│ ├── App.css24│ ├── App.js25│ ├── App.test.js26│ ├── index.css27│ ├── index.js28│ ├── logo.svg29│ ├── reportWebVitals.js30│ └── setupTests.js31└── yarn.lock
5. package.json の編集
Create React App によって生成された package.json を改変します。マニュアルで改変した箇所はハイライトしている 5 〜 6 行目と 18 〜 25 行目になります。5 〜 6 行目の main と homepage は,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 ファイルをダブルクリックすると起動時と同様に,デスクトップアプリが起動すると思います。
7. おわりに
ここまで,ホットリロード + ビルドに対応した React + Electron の開発環境構築手順について記述してきました。当初の予定通り,ホットリロード + ビルドには対応させることが出来ましたが,パッケージングに対応させることが出来ていないので,今後はパッケージングにも対応させていきたいと思います。