Node.js
reactjs
Electron
redux
ReactDay 15

React Tutorial Example (Electron)

More than 1 year has passed since last update.

ReactRedux は 主に Webアプリケーション のライブラリなのですが
Electron を利用すると ディスクトップ アプリケーション を作成することができます。
前回 のアプリケーションを ディスクトップ アプリケーション にしましょう。

Learning

  • Electron で ディスクトップ アプリケーション を作成する。
  • Electron の メニューIPC(プロセス間通信) を利用する。

Environment

  • node: v6.9.2
  • npm: v4.0.5
  • yarn: v0.18.0

Comment Box Form

  • 完成される Source Code のファイルリストです。
    • main.jsserver.js の2ファイルが追加されます。
$ tree -a -I node_modules
.
├── .babelrc
├── app
│   ├── actions
│   │   └── index.js
│   ├── components
│   │   ├── Comment
│   │   │   └── index.js
│   │   ├── CommentBox
│   │   │   ├── index.js
│   │   │   └── style.css
│   │   ├── CommentForm
│   │   │   └── index.js
│   │   ├── CommentList
│   │   │   └── index.js
│   │   └── global.css
│   ├── containers
│   │   └── App
│   │       ├── index.js
│   │       └── style.css
│   ├── index.js
│   ├── reducers
│   │   └── index.js
│   └── store
│       └── index.js
├── test
│   └── app
│       ├── actions
│       │   └── index.test.js
│       ├── components
│       │   ├── Comment
│       │   │   └── index.test.js
│       │   └── CommentBox
│       │       └── index.test.js
│       └── reducers
│           └── index.test.js
├── index.html
├── main.js
├── package.json
├── server.js
├── webpack.config.js
└── yarn.lock

Let's hands-on

Setup application

  • git clone コマンドでアプリケーションをダウンロードします。
  • yarn コマンドで依存するモジュールをインストールします。
$ git clone https://github.com/ogom/react-comment-box-example.git
$ cd react-comment-box-example
$ git checkout jest
$ yarn

API ServerReact Tutorial Example (Express) をご利用ください。

Electron

  • Electron のモジュールをインストールします。
$ yarn add --dev electron

main.js

  • main.js ファイルに Electron の設定をします。
    • index.html ファイルを参照しています。
main.js
import { app, BrowserWindow } from 'electron'

let mainWindow

function createWindow () {
  mainWindow = new BrowserWindow({width: 800, height: 600})
  mainWindow.loadURL(`file://${__dirname}/index.html`)
  mainWindow.webContents.openDevTools()
  mainWindow.on('closed', () => {
    mainWindow = null
  })
}

app.on('ready', createWindow)

app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit()
  }
})

app.on('activate', () => {
  if (mainWindow === null) {
    createWindow()
  }
})
  • package.json ファイルに mainscripts の設定します。
  • Electron でも babel を利用するので babel-registerrequire に設定します。
package.json
   "name": "react-comment-box-example",
   "version": "1.0.0",
   "private": true,
+  "main": "main.js",
   "scripts": {
-    "start": "webpack-dev-server -d --progress --colors",
+    "start": "electron --require babel-register .",
     "test": "jest",
     "test:watch": "npm test -- --watch"
   },
  • yarn start コマンドで Electron が起動します。
$ yarn start

electron_main

bundle.js ファイルがロードできないエラーが発生しています。 ( Failed to load resource: net::ERR_FILE_NOT_FOUND )
なので、bundle.js ファイルを参照できるように webpack の設定をしましょう。

server.js

  • server.js ファイルに webpack の設定をします。
    • webpack.config.js ファイルを参照しています。
server.js
import webpack from 'webpack'
import WebpackDevServer from 'webpack-dev-server'
import config from './webpack.config'

const options = {
  publicPath: config.output.publicPath,
  hot: true,
  inline: true,
  historyApiFallback: true,
  stats: {
    colors: true,
    hash: false,
    timings: true,
    chunks: false,
    chunkModules: false,
    modules: false
  }
}

const compiler = webpack(config)
const server = new WebpackDevServer(compiler, options)

server.listen(4000)
  • main.js ファイルに server.js をインポートします。
    • Electron の実行で webpack も実行されるようになります。
main.js
+import server from './server'
 import { app, BrowserWindow } from 'electron'

 let mainWindow
  • webpack.config.js ファイルに entry と output の設定をします。
webpack.config.js
 var webpack = require('webpack')
