Electron + React + Redux でボイラープレート作ってみた

先日、[React+Redux入門 - Qiita](http://qiita.com/erukiti/items/e16aa13ad81d5938374e) という記事を書きました。さて、React+Reduxと同じように流行っているイケているフレームワークと言えば、Electronですよね。Electron + React + Reduxのボイラープレートを作ってみましたので、今日はその解説をいたします。

* [erukiti/tpl-electron-react-redux: Electron + React + Redux のボイラープレート](https://github.com/erukiti/tpl-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

### 動かし方

#### 導入

```sh
$ mkdir <プロジェクトの名前>
$ cd <プロジェクトの名前>
$ git init
$ git remote add upstream git@github.com:erukiti/tpl-electron-react-redux.git
$ git pull upstream master
$ npm i
```

#### 開発用の確認

```sh
$ npm start
```

webpackのwatchモードが起動してElectronのアプリケーションが立ち上がります。ファイルを編集したらアプリケーションがリスタートします。

#### テスト

```sh
$ npm test
```

ユニットテストが走ります。power-assert + mocha に対応しています。

#### 配布用パッケージ作成

```sh
$ npm run build
```

release ディレクトリ下に配布パッケージが作成されます。

* MacOS 64bit
* Windows 32bit
* Windows 64bit

## 解説

前述しましたが、gulpなどは使わず、watchやbuild用のスクリプトが付属していて、JSのコードとしてwatch/buildが走ります。

### watch

webpackをrequireしてソースファイルが更新されたらelectron-connectを使って再起動をするようにしたスクリプトです。gulpを使って実現する場合に比べて再コンパイルが速いです。

```javascript
'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が良い場合はオプションをいじればできるはず)

リファクタリングしろよ感はあります…。

```javascript
'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))

        })

    })

})
```

## まとめ

* [erukiti/tpl-electron-react-redux: Electron + React + Redux のボイラープレート](https://github.com/erukiti/tpl-electron-react-redux)