先日、React+Redux入門 - Qiita という記事を書きました。さて、React+Reduxと同じように流行っているイケているフレームワークと言えば、Electronですよね。Electron + React + Reduxのボイラープレートを作ってみましたので、今日はその解説をいたします。
内容
Visual Studio Codeでなるべくいい感じに開発出来るように目指したもので、内容としては単純なカウントアップのサンプルコードになっています。Electronの弱点である配布ファイルがでかくなりすぎる問題の対応のためファイル結合をwebpackで行っています。webpackではSassやJSONなど様々なファイルを結合することができます。
gulpなどは使用しておらず、watchやbuildの為のJSスクリプトが付属します。
構成
- 言語・ライブラリ・フレームワーク
- ES2015/JSX
- Electron
- React + Redux
- 開発・ビルド環境
- webpack
- electron-connect
- babel
- eslint
- jsbeautify
- editorconfig
- power-assert + mocha
- electron-packager
動かし方
導入
$ mkdir <プロジェクトの名前>
$ cd <プロジェクトの名前>
$ git init
$ git remote add upstream git@github.com:erukiti/tpl-electron-react-redux.git
$ git pull upstream master
$ npm i
開発用の確認
$ npm start
webpackのwatchモードが起動してElectronのアプリケーションが立ち上がります。ファイルを編集したらアプリケーションがリスタートします。
テスト
$ npm test
ユニットテストが走ります。power-assert + mocha に対応しています。
配布用パッケージ作成
$ npm run build
release ディレクトリ下に配布パッケージが作成されます。
- MacOS 64bit
- Windows 32bit
- Windows 64bit
解説
前述しましたが、gulpなどは使わず、watchやbuild用のスクリプトが付属していて、JSのコードとしてwatch/buildが走ります。
watch
webpackをrequireしてソースファイルが更新されたらelectron-connectを使って再起動をするようにしたスクリプトです。gulpを使って実現する場合に比べて再コンパイルが速いです。
'use strict'
const webpack = require('webpack')
const conf = require('./webpack.config.js')
const compiler = webpack(conf)
let electron = null
compiler.watch({}, (err, stats) => {
if (err) {
// FIXME
console.dir(err)
return
}
if (stats.hasWarnings()) {
stats.compilation.warnings.forEach(warning => {
// FIXME
console.dir(warning)
})
}
if (stats.hasErrors()) {
stats.compilation.errors.forEach(error => {
console.log(error.error.toString())
if (error.error.codeFrame) {
console.log(error.error.codeFrame)
}
})
return
}
if (!electron) {
electron = require('electron-connect').server.create({ path: 'build/' })
electron.start()
electron.on('quit', () => process.exit(0))
} else {
electron.restart()
}
})
build
watchの時とは違いrequireしたwebackでrunをして一回のみ実行します。webpackが通ったあとは、electron-packagerでDarwin/Win32向けにそれぞれパッケージングと7zで圧縮をかけます。(zipが良い場合はオプションをいじればできるはず)
リファクタリングしろよ感はあります…。
'use strict'
const webpack = require('webpack')
const conf = require('./webpack.config.js')
const packager = require('electron-packager')
const Zip = require('node-7z')
const fs = require('fs')
const compiler = webpack(conf)
const confPackage = JSON.parse(fs.readFileSync('./package.json'))
compiler.run((err, stats) => {
if (err) {
// FIXME
console.dir(err)
return
}
if (stats.hasWarnings()) {
stats.compilation.warnings.forEach(warning => {
// FIXME
console.dir(warning)
})
}
if (stats.hasErrors()) {
stats.compilation.errors.forEach(error => {
console.log(error.error.toString())
if (error.error.codeFrame) {
console.log(error.error.codeFrame)
}
})
return
}
const packagerConfDarwin = {
dir: 'build',
out: 'release/',
name: confPackage.name,
arch: 'x64',
asar: true,
platform: 'darwin',
version: confPackage.dependencies['electron-prebuilt'],
icon: 'src/app.icns',
overwrite: true
}
if (process.env.ELECTRON_SIGN_DARWIN) {
packagerConfDarwin['sign'] = process.env.ELECTRON_SIGN_DARWIN
}
packager(packagerConfDarwin, (err2, path) => {
let archive = new Zip()
archive.add(`release/${confPackage.name}-darwin-${confPackage.version}.7z`, `release/${confPackage.name}-darwin-x64/`, {
m0: '=BCJ',
m1: '=LZMA:d=21'
}).then(() => {
}).catch(err3 => console.error(err3))
})
const packagerConfWin32 = {
dir: 'build',
out: 'release/',
name: confPackage.name,
arch: ['ia32', 'x64'],
asar: true,
platform: 'win32',
version: confPackage.dependencies['electron-prebuilt'],
icon: 'tmp/app.ico',
overwrite: true
}
packager(packagerConfWin32, (err2, pathes) => {
pathes.forEach((path) => {
const a = path.split('-')
const platform = a[1]
const arch = a[2]
let archive = new Zip()
archive.add(`release/${confPackage.name}-${platform}-${arch}-${confPackage.version}.7z`, path, {
m0: '=BCJ',
m1: '=LZMA:d=21'
}).then(() => {
}).catch(err3 => console.error(err3))
})
})
})