+var path = require('path')

 module.exports = {
-  entry: './app/index',
+  entry: [
+    'webpack-dev-server/client?http://localhost:4000/',
+    'webpack/hot/dev-server',
+    path.join(__dirname, './app/index')
+  ],
   output: {
+    path: path.join(__dirname, './dist'),
+    publicPath: 'http://localhost:4000/dist/',
     filename: 'bundle.js'
   },
  • index.html ファイルの bundle.js ファイルの参照先を変更します。
index.html
   </head>
   <body>
     <div id='content'></div>
-    <script src='/bundle.js'></script>
+    <script src='http://localhost:4000/dist/bundle.js'></script>
   </body>
 </html>

electron_server

bundle.js ファイルがロードできるので Comment Box が表示されました。
それに Hot Module Replacement の機能も使えるので開発が捗ります。

Menu

  • ディスクトップ アプリケーションのメニューは簡単に作成することができます。
  • Comment のメニューに Clear のサブメニューを作成します。
    • アプリケーションを終了する quit のメニューもあると開発が捗ります。
main.js
 import server from './server'
-import { app, BrowserWindow } from 'electron'
+import { app, BrowserWindow, Menu, dialog } from 'electron'

 let mainWindow

 function createWindow () {
   mainWindow = new BrowserWindow({width: 800, height: 600})
   mainWindow.loadURL(`file://${__dirname}/index.html`)
   mainWindow.webContents.openDevTools()
   mainWindow.on('closed', () => {
     mainWindow = null
   })
+
+  Menu.setApplicationMenu(
+    Menu.buildFromTemplate(
+      [
+        {
+          label: 'App',
+          submenu: [
+            {
+              role: 'quit'
+            }
+          ]
+        },
+        {
+          label: 'Comment',
+          submenu: [
+            {
+              label: 'Clear',
+              click(item, focusedWindow) {
+                dialog.showMessageBox({
+                  type: 'info',
+                  message: 'Message!',
+                  detail: 'message detail.',
+                  buttons: ['OK']
+                })
+              }
+            }
+          ]
+        }
+      ]
+    )
+  )
 }
  • メニューをクリックすると Message! のダイアログが表示されます。

electron_menu

IPC

  • Electron のメニューとブラウザを連携するには IPC を利用します。
  • IPCstore.dispatch(action) を設定すると Menu -> Redux -> React で連携が可能です。
    • さらに ipcRenderer.send('ipc::getState' store.getState())State をメニューに送信することもできます。
app/index.js
 import React from 'react'
 import ReactDOM from 'react-dom'
 import { Provider } from 'react-redux'
+import { ipcRenderer } from 'electron'

 import App from './containers/App'
 import configureStore from './store'

 const store = configureStore()

 ReactDOM.render(
   <Provider store={store}>
     <App />
   </Provider>,
   document.getElementById('content')
 )
+
+ipcRenderer.on('ipc::dispatch', (e, action) => {
+  store.dispatch(action)
+})
  • Actions はブラウザの app/actions/index.js ファイルをインポートすると DRY ですね。
main.js
+import * as Actions from './app/actions'
 import server from './server'
 import { app, BrowserWindow, Menu, dialog } from 'electron'
  • 先ほどのメニューに send('ipc::dispatch', Actions.showComments([])) を設定します。
main.js
               label: 'Clear',
               click(item, focusedWindow) {
                 dialog.showMessageBox({
                   type: 'info',
                   message: 'Message!',
                   detail: 'message detail.',
                   buttons: ['OK']
                 })
+                focusedWindow.webContents.send('ipc::dispatch', Actions.showComments([]))
               }
             }
           ]
  • webpack.config.js ファイルに target: 'electron-renderer' を設定します。
    • server.js ファイルに設定した項目は除去します。
webpack.config.js
-  devServer: {
-    hot: true,
-    port: 4000,
-    inline: true,
-    historyApiFallback: true
-  }
+  target: 'electron-renderer'
 }
  • これでメニューをクリックして Message! のダイアログの OK をクリックするとコメントがクリアされます。

electron_ipc

Congrats!


リリースをするためには electron-packager などでパッケージを作成するなど、いくつかの手順がありますが
Webアプリケーション のテクニックで ディスクトップ アプリケーション が作成できるのは便利ですね。
また、モバイル アプリケーションは React Native を利用すると作成できますよ。

React やその周辺のテクノロジーは変化が活発で、発展が楽しみなプロダクトです。
(React TutorialComment Box から Tic tac toe に変更されました :laughing:)

Enjoy React