はじめに
JSONな設定ファイルを編集する環境としてExcelを使ったけど、
ExcelからJSON吐き出すのが面倒だった
JSONが扱いやすい環境としてWebアプリを調べたところElectronが作りやすそうだったのでお試し.
Web界隈は環境の変化が激しいせいか導入が難しかった
つまずいたこと
- React公式チュートリアルはtype="text/babel"でjsx変換してる
- 実行前に変換済みファイル吐いたほうがミス探しやすいと思う
- gulp以外の変換ツールだとrequire('electron')で怒られることがあった。
- 正しい設定あると思うけど正解探すのが大変だった
- TypeScript使ったほうが作りやすいけど設定がさらに複雑に
- gulpなにそれ状態からElectron+React+Babel+Gulp+TypeScriptとか死ねる
- 一通り基本設定が出来てからTypeScript対応したほうが無難っぽい
開発環境
Windows10 + Git Bash
使用ツール
- Electron
- node.js + Chromiumなアプリ開発環境
- React
- Facebook製MVCのView関連ライブラリ
- Photon
- Electronアプリに綺麗な見た目を
- Babel
- React向けファイル変換
- Gulp
- ファイル変換の自動化
環境構築
node.jsインストール
windowsでのnode.js環境構築はnodistを使った。
インストーラーがあるので楽ちん
Electronインストール
$ npm install -g electron
photon
npmリポジトリツリーにいないようなのでgithubURLで
$ npm install https://github.com/connors/photon
React
$ npm install --save-dev react react-dom
Babel
$ npm install -g babel-cli
$ npm install --save-dev babel-preset-es2015
ファイル指定変化はこんな感じ
$ babel before.js -o after.js
ファイル修正のたびにbabelコマンドを打つのは大変なのでgulpを使う
gulp
-g付きでgulpコマンドが使えるように
$ npm install -g gulp-babel
プロジェクトフォルダにも
$ npm install --save-dev gulp
サンプルアプリ
起動時にconfig.jsonを読み込んでUIに反映する
アプリフォルダ構成
app/
|--build/ ※変換後jsファイル置き場
|--data/
| |--config.json
|--lib/
| |--main.js ※Electronアプリメイン
| |--fileUtil.js ※ファイル操作関連
|--node_modules/ ※ローカルインストールしたnode.jsモジュール群
| |--photon/
| |--react/
| |--react-dom/
| |--...
|--view/ ※Electronアプリ 描画群
| |--index.html
| |--index.js ※UI構築メイン babelで変換される
| |--component.js ※UI構築サブ babelで変換される
|--.babelrc ※babel設定
|--gulpfile.js ※gulp設定
|--package.json ※electronアプリ設定
アプリ構築
.babelrcの設定
$ cat .babelrc
{ "presets": ["react"] }
gulpfile.jsの設定
$ cat gulpfile.js
var gulp = require('gulp');
var babel = require('gulp-babel');
gulp.task('babel', function () {
return gulp.src('./view/*.js') //view/以下の.jsを変換対象に
.pipe(babel())
.pipe(gulp.dest('./build')); //build/に出力
});
gulp.task('watch', function () {
gulp.watch(src, ['babel']);
});
gulp.task('default', ['babel']);
package.jsonの設定
$ cat package.json
{
"name": "electron",
"version": "1.0.0",
"description": "",
"main": "lib/main.js",
"scripts": {
"start": "electron .",
},
"keywords": [],
"author": "",
"dependencies": {
"react": "^0.14.0",
"react-dom": "^0.14.0"
},
"devDependencies": {
"babel-plugin-transform-react-jsx": "^6.8.0",
"gulp": "^3.9.1",
"gulp-babel": "^6.1.2"
}
}
lib/main.js
本家サンプルのまんま
const {app, BrowserWindow} = require('electron')
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let win
function createWindow () {
// Create the browser window.
win = new BrowserWindow({width: 800, height: 600})
// and load the index.html of the app.
win.loadURL(`file://${__dirname}/../view/index.html`)
// Open the DevTools.
win.webContents.openDevTools()
// Emitted when the window is closed.
win.on('closed', () => {
// Dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
win = null
})
}
// 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.on('ready', createWindow)
// Quit when all windows are closed.
app.on('window-all-closed', () => {
// On macOS it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', () => {
// 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 (win === null) {
createWindow()
}
})
lib/fileUtil.js
const fs = require("fs");
//JSONファイルをテキストのまま取得
exports.readJsonPlain = (path, callback) => {
fs.readFile(path, 'utf8', (err, data) => {
if(!err)
{
callback(data);
}
else {
console.log(err);
}
});
}
//JSONファイルをオブジェクト化して取得
exports.readJson = (path, callback) => {
fs.readFile(path, 'utf8', (err, data) => {
if(!err)
{
callback( JSON.parse(data) );
}
else {
console.log(err);
}
});
}
view/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<!-- photon css読み込み -->
<link rel="stylesheet" type="text/css" href="../node_modules/photon/dist/css/photon.css" />
<title>Electron Sample</title>
</head>
<body>
<div class="window">
<!-- ツールバー -->
<header class="toolbar toolbar-header">
</header>
<div class="window-content">
<div class="pane-group">
<!-- 左側サイドバー idで要素特定してReactで挿入する-->
<div class="pane pane-sm sidebar" id="sidebar-left">
</div>
<!-- 右側サイドバー -->
<div class="pane pane-sm sidebar" id="sidebar-right">
</div>
</div>
</div>
</div>
<script src="../node_modules/react/dist/react.js"></script>
<script src="../node_modules/react-dom/dist/react-dom.js"></script>
<!-- 変換後jsを読みこませるのでbuild/以下のjsを指定 -->
<script src="../build/index.js"></script>
</body>
</html>
view/index.js
//ファイル読み込みはメインプロセス側で動作させてみる
const remote = require('electron').remote;
const fileUtil = remote.require('./fileUtil');
const React = require('react');
const ReactDOM = require('react-dom');
//変換後jsを読み込むのでbuild/以下のファイルを指定
const SidebarLeft = require('../build/sidebar-left');
document.addEventListener( "DOMContentLoaded", () => {
//file path はアプリルートフォルダから相対で。
//`file://${__dirname}/../data/config.json`はエラーになった
fileUtil.readJson(`data/config.json`, (data) => {
const sidebar = document.getElementById('sidebar-left');
ReactDOM.render(
<SidebarLeft data={data}/>,
sidebar );
});
});
view/sidebar-left.js
const React = require("react");
var SidebarLeft = React.createClass({
render: function() {
var nodes = [];
for(var v in this.props.data)
{
nodes.push(
<h1>{v}</h1>
);
}
return (
<div className="test">{nodes}</div>
);
}
});
module.exports = SidebarLeft;