今回は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.htmlにsfgrid.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系の開発者/会社であれば日ごろ使い慣れた技術やライブラリを活用してデスクトップアプリが作成できるというのは大きなメリットだと思います。
ぜひ一度試してみてください。


ポスト
