2017.05.23

ElectronでReact/Salesforceなデスクトップアプリを作ってみた

  • このエントリーをはてなブックマークに追加
  • follow us in feedly

今回はElectronを使い、SuPICEで使用しているReact等のWeb技術を活用したデスクトップアプリを作成する方法をご紹介します。

ElectronはGitHub社が開発したオープンソースのフレームワークです。
JavaScriptでクロスプラットフォームなデスクトップアプリを開発することができます。

AtomエディターやVisual Studio Code、slackのアプリ版などがElectronで作られています。
http://electron.atom.io/


では早速Electronによるデスクトップアプリ開発を試してみましょう。
まず通常のWebアプリを作成し、それをElectronでデスクトップアプリ化してみます。

※完成したソースはこちらに置きました
https://github.com/ShinjiJapan/ReactElectronSalesforce


1.通常のWebアプリ作成

適当なディレクトリを作成し以下を実行します

・package.json作成

npm init -y

・http-serverのインストール

npm i -D http-server

出来上がったpackage.jsonのscriptsを以下のように書き換えます

  "scripts": {
    "start": "http-server -o"
  },

index.htmlファイルを以下の内容で作成します
index.html

<!DOCTYPE html>
<html>
	<head>
		<title>Hello Electron-React-Salesforce</title>
	</head>
	<body>
		<h1>Hello World!</h1>
		<div id="container">
		</div>
	</body>
</html>

あとは以下のコマンドを実行してみてください。
npm start

ブラウザが起動しHello World!と表示されたと思います。


2.ElectronでWebアプリをデスクトップアプリ化

・electronのインストール

npm install electron-prebuilt -g

index.js作成

先ほどのディレクトリにElectronのデスクトップアプリとして起動する際のエントリーポイントとなるファイルを作成します。
アプリケーションを開く際のウィンドウの設定等をJavaScriptで記述します。

index.js

var app = require('app');  // アプリケーション作成用モジュールをロード
var BrowserWindow = require('browser-window'); 
//  クラッシュレポート
require('crash-reporter').start();
var mainWindow = null;
// 全てのウィンドウが閉じたらアプリケーションを終了します。
app.on('window-all-closed', function() {
    app.quit();
});
// アプリケーションの初期化が完了したら呼び出されます。
app.on('ready', function() {
  // メインウィンドウを作成します。
  mainWindow = new BrowserWindow({width: 1000, height: 850});
  // メインウィンドウに表示するURLを指定します。
  mainWindow.loadUrl('file://' + __dirname + '/index.html');
  // メインウィンドウが閉じられたときの処理
  mainWindow.on('closed', function() {
    mainWindow = null;
  });
});

次にpackage.jsonのscriptsを以下のように書き換えます

package.json

  "scripts": {
    "start": "electron ."

  },

以上でWebアプリがデスクトップアプリ化されました。
もう一度
npm start
を実行してみてください。


今度はブラウザではなくウィンドウが立ち上がり先ほどと同じ画面が表示されたと思います。

やったことはほぼindex.jsを記述しただけ。
これで既存のWebアプリをデスクトップアプリ化することができます。
簡単ですね。

3.Reactが動くように

ReactおよびBabelをインストールします

npm i -S react
npm i -S react-dom
npm i -S babel babel-preset-react babel-preset-es2015 babel-preset-0

clientというディレクトリを作成し、App.jsxを作成します。
client/App.jsx

import React from 'react';
import ReactDOM from 'react-dom';

let container = document.querySelector("#container");

ReactDOM.render(<div>Hello React!</div>, container);

index.htmlにApp.jsxを呼び出すscriptを追加します

index.html

<!DOCTYPE html>
<html>
	<head>
		<title>Hello Electron-React-Salesforce</title>
	</head>
	<body>
		<div id="container">
		</div>
		<script>
			require('babel-core/register')({
				presets: ['es2015',"0", 'react']
			});
			require("./client/App.jsx");
		</script>
	</body>
</html>

babel-core/registerは、それ以降にrequireしたファイルを自動的にBabelでコンパイルしてくれます。


npm startを実行すると

のように表示され、Reactが正しく動くことがわかります。


4.Salesforceと通信してみる

Salesforceにログイン後、SOQLを発行し取得したデータを一覧表示してみます。

・JavaScriptからSalesforceのAPIを実行するためのライブラリをインストール

npm i -S jsforce

・データを一覧表示するためのライブラリをインストール

npm i -S griddle-react

App.jsxを修正

client/App.jsx

import React from 'react'
import ReactDOM from 'react-dom';
import Root from './Root.jsx'

let container = document.querySelector("#container")

ReactDOM.render(<Root />, container)


clientディレクトリに
Root.jsx
Login.jsx
DataGrid.jsx
を作成

client/Root.jsx

import React from 'react';
import jsforce from 'jsforce';
import Griddle from 'griddle-react';
import DataGrid from './DataGrid.jsx'
import Login from './Login.jsx'

class Root extends React.Component{

	constructor(props) {
		super(props);
		
		this.state = {
			isLogin:false
		};
	}
	
	setIsLogin(isLogin){
		this.setState({isLogin:isLogin});
	}

render(){
	return (<div>
				{this.state.isLogin
					? (<DataGrid/>)
					: (<Login setIsLogin={(isLogin)=>{this.setIsLogin(isLogin)}}/>)}
			</div>);
	}
}

