【Electron + react + flowtype】TweetDeckライクなpixivクライアントPixivDeckをつくった

electron + react + flowtype + styled-components + webpackでpixivのデスクトップアプリを書いた。
前半にアプリについて、後半に技術的なことを書くので、どんな技術を使ったかのみ知りたい人は前半飛ばしてください。


<a href="https://github.com/akameco/PixivDeck">
<img src="https://raw.githubusercontent.com/akameco/PixivDeck/master/static/Icon.png" width="100px"/>
</a>

[GitHub - akameco/PixivDeck: TweetDeck like pixiv client for desktop](https://github.com/akameco/PixivDeck)

[ダウンロードページ](https://github.com/akameco/PixivDeck/releases)


## スクリーンショット

### TweetDeckライクなpixivイラストビューア


### スクロール(IntersectionObserverで画像の遅延読み込み)

<img src="https://i.gyazo.com/b9b519f35505d032da8bebf55085a790.gif"/>

<hr/>

### カラムの移動([react-sortable-pane](https://github.com/bokuweb/react-sortable-pane) bokuwebさんに感謝)

<img src="https://i.gyazo.com/ae96a85ccea972427b45338f7da4ad62.gif"/>

<hr/>

### タグをクリックで新しいカラムの追加

<img src="https://i.gyazo.com/e7e0f05ed1b17b6821fa7e1587bd3c62.gif"/>

<hr/>

### Lightbox

<img src="https://i.gyazo.com/e280d761c575718eca792dd07d613593.gif"/>

<hr/>

### ドロワー

<img src="https://camo.githubusercontent.com/b956e19c3f4168f8bb06cb6c5e0076358cef215a/68747470733a2f2f692e6779617a6f2e636f6d2f61653465386262616137346261636430643961393734396339333264616331332e676966">

### その他機能
検索+◯◯users入りを自動表示
履歴機能
コンテキストメニューからイラストの保存
R18フィルタ
タグのフィルタ(見たくないタグってあるよね！)
リンクをデフォルトのブラウザで開く

等

## ダウンロード
macOSのみ動作確認。他の環境も一応ビルドはしたので、試してみてほしい。
windows環境はwin10アップデート失敗して無限再起動という永劫に囚われたので試せてない。
// 2016/10/30 追記
windows環境でも正しく動作する模様

[ダウンロード · akameco/PixivDeck](https://github.com/akameco/PixivDeck/releases)

ダウンロードしてzip解凍して、Applicationsにディレクトリに入れればOK。
署名にマネーがかかり、それを払うと生活費が消えて死ぬしかないので、初回起動は右クリックから起動してほしい。
ソースはもちろんgithubに公開してるので、自分でビルドしても使える。

[akameco/PixivDeck: TweetDeck like pixiv client for desktop](https://github.com/akameco/PixivDeck)


# 技術的なこと
個々のライブラリについては他に素晴らしい記事があると思うので、ざっくり所感だけを書く。

## 構成
electron + react + redux + flowtype + react-css-modules

## flowtypeとredux middleware
jsは別に型を必要としてないが、reactとreduxが型を必要としてる。
propTypes?それ恩恵あるの？と感じてるならすぐにflowtype導入すべきだ。

普通にエディタの設定を行えば、リアルタイムでエラーや補完の恩恵を受けることができる。
例えば、propsが渡されてないとエラーを出したり、jsxで渡すpropsも補完されて嬉しい。

flowtypeについては、以下の記事を参考にした。

[型なき世界のためのflowtype入門 - Qiita](http://qiita.com/mizchi/items/95ee0101ac22e4b7b662)

また、redux-thunkその他もろもろだとせっかく書いたflowの型定義の使いまわしがちょっとわからなかったので素のmiddlewareでロジック全部書いた。

flowの型情報があるので、エディタの補完が効いて気持ちよく書ける。
参考までに認証の部分は以下のようになっている。

flowtypeがifを解釈してくれるので、どのActionがどんなpayloadを持つか推論し、補完やエラーを出す。
`logout`アクションは`name`プロパティを持ってないのでif内でアクセスしたら補完もでないし即座にエラー。


loginアクションの場合
![redux1.gif](https://qiita-image-store.s3.amazonaws.com/0/15319/95346e28-d212-776f-1303-5f89823ca466.gif)

logoutアクションの場合
![redux2.gif](https://qiita-image-store.s3.amazonaws.com/0/15319/4f6c53a3-6b4a-405f-12eb-eecb5256dfa3.gif)


これを実現するには、actionとstoreとdispachの型定義を自分で書く必要ある。

```js
export type Action = ColumnAction | ManageAction | IpcAction | FilterAction | HistoryAction;

export type Dispatch = (action: Action) => Action;

export type State = {
	columns: Array<ColumnType>,
	entities: {
		users: Users,
		works: Works
	},
	manage: Manage,
	filter: Filter,
	history: History
};

export type Store = {
	dispatch: Dispatch,
	getState: () => State,
};
```

redux公式の型定義はクソの役にもならないので無視。
storeの型定義も自分で用意すれば、react-reduxのconnect内でも補完がガンガン効いて便利。

また、外部ライブラリの型情報とかいらんのでほとんどanyにして無視した。

// 2016/10/30
reduxの型定義は糞だったが、以下の変更により使いやすくなった。これはflow-typedにも取り込まれたので、今はそちらを使ったほうがいい。

[add examples with Flowtype support + libdefs for redux and react-redu… · reactjs/redux@85e2368](https://github.com/reactjs/redux/commit/85e2368ea9ff9b308fc873921ddf41929638f130)

## CSS Modules
react-css-modulesを使った。

[gajus/react-css-modules: Seamless mapping of class names to CSS modules inside of React components.](https://github.com/gajus/react-css-modules)

普通にcssを書けるので悩むことは少ない。
が、デコレーターがないとexportとclass定義分ける必要があって面倒なのでreact-css-modulesを使うならデコレーターを使うとよさそう。

```js
import css from 'react-css-modules';
import styles from './table.css';

@css(styles)
export default class ...
```

それにしてもcssに型ほしい。

// 2016-10-30 追記
facebookがそうであるように、デコレーターはstable stageになるまでは使わない方がいいと考えが変わった。
理由は、[create-react-app/README.md - Can I Use Decorators?](https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#can-i-use-decorators) を参考にするといいと思う。

// 2017-02-16 追記
react-css-modulesは複雑さをもたらすだけだった。react-css-modulesが解決することは小さく、導入のメリットはない。

代わりに[styled-components](https://github.com/styled-components/styled-components)を導入した。

[styled-components - Qiita](http://qiita.com/akameco/items/f3d12cf0108add06a140)

classNameによるあの煩わしいマッピングをしなくていい。この効果は大きい。classNameのタイポでスタイルが当たらないとか。使われてないcssだとか。そんなバカみたいなことを考えずに済む。
導入前には考えてなかったが、スタイルの単位でコンポーネントに分割されて、さらにclassNameを書かないのでコードの見た目もきれいになって圧倒的に見やすくなった。

正直他のCSS in JSより頭ひとつ抜けてる。


## クラスプロパティ
`constructor`で`bind`もしくは`bind operator`を使うなど方法があるが、クラスプロパティを使うのが一番楽であることは確実だと思う。
たいていhandleのプレフィックスからはじまるハンドラが、クラスプロパティになるので、他のメソッドと区別がついていい感じ。

```js
	handleOnChange = state => {
		this.setState(state);
	}
```

// 2016-10-30 追記
これを強制するeslintのルールを使っている。

[eslint-plugin-react/jsx-handler-names.md at master · yannickcr/eslint-plugin-react](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-handler-names.md)



## IntersectionObserver
無限スクロールに使ってみた。

Intersection Observerについては以下の記事を参考にした。

[Intersection Observer を用いた要素出現検出の最適化 | blog.jxck.io](https://blog.jxck.io/entries/2016-06-25/intersection-observer.html)

Chromeしか実装されてないが、Electronは実質Chromeなので新しいAPIがすぐに使えて楽しい。
主に画像の遅延読み込みに使ってる。
表示される◯◯px前に読み込みみたいなことを簡単に書けて便利。

// 2016-10-30
番兵として無限スクロールに使っているのと、画像自体の遅延読み込みの2箇所に使っている。

参考
[Intersection Observer を用いた要素出現検出の最適化 | blog.jxck.io](https://blog.jxck.io/entries/2016-06-25/intersection-observer.html)

## リファラ偽装とpixiv API
pixivの画像を表示させるために、リファラ偽装をする必要があった。
[electron-referer](https://github.com/akameco/electron-referer)を使えば1行で書ける。

```js
require('electron-referer')('http://www.pixiv.net/');
```

参考
[electronでリファラコントロール - Qiita](http://qiita.com/akameco/items/bffa354da07b65efee6d)

pixivの公式APIはないので、非公式のAPIラッパーを使う必要がある。
pythonのライブラリがあったので、node用に移植した。

旧API(public API)は[pixiv.js](https://github.com/akameco/pixiv)、新API(APP API)は[pixiv-app-api](https://github.com/akameco/pixiv-app-api)を使うと取得できる。
旧APIはいつなくなるかわからないので新APIを使うのをオススメする。

## DevtoolとAtom開発環境
react devtoolとredux devtoolは必須なので、`electron-load-devtool`を使っている。

参考
[electronに手軽にDevTools Extensionを導入する - Qiita](http://qiita.com/akameco/items/7d261793dbe2d8500f33)


flowtypeとAtomの補完やLintの設定は以下。
windows？知らない子ですね。

[Atomにflowtype環境をつくる - Qiita](http://qiita.com/akameco/items/a1319faa7c86b129c685)

## ビルドおよびwebpack設定
はじめbrowserifyオンリーでやっていたが、HMRをやりたいのでレンダラープロセスはwebpackに移行ということをやっていたら、browseriyとwebpackを両方使う状況に陥った。最悪！
ある程度の規模になるならはじめからwebpackを使った方がいいと思う。

また、gulpとか見るだけで気分が悪くなるので基本的にnpm scriptを使う。
が、`electron-packager`のignoreオプションが長くなりすぎるので、仕方なくパッケージング用のスクリプト書いた。
以下、npm script部分の抜き出し。

```json
    "start": "cross-env NODE_ENV=development electron -r babel-register -r babel-polyfill main.development.js",
    "watch": "cross-env NODE_ENV=development webpack-dev-server --hot --inline --progress --colors --port 3000",
    "build:r": "cross-env NODE_ENV=production webpack --config webpack.config.production.js --progress --colors",
    "build:m": "cross-env NODE_ENV=production webpack --config webpack.config.electron.js --progress --colors",
    "build": "npm run build:r && npm run build:m",
    "pack:macos": "node pack.js macos",
    "pack:windows": "node pack.js windows",
    "pack:linux": "node pack.js linux",
    "pack": "npm run pack:macos && npm run pack:windows && npm run pack:linux",
    "zip:macos": "cd release/PixivDeck-darwin-x64 && zip -ryXq9 ../PixivDeck-macos-${npm_package_version}.zip PixivDeck.app",
    "zip:windows": "cd release/PixivDeck-win32-ia32 && zip -ryq9 ../PixivDeck-windows-${npm_package_version}.zip *",
    "zip:linux": "cd release/PixivDeck-linux-x64 && zip -ryq9 ../PixivDeck-linux-${npm_package_version}.zip *",
    "zip": "npm run zip:macos && npm run zip:windows && npm run zip:linux",
    "release": "rm -fr release && npm run build && npm run pack && npm run zip",
    "test": "xo && flow"
```

## 終わりに
これ作ってる間にpixivのAPIが変わり、今まで糞JSONが返ってきていたのが常識的なJSONを返すようになった。
関係ないけどandroidアプリのエンドポイントにiphoneとかいうワードが入ってたりな！

で、PixivDeckは旧APIに依存しているので、いつまでメンテを続けるかはわからないことだけは書いときます。

// 2016-10-30 追記
型定義のおかげか案外サクッと新APIに移行できたが、返却されるAPIからR18フラグが消えたので簡単に表示/非表示を切り替えられなくなって不便。スマホアプリでも電車で見るときとかR18非表示にサクッと切り替えられると便利だと思うんだけどなぁ

今回flowtypeを使ってみましたがReact+reduxにはflowtype必須であるという気持ちにまでなったので、一度試してみるといいと思います。
また、ほとんど１ヶ月前にできてて放置してたので、ところどころ所感があやふやなところあったりなかったりしますごめんなさい。

実験的意味合いの強い遊びのプロジェクトですが、使ってくれたら嬉しいです。

[akameco/PixivDeck: TweetDeck like pixiv client for desktop](https://github.com/akameco/PixivDeck)