export default Root

client/Login.jsx

import React from 'react';
import jsforce from 'jsforce';

class Login extends React.Component{
	constructor(props) {
		super(props);

		this.state = {
			id: "",
			password: "",
			isBusy:false
		};
	}
	
	propTypes:{
		setIsLogin:React.PropTypes.func.isRequired
	};
	
	statics: {
		conn:null
	};
	
	//salesforceへのログイン処理
	onLoginButtonClick(){
		this.setState({ isBusy: true  });
		
		Login.conn = new jsforce.Connection()
		Login.conn.login(this.state.id,this.state.password , (err, res) => {
			if(err){
				this.setState({ isBusy: false  });
				return;
			}
			
			this.setState({ isBusy: false  });
			this.props.setIsLogin(true);
		});
	}

	render(){
		const {isLogin} = this.props;
		return this.state.isBusy
		? (<div>Loading...</div>)
		: (<div>
				<label>Id:</label><input className="block" value={this.state.id} onChange={e=>{this.setState({id:e.target.value})}}/><br/>
				<label>Password + token:</label><input type="password" value={this.state.password} onChange={e=>{this.setState({password:e.target.value})}}/><br/>
				<input type="button" value="Login" onClick={()=>{this.onLoginButtonClick(this.state.id,this.state.password)}}/>
			</div>);
	}
}

export default Login

client/DataGrid.jsx

import React from 'react';
import Griddle from 'griddle-react';
import Login from './Login.jsx'

class DataGrid extends React.Component{

	constructor(props) {
		super(props);

		this.state = {
			isBusy:false,
			soqlText:"SELECT Id, Name,Industry,AnnualRevenue FROM Account",
			records:null
		};
	}

	//SOQLを発行しデータを取得
	onGetButtonClick(){
		this.setState({ isBusy: true  });
		Login.conn.query(this.state.soqlText, (err, res) => {
			if (err) {
				this.setState({ records: null  });
				console.log(JSON.stringify(err));
				this.setState({ isBusy: false  });
				return
			}
	
			try{
				const result = res.records.map(x => { delete x.attributes; return x;});
				this.setState({ records: result  });
				this.setState({ isBusy: false  });
			} catch(err) {
				alert(err);
			}
		});
	}

render(){
	return this.state.isBusy
		? <div>Loading...</div>
		: (<div>
				<textarea
					value={this.state.soqlText}
					onChange={e=>{this.setState({soqlText:e.target.value})}}
				/><br/>
				<input
					type="button"
					value="Exec SOQL"
					onClick={()=>{this.onGetButtonClick()}}
				/>
				{this.state.records
					?	<Griddle
							results={this.state.records}
							showFilter={true}
							resultsPerPage={20}
						/>
					:	<div/>}
		</div>);
	}
}

export default DataGrid

clientディレクトリ配下にcssディレクトリを作成し、以下のsfgrid.cssを作成

client/css/sfgrid.css

label,input {
	display: block;
	width: 250px;
	float: left;
	margin-bottom: 10px;
}
label {
	text-align: right;
	padding-right: 15px;
}
br {
	clear: left;
}
textarea{
	width: 600px;
}

index.htmlsfgrid.css呼び出しを追加

index.html

<!DOCTYPE html>
<html>
	<head>
		<title>electron react salesforce</title>
		<link href="./client/css/sfgrid.css" class="stylesheet"/>
	</head>
	<body>
		<div id="container">
		</div>
		<script>
			require('babel-core/register')({
				presets: ['es2015',"0", 'react']
			});
			require("./client/App.jsx");
		</script>
	</body>
</html>

これでnpm startを実行するとログイン画面が表示され

Id、Passwordを入力しLoginボタンを押すとSOQL入力画面が表示されます。


SOQLを入力しExec SOQLボタンを押すと取得結果が一覧表示されます。


このようにElectronでは豊富なJavaScriptのライブラリを利用しながらデスクトップアプリの開発を行うことができます。
また、通常のWebアプリであればクライアントからSalesforceへの通信はクロスドメインとなるため行えませんがElectronはデスクトップアプリであるため問題なく通信が行えます。

ElectronにはネイティブAPIも用意されており、Webアプリでは実現できない様々な事が行えます
(ただしネイティブAPIによってはプラットフォーム依存となってしまう場合があります)

ネイティブAPI
https://github.com/atom/electron/blob/master/docs/tutorial/desktop-environment-integration.md


5.Package化

せっかく作ったアプリを配布できるようにしてみましょう。

・electronパッケージャーのインストール

npm i electron-packager -g

electron-packagerをインストール後、以下のコマンドを実行します

electron-packager . AppName --platform=win32 --arch=x64 --version=0.36.7

. : ソースディレクトリ
AppName:アプリ名
platform:パッケージを作成対象のプラットフォーム
arch:対象のアーキテクチャ
version:Electronのバージョン

実行後しばらく待つとAppName-win32-x64ディレクトリにAppName.exeが作成されます。


以上でElectronによるデスクトップアプリの開発のご紹介を終わります。

Web系の開発者/会社であれば日ごろ使い慣れた技術やライブラリを活用してデスクトップアプリが作成できるというのは大きなメリットだと思います。

ぜひ一度試してみてください。

1 件

関連する